Skip to content

Commit 8872365

Browse files
committed
perf(vite): patch assets plugin for hook filters
hi-ogawa/vite-plugins#1328
1 parent d33b983 commit 8872365

3 files changed

Lines changed: 359 additions & 9 deletions

File tree

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
diff --git a/dist/index.js b/dist/index.js
2+
index ea1c38a7b262713dd3a8691a65bb4ba5d89ab9f7..2b8558efebc4fe5a22cf7b1b27c05473e9295de2 100644
3+
--- a/dist/index.js
4+
+++ b/dist/index.js
5+
@@ -31,16 +31,25 @@ function parseAssetsVirtual(id) {
6+
7+
//#endregion
8+
//#region src/plugins/utils.ts
9+
+function escapeRegExp(string) {
10+
+ return string.replace(/[-\\^$*+?.()|[\]{}]/g, String.raw`\$&`);
11+
+}
12+
function createVirtualPlugin(name, load) {
13+
name = "virtual:" + name;
14+
return {
15+
name: `rsc:virtual-${name}`,
16+
- resolveId: { handler(source, _importer, _options) {
17+
- return source === name ? "\0" + name : void 0;
18+
- } },
19+
- load: { handler(id, options) {
20+
- if (id === "\0" + name) return load.apply(this, [id, options]);
21+
- } }
22+
+ resolveId: {
23+
+ filter: { id: /* @__PURE__ */ new RegExp("^" + escapeRegExp(name) + "$") },
24+
+ handler(source, _importer, _options) {
25+
+ return source === name ? "\0" + name : void 0;
26+
+ }
27+
+ },
28+
+ load: {
29+
+ filter: { id: /* @__PURE__ */ new RegExp("^\\0" + escapeRegExp(name) + "$") },
30+
+ handler(id, options) {
31+
+ if (id === "\0" + name) return load.apply(this, [id, options]);
32+
+ }
33+
+ }
34+
};
35+
}
36+
function normalizeRelativePath(s) {
37+
@@ -241,72 +250,79 @@ function assetsPlugin(pluginOpts) {
38+
const serverEnvironments = pluginOpts?.serverEnvironments ?? ["ssr"];
39+
if (serverEnvironments.includes(name)) return { build: { emitAssets: true } };
40+
},
41+
- transform: { async handler(code, id, _options) {
42+
- if (!code.includes("import.meta.vite.assets")) return;
43+
- const output = new MagicString(code);
44+
- const strippedCode = stripLiteral(code);
45+
- const newImports = /* @__PURE__ */ new Set();
46+
- for (const match of code.matchAll(/import\.meta\.vite\.assets\(([\s\S]*?)\)/dg)) {
47+
- const [start, end] = match.indices[0];
48+
- if (!strippedCode.slice(start, end).includes("import.meta.vite.assets")) continue;
49+
- if (this.environment.name === "client") {
50+
- const replacement$1 = `(${JSON.stringify(EMPTY_ASSETS)})`;
51+
- output.update(start, end, replacement$1);
52+
- continue;
53+
+ transform: {
54+
+ filter: { code: /import\.meta\.vite\.assets\(/ },
55+
+ async handler(code, id, _options) {
56+
+ const output = new MagicString(code);
57+
+ const strippedCode = stripLiteral(code);
58+
+ const newImports = /* @__PURE__ */ new Set();
59+
+ for (const match of code.matchAll(/import\.meta\.vite\.assets\(([\s\S]*?)\)/dg)) {
60+
+ const [start, end] = match.indices[0];
61+
+ if (!strippedCode.slice(start, end).includes("import.meta.vite.assets")) continue;
62+
+ if (this.environment.name === "client") {
63+
+ const replacement$1 = `(${JSON.stringify(EMPTY_ASSETS)})`;
64+
+ output.update(start, end, replacement$1);
65+
+ continue;
66+
+ }
67+
+ const argCode = match[1].trim();
68+
+ const options = {
69+
+ import: id,
70+
+ environment: void 0,
71+
+ asEntry: false
72+
+ };
73+
+ if (argCode) {
74+
+ const argValue = evalValue(argCode);
75+
+ Object.assign(options, argValue);
76+
+ }
77+
+ const environments = options.environment ? [options.environment] : ["client", this.environment.name];
78+
+ const importedNames = [];
79+
+ for (const environment of environments) {
80+
+ const importSource = toAssetsVirtual({
81+
+ import: options.import,
82+
+ importer: id,
83+
+ environment,
84+
+ entry: options.asEntry ? "1" : ""
85+
+ });
86+
+ const hash = hashString(importSource);
87+
+ const importedName = `__assets_${hash}`;
88+
+ newImports.add(`;import ${importedName} from ${JSON.stringify(importSource)};\n`);
89+
+ importedNames.push(importedName);
90+
+ }
91+
+ let replacement = importedNames[0];
92+
+ if (importedNames.length > 1) {
93+
+ newImports.add(`;import * as __assets_runtime from "virtual:fullstack/runtime";\n`);
94+
+ replacement = `__assets_runtime.mergeAssets(${importedNames.join(", ")})`;
95+
+ }
96+
+ output.update(start, end, `(${replacement})`);
97+
}
98+
- const argCode = match[1].trim();
99+
- const options = {
100+
- import: id,
101+
- environment: void 0,
102+
- asEntry: false
103+
- };
104+
- if (argCode) {
105+
- const argValue = evalValue(argCode);
106+
- Object.assign(options, argValue);
107+
- }
108+
- const environments = options.environment ? [options.environment] : ["client", this.environment.name];
109+
- const importedNames = [];
110+
- for (const environment of environments) {
111+
- const importSource = toAssetsVirtual({
112+
- import: options.import,
113+
- importer: id,
114+
- environment,
115+
- entry: options.asEntry ? "1" : ""
116+
- });
117+
- const hash = hashString(importSource);
118+
- const importedName = `__assets_${hash}`;
119+
- newImports.add(`;import ${importedName} from ${JSON.stringify(importSource)};\n`);
120+
- importedNames.push(importedName);
121+
+ if (output.hasChanged()) {
122+
+ for (const newImport of newImports) output.append(newImport);
123+
+ return {
124+
+ code: output.toString(),
125+
+ map: output.generateMap({ hires: "boundary" })
126+
+ };
127+
}
128+
- let replacement = importedNames[0];
129+
- if (importedNames.length > 1) {
130+
- newImports.add(`;import * as __assets_runtime from "virtual:fullstack/runtime";\n`);
131+
- replacement = `__assets_runtime.mergeAssets(${importedNames.join(", ")})`;
132+
- }
133+
- output.update(start, end, `(${replacement})`);
134+
}
135+
- if (output.hasChanged()) {
136+
- for (const newImport of newImports) output.append(newImport);
137+
- return {
138+
- code: output.toString(),
139+
- map: output.generateMap({ hires: "boundary" })
140+
- };
141+
- }
142+
- } },
143+
- resolveId: { handler(source) {
144+
- if (source === "virtual:fullstack/runtime") return "\0" + source;
145+
- if (source.startsWith("virtual:fullstack/assets?")) return "\0" + source;
146+
- if (source === "virtual:fullstack/assets-manifest") {
147+
- assert.notEqual(this.environment.name, "client");
148+
- assert.equal(this.environment.mode, "build");
149+
- return {
150+
- id: source,
151+
- external: true
152+
- };
153+
+ },
154+
+ resolveId: {
155+
+ filter: { id: /^virtual:fullstack\// },
156+
+ handler(source) {
157+
+ if (source === "virtual:fullstack/runtime") return "\0" + source;
158+
+ if (source.startsWith("virtual:fullstack/assets?")) return "\0" + source;
159+
+ if (source === "virtual:fullstack/assets-manifest") {
160+
+ assert.notEqual(this.environment.name, "client");
161+
+ assert.equal(this.environment.mode, "build");
162+
+ return {
163+
+ id: source,
164+
+ external: true
165+
+ };
166+
+ }
167+
}
168+
- } },
169+
- load: { async handler(id) {
170+
- if (id === "\0virtual:fullstack/runtime") return `//#region src/runtime.ts
171+
+ },
172+
+ load: {
173+
+ filter: { id: /^\0virtual:fullstack\// },
174+
+ async handler(id) {
175+
+ if (id === "\0virtual:fullstack/runtime") return `//#region src/runtime.ts
176+
function mergeAssets(...args) {
177+
const js = uniqBy(args.flatMap((h) => h.js), (a) => a.href);
178+
const css = uniqBy(args.flatMap((h) => h.css), (a) => a.href);
179+
@@ -333,20 +349,21 @@ function uniqBy(array, key) {
180+
181+
//#endregion
182+
export { mergeAssets };`;
183+
- const parsed = parseAssetsVirtual(id);
184+
- if (!parsed) return;
185+
- assert.notEqual(this.environment.name, "client");
186+
- const resolved = await this.resolve(parsed.import, parsed.importer);
187+
- assert(resolved, `Failed to resolve: ${parsed.import}`);
188+
- const s = new MagicString("");
189+
- const code = await processAssetsImport(this, resolved.id, {
190+
- environment: parsed.environment,
191+
- isEntry: !!parsed.entry
192+
- });
193+
- s.append(`export default ${code};\n`);
194+
- if (this.environment.mode === "build") s.prepend(`import __assets_manifest from "virtual:fullstack/assets-manifest";\n`);
195+
- return s.toString();
196+
- } },
197+
+ const parsed = parseAssetsVirtual(id);
198+
+ if (!parsed) return;
199+
+ assert.notEqual(this.environment.name, "client");
200+
+ const resolved = await this.resolve(parsed.import, parsed.importer);
201+
+ assert(resolved, `Failed to resolve: ${parsed.import}`);
202+
+ const s = new MagicString("");
203+
+ const code = await processAssetsImport(this, resolved.id, {
204+
+ environment: parsed.environment,
205+
+ isEntry: !!parsed.entry
206+
+ });
207+
+ s.append(`export default ${code};\n`);
208+
+ if (this.environment.mode === "build") s.prepend(`import __assets_manifest from "virtual:fullstack/assets-manifest";\n`);
209+
+ return s.toString();
210+
+ }
211+
+ },
212+
renderChunk(code, chunk) {
213+
if (code.includes("virtual:fullstack/assets-manifest")) {
214+
const replacement = normalizeRelativePath(path.relative(path.join(chunk.fileName, ".."), BUILD_ASSETS_MANIFEST_NAME));
215+
@@ -393,6 +410,7 @@ export { mergeAssets };`;
216+
sharedDuringBuild: true,
217+
resolveId: {
218+
order: "pre",
219+
+ filter: { id: /[?&]assets/ },
220+
handler(source) {
221+
const { query } = parseIdQuery(source);
222+
const value = query["assets"];
223+
@@ -401,41 +419,44 @@ export { mergeAssets };`;
224+
}
225+
}
226+
},
227+
- load: { async handler(id) {
228+
- if (id === "\0virtual:fullstack/empty-assets") return `export default ${JSON.stringify(EMPTY_ASSETS)}`;
229+
- const { filename, query } = parseIdQuery(id);
230+
- const value = query["assets"];
231+
- if (typeof value !== "undefined") {
232+
- const s = new MagicString("");
233+
- const codes = [];
234+
- if (value) {
235+
- const code = await processAssetsImport(this, filename, {
236+
- environment: value,
237+
- isEntry: value === "client"
238+
- });
239+
- codes.push(code);
240+
- } else {
241+
- const code1 = await processAssetsImport(this, filename, {
242+
- environment: "client",
243+
- isEntry: false
244+
- });
245+
- const code2 = await processAssetsImport(this, filename, {
246+
- environment: this.environment.name,
247+
- isEntry: false
248+
- });
249+
- codes.push(code1, code2);
250+
- }
251+
- s.append(`
252+
+ load: {
253+
+ filter: { id: [/^\0virtual:fullstack\/empty-assets$/, /[?&]assets/] },
254+
+ async handler(id) {
255+
+ if (id === "\0virtual:fullstack/empty-assets") return `export default ${JSON.stringify(EMPTY_ASSETS)}`;
256+
+ const { filename, query } = parseIdQuery(id);
257+
+ const value = query["assets"];
258+
+ if (typeof value !== "undefined") {
259+
+ const s = new MagicString("");
260+
+ const codes = [];
261+
+ if (value) {
262+
+ const code = await processAssetsImport(this, filename, {
263+
+ environment: value,
264+
+ isEntry: value === "client"
265+
+ });
266+
+ codes.push(code);
267+
+ } else {
268+
+ const code1 = await processAssetsImport(this, filename, {
269+
+ environment: "client",
270+
+ isEntry: false
271+
+ });
272+
+ const code2 = await processAssetsImport(this, filename, {
273+
+ environment: this.environment.name,
274+
+ isEntry: false
275+
+ });
276+
+ codes.push(code1, code2);
277+
+ }
278+
+ s.append(`
279+
import * as __assets_runtime from "virtual:fullstack/runtime";\n
280+
export default __assets_runtime.mergeAssets(${codes.join(", ")});
281+
`);
282+
- if (this.environment.mode === "build") s.prepend(`import __assets_manifest from "virtual:fullstack/assets-manifest";\n`);
283+
- return {
284+
- code: s.toString(),
285+
- moduleSideEffects: false
286+
- };
287+
+ if (this.environment.mode === "build") s.prepend(`import __assets_manifest from "virtual:fullstack/assets-manifest";\n`);
288+
+ return {
289+
+ code: s.toString(),
290+
+ moduleSideEffects: false
291+
+ };
292+
+ }
293+
}
294+
- } },
295+
+ },
296+
hotUpdate(ctx) {
297+
if (this.environment.name === "rsc") {
298+
const mods = collectModuleDependents(ctx.modules);
299+
@@ -556,11 +577,13 @@ function patchViteClientPlugin() {
300+
}
301+
return {
302+
name: "fullstack:patch-vite-client",
303+
- transform: { handler(code, id) {
304+
- if (id === viteClientPath) {
305+
- if (code.includes("linkSheetsMap")) return;
306+
- const s = new MagicString(code);
307+
- s.prependLeft(code.indexOf("const sheetsMap"), `\
308+
+ transform: {
309+
+ filter: { id: /* @__PURE__ */ new RegExp("^" + escapeRegExp(viteClientPath) + "$") },
310+
+ handler(code, id) {
311+
+ if (id === viteClientPath) {
312+
+ if (code.includes("linkSheetsMap")) return;
313+
+ const s = new MagicString(code);
314+
+ s.prependLeft(code.indexOf("const sheetsMap"), `\
315+
const linkSheetsMap = new Map();
316+
document
317+
.querySelectorAll('link[rel="stylesheet"][data-vite-dev-id]')
318+
@@ -568,8 +591,8 @@ document
319+
linkSheetsMap.set(el.getAttribute('data-vite-dev-id'), el)
320+
});
321+
`);
322+
- s.appendLeft(endIndexOf(code, `function updateStyle(id, content) {`), `if (linkSheetsMap.has(id)) { return }`);
323+
- s.appendLeft(endIndexOf(code, `function removeStyle(id) {`), `
324+
+ s.appendLeft(endIndexOf(code, `function updateStyle(id, content) {`), `if (linkSheetsMap.has(id)) { return }`);
325+
+ s.appendLeft(endIndexOf(code, `function removeStyle(id) {`), `
326+
const link = linkSheetsMap.get(id);
327+
if (link) {
328+
document
329+
@@ -584,9 +607,10 @@ if (link) {
330+
linkSheetsMap.delete(id)
331+
}
332+
`);
333+
- return s.toString();
334+
+ return s.toString();
335+
+ }
336+
}
337+
- } }
338+
+ }
339+
};
340+
}
341+
function patchVueScopeCssHmr() {

pnpm-lock.yaml

Lines changed: 7 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)