Skip to content

Commit 1843c28

Browse files
authored
feat(fullstack)!: universal route assets as default (#1196)
1 parent cfcceae commit 1843c28

File tree

10 files changed

+82
-79
lines changed

10 files changed

+82
-79
lines changed

packages/fullstack/README.md

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ function assets({
2020

2121
The goal of the API is to cover following use cases in SSR application:
2222

23-
- for server entry to access client entry
23+
- Server entry can access client entry
2424

2525
```js
2626
// [server.js] server entry injecting client entry during SSR
@@ -39,41 +39,37 @@ function renderHtml() {
3939
}
4040
```
4141

42-
- for universal route to access assets within its route
42+
- Universal route (CSR and SSR) can access assets for its route
4343
- see [`examples/react-router`](./examples/react-router) and [`examples/vue-router`](./examples/vue-router) for concrete integrations.
4444

4545
```js
4646
// [routes.js] hypothetical router library's routes declaration
4747
export const routes = [
48+
{
49+
path: "/"
50+
route: () => import("./pages/index.js"),
51+
routeAssets: import.meta.vite.assets({ import: "./pages/index.js" })
52+
},,
4853
{
4954
path: "/about"
5055
route: () => import("./pages/about.js"),
51-
routeAssets: mergeAssets(
52-
import.meta.vite.assets({
53-
import: "./pages/about.js",
54-
environment: "client",
55-
}),
56-
import.meta.vite.assets({
57-
import: "./pages/about.js",
58-
environment: "ssr",
59-
}),
60-
)
56+
routeAssets: import.meta.vite.assets({ import: "./pages/about.js" })
6157
},
6258
...
6359
]
6460
```
6561

66-
- server only app to access css
62+
- Server only page can access its css dependencies
6763

6864
```js
6965
// [server.js]
7066
import "./styles.css" // this will be included in `assets.css` below
7167

7268
function renderHtml() {
7369
const assets = import.meta.vite.assets({
74-
// both `import` and `environment` is optional and they are default to current module and environment
70+
// `import` is optional and the default is current module, which is `./server.js` in this case:
7571
// import: "./server.js",
76-
// environment: "ssr",
72+
environment: "ssr",
7773
});
7874
const head = `
7975
<link type="stylesheet" href=${JSON.stringify(assets.css[0].href)}></script>

packages/fullstack/examples/basic/src/entry.server.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ function Root() {
1616
environment: "client",
1717
asEntry: true,
1818
});
19-
const serverAssets = import.meta.vite.assets();
19+
const serverAssets = import.meta.vite.assets({
20+
environment: "ssr",
21+
});
2022
const assets = mergeAssets(clientAssets, serverAssets);
2123

2224
return (

packages/fullstack/examples/cloudflare/src/entry.server.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ function Root() {
1616
environment: "client",
1717
asEntry: true,
1818
});
19-
const serverAssets = import.meta.vite.assets();
19+
const serverAssets = import.meta.vite.assets({
20+
environment: "ssr",
21+
});
2022
const assets = mergeAssets(clientAssets, serverAssets);
2123

2224
return (

packages/fullstack/examples/island/src/entry.server.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ function Root() {
1313
const clientAssets = import.meta.vite.assets({
1414
import: "./entry.client.tsx",
1515
environment: "client",
16+
asEntry: true,
17+
});
18+
const serverAssets = import.meta.vite.assets({
19+
environment: "ssr",
1620
});
17-
const serverAssets = import.meta.vite.assets();
1821
const assets = mergeAssets(clientAssets, serverAssets);
1922

2023
return (

packages/fullstack/examples/island/vite.config.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ export default defineConfig((_env) => ({
1212
react(),
1313
reactHmrPreamblePlugin(),
1414
],
15+
optimizeDeps: {
16+
entries: ["./src/entry.client.tsx"],
17+
},
1518
environments: {
1619
client: {
1720
build: {
1821
outDir: "./dist/client",
19-
rollupOptions: {
20-
input: {
21-
index: "./src/entry.client.tsx",
22-
},
23-
},
2422
},
2523
},
2624
ssr: {

packages/fullstack/examples/react-router/src/routes.ts

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ export const routes: RouteObject[] = [
1212
assets: [
1313
import.meta.vite.assets({
1414
import: "./root",
15-
environment: "client",
16-
}),
17-
import.meta.vite.assets({
18-
import: "./root",
19-
environment: "ssr",
2015
}),
2116
// include client entry for ssr modulepreload
2217
import.meta.vite.assets({
@@ -32,14 +27,8 @@ export const routes: RouteObject[] = [
3227
lazy: () => import("./routes/index"),
3328
handle: {
3429
assets: [
35-
// TODO: we don't need to repeated if we eagerly transform client on dev.
36-
import.meta.vite.assets({
37-
import: "./routes/index",
38-
environment: "client",
39-
}),
4030
import.meta.vite.assets({
4131
import: "./routes/index",
42-
environment: "ssr",
4332
}),
4433
],
4534
} satisfies CustomHandle,
@@ -52,11 +41,6 @@ export const routes: RouteObject[] = [
5241
assets: [
5342
import.meta.vite.assets({
5443
import: "./routes/about",
55-
environment: "client",
56-
}),
57-
import.meta.vite.assets({
58-
import: "./routes/about",
59-
environment: "ssr",
6044
}),
6145
],
6246
} satisfies CustomHandle,

packages/fullstack/examples/vue-router/src/routes.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ export const routes: RouteRecordRaw[] = [
1717
useAssets(
1818
import.meta.vite.assets({
1919
import: "./routes/index.vue",
20-
environment: "client",
21-
}),
22-
import.meta.vite.assets({
23-
import: "./routes/index.vue",
24-
environment: "ssr",
2520
}),
2621
);
2722
next();
@@ -35,11 +30,6 @@ export const routes: RouteRecordRaw[] = [
3530
useAssets(
3631
import.meta.vite.assets({
3732
import: "./routes/about.vue",
38-
environment: "client",
39-
}),
40-
import.meta.vite.assets({
41-
import: "./routes/about.vue",
42-
environment: "ssr",
4333
}),
4434
);
4535
next();
@@ -53,11 +43,6 @@ export const routes: RouteRecordRaw[] = [
5343
useAssets(
5444
import.meta.vite.assets({
5545
import: "./routes/not-found.vue",
56-
environment: "client",
57-
}),
58-
import.meta.vite.assets({
59-
import: "./routes/not-found.vue",
60-
environment: "ssr",
6146
}),
6247
);
6348
next();
@@ -68,11 +53,6 @@ export const routes: RouteRecordRaw[] = [
6853
const rootAssets = [
6954
import.meta.vite.assets({
7055
import: "./root.vue",
71-
environment: "client",
72-
}),
73-
import.meta.vite.assets({
74-
import: "./root.vue",
75-
environment: "ssr",
7656
}),
7757
import.meta.vite.assets({
7858
import: "./framework/entry.client.tsx",

packages/fullstack/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
},
2525
"dependencies": {
2626
"magic-string": "^0.30.17",
27-
"srvx": "^0.8.7"
27+
"srvx": "^0.8.7",
28+
"strip-literal": "^3.1.0"
2829
},
29-
"devDependencies": {},
3030
"peerDependencies": {
3131
"vite": "^6.0.0 || ^7.0.0"
3232
}

packages/fullstack/src/plugin.ts

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import path from "node:path";
44
import { fileURLToPath } from "node:url";
55
import MagicString from "magic-string";
66
import { toNodeHandler } from "srvx/node";
7+
import { stripLiteral } from "strip-literal";
78
import {
89
DevEnvironment,
910
type Plugin,
@@ -131,8 +132,8 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
131132
async handler(code, id, _options) {
132133
if (!code.includes("import.meta.vite.assets")) return;
133134

134-
// TODO: strip comments
135135
const output = new MagicString(code);
136+
const strippedCode = stripLiteral(code);
136137

137138
const emptyResult: ImportAssetsResult = {
138139
js: [],
@@ -146,6 +147,15 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
146147
)) {
147148
const [start, end] = match.indices![0]!;
148149

150+
// skip if inside comment or string literal
151+
if (
152+
!strippedCode
153+
.slice(start, end)
154+
.includes("import.meta.vite.assets")
155+
) {
156+
continue;
157+
}
158+
149159
// No-op on client since vite build handles preload/css for dynamic import on client.
150160
// https://vite.dev/guide/features.html#async-chunk-loading-optimization
151161
if (this.environment.name === "client") {
@@ -155,28 +165,44 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
155165
}
156166

157167
const argCode = match[1]!.trim();
158-
const options: Required<ImportAssetsOptions> = {
168+
const options = {
159169
import: id,
160-
environment: this.environment.name,
170+
environment: undefined,
161171
asEntry: false,
162-
};
172+
} satisfies ImportAssetsOptions;
163173
if (argCode) {
164174
const argValue = evalValue<ImportAssetsOptions>(argCode);
165175
Object.assign(options, argValue);
166176
}
167177

168-
const importSource = toAssetsVirtual({
169-
import: options.import,
170-
importer: id,
171-
environment: options.environment,
172-
entry: options.asEntry ? "1" : "",
173-
});
174-
const hash = hashString(importSource);
175-
const importedName = `__assets_${hash}`;
176-
newImports.add(
177-
`;import ${importedName} from ${JSON.stringify(importSource)};\n`,
178-
);
179-
output.update(start, end, `(${importedName})`);
178+
// when `environment` is omitted, import both client and
179+
// current environment (i.e. treat is as universal route)
180+
const environments = options.environment
181+
? [options.environment]
182+
: ["client", this.environment.name];
183+
const importedNames: string[] = [];
184+
for (const environment of environments) {
185+
const importSource = toAssetsVirtual({
186+
import: options.import,
187+
importer: id,
188+
environment,
189+
entry: options.asEntry ? "1" : "",
190+
});
191+
const hash = hashString(importSource);
192+
const importedName = `__assets_${hash}`;
193+
newImports.add(
194+
`;import ${importedName} from ${JSON.stringify(importSource)};\n`,
195+
);
196+
importedNames.push(importedName);
197+
}
198+
let replacement = importedNames[0]!;
199+
if (importedNames.length > 1) {
200+
newImports.add(
201+
`;import * as __assets_runtime from "@hiogawa/vite-plugin-fullstack/runtime";\n`,
202+
);
203+
replacement = `__assets_runtime.mergeAssets(${importedNames.join(", ")})`;
204+
}
205+
output.update(start, end, `(${replacement})`);
180206
}
181207

182208
if (output.hasChanged()) {

pnpm-lock.yaml

Lines changed: 14 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)