Skip to content

Commit ac50532

Browse files
committed
feat: implement css aware lazy and migrate routes
1 parent a3edc18 commit ac50532

10 files changed

Lines changed: 222 additions & 869 deletions

File tree

packages/start/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"dependencies": {
3838
"@babel/core": "^7.28.3",
3939
"@babel/traverse": "^7.28.3",
40+
"@babel/types": "^7.28.5",
4041
"@solidjs/meta": "^0.29.4",
4142
"@tanstack/server-functions-plugin": "^1.133.11",
4243
"@types/babel__traverse": "^7.28.0",

packages/start/src/config/index.ts

Lines changed: 5 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1+
import { TanStackServerFnPlugin } from "@tanstack/server-functions-plugin";
2+
import { defu } from "defu";
13
import { globSync } from "node:fs";
24
import { extname, isAbsolute, join, normalize } from "node:path";
35
import { fileURLToPath } from "node:url";
4-
import { TanStackServerFnPlugin } from "@tanstack/server-functions-plugin";
5-
import { defu } from "defu";
6-
import { type PluginOption, type ViteDevServer } from "vite";
6+
import { type PluginOption } from "vite";
77
import solid, { type Options as SolidOptions } from "vite-plugin-solid";
88

9-
import { isCssModulesFile } from "../server/collect-styles.ts";
109
import {
1110
DEFAULT_EXTENSIONS,
1211
VIRTUAL_MODULES,
@@ -19,6 +18,7 @@ import {
1918
} from "./fs-router.ts";
2019
import { fsRoutes } from "./fs-routes/index.ts";
2120
import type { BaseFileSystemRouter } from "./fs-routes/router.ts";
21+
import lazy from "./lazy.ts";
2222
import { manifest } from "./manifest.ts";
2323
import { parseIdQuery } from "./utils.ts";
2424

@@ -158,7 +158,6 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
158158
},
159159
},
160160
manifest(start),
161-
css(),
162161
fsRoutes({
163162
routers: {
164163
client: new SolidStartClientFileRouter({
@@ -172,6 +171,7 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
172171
}),
173172
},
174173
}),
174+
lazy(),
175175
// Must be placed after fsRoutes, as treeShake will remove the
176176
// server fn exports added in by this plugin
177177
TanStackServerFnPlugin({
@@ -236,38 +236,3 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
236236
}),
237237
];
238238
}
239-
240-
function css(): PluginOption {
241-
let viteServer!: ViteDevServer;
242-
const cssModules: Record<string, any> = {};
243-
return {
244-
name: "solid-start:css-hmr",
245-
configureServer(dev) {
246-
viteServer = dev;
247-
},
248-
async handleHotUpdate({ file, server }) {
249-
if (file.endsWith(".css")) {
250-
const resp = await server.transformRequest(file);
251-
if (!resp) return;
252-
const json = resp.code
253-
.match(/const __vite__css = .*\n/)?.[0]
254-
?.slice("const __vite__css = ".length);
255-
if (!json) return;
256-
resp.code = JSON.parse(json);
257-
viteServer.ws.send({
258-
type: "custom",
259-
event: "css-update",
260-
data: {
261-
file,
262-
contents: resp.code,
263-
},
264-
});
265-
}
266-
},
267-
transform(code, id) {
268-
if (isCssModulesFile(id)) {
269-
cssModules[id] = code;
270-
}
271-
},
272-
};
273-
}

packages/start/src/config/lazy.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import type { PluginItem } from "@babel/core";
2+
import babel from "@babel/core";
3+
import * as t from "@babel/types";
4+
import { sep as osSep } from "path";
5+
import { basename, relative, sep } from "path/posix";
6+
import { PluginOption } from "vite";
7+
8+
const idTransform = (id: string): PluginItem => {
9+
return {
10+
visitor: {
11+
Program(path) {
12+
path.node.body.unshift(
13+
t.exportNamedDeclaration(
14+
t.variableDeclaration("const", [
15+
t.variableDeclarator(t.identifier("id$$"), t.stringLiteral(id))
16+
])
17+
)
18+
);
19+
}
20+
}
21+
};
22+
};
23+
24+
const importTransform = (): PluginItem => {
25+
return {
26+
visitor: {
27+
ImportDeclaration(path) {
28+
if (path.node.source.value !== "solid-js") return;
29+
path.traverse({
30+
ImportSpecifier(subPath) {
31+
if (subPath.node.local.name !== "lazy") return;
32+
subPath.remove();
33+
path.insertAfter(
34+
t.importDeclaration(
35+
[t.importSpecifier(t.identifier("lazy"), t.identifier("lazy"))],
36+
t.stringLiteral("@solidjs/start")
37+
)
38+
);
39+
}
40+
});
41+
}
42+
}
43+
};
44+
};
45+
46+
const lazy = (): PluginOption => {
47+
const cwd = process.cwd().replaceAll(osSep, sep);
48+
return {
49+
name: "solid-lazy-css",
50+
enforce: "pre",
51+
async transform(src, id) {
52+
// TODO: Try to exclude files by dynamicImport info in moduleGraph
53+
54+
if (!id.match(/(ts|js)x(\?.*)?$/)) return;
55+
56+
// The transformed files either import "lazy" or css files
57+
// Therefore we skip, if the src doesn't have any import
58+
if (src.indexOf("import") === -1) return;
59+
60+
const hasLazy = src.indexOf("lazy") !== -1;
61+
const localId = relative(cwd, id);
62+
63+
const transformed = await babel.transformAsync(src, {
64+
plugins:
65+
hasLazy
66+
? [idTransform(localId), importTransform()]
67+
: [idTransform(localId)],
68+
parserOpts: {
69+
plugins: ["jsx", "typescript"]
70+
},
71+
filename: basename(id),
72+
ast: false,
73+
sourceMaps: true,
74+
configFile: false,
75+
babelrc: false,
76+
sourceFileName: id
77+
});
78+
79+
if (!transformed?.code) return;
80+
81+
const { code, map } = transformed;
82+
return { code, map };
83+
}
84+
};
85+
};
86+
87+
export default lazy;

packages/start/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ export { useAssets } from "./shared/assets.ts";
1616
export { default as clientOnly } from "./shared/clientOnly.ts";
1717
export { GET } from "./shared/GET.ts";
1818
export { HttpStatusCode } from "./shared/HttpStatusCode.ts";
19+
export { default as lazy } from "./shared/lazy.ts";
1920
export { getServerFunctionMeta } from "./shared/serverFunction.ts";

packages/start/src/router.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { getManifest } from "solid-start:get-manifest";
21
import { getRequestEvent, isServer } from "solid-js/web";
32

4-
import lazyRoute from "./server/lazyRoute.tsx";
53
import { pageRoutes as routeConfigs } from "./server/routes.ts";
64
import type { PageEvent } from "./server/types.ts";
5+
import lazy from "./shared/lazy.ts";
76

87
export function createRoutes() {
98
function createRoute(route: any) {
@@ -15,7 +14,7 @@ export function createRoutes() {
1514
filesystem: true
1615
},
1716
component:
18-
route.$component && lazyRoute(route.$component, getManifest("client"), getManifest("ssr")),
17+
route.$component && lazy(route.$component.import),
1918
children: route.children ? route.children.map(createRoute) : undefined
2019
};
2120
}

packages/start/src/server/lazyRoute.tsx

Lines changed: 0 additions & 139 deletions
This file was deleted.

packages/start/src/server/manifest/prod-ssr-manifest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function getSsrProdManifest() {
2828
const json: Record<string, any> = {};
2929

3030
const entryKeys = Object.keys(viteManifest)
31-
.filter((id) => viteManifest[id]?.isEntry)
31+
.filter((id) => viteManifest[id]?.isEntry || viteManifest[id]?.isDynamicEntry)
3232
.map((id) => id);
3333

3434
for (const entryKey of entryKeys) {

packages/start/src/shared/assets.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createRenderEffect, createResource, onCleanup, sharedConfig } from "solid-js";
1+
import { createRenderEffect, createResource, JSX, onCleanup, sharedConfig } from "solid-js";
22
import { getRequestEvent, isServer, useAssets as useAssets_ } from "solid-js/web";
33
import { renderAsset, type Asset } from "../server/renderAsset.tsx";
44

@@ -125,3 +125,21 @@ export const useAssets = (assets: Asset[], nonce?: string) => {
125125
}
126126
});
127127
};
128+
129+
export const preloadStyles = (assets: Asset[]) => {
130+
if (import.meta.env.SSR) return;
131+
for (const asset of assets) {
132+
const attrs = asset.attrs as JSX.LinkHTMLAttributes<HTMLLinkElement>;
133+
if (!attrs.href || attrs.rel !== "stylesheet") return;
134+
135+
let element = document.head.querySelector(`link[href="${attrs.href}"]`);
136+
if (element) return;
137+
138+
// create a link preload element for the css file so it starts loading but doesnt get attached
139+
element = document.createElement("link");
140+
element.setAttribute("rel", "preload");
141+
element.setAttribute("as", "style");
142+
element.setAttribute("href", attrs.href);
143+
document.head.appendChild(element);
144+
}
145+
};

0 commit comments

Comments
 (0)