Skip to content

Commit f6b2ece

Browse files
authored
feat(fullstack): expose Assets.merge method (#1285)
See updated readme / docs. This reduces the need of accessing `@hiogawa/vite-plugin-fullstack/runtime`. This would help when internalizing entire plugin inside Nitro - nitrojs/nitro#3662
1 parent 9b4cf88 commit f6b2ece

10 files changed

Lines changed: 54 additions & 38 deletions

File tree

packages/fullstack/README.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,10 @@ export function renderHtml(content) {
5353
<!DOCTYPE html>
5454
<html>
5555
<head>
56-
${clientAssets.css.map(css =>
56+
${clientAssets.css.map(css =>
5757
`<link rel="stylesheet" href="${css.href}" />`
5858
).join('\n')}
59-
${clientAssets.js.map(js =>
59+
${clientAssets.js.map(js =>
6060
`<link rel="modulepreload" href="${js.href}" />`
6161
).join('\n')}
6262
<script type="module" src="${clientAssets.entry}"></script>
@@ -109,19 +109,24 @@ export function renderHtml() {
109109
}
110110
```
111111

112-
### Runtime Helpers
112+
### Merging assets
113113

114-
The plugin provides a utility function `mergeAssets` to combine multiple assets objects into a single deduplicated assets object.
114+
Each `?assets` import provides a `merge` method to combine multiple assets objects into a single deduplicated assets object. This is useful for aggregating assets from multiple route components or modules.
115115

116116
```js
117-
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
117+
import route1Assets from "./pages/layout.js?assets";
118+
import route2Assets from "./pages/home.js?assets";
119+
120+
const mergedAssets = route1Assets.merge(route2Assets);
121+
// Result: { js: [...], css: [...] } with deduplicated entries
122+
```
118123

119-
// Example: Merging assets from multiple route components
120-
const route1Assets = await import("./pages/layout.js?assets");
121-
const route2Assets = await import("./pages/home.js?assets");
124+
Alternatively, the package exports `mergeAssets` utility from `@hiogawa/vite-plugin-fullstack/runtime`:
125+
126+
```js
127+
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
122128

123129
const mergedAssets = mergeAssets(route1Assets, route2Assets);
124-
// Result: { js: [...], css: [...] } with deduplicated entries
125130
```
126131

127132
### Configuration

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { renderToReadableStream } from "react-dom/server.edge";
22
import { App } from "./App";
33
import "./index.css";
4-
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
54
import clientAssets from "./entry.client.tsx?assets=client";
65
import serverAssets from "./entry.server.tsx?assets=ssr";
76

@@ -13,7 +12,7 @@ async function handler(_request: Request): Promise<Response> {
1312
}
1413

1514
function Root() {
16-
const assets = mergeAssets(clientAssets, serverAssets);
15+
const assets = clientAssets.merge(serverAssets);
1716

1817
return (
1918
<html>

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { renderToReadableStream } from "react-dom/server.edge";
22
import { App } from "./App";
33
import "./index.css";
4-
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
54
import clientAssets from "./entry.client.tsx?assets=client";
65
import serverAssets from "./entry.server.tsx?assets=ssr";
76

@@ -13,7 +12,7 @@ async function handler(_request: Request): Promise<Response> {
1312
}
1413

1514
function Root() {
16-
const assets = mergeAssets(clientAssets, serverAssets);
15+
const assets = clientAssets.merge(serverAssets);
1716

1817
return (
1918
<html>
@@ -30,7 +29,7 @@ function Root() {
3029
crossOrigin=""
3130
/>
3231
))}
33-
<script type="module" src={clientAssets.entry}></script>
32+
<script type="module" src={assets.entry}></script>
3433
</head>
3534
<body>
3635
<div id="root">

packages/fullstack/examples/data-fetching/src/framework/entry.server.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
21
import { RPCHandler } from "@orpc/server/fetch";
32
import { renderToReadableStream } from "react-dom/server.edge";
43
import { App } from "../app";
@@ -13,7 +12,7 @@ import {
1312
} from "@tanstack/react-query";
1413

1514
const rpcHandler = new RPCHandler(__rpc_router__);
16-
const assets = mergeAssets(clientAssets, serverAssets);
15+
const assets = clientAssets.merge(serverAssets);
1716

1817
async function handler(request: Request): Promise<Response> {
1918
const rpcResult = await rpcHandler.handle(request, { prefix: "/rpc" });

packages/fullstack/examples/react-router/src/framework/entry.server.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
21
import { renderToReadableStream } from "react-dom/server.edge";
32
import {
43
StaticRouterProvider,
@@ -21,8 +20,7 @@ async function handler(request: Request): Promise<Response> {
2120
const router = createStaticRouter(dataRoutes, context);
2221

2322
// collect assets from matched routes
24-
const assets = mergeAssets(
25-
clientEntry,
23+
const assets = clientEntry.merge(
2624
...(await Promise.all(
2725
context.matches
2826
.map(

packages/fullstack/examples/remix/src/framework/entry.server.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import assert from "node:assert";
2-
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
32
import type { Remix } from "@remix-run/dom";
43
import { jsx } from "@remix-run/dom/jsx-runtime";
54
import { renderToStream } from "@remix-run/dom/server";
@@ -26,7 +25,7 @@ async function handler(request: Request): Promise<Response> {
2625
}
2726

2827
// match route and render page
29-
const assets = mergeAssets(clientAssets, serverAssets);
28+
const assets = clientAssets.merge(serverAssets);
3029
const match = routes[url.pathname as "/"] ?? routes["*"];
3130
const content = await (await match()).default();
3231

packages/fullstack/examples/vue-router/src/framework/entry.server.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { mergeAssets } from "@hiogawa/vite-plugin-fullstack/runtime";
21
import { createHead, transformHtmlTemplate } from "unhead/server";
32
import { createSSRApp } from "vue";
43
import { RouterView, createMemoryHistory, createRouter } from "vue-router";
@@ -24,8 +23,7 @@ async function handler(request: Request): Promise<Response> {
2423
await router.isReady();
2524

2625
// collect assets from current route
27-
const assets = mergeAssets(
28-
clientEntry,
26+
const assets = clientEntry.merge(
2927
...(await Promise.all(
3028
router.currentRoute.value.matched
3129
.map((to) => to.meta.assets)

packages/fullstack/src/plugin.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ import {
1616
isRunnableDevEnvironment,
1717
normalizePath,
1818
} from "vite";
19-
import type { ImportAssetsOptions, ImportAssetsResult } from "../types/shared";
19+
import type {
20+
ImportAssetsOptions,
21+
ImportAssetsResultRaw,
22+
} from "../types/shared";
2023
import {
2124
parseAssetsVirtual,
2225
parseIdQuery,
@@ -118,7 +121,7 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
118121
},
119122
) {
120123
if (ctx.environment.mode === "dev") {
121-
const result: ImportAssetsResult = {
124+
const result: ImportAssetsResultRaw = {
122125
entry: undefined, // defined only on client
123126
js: [], // always empty
124127
css: [], // defined only on server
@@ -362,7 +365,7 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
362365
`[vite-plugin-fullstack] failed to find built chunk for ${meta.id} imported by ${meta.importerEnvironment} environment`,
363366
);
364367
}
365-
const result: ImportAssetsResult = {
368+
const result: ImportAssetsResultRaw = {
366369
js: [],
367370
css: [],
368371
};
@@ -440,6 +443,9 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
440443
return `\0virtual:fullstack/empty-assets`;
441444
}
442445
}
446+
if (source === "virtual:fullstack/runtime") {
447+
return this.resolve("./runtime.js", import.meta.filename);
448+
}
443449
},
444450
},
445451
load: {
@@ -455,12 +461,13 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
455461
// assets=ssr => { environment: "ssr", isEntry: false }
456462
// assets => { environment: "client", isEntry: false }, { environment: <this>, isEntry: false }
457463
const s = new MagicString("");
464+
const codes: string[] = [];
458465
if (value) {
459466
const code = await processAssetsImport(this, filename, {
460467
environment: value,
461468
isEntry: value === "client",
462469
});
463-
s.append(`export default ${code};\n`);
470+
codes.push(code);
464471
} else {
465472
const code1 = await processAssetsImport(this, filename, {
466473
environment: "client",
@@ -470,11 +477,12 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
470477
environment: this.environment.name,
471478
isEntry: false,
472479
});
473-
s.append(
474-
`import * as __assets_runtime from "@hiogawa/vite-plugin-fullstack/runtime";\n` +
475-
`export default __assets_runtime.mergeAssets(${code1}, ${code2});\n`,
476-
);
480+
codes.push(code1, code2);
477481
}
482+
s.append(`
483+
import * as __assets_runtime from "virtual:fullstack/runtime";\n
484+
export default __assets_runtime.mergeAssets(${codes.join(", ")});
485+
`);
478486
if (this.environment.mode === "build") {
479487
s.prepend(
480488
`import __assets_manifest from "virtual:fullstack/assets-manifest";\n`,
@@ -547,7 +555,7 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
547555
];
548556
}
549557

550-
const EMPTY_ASSETS: ImportAssetsResult = {
558+
const EMPTY_ASSETS: ImportAssetsResultRaw = {
551559
js: [],
552560
css: [],
553561
};
@@ -633,7 +641,7 @@ function hasSpecialCssQuery(id: string): boolean {
633641

634642
type BuildAssetsManifest = {
635643
[environment: string]: {
636-
[import_: string]: ImportAssetsResult;
644+
[import_: string]: ImportAssetsResultRaw;
637645
};
638646
};
639647

packages/fullstack/src/runtime.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import type { ImportAssetsResult } from "../types/shared";
1+
import type {
2+
ImportAssetsResult,
3+
ImportAssetsResultRaw,
4+
} from "../types/shared";
25

36
export type { ImportAssetsResult };
47

5-
export function mergeAssets(...args: ImportAssetsResult[]): ImportAssetsResult {
8+
export function mergeAssets(
9+
...args: ImportAssetsResultRaw[]
10+
): ImportAssetsResult {
611
const js = uniqBy(
712
args.flatMap((h) => h.js),
813
(a) => a.href,
@@ -11,7 +16,9 @@ export function mergeAssets(...args: ImportAssetsResult[]): ImportAssetsResult {
1116
args.flatMap((h) => h.css),
1217
(a) => a.href,
1318
);
14-
return { js, css };
19+
const entry = args.filter((arg) => arg.entry)?.[0]?.entry;
20+
const raw: ImportAssetsResultRaw = { entry, js, css };
21+
return { ...raw, merge: (...args) => mergeAssets(raw, ...args) };
1522
}
1623

1724
// merging is cumbersome because of `data-vite-dev-id` :(

packages/fullstack/types/shared.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,11 @@ export type ImportAssetsOptions = {
1818
};
1919

2020
// TODO: rename to just Assets?
21-
export type ImportAssetsResult = {
21+
export type ImportAssetsResult = ImportAssetsResultRaw & {
22+
merge(...args: ImportAssetsResultRaw[]): ImportAssetsResult;
23+
};
24+
25+
export type ImportAssetsResultRaw = {
2226
entry?: string;
2327
js: { href: string }[];
2428
css: CssLinkAttributes[];

0 commit comments

Comments
 (0)