Skip to content

Commit 989fcc9

Browse files
authored
feat(fullstack): expose ViteBuilder.writeAssetsManifest (#1288)
This allows a way to avoid rather inflexible `buildApp` hook and let users / downstream integration to invoke `writeAssetsManifest` at the appropriate timing. This will help nitrojs/nitro#3662 ## todo - [x] docs - [x] tests
1 parent 11ee6cb commit 989fcc9

4 files changed

Lines changed: 108 additions & 78 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,11 @@ export default defineConfig({
168168
},
169169
builder: {
170170
async buildApp(builder) {
171-
// Currently, the plugin requires this specific build order
172-
// to dynamically add client entries
171+
// The plugin requires "ssr -> client" build order to support dynamically adding client entries
173172
await builder.build(builder.environments["ssr"]!);
174173
await builder.build(builder.environments["client"]!);
174+
// `writeAssetsManifest` is exposed under `builder` to allow flexible build pipeline
175+
await builder.writeAssetsManifest()
175176
}
176177
}
177178
})

examples/basic/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default defineConfig((_env) => ({
3636
async buildApp(builder) {
3737
await builder.build(builder.environments["ssr"]!);
3838
await builder.build(builder.environments["client"]!);
39+
await builder.writeAssetsManifest();
3940
},
4041
},
4142
}));

src/plugin.ts

Lines changed: 93 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
type Plugin,
1212
type ResolvedConfig,
1313
type Rollup,
14+
type ViteBuilder,
1415
type ViteDevServer,
1516
isCSSRequest,
1617
isRunnableDevEnvironment,
@@ -155,6 +156,84 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
155156
}
156157
}
157158

159+
let writeAssetsManifestCalled = false;
160+
async function writeAssetsManifest(builder: ViteBuilder) {
161+
if (writeAssetsManifestCalled) return;
162+
writeAssetsManifestCalled = true;
163+
164+
// build manifest of imported assets
165+
const manifest: BuildAssetsManifest = {};
166+
for (const [environmentName, metas] of Object.entries(
167+
importAssetsMetaMap,
168+
)) {
169+
const bundle = bundleMap[environmentName]!;
170+
const assetDepsMap = collectAssetDeps(bundle);
171+
for (const [id, meta] of Object.entries(metas)) {
172+
const found = assetDepsMap[id];
173+
if (!found) {
174+
builder.config.logger.error(
175+
`[vite-plugin-fullstack] failed to find built chunk for ${meta.id} imported by ${meta.importerEnvironment} environment`,
176+
);
177+
return;
178+
}
179+
const result: ImportAssetsResultRaw = {
180+
js: [],
181+
css: [],
182+
};
183+
const { chunk, deps } = found;
184+
// TODO: base
185+
if (environmentName === "client") {
186+
result.entry = `/${chunk.fileName}`;
187+
result.js = deps.js.map((fileName) => ({
188+
href: `/${fileName}`,
189+
}));
190+
}
191+
result.css = deps.css.map((fileName) => ({
192+
href: `/${fileName}`,
193+
}));
194+
195+
// add single css when `cssCodeSplit: false`
196+
// https://github.com/vitejs/vite/blob/3a92bc79b306a01b8aaf37f80b2239eaf6e488e7/packages/vite/src/node/plugins/css.ts#L999-L1011
197+
if (!builder.environments[environmentName]!.config.build.cssCodeSplit) {
198+
const singleCss = Object.values(bundle).find(
199+
(v) =>
200+
v.type === "asset" && v.originalFileNames.includes("style.css"),
201+
);
202+
if (singleCss) {
203+
result.css.push({ href: `/${singleCss.fileName}` });
204+
}
205+
}
206+
207+
(manifest[environmentName] ??= {})[meta.key] = result;
208+
}
209+
}
210+
211+
// write manifest to importer environments
212+
const importerEnvironments = new Set(
213+
Object.values(importAssetsMetaMap)
214+
.flatMap((metas) => Object.values(metas))
215+
.flatMap((meta) => meta.importerEnvironment),
216+
);
217+
for (const environmentName of importerEnvironments) {
218+
const outDir = builder.environments[environmentName]!.config.build.outDir;
219+
fs.writeFileSync(
220+
path.join(outDir, BUILD_ASSETS_MANIFEST_NAME),
221+
`export default ${JSON.stringify(manifest, null, 2)};`,
222+
);
223+
224+
// copy assets to client (mainly for server css)
225+
const clientOutDir = builder.environments["client"]!.config.build.outDir;
226+
for (const asset of Object.values(bundleMap[environmentName]!)) {
227+
if (asset.type === "asset") {
228+
const srcFile = path.join(outDir, asset.fileName);
229+
const destFile = path.join(clientOutDir, asset.fileName);
230+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
231+
fs.copyFileSync(srcFile, destFile);
232+
}
233+
}
234+
}
235+
}
236+
158237
return [
159238
{
160239
name: "fullstack:assets",
@@ -348,85 +427,23 @@ export function assetsPlugin(pluginOpts?: FullstackPluginOptions): Plugin[] {
348427
}
349428
}
350429
},
430+
buildApp: {
431+
order: "pre",
432+
async handler(builder) {
433+
// expose writeAssetsManifest to builder
434+
builder.writeAssetsManifest = async () => {
435+
await writeAssetsManifest(builder);
436+
};
437+
},
438+
},
439+
},
440+
{
441+
name: "fullstack:write-assets-manifest-post",
351442
buildApp: {
352443
order: "post",
353444
async handler(builder) {
354-
// build manifest of imported assets
355-
const manifest: BuildAssetsManifest = {};
356-
for (const [environmentName, metas] of Object.entries(
357-
importAssetsMetaMap,
358-
)) {
359-
const bundle = bundleMap[environmentName]!;
360-
const assetDepsMap = collectAssetDeps(bundle);
361-
for (const [id, meta] of Object.entries(metas)) {
362-
const found = assetDepsMap[id];
363-
if (!found) {
364-
return this.error(
365-
`[vite-plugin-fullstack] failed to find built chunk for ${meta.id} imported by ${meta.importerEnvironment} environment`,
366-
);
367-
}
368-
const result: ImportAssetsResultRaw = {
369-
js: [],
370-
css: [],
371-
};
372-
const { chunk, deps } = found;
373-
// TODO: base
374-
if (environmentName === "client") {
375-
result.entry = `/${chunk.fileName}`;
376-
result.js = deps.js.map((fileName) => ({
377-
href: `/${fileName}`,
378-
}));
379-
}
380-
result.css = deps.css.map((fileName) => ({
381-
href: `/${fileName}`,
382-
}));
383-
384-
// add single css when `cssCodeSplit: false`
385-
// https://github.com/vitejs/vite/blob/3a92bc79b306a01b8aaf37f80b2239eaf6e488e7/packages/vite/src/node/plugins/css.ts#L999-L1011
386-
if (
387-
!builder.environments[environmentName]!.config.build
388-
.cssCodeSplit
389-
) {
390-
const singleCss = Object.values(bundle).find(
391-
(v) =>
392-
v.type === "asset" &&
393-
v.originalFileNames.includes("style.css"),
394-
);
395-
if (singleCss) {
396-
result.css.push({ href: `/${singleCss.fileName}` });
397-
}
398-
}
399-
400-
(manifest[environmentName] ??= {})[meta.key] = result;
401-
}
402-
}
403-
404-
// write manifest to importer environments
405-
const importerEnvironments = new Set(
406-
Object.values(importAssetsMetaMap)
407-
.flatMap((metas) => Object.values(metas))
408-
.flatMap((meta) => meta.importerEnvironment),
409-
);
410-
for (const environmentName of importerEnvironments) {
411-
const outDir =
412-
builder.environments[environmentName]!.config.build.outDir;
413-
fs.writeFileSync(
414-
path.join(outDir, BUILD_ASSETS_MANIFEST_NAME),
415-
`export default ${JSON.stringify(manifest, null, 2)};`,
416-
);
417-
418-
// copy assets to client (mainly for server css)
419-
const clientOutDir =
420-
builder.environments["client"]!.config.build.outDir;
421-
for (const asset of Object.values(bundleMap[environmentName]!)) {
422-
if (asset.type === "asset") {
423-
const srcFile = path.join(outDir, asset.fileName);
424-
const destFile = path.join(clientOutDir, asset.fileName);
425-
fs.mkdirSync(path.dirname(destFile), { recursive: true });
426-
fs.copyFileSync(srcFile, destFile);
427-
}
428-
}
429-
}
445+
// ensure this is called at least once
446+
await builder.writeAssetsManifest();
430447
},
431448
},
432449
},

types/index.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,14 @@ declare global {
1010
};
1111
}
1212
}
13+
14+
declare module "vite" {
15+
interface ViteBuilder {
16+
/**
17+
* The plugin injects this method to allow flexible build pipeline
18+
* for downstream integrations. This will be automatically called during the plugin
19+
* post `buildApp` hook when it was not called by users.
20+
*/
21+
writeAssetsManifest(): Promise<void>;
22+
}
23+
}

0 commit comments

Comments
 (0)