@vizejs/vite-plugin 0.303.0 → 0.310.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +494 -84
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -4,9 +4,10 @@ import crypto, { createHash } from "node:crypto";
|
|
|
4
4
|
import * as native from "@vizejs/native";
|
|
5
5
|
import { applyViteDefineReplacements, buildInspectorGraph, chunkVitePrecompileFiles, classifyVitePluginRequest, createViteBareImportBases, createViteBareImportCandidates, createViteVirtualId, detectViteHmrUpdateType, diffVitePrecompileFiles, generateViteHmrCode, hasViteHmrChanges, isViteBareSpecifier, normalizeViteCssModuleFilename, normalizeViteDevMiddlewareUrl, normalizeVitePrecompileBatchSize, normalizeViteRequireBase, normalizeViteResolvedVuePath, resolveViteAliasRequest, resolveViteCssImports, resolveViteRelativeImport, resolveViteVuePath, rewriteViteDynamicTemplateImports, rewriteViteImportMetaGlobBase, rewriteViteStaticAssetUrls, scopeViteCssForPipeline, shouldApplyViteDefineInVirtualModule, splitViteIdQuery, toViteBrowserImportPrefix, transformViteCssVarsForPipeline } from "@vizejs/native";
|
|
6
6
|
import { parseSync } from "oxc-parser";
|
|
7
|
+
import path from "node:path";
|
|
7
8
|
import fs from "node:fs";
|
|
8
9
|
import { glob } from "tinyglobby";
|
|
9
|
-
import
|
|
10
|
+
import zlib from "node:zlib";
|
|
10
11
|
import { pathToFileURL } from "node:url";
|
|
11
12
|
import * as vite from "vite";
|
|
12
13
|
//#region src/hmr.ts
|
|
@@ -86,9 +87,15 @@ function isNode(value) {
|
|
|
86
87
|
function getNodeStart(node) {
|
|
87
88
|
return typeof node?.start === "number" ? node.start : null;
|
|
88
89
|
}
|
|
90
|
+
function getNodeEnd(node) {
|
|
91
|
+
return typeof node?.end === "number" ? node.end : null;
|
|
92
|
+
}
|
|
89
93
|
function getNodeName(node) {
|
|
90
94
|
return isNode(node) && typeof node.name === "string" ? node.name : null;
|
|
91
95
|
}
|
|
96
|
+
function isSfcMainDefaultExport(defaultExport) {
|
|
97
|
+
return isIdentifierNamed(isNode(defaultExport?.declaration) ? defaultExport.declaration : null, SFC_MAIN_NAME);
|
|
98
|
+
}
|
|
92
99
|
function parseProgram(code) {
|
|
93
100
|
try {
|
|
94
101
|
const result = parseSync(OUTPUT_PARSE_ID, code);
|
|
@@ -146,7 +153,9 @@ function analyzeFastDefaultOutput(code) {
|
|
|
146
153
|
hasNamedRenderExport: false,
|
|
147
154
|
hasNamedSsrRenderExport: false,
|
|
148
155
|
defaultExportStart,
|
|
149
|
-
defaultExportKeywordEnd: defaultExportStart + 14
|
|
156
|
+
defaultExportKeywordEnd: defaultExportStart + 14,
|
|
157
|
+
defaultExportEnd: null,
|
|
158
|
+
defaultExportIsSfcMain: false
|
|
150
159
|
};
|
|
151
160
|
}
|
|
152
161
|
function getExportDefaultKeywordEnd(code, defaultExport) {
|
|
@@ -172,7 +181,9 @@ function analyzeModuleOutput(code) {
|
|
|
172
181
|
hasNamedRenderExport: exportedNames.includes("render"),
|
|
173
182
|
hasNamedSsrRenderExport: exportedNames.includes("ssrRender"),
|
|
174
183
|
defaultExportKeywordEnd,
|
|
175
|
-
defaultExportStart
|
|
184
|
+
defaultExportStart,
|
|
185
|
+
defaultExportEnd: getNodeEnd(defaultExport),
|
|
186
|
+
defaultExportIsSfcMain: isSfcMainDefaultExport(defaultExport)
|
|
176
187
|
};
|
|
177
188
|
}
|
|
178
189
|
function rewriteDefaultExportToSfcMain(code, moduleInfo = {
|
|
@@ -189,12 +200,29 @@ function rewriteDefaultExportToSfcMain(code, moduleInfo = {
|
|
|
189
200
|
if (exportStart == null || keywordEnd == null) return code;
|
|
190
201
|
return `${code.slice(0, exportStart)}const ${SFC_MAIN_NAME} =${code.slice(keywordEnd)}`;
|
|
191
202
|
}
|
|
192
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Locate `export default _sfc_main` when the caller has no analysis to hand.
|
|
205
|
+
*
|
|
206
|
+
* The string test is a sound pre-filter: the AST check below only succeeds when
|
|
207
|
+
* the default export's declaration *is* the `_sfc_main` identifier, which cannot
|
|
208
|
+
* happen unless that name occurs in the module.
|
|
209
|
+
*/
|
|
210
|
+
function findSfcMainDefaultExport(code) {
|
|
211
|
+
if (!code.includes(SFC_MAIN_NAME)) return {
|
|
212
|
+
defaultExportStart: null,
|
|
213
|
+
defaultExportEnd: null,
|
|
214
|
+
defaultExportIsSfcMain: false
|
|
215
|
+
};
|
|
193
216
|
const defaultExport = findDefaultExport(parseProgram(code));
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
return {
|
|
218
|
+
defaultExportStart: getNodeStart(defaultExport),
|
|
219
|
+
defaultExportEnd: getNodeEnd(defaultExport),
|
|
220
|
+
defaultExportIsSfcMain: isSfcMainDefaultExport(defaultExport)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
function insertBeforeSfcMainDefaultExport(code, insertion, options = {}) {
|
|
224
|
+
const { defaultExportStart: exportStart, defaultExportEnd: exportEnd, defaultExportIsSfcMain } = options.moduleInfo ?? findSfcMainDefaultExport(code);
|
|
225
|
+
if (!defaultExportIsSfcMain || exportStart == null) return code;
|
|
198
226
|
if (options.normalizeSemicolon && exportEnd != null) {
|
|
199
227
|
const suffixStart = code[exportEnd] === ";" ? exportEnd + 1 : exportEnd;
|
|
200
228
|
return `${code.slice(0, exportStart)}${insertion}\nexport default ${SFC_MAIN_NAME};${code.slice(suffixStart)}`;
|
|
@@ -374,6 +402,36 @@ function insertAfterStaticImports(output, imports) {
|
|
|
374
402
|
function generateScopeId(filename) {
|
|
375
403
|
return createHash("sha256").update(filename).digest("hex").slice(0, 8);
|
|
376
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Whether the SFC's `<style>` blocks are handed to Vite as virtual imports.
|
|
407
|
+
*
|
|
408
|
+
* Some blocks require Vite's CSS pipeline (preprocessor or CSS Modules), and a
|
|
409
|
+
* production client build routes plain CSS through it too so nesting,
|
|
410
|
+
* minification, and chunk ownership still apply. In both cases the blocks are
|
|
411
|
+
* emitted as imports and `compiled.css` is not used.
|
|
412
|
+
*/
|
|
413
|
+
function usesStyleImports(compiled, options) {
|
|
414
|
+
return !!options.filePath && !!compiled.styles?.length && (hasDelegatedStyles(compiled) || !options.ssr && options.isProduction && !!options.extractCss);
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Whether `generateOutput` will embed `compiled.css` in the module.
|
|
418
|
+
*
|
|
419
|
+
* The only consumer of `compiled.css` is the inline `<style>` injection, so this
|
|
420
|
+
* is also the only case in which resolving the CSS's `@import`s affects the
|
|
421
|
+
* output. Callers that would otherwise resolve `compiled.css` eagerly consult
|
|
422
|
+
* this first, which keeps the condition in one place instead of duplicating
|
|
423
|
+
* `generateOutput`'s branch structure at the call site.
|
|
424
|
+
*
|
|
425
|
+
* That guard matters because resolving `@import`s reads and inlines files and
|
|
426
|
+
* crosses the native boundary with the whole stylesheet. A production client
|
|
427
|
+
* build hands plain `<style>` blocks to Vite as virtual imports and an SSR build
|
|
428
|
+
* emits no CSS at all, so in both cases the resolved text was previously built
|
|
429
|
+
* and discarded, once per styled SFC on every build (270 discarded calls on the
|
|
430
|
+
* 300-file bench corpus).
|
|
431
|
+
*/
|
|
432
|
+
function embedsInlineCss(compiled, options) {
|
|
433
|
+
return !usesStyleImports(compiled, options) && !options.ssr && !!compiled.css && !(options.isProduction && !!options.extractCss);
|
|
434
|
+
}
|
|
377
435
|
function generateOutput(compiled, options) {
|
|
378
436
|
const { isProduction, isDev, ssr, hmrUpdateType, extractCss, filePath } = options;
|
|
379
437
|
let output = compiled.code;
|
|
@@ -387,7 +445,7 @@ function generateOutput(compiled, options) {
|
|
|
387
445
|
if (compiled.hasScoped && compiled.scopeId) output += `\n_sfc_main.__scopeId = "data-v-${compiled.scopeId}";`;
|
|
388
446
|
output += "\nexport default _sfc_main;";
|
|
389
447
|
} else if (hasExportDefault && hasSfcMainDefined) {
|
|
390
|
-
if (compiled.hasScoped && compiled.scopeId) output = insertBeforeSfcMainDefaultExport(output, `_sfc_main.__scopeId = "data-v-${compiled.scopeId}"
|
|
448
|
+
if (compiled.hasScoped && compiled.scopeId) output = insertBeforeSfcMainDefaultExport(output, `_sfc_main.__scopeId = "data-v-${compiled.scopeId}";`, { moduleInfo });
|
|
391
449
|
} else if (!hasExportDefault && !hasSfcMainDefined && hasNamedRenderExport) {
|
|
392
450
|
output += "\nconst _sfc_main = {};";
|
|
393
451
|
if (compiled.hasScoped && compiled.scopeId) output += `\n_sfc_main.__scopeId = "data-v-${compiled.scopeId}";`;
|
|
@@ -399,7 +457,7 @@ function generateOutput(compiled, options) {
|
|
|
399
457
|
output += "\n_sfc_main.ssrRender = ssrRender;";
|
|
400
458
|
output += "\nexport default _sfc_main;";
|
|
401
459
|
}
|
|
402
|
-
if (
|
|
460
|
+
if (usesStyleImports(compiled, options)) {
|
|
403
461
|
const styleImports = [];
|
|
404
462
|
const cssModuleImports = [];
|
|
405
463
|
for (const block of compiled.styles) {
|
|
@@ -454,13 +512,39 @@ function toPluginVisibleVirtualId(realPath, ssr = false, querySuffix = "") {
|
|
|
454
512
|
const rest = params.toString();
|
|
455
513
|
return `${realPath}.ts?vue&${ssr ? "vize-ssr" : "vize"}${rest ? `&${rest}` : ""}`;
|
|
456
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* String pre-gate for {@link fromPluginVisibleVirtualId}, so ordinary module IDs
|
|
517
|
+
* never cross the native boundary (#3427).
|
|
518
|
+
*
|
|
519
|
+
* A non-null result requires `request.path` to end with `.vue.ts` or `.vue.tsx`
|
|
520
|
+
* and `request.querySuffix` to be non-empty. `request.path` is `id` up to the
|
|
521
|
+
* first `?` and `querySuffix` is non-empty exactly when that `?` exists, so both
|
|
522
|
+
* conditions imply these two substring tests. The tests are strictly weaker, so
|
|
523
|
+
* everything the old code accepted still reaches the classifier.
|
|
524
|
+
*/
|
|
525
|
+
function mayBePluginVisibleVirtualId(id) {
|
|
526
|
+
return !id.startsWith("\0") && id.includes(".vue.ts") && id.includes("?");
|
|
527
|
+
}
|
|
457
528
|
function fromPluginVisibleVirtualId(id) {
|
|
458
|
-
if (id
|
|
529
|
+
if (!mayBePluginVisibleVirtualId(id)) return null;
|
|
459
530
|
const request = classifyVitePluginRequest(id);
|
|
460
531
|
if (!isPluginVisibleVueVirtualPath(request.path) || !request.querySuffix) return null;
|
|
461
532
|
const params = new URLSearchParams(request.querySuffix.slice(1));
|
|
462
533
|
if (!params.has("vue") || !params.has("vize") && !params.has("vize-ssr")) return null;
|
|
463
|
-
return stripPluginVisibleVueVirtualSuffix(
|
|
534
|
+
return stripPluginVisibleVueVirtualSuffix(stripFsPrefix(request.path));
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* The `path` a second `classifyVitePluginRequest(request.normalizedFsId ?? id)`
|
|
538
|
+
* used to recompute (#3427).
|
|
539
|
+
*
|
|
540
|
+
* `normalizedFsId` is `Some` exactly when the pre-`?` path starts with `/@fs`,
|
|
541
|
+
* and its value is that path with the four-byte prefix removed plus the original
|
|
542
|
+
* query suffix. Re-splitting that at the first `?` therefore yields the path
|
|
543
|
+
* without the prefix — and when `normalizedFsId` is `undefined` the second call
|
|
544
|
+
* classified `id` itself and yielded `request.path` unchanged.
|
|
545
|
+
*/
|
|
546
|
+
function stripFsPrefix(path) {
|
|
547
|
+
return path.startsWith("/@fs") ? path.slice(4) : path;
|
|
464
548
|
}
|
|
465
549
|
function isPluginVisibleVueVirtualPath(path) {
|
|
466
550
|
return path.endsWith(".vue.ts") || path.endsWith(".vue.tsx");
|
|
@@ -603,6 +687,84 @@ function clearBuildCaches(state) {
|
|
|
603
687
|
state.viteResolveCache?.clear();
|
|
604
688
|
}
|
|
605
689
|
//#endregion
|
|
690
|
+
//#region src/plugin/compiled-module-cache.ts
|
|
691
|
+
/**
|
|
692
|
+
* Compiled-module caches carrying a reverse index from a `src`-imported
|
|
693
|
+
* dependency to the SFCs that pulled it in.
|
|
694
|
+
*
|
|
695
|
+
* Every hot update has to answer "which SFCs own this changed file?" before it
|
|
696
|
+
* can do anything else, and that answer used to come from a full scan of both
|
|
697
|
+
* caches with a `path.resolve` per dependency per cached file. HMR latency then
|
|
698
|
+
* grew with the number of components in the project, on the interactive path,
|
|
699
|
+
* for every save — including saves of ordinary `.vue` files, because the scan
|
|
700
|
+
* runs before the `.vue` fast path.
|
|
701
|
+
*
|
|
702
|
+
* The index is maintained by overriding `set`/`delete`/`clear` rather than by a
|
|
703
|
+
* second structure updated at each call site. `state.cache` is handed to
|
|
704
|
+
* `compileFile` and `compileBatch`, and is also mutated directly from
|
|
705
|
+
* `precompile-run.ts`, `hmr.ts` and `state.ts`; a structure kept in sync by hand
|
|
706
|
+
* across those would drift the first time a new writer appeared. Subclassing
|
|
707
|
+
* puts the bookkeeping where the mutation is.
|
|
708
|
+
*
|
|
709
|
+
* The keys are `path.resolve`d exactly as the previous scan resolved them, so a
|
|
710
|
+
* dependency recorded as a relative path indexes and looks up identically.
|
|
711
|
+
*/
|
|
712
|
+
var CompiledModuleCache = class extends Map {
|
|
713
|
+
#ownersByDependency = /* @__PURE__ */ new Map();
|
|
714
|
+
/**
|
|
715
|
+
* Deliberately takes no entries: `Map`'s constructor would call the
|
|
716
|
+
* overridden `set` from inside `super()`, before `#ownersByDependency` exists.
|
|
717
|
+
*/
|
|
718
|
+
constructor() {
|
|
719
|
+
super();
|
|
720
|
+
}
|
|
721
|
+
set(file, compiled) {
|
|
722
|
+
this.#unindex(file);
|
|
723
|
+
super.set(file, compiled);
|
|
724
|
+
for (const dependency of compiled.dependencies ?? []) {
|
|
725
|
+
const key = path.resolve(dependency);
|
|
726
|
+
const owners = this.#ownersByDependency.get(key);
|
|
727
|
+
if (owners) owners.add(file);
|
|
728
|
+
else this.#ownersByDependency.set(key, new Set([file]));
|
|
729
|
+
}
|
|
730
|
+
return this;
|
|
731
|
+
}
|
|
732
|
+
delete(file) {
|
|
733
|
+
this.#unindex(file);
|
|
734
|
+
return super.delete(file);
|
|
735
|
+
}
|
|
736
|
+
clear() {
|
|
737
|
+
this.#ownersByDependency.clear();
|
|
738
|
+
super.clear();
|
|
739
|
+
}
|
|
740
|
+
/** The cached SFCs that `src`-import `resolvedDependency`. */
|
|
741
|
+
ownersOf(resolvedDependency) {
|
|
742
|
+
const owners = this.#ownersByDependency.get(resolvedDependency);
|
|
743
|
+
return owners ? [...owners] : [];
|
|
744
|
+
}
|
|
745
|
+
#unindex(file) {
|
|
746
|
+
for (const dependency of super.get(file)?.dependencies ?? []) {
|
|
747
|
+
const key = path.resolve(dependency);
|
|
748
|
+
const owners = this.#ownersByDependency.get(key);
|
|
749
|
+
if (!owners) continue;
|
|
750
|
+
owners.delete(file);
|
|
751
|
+
if (owners.size === 0) this.#ownersByDependency.delete(key);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
};
|
|
755
|
+
/**
|
|
756
|
+
* Owners of `resolvedDependency` in one cache.
|
|
757
|
+
*
|
|
758
|
+
* Plain `Map`s — the hand-built states in unit tests — keep the original linear
|
|
759
|
+
* scan, so an unindexed cache answers exactly what the indexed one does.
|
|
760
|
+
*/
|
|
761
|
+
function ownersOfDependency(cache, resolvedDependency) {
|
|
762
|
+
if (cache instanceof CompiledModuleCache) return cache.ownersOf(resolvedDependency);
|
|
763
|
+
const owners = [];
|
|
764
|
+
for (const [file, compiled] of cache) if (compiled.dependencies?.some((dependency) => path.resolve(dependency) === resolvedDependency)) owners.push(file);
|
|
765
|
+
return owners;
|
|
766
|
+
}
|
|
767
|
+
//#endregion
|
|
606
768
|
//#region src/compile-options.ts
|
|
607
769
|
function buildCompileFileOptions(filePath, options) {
|
|
608
770
|
return {
|
|
@@ -819,9 +981,14 @@ function compileBatch(files, cache, options) {
|
|
|
819
981
|
}
|
|
820
982
|
return result;
|
|
821
983
|
}
|
|
822
|
-
/**
|
|
984
|
+
/**
|
|
985
|
+
* SHA-256 of the exact source text handed to the compiler.
|
|
986
|
+
*
|
|
987
|
+
* `base64url` rather than hex: the same 256 bits in 43 characters instead of 64,
|
|
988
|
+
* and the index carries one of these per entry.
|
|
989
|
+
*/
|
|
823
990
|
function hashPrecompileSource(source) {
|
|
824
|
-
return crypto.createHash("sha256").update(source, "utf8").digest("
|
|
991
|
+
return crypto.createHash("sha256").update(source, "utf8").digest("base64url");
|
|
825
992
|
}
|
|
826
993
|
/** Key-independent JSON: object key order must not change the hash. */
|
|
827
994
|
function stableStringify(value) {
|
|
@@ -872,13 +1039,241 @@ function resolveCompilerIdentity() {
|
|
|
872
1039
|
*/
|
|
873
1040
|
function computePrecompileCacheKey(compileOptions) {
|
|
874
1041
|
const material = stableStringify({
|
|
875
|
-
format:
|
|
1042
|
+
format: 2,
|
|
876
1043
|
compiler: resolveCompilerIdentity(),
|
|
877
1044
|
options: compileOptions
|
|
878
1045
|
});
|
|
879
1046
|
return crypto.createHash("sha256").update(material, "utf8").digest("hex").slice(0, 32);
|
|
880
1047
|
}
|
|
881
1048
|
//#endregion
|
|
1049
|
+
//#region src/plugin/precompile-cache-store.ts
|
|
1050
|
+
/**
|
|
1051
|
+
* On-disk container for the persistent pre-compile cache.
|
|
1052
|
+
*
|
|
1053
|
+
* The first format stored the whole manifest as one JSON document, which meant
|
|
1054
|
+
* every compiled module's `code` went to disk as a JSON-escaped string: ~87% of
|
|
1055
|
+
* the bytes were that one field, and a cold build paid `JSON.stringify` over all
|
|
1056
|
+
* of it while a warm build paid `JSON.parse` back. This container splits the
|
|
1057
|
+
* two concerns instead:
|
|
1058
|
+
*
|
|
1059
|
+
* ```text
|
|
1060
|
+
* <header JSON>\n<compressed index><compressed payload>
|
|
1061
|
+
* ```
|
|
1062
|
+
*
|
|
1063
|
+
* - The **header** is one line of plain JSON naming the format, the cache key,
|
|
1064
|
+
* the codec, and the exact byte length of the two bodies.
|
|
1065
|
+
* - The **index** is a compressed JSON array of `[relativePath, sourceHash,
|
|
1066
|
+
* recordLength]`. Record offsets are *not* stored: they are the running sum of
|
|
1067
|
+
* the lengths, so there is no offset that can point somewhere else.
|
|
1068
|
+
* - The **payload** is the records back to back, each one
|
|
1069
|
+
* `<meta JSON>\n<code utf8><css utf8>\n`. `code` and `css` are raw UTF-8, so
|
|
1070
|
+
* the 90% of the manifest that is compiled output is never escaped, parsed, or
|
|
1071
|
+
* re-quoted -- only sliced out.
|
|
1072
|
+
*
|
|
1073
|
+
* `meta` is `[codeLength, cssLength, everythingElse]`, where `everythingElse` is
|
|
1074
|
+
* the compiled module minus `code`/`css` **by rest destructuring**, not by a
|
|
1075
|
+
* hand-copied field list. A field added to `CompiledModule` later is therefore
|
|
1076
|
+
* carried through automatically instead of being silently dropped.
|
|
1077
|
+
*
|
|
1078
|
+
* Every structural expectation above is re-checked on read and any failure
|
|
1079
|
+
* returns `null`, which the caller treats as "no cache" -- a full recompile.
|
|
1080
|
+
* See `decodePrecompileManifest`.
|
|
1081
|
+
*/
|
|
1082
|
+
/** File extension of the container. Not `.json`: it is a header plus two blobs. */
|
|
1083
|
+
const PRECOMPILE_CACHE_EXTENSION = ".vpc";
|
|
1084
|
+
const LF = 10;
|
|
1085
|
+
const NEWLINE = Buffer.from("\n");
|
|
1086
|
+
const EMPTY = Buffer.alloc(0);
|
|
1087
|
+
/**
|
|
1088
|
+
* Whether `module` may be persisted.
|
|
1089
|
+
*
|
|
1090
|
+
* Modules assembled from `src` imports depend on sibling files that this cache
|
|
1091
|
+
* does not hash, so they are recompiled on every cold start instead.
|
|
1092
|
+
*/
|
|
1093
|
+
function isPersistablePrecompileModule(module) {
|
|
1094
|
+
return !module.dependencies || module.dependencies.length === 0;
|
|
1095
|
+
}
|
|
1096
|
+
function isCompiledModule(value) {
|
|
1097
|
+
if (value === null || typeof value !== "object") return false;
|
|
1098
|
+
const module = value;
|
|
1099
|
+
if (typeof module.code !== "string" || typeof module.scopeId !== "string") return false;
|
|
1100
|
+
if (typeof module.hasScoped !== "boolean") return false;
|
|
1101
|
+
if (module.css !== void 0 && typeof module.css !== "string") return false;
|
|
1102
|
+
if (module.styles !== void 0 && !Array.isArray(module.styles)) return false;
|
|
1103
|
+
if (module.macroArtifacts !== void 0 && !Array.isArray(module.macroArtifacts)) return false;
|
|
1104
|
+
return isPersistablePrecompileModule(module);
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* `zstd` when this Node has it, `gzip` otherwise.
|
|
1108
|
+
*
|
|
1109
|
+
* The sync zstd bindings arrived in Node 22.15 / 23.8 and this package supports
|
|
1110
|
+
* Node >= 22, so the codec is detected rather than assumed. Both codecs carry
|
|
1111
|
+
* an integrity check of their own -- zstd with `checksumFlag`, gzip with its
|
|
1112
|
+
* trailing CRC32 -- so bit rot inside either body fails decompression instead of
|
|
1113
|
+
* being decoded into a plausible-looking module.
|
|
1114
|
+
*/
|
|
1115
|
+
const hasZstd = typeof zlib.zstdCompressSync === "function" && typeof zlib.zstdDecompressSync === "function";
|
|
1116
|
+
const ZSTD_PARAMS = hasZstd ? { params: { [zlib.constants.ZSTD_c_checksumFlag]: 1 } } : void 0;
|
|
1117
|
+
function compressBody(body) {
|
|
1118
|
+
return hasZstd ? zlib.zstdCompressSync(body, ZSTD_PARAMS) : zlib.gzipSync(body, { level: 1 });
|
|
1119
|
+
}
|
|
1120
|
+
/** `null` for an unknown codec, or one this Node cannot read. */
|
|
1121
|
+
function decompressBody(codec, body) {
|
|
1122
|
+
if (codec === "zstd") return hasZstd ? zlib.zstdDecompressSync(body) : null;
|
|
1123
|
+
if (codec === "gzip") return zlib.gunzipSync(body);
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
/** `<meta JSON>\n<code><css>\n` -- the trailing LF marks the record boundary. */
|
|
1127
|
+
function encodeRecord(module) {
|
|
1128
|
+
const { code, css, ...rest } = module;
|
|
1129
|
+
const codeBytes = Buffer.from(code, "utf8");
|
|
1130
|
+
const cssBytes = css === void 0 ? null : Buffer.from(css, "utf8");
|
|
1131
|
+
const meta = Buffer.from(JSON.stringify([
|
|
1132
|
+
codeBytes.length,
|
|
1133
|
+
cssBytes === null ? -1 : cssBytes.length,
|
|
1134
|
+
rest
|
|
1135
|
+
]), "utf8");
|
|
1136
|
+
return Buffer.concat([
|
|
1137
|
+
meta,
|
|
1138
|
+
NEWLINE,
|
|
1139
|
+
codeBytes,
|
|
1140
|
+
cssBytes ?? EMPTY,
|
|
1141
|
+
NEWLINE
|
|
1142
|
+
]);
|
|
1143
|
+
}
|
|
1144
|
+
/** Serialize the container. Never throws for well-typed entries. */
|
|
1145
|
+
function encodePrecompileManifest(options) {
|
|
1146
|
+
const { key, root, entries } = options;
|
|
1147
|
+
const index = [];
|
|
1148
|
+
const records = [];
|
|
1149
|
+
for (const [file, entry] of entries) {
|
|
1150
|
+
const record = encodeRecord(entry.module);
|
|
1151
|
+
index.push([
|
|
1152
|
+
path.relative(root, file),
|
|
1153
|
+
entry.hash,
|
|
1154
|
+
record.length
|
|
1155
|
+
]);
|
|
1156
|
+
records.push(record);
|
|
1157
|
+
}
|
|
1158
|
+
const indexBody = compressBody(Buffer.from(JSON.stringify(index), "utf8"));
|
|
1159
|
+
const payloadBody = compressBody(Buffer.concat(records));
|
|
1160
|
+
const header = Buffer.from(JSON.stringify({
|
|
1161
|
+
format: 2,
|
|
1162
|
+
key,
|
|
1163
|
+
codec: hasZstd ? "zstd" : "gzip",
|
|
1164
|
+
index: indexBody.length,
|
|
1165
|
+
payload: payloadBody.length
|
|
1166
|
+
}), "utf8");
|
|
1167
|
+
return Buffer.concat([
|
|
1168
|
+
header,
|
|
1169
|
+
NEWLINE,
|
|
1170
|
+
indexBody,
|
|
1171
|
+
payloadBody
|
|
1172
|
+
]);
|
|
1173
|
+
}
|
|
1174
|
+
function isByteLength(value) {
|
|
1175
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1176
|
+
}
|
|
1177
|
+
function isIndexRow(value) {
|
|
1178
|
+
return Array.isArray(value) && value.length === 3 && typeof value[0] === "string" && value[0].length > 0 && typeof value[1] === "string" && value[1].length > 0 && isByteLength(value[2]) && value[2] > 0;
|
|
1179
|
+
}
|
|
1180
|
+
/** Rebuild one module from its payload record, or `null` if the record is not one. */
|
|
1181
|
+
function decodeRecord(record) {
|
|
1182
|
+
if (record.at(-1) !== LF) return null;
|
|
1183
|
+
const metaEnd = record.indexOf(LF);
|
|
1184
|
+
if (metaEnd < 0 || metaEnd >= record.length - 1) return null;
|
|
1185
|
+
let meta;
|
|
1186
|
+
try {
|
|
1187
|
+
meta = JSON.parse(record.toString("utf8", 0, metaEnd));
|
|
1188
|
+
} catch {
|
|
1189
|
+
return null;
|
|
1190
|
+
}
|
|
1191
|
+
if (!Array.isArray(meta) || meta.length !== 3) return null;
|
|
1192
|
+
const [codeLength, cssLength, rest] = meta;
|
|
1193
|
+
if (!isByteLength(codeLength) || typeof cssLength !== "number") return null;
|
|
1194
|
+
if (!isByteLength(cssLength) && cssLength !== -1) return null;
|
|
1195
|
+
const cssBytes = cssLength === -1 ? 0 : cssLength;
|
|
1196
|
+
if (metaEnd + 1 + codeLength + cssBytes + 1 !== record.length) return null;
|
|
1197
|
+
if (typeof rest !== "object" || rest === null || Array.isArray(rest)) return null;
|
|
1198
|
+
if ("code" in rest || "css" in rest) return null;
|
|
1199
|
+
const codeStart = metaEnd + 1;
|
|
1200
|
+
const cssStart = codeStart + codeLength;
|
|
1201
|
+
const module = {
|
|
1202
|
+
...rest,
|
|
1203
|
+
code: record.toString("utf8", codeStart, cssStart)
|
|
1204
|
+
};
|
|
1205
|
+
if (cssLength !== -1) module.css = record.toString("utf8", cssStart, cssStart + cssLength);
|
|
1206
|
+
return isCompiledModule(module) ? module : null;
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Parse the container, or return `null`.
|
|
1210
|
+
*
|
|
1211
|
+
* `null` is indistinguishable from having no cache at all, which is exactly the
|
|
1212
|
+
* safe outcome: recompile everything. Every gate below fails that way --
|
|
1213
|
+
* a missing or unparsable header, a foreign `format` or `key`, a codec this Node
|
|
1214
|
+
* cannot read, body lengths that do not account for the file exactly (a
|
|
1215
|
+
* truncated or partially written container), a body that fails its own
|
|
1216
|
+
* decompression checksum, an index that is not the expected shape, and record
|
|
1217
|
+
* lengths that do not sum to the payload. An individual entry whose record does
|
|
1218
|
+
* not decode into a valid `CompiledModule` is dropped on its own, as in format 1;
|
|
1219
|
+
* because offsets come from the index alone, one bad record cannot shift the
|
|
1220
|
+
* others.
|
|
1221
|
+
*/
|
|
1222
|
+
function decodePrecompileManifest(bytes, options) {
|
|
1223
|
+
const { key, root, onReject } = options;
|
|
1224
|
+
const reject = (reason) => {
|
|
1225
|
+
onReject?.(reason);
|
|
1226
|
+
return null;
|
|
1227
|
+
};
|
|
1228
|
+
const headerEnd = bytes.indexOf(LF);
|
|
1229
|
+
if (headerEnd < 0) return reject("no header");
|
|
1230
|
+
let header;
|
|
1231
|
+
try {
|
|
1232
|
+
header = JSON.parse(bytes.toString("utf8", 0, headerEnd));
|
|
1233
|
+
} catch {
|
|
1234
|
+
return reject("unparsable header");
|
|
1235
|
+
}
|
|
1236
|
+
if (typeof header !== "object" || header === null || Array.isArray(header)) return reject("unrecognized header");
|
|
1237
|
+
if (header.format !== 2 || header.key !== key) return reject("foreign format or key");
|
|
1238
|
+
if (!isByteLength(header.index) || !isByteLength(header.payload)) return reject("unrecognized body lengths");
|
|
1239
|
+
const indexStart = headerEnd + 1;
|
|
1240
|
+
const payloadStart = indexStart + header.index;
|
|
1241
|
+
if (payloadStart + header.payload !== bytes.length) return reject("truncated container");
|
|
1242
|
+
let bodies;
|
|
1243
|
+
try {
|
|
1244
|
+
const indexText = decompressBody(header.codec, bytes.subarray(indexStart, payloadStart));
|
|
1245
|
+
const payloadText = decompressBody(header.codec, bytes.subarray(payloadStart));
|
|
1246
|
+
bodies = indexText === null || payloadText === null ? null : [indexText, payloadText];
|
|
1247
|
+
} catch {
|
|
1248
|
+
return reject("corrupt body");
|
|
1249
|
+
}
|
|
1250
|
+
if (bodies === null) return reject(`unsupported codec ${JSON.stringify(header.codec)}`);
|
|
1251
|
+
const [indexText, payload] = bodies;
|
|
1252
|
+
let index;
|
|
1253
|
+
try {
|
|
1254
|
+
index = JSON.parse(indexText.toString("utf8"));
|
|
1255
|
+
} catch {
|
|
1256
|
+
return reject("unparsable index");
|
|
1257
|
+
}
|
|
1258
|
+
if (!Array.isArray(index)) return reject("unrecognized index");
|
|
1259
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1260
|
+
let offset = 0;
|
|
1261
|
+
for (const row of index) {
|
|
1262
|
+
if (!isIndexRow(row)) return reject("unrecognized index row");
|
|
1263
|
+
const [relative, hash, length] = row;
|
|
1264
|
+
const end = offset + length;
|
|
1265
|
+
if (end > payload.length) return reject("index overruns payload");
|
|
1266
|
+
const module = decodeRecord(payload.subarray(offset, end));
|
|
1267
|
+
offset = end;
|
|
1268
|
+
if (module !== null) entries.set(path.resolve(root, relative), {
|
|
1269
|
+
hash,
|
|
1270
|
+
module
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
if (offset !== payload.length) return reject("payload not fully described by the index");
|
|
1274
|
+
return entries;
|
|
1275
|
+
}
|
|
1276
|
+
//#endregion
|
|
882
1277
|
//#region src/plugin/precompile-cache.ts
|
|
883
1278
|
/**
|
|
884
1279
|
* Persistent (on-disk) pre-compile cache.
|
|
@@ -892,10 +1287,11 @@ function computePrecompileCacheKey(compileOptions) {
|
|
|
892
1287
|
*
|
|
893
1288
|
* The two invalidation gates -- manifest identity and per-entry source hash --
|
|
894
1289
|
* live in `./precompile-cache-key.ts`, which documents why each one is safe.
|
|
895
|
-
* Two more gates live
|
|
1290
|
+
* Two more gates live in `./precompile-cache-store.ts`, which owns the on-disk
|
|
1291
|
+
* container:
|
|
896
1292
|
*
|
|
897
1293
|
* - **Shape.** Entries are validated before use and dropped individually if
|
|
898
|
-
* they do not describe a complete `CompiledModule`. The
|
|
1294
|
+
* they do not describe a complete `CompiledModule`. The container's own
|
|
899
1295
|
* `format`/`key` are re-checked after parsing, so a manifest reached by any
|
|
900
1296
|
* route other than its key still gets rejected.
|
|
901
1297
|
* - **`src` imports.** SFCs that pull blocks in through `<script src>` /
|
|
@@ -913,30 +1309,6 @@ function computePrecompileCacheKey(compileOptions) {
|
|
|
913
1309
|
const PRECOMPILE_CACHE_DIR = path.join("node_modules", ".vize", "vite-precompile");
|
|
914
1310
|
/** Set to `0`/`false` to force a full recompile without editing the config. */
|
|
915
1311
|
const PRECOMPILE_CACHE_ENV = "VIZE_PRECOMPILE_CACHE";
|
|
916
|
-
/**
|
|
917
|
-
* Whether `module` may be persisted.
|
|
918
|
-
*
|
|
919
|
-
* Modules assembled from `src` imports depend on sibling files that this cache
|
|
920
|
-
* does not hash, so they are recompiled on every cold start instead.
|
|
921
|
-
*/
|
|
922
|
-
function isPersistablePrecompileModule(module) {
|
|
923
|
-
return !module.dependencies || module.dependencies.length === 0;
|
|
924
|
-
}
|
|
925
|
-
function isCompiledModule(value) {
|
|
926
|
-
if (typeof value !== "object" || value === null) return false;
|
|
927
|
-
const module = value;
|
|
928
|
-
if (typeof module.code !== "string" || typeof module.scopeId !== "string") return false;
|
|
929
|
-
if (typeof module.hasScoped !== "boolean") return false;
|
|
930
|
-
if (module.css !== void 0 && typeof module.css !== "string") return false;
|
|
931
|
-
if (module.styles !== void 0 && !Array.isArray(module.styles)) return false;
|
|
932
|
-
if (module.macroArtifacts !== void 0 && !Array.isArray(module.macroArtifacts)) return false;
|
|
933
|
-
return isPersistablePrecompileModule(module);
|
|
934
|
-
}
|
|
935
|
-
function isCacheEntry(value) {
|
|
936
|
-
if (typeof value !== "object" || value === null) return false;
|
|
937
|
-
const entry = value;
|
|
938
|
-
return typeof entry.hash === "string" && entry.hash.length > 0 && isCompiledModule(entry.module);
|
|
939
|
-
}
|
|
940
1312
|
/** Whether the environment forces the cache off. */
|
|
941
1313
|
function isPrecompileCacheDisabledByEnv(env = process.env) {
|
|
942
1314
|
const value = env[PRECOMPILE_CACHE_ENV];
|
|
@@ -958,8 +1330,8 @@ function openPrecompileCache(options) {
|
|
|
958
1330
|
const { root, compileOptions, onDiagnostic, env = process.env } = options;
|
|
959
1331
|
if (!root || isPrecompileCacheDisabledByEnv(env)) return createDisabledPrecompileCache();
|
|
960
1332
|
const key = computePrecompileCacheKey(compileOptions);
|
|
961
|
-
const file = path.join(root, PRECOMPILE_CACHE_DIR, `${key}
|
|
962
|
-
const entries = readManifestEntries(file, key, onDiagnostic);
|
|
1333
|
+
const file = path.join(root, PRECOMPILE_CACHE_DIR, `${key}${PRECOMPILE_CACHE_EXTENSION}`);
|
|
1334
|
+
const entries = readManifestEntries(file, key, root, onDiagnostic);
|
|
963
1335
|
let dirty = false;
|
|
964
1336
|
return {
|
|
965
1337
|
file,
|
|
@@ -992,51 +1364,64 @@ function openPrecompileCache(options) {
|
|
|
992
1364
|
},
|
|
993
1365
|
flush() {
|
|
994
1366
|
if (!dirty) return false;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1367
|
+
let container;
|
|
1368
|
+
try {
|
|
1369
|
+
container = encodePrecompileManifest({
|
|
1370
|
+
key,
|
|
1371
|
+
root,
|
|
1372
|
+
entries
|
|
1373
|
+
});
|
|
1374
|
+
} catch (error) {
|
|
1375
|
+
onDiagnostic?.(`Failed to encode pre-compile cache ${file}:`, error);
|
|
1376
|
+
return false;
|
|
1377
|
+
}
|
|
1378
|
+
if (!writeManifest(file, container, onDiagnostic)) return false;
|
|
1379
|
+
removeFormat1Manifests(path.dirname(file));
|
|
1000
1380
|
dirty = false;
|
|
1001
1381
|
return true;
|
|
1002
1382
|
}
|
|
1003
1383
|
};
|
|
1004
1384
|
}
|
|
1005
1385
|
/**
|
|
1006
|
-
*
|
|
1386
|
+
* Drop the `.json` manifests format 1 left behind.
|
|
1387
|
+
*
|
|
1388
|
+
* Nothing can read them any more, and they are the reason this change exists:
|
|
1389
|
+
* ~9 KB per SFC, so ~27 MB for a 3000-SFC project sitting in `node_modules`
|
|
1390
|
+
* forever after an upgrade. Only `.json` is removed, which is exactly the set
|
|
1391
|
+
* format 1 wrote; every live manifest is a `.vpc`, and a project legitimately
|
|
1392
|
+
* keeps one per compile-option set. Best effort -- a failure here is not worth
|
|
1393
|
+
* a diagnostic, let alone a failed build.
|
|
1394
|
+
*/
|
|
1395
|
+
function removeFormat1Manifests(dir) {
|
|
1396
|
+
try {
|
|
1397
|
+
for (const name of fs.readdirSync(dir)) if (name.endsWith(".json")) fs.rmSync(path.join(dir, name), { force: true });
|
|
1398
|
+
} catch {}
|
|
1399
|
+
}
|
|
1400
|
+
/**
|
|
1401
|
+
* Read the container, or return an empty map.
|
|
1007
1402
|
*
|
|
1008
1403
|
* A missing, truncated, corrupt, or foreign manifest is indistinguishable from
|
|
1009
1404
|
* no cache at all, which is exactly the safe outcome: recompile everything.
|
|
1010
1405
|
*/
|
|
1011
|
-
function readManifestEntries(file, key, onDiagnostic) {
|
|
1012
|
-
|
|
1013
|
-
let raw;
|
|
1406
|
+
function readManifestEntries(file, key, root, onDiagnostic) {
|
|
1407
|
+
let bytes;
|
|
1014
1408
|
try {
|
|
1015
|
-
|
|
1409
|
+
bytes = fs.readFileSync(file);
|
|
1016
1410
|
} catch {
|
|
1017
|
-
return
|
|
1018
|
-
}
|
|
1019
|
-
let parsed;
|
|
1020
|
-
try {
|
|
1021
|
-
parsed = JSON.parse(raw);
|
|
1022
|
-
} catch (error) {
|
|
1023
|
-
onDiagnostic?.(`Discarding corrupt pre-compile cache ${file}:`, error);
|
|
1024
|
-
return entries;
|
|
1025
|
-
}
|
|
1026
|
-
const manifest = parsed;
|
|
1027
|
-
if (typeof manifest !== "object" || manifest === null || manifest.format !== 1 || manifest.key !== key || typeof manifest.entries !== "object" || manifest.entries === null) {
|
|
1028
|
-
onDiagnostic?.(`Ignoring pre-compile cache ${file}: unrecognized manifest`);
|
|
1029
|
-
return entries;
|
|
1411
|
+
return /* @__PURE__ */ new Map();
|
|
1030
1412
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1413
|
+
return decodePrecompileManifest(bytes, {
|
|
1414
|
+
key,
|
|
1415
|
+
root,
|
|
1416
|
+
onReject: (reason) => onDiagnostic?.(`Ignoring pre-compile cache ${file}: ${reason}`)
|
|
1417
|
+
}) ?? /* @__PURE__ */ new Map();
|
|
1033
1418
|
}
|
|
1034
1419
|
/** Write through a sibling temp file so a crash cannot leave a partial manifest. */
|
|
1035
|
-
function writeManifest(file,
|
|
1420
|
+
function writeManifest(file, container, onDiagnostic) {
|
|
1036
1421
|
const temp = `${file}.${process.pid}.${crypto.randomBytes(4).toString("hex")}.tmp`;
|
|
1037
1422
|
try {
|
|
1038
1423
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1039
|
-
fs.writeFileSync(temp,
|
|
1424
|
+
fs.writeFileSync(temp, container);
|
|
1040
1425
|
fs.renameSync(temp, file);
|
|
1041
1426
|
return true;
|
|
1042
1427
|
} catch (error) {
|
|
@@ -1599,10 +1984,10 @@ function isPotentialVizeResolveId(id) {
|
|
|
1599
1984
|
function classifyImporterRequest(importer) {
|
|
1600
1985
|
return importer ? classifyVitePluginRequest(importer) : null;
|
|
1601
1986
|
}
|
|
1602
|
-
function isPotentialVizeImporter(importer
|
|
1987
|
+
function isPotentialVizeImporter(importer) {
|
|
1603
1988
|
if (importer === void 0) return false;
|
|
1604
1989
|
if (importer.startsWith("\0") || importer.startsWith("vize:")) return true;
|
|
1605
|
-
return
|
|
1990
|
+
return importer.includes(".vue");
|
|
1606
1991
|
}
|
|
1607
1992
|
function shouldCompileVueSfcRequest(request) {
|
|
1608
1993
|
if (!request.isVueSfcPath || request.isVueStyleQuery || request.hasMacroQuery || request.hasDefinePageQuery) return false;
|
|
@@ -1636,8 +2021,8 @@ async function resolveAliasedVueImport(ctx, state, id, importer, isSsrRequest, h
|
|
|
1636
2021
|
return null;
|
|
1637
2022
|
}
|
|
1638
2023
|
async function resolveIdHook(ctx, state, id, importer, options) {
|
|
2024
|
+
if (!isPotentialVizeResolveId(id) && !isPotentialVizeImporter(importer)) return null;
|
|
1639
2025
|
const importerRequest = classifyImporterRequest(importer);
|
|
1640
|
-
if (!isPotentialVizeResolveId(id) && !isPotentialVizeImporter(importer, importerRequest)) return null;
|
|
1641
2026
|
const isBuild = state.server === null;
|
|
1642
2027
|
const isDependencyScan = !!options?.scan;
|
|
1643
2028
|
const isSsrRequest = !!options?.ssr || (importerRequest?.isVizeSsrVirtual ?? false) || (importer ? isPluginVisibleSsrVirtualId(importer) : false);
|
|
@@ -2067,19 +2452,19 @@ function loadCompiledSfcModule(state, realPath, isSsr, currentBase, loadOptions)
|
|
|
2067
2452
|
if (!compiled) return null;
|
|
2068
2453
|
for (const watchFile of new Set([realPath, ...compiled.dependencies ?? []])) loadOptions?.addWatchFile?.(watchFile);
|
|
2069
2454
|
const hasDelegated = hasDelegatedStyles(compiled);
|
|
2070
|
-
const
|
|
2071
|
-
if (compiled.css && !hasDelegated) compiled = {
|
|
2072
|
-
...compiled,
|
|
2073
|
-
css: resolveCssImports(compiled.css, realPath, state.cssAliasRules, state.server !== null, currentBase)
|
|
2074
|
-
};
|
|
2075
|
-
const generatedOutput = generateOutput(compiled, {
|
|
2455
|
+
const outputOptions = {
|
|
2076
2456
|
isProduction: state.isProduction,
|
|
2077
2457
|
isDev: state.server !== null && !isSsr,
|
|
2078
2458
|
ssr: isSsr,
|
|
2079
|
-
hmrUpdateType:
|
|
2459
|
+
hmrUpdateType: loadOptions?.ssr ? void 0 : state.pendingHmrUpdateTypes.get(realPath),
|
|
2080
2460
|
extractCss,
|
|
2081
2461
|
filePath: realPath
|
|
2082
|
-
}
|
|
2462
|
+
};
|
|
2463
|
+
if (compiled.css && !hasDelegated && embedsInlineCss(compiled, outputOptions)) compiled = {
|
|
2464
|
+
...compiled,
|
|
2465
|
+
css: resolveCssImports(compiled.css, realPath, state.cssAliasRules, state.server !== null, currentBase)
|
|
2466
|
+
};
|
|
2467
|
+
const generatedOutput = generateOutput(compiled, outputOptions);
|
|
2083
2468
|
const normalizedOutput = rewriteImportMetaGlobBase(rewriteStaticAssetUrls(rewriteDynamicTemplateImports(isSsr ? normalizeVueServerRendererImport(generatedOutput) : generatedOutput, state.dynamicImportAliasRules), state.dynamicImportAliasRules), realPath, state.root);
|
|
2084
2469
|
if (!loadOptions?.ssr) state.pendingHmrUpdateTypes.delete(realPath);
|
|
2085
2470
|
return {
|
|
@@ -2232,10 +2617,20 @@ async function transformHook(state, code, id, options) {
|
|
|
2232
2617
|
//#region src/plugin/hmr.ts
|
|
2233
2618
|
const VIZE_COMPONENTS_CSS_BASENAME = "vize-components.css";
|
|
2234
2619
|
const VIZE_COMPONENTS_CSS_FILE = `assets/${VIZE_COMPONENTS_CSS_BASENAME}`;
|
|
2620
|
+
/**
|
|
2621
|
+
* The cached SFCs that pulled `dependencyFile` in through
|
|
2622
|
+
* `<script src>` / `<template src>` / `<style src>`.
|
|
2623
|
+
*
|
|
2624
|
+
* This runs as the first statement of every hot update, before the `.vue` fast
|
|
2625
|
+
* path, so editing an ordinary SFC pays for it too. It used to walk both caches
|
|
2626
|
+
* end to end with a `path.resolve` per dependency, which made HMR latency grow
|
|
2627
|
+
* with the number of components in the project; the caches now carry a reverse
|
|
2628
|
+
* index that answers it in constant time. See `compiled-module-cache.ts`.
|
|
2629
|
+
*/
|
|
2235
2630
|
function getVueFilesDependingOn(state, dependencyFile) {
|
|
2236
2631
|
const normalizedDependency = path.resolve(dependencyFile);
|
|
2237
2632
|
const owners = /* @__PURE__ */ new Set();
|
|
2238
|
-
for (const cache of [state.cache, state.ssrCache]) for (const
|
|
2633
|
+
for (const cache of [state.cache, state.ssrCache]) for (const vueFile of ownersOfDependency(cache, normalizedDependency)) owners.add(vueFile);
|
|
2239
2634
|
return [...owners];
|
|
2240
2635
|
}
|
|
2241
2636
|
function unique(values) {
|
|
@@ -2420,7 +2815,22 @@ function normalizeVirtualStyleId(id) {
|
|
|
2420
2815
|
if (!withoutPrefix.includes("?vue")) return id;
|
|
2421
2816
|
return withoutPrefix.replace(/\.module\.\w+$/, "").replace(/\.\w+$/, "");
|
|
2422
2817
|
}
|
|
2818
|
+
/**
|
|
2819
|
+
* String pre-gate for {@link transformScopedPreprocessorCss} (#3427).
|
|
2820
|
+
*
|
|
2821
|
+
* The post-transform plugin's `transform` hook sees every module in the graph,
|
|
2822
|
+
* and without this every one of them crossed the NAPI boundary to be told it is
|
|
2823
|
+
* not a style query. The native `isVueStyleQuery` is
|
|
2824
|
+
* `query.contains("vue&type=style") || query.contains("vue=&type=style")`, both
|
|
2825
|
+
* of which contain `type=style`; the query is a substring of the id, and
|
|
2826
|
+
* `normalizeVirtualStyleId` only ever deletes characters, so a normalized id
|
|
2827
|
+
* that classifies as a style query implies `type=style` in the raw id.
|
|
2828
|
+
*/
|
|
2829
|
+
function mayBeVueStyleQuery(id) {
|
|
2830
|
+
return id.includes("type=style");
|
|
2831
|
+
}
|
|
2423
2832
|
function transformScopedPreprocessorCss(code, id) {
|
|
2833
|
+
if (!mayBeVueStyleQuery(id)) return null;
|
|
2424
2834
|
const request = classifyVitePluginRequest(normalizeVirtualStyleId(id));
|
|
2425
2835
|
if (!request.isVueStyleQuery || !request.styleScoped || !request.styleLang || request.styleLang === "css") return null;
|
|
2426
2836
|
return scopeCssForPipeline(code, request.styleScoped);
|
|
@@ -2926,8 +3336,8 @@ function resolveCompatibilityOptions(options, compilerConfig = {}) {
|
|
|
2926
3336
|
function vize(options = {}) {
|
|
2927
3337
|
if (isLegacyVueCompatibilityMode(options)) return [createLegacyVueCompatibilityPlugin(options)];
|
|
2928
3338
|
const state = {
|
|
2929
|
-
cache:
|
|
2930
|
-
ssrCache:
|
|
3339
|
+
cache: new CompiledModuleCache(),
|
|
3340
|
+
ssrCache: new CompiledModuleCache(),
|
|
2931
3341
|
collectedCss: /* @__PURE__ */ new Map(),
|
|
2932
3342
|
precompileMetadata: /* @__PURE__ */ new Map(),
|
|
2933
3343
|
pendingHmrUpdateTypes: /* @__PURE__ */ new Map(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizejs/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.310.0",
|
|
4
4
|
"description": "High-performance native Vite plugin for Vue SFC compilation powered by Vize",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"compiler",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@vizejs/native": "0.
|
|
48
|
+
"@vizejs/native": "0.310.0",
|
|
49
49
|
"oxc-parser": "0.133.0",
|
|
50
50
|
"tinyglobby": "0.2.16",
|
|
51
|
-
"vize": "0.
|
|
51
|
+
"vize": "0.310.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "25.9.2",
|