@vizejs/vite-plugin 0.22.0 → 0.23.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.js +140 -34
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -8,6 +8,13 @@ import { transformWithOxc } from "vite";
|
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
9
|
|
|
10
10
|
//#region src/hmr.ts
|
|
11
|
+
function didHashChange(prevHash, nextHash) {
|
|
12
|
+
return prevHash !== nextHash;
|
|
13
|
+
}
|
|
14
|
+
function hasHmrChanges(prev, next) {
|
|
15
|
+
if (!prev) return true;
|
|
16
|
+
return didHashChange(prev.scriptHash, next.scriptHash) || didHashChange(prev.templateHash, next.templateHash) || didHashChange(prev.styleHash, next.styleHash);
|
|
17
|
+
}
|
|
11
18
|
/**
|
|
12
19
|
* Detect the type of HMR update needed based on content hash changes.
|
|
13
20
|
*
|
|
@@ -17,10 +24,10 @@ import { createRequire } from "node:module";
|
|
|
17
24
|
*/
|
|
18
25
|
function detectHmrUpdateType(prev, next) {
|
|
19
26
|
if (!prev) return "full-reload";
|
|
20
|
-
const scriptChanged = prev.scriptHash
|
|
27
|
+
const scriptChanged = didHashChange(prev.scriptHash, next.scriptHash);
|
|
21
28
|
if (scriptChanged) return "full-reload";
|
|
22
|
-
const templateChanged = prev.templateHash
|
|
23
|
-
const styleChanged = prev.styleHash
|
|
29
|
+
const templateChanged = didHashChange(prev.templateHash, next.templateHash);
|
|
30
|
+
const styleChanged = didHashChange(prev.styleHash, next.styleHash);
|
|
24
31
|
if (styleChanged && !templateChanged) return "style-only";
|
|
25
32
|
if (templateChanged) return "template-only";
|
|
26
33
|
return "full-reload";
|
|
@@ -158,6 +165,9 @@ function hasDelegatedStyles(compiled) {
|
|
|
158
165
|
if (!compiled.styles) return false;
|
|
159
166
|
return compiled.styles.some((s) => needsPreprocessor(s) || isCssModule(s));
|
|
160
167
|
}
|
|
168
|
+
function supportsTemplateOnlyHmr(output) {
|
|
169
|
+
return /(?:^|\n)(?:_sfc_main|__sfc__)\.render\s*=\s*render\b/m.test(output);
|
|
170
|
+
}
|
|
161
171
|
function generateScopeId(filename) {
|
|
162
172
|
const hash = createHash("sha256").update(filename).digest("hex");
|
|
163
173
|
return hash.slice(0, 8);
|
|
@@ -176,6 +186,7 @@ function generateOutput(compiled, options) {
|
|
|
176
186
|
let output = compiled.code;
|
|
177
187
|
const exportDefaultRegex = /^export default /m;
|
|
178
188
|
const hasExportDefault = exportDefaultRegex.test(output);
|
|
189
|
+
const hasNamedRenderExport = /^export function render\b/m.test(output);
|
|
179
190
|
const hasSfcMainDefined = /\bconst\s+_sfc_main\s*=/.test(output);
|
|
180
191
|
if (hasExportDefault && !hasSfcMainDefined) {
|
|
181
192
|
output = output.replace(exportDefaultRegex, "const _sfc_main = ");
|
|
@@ -183,6 +194,11 @@ function generateOutput(compiled, options) {
|
|
|
183
194
|
output += "\nexport default _sfc_main;";
|
|
184
195
|
} else if (hasExportDefault && hasSfcMainDefined) {
|
|
185
196
|
if (compiled.hasScoped && compiled.scopeId) output = output.replace(/^export default _sfc_main/m, `_sfc_main.__scopeId = "data-v-${compiled.scopeId}";\nexport default _sfc_main`);
|
|
197
|
+
} else if (!hasExportDefault && !hasSfcMainDefined && hasNamedRenderExport) {
|
|
198
|
+
output += "\nconst _sfc_main = {};";
|
|
199
|
+
if (compiled.hasScoped && compiled.scopeId) output += `\n_sfc_main.__scopeId = "data-v-${compiled.scopeId}";`;
|
|
200
|
+
output += "\n_sfc_main.render = render;";
|
|
201
|
+
output += "\nexport default _sfc_main;";
|
|
186
202
|
}
|
|
187
203
|
const useDelegatedStyles = hasDelegatedStyles(compiled) && filePath;
|
|
188
204
|
if (useDelegatedStyles) {
|
|
@@ -241,7 +257,10 @@ const __vize_css_id__ = ${cssId};
|
|
|
241
257
|
})();
|
|
242
258
|
${output}`;
|
|
243
259
|
}
|
|
244
|
-
if (!isProduction && isDev && hasExportDefault)
|
|
260
|
+
if (!isProduction && isDev && hasExportDefault) {
|
|
261
|
+
const effectiveHmrUpdateType = hmrUpdateType === "template-only" && !supportsTemplateOnlyHmr(output) ? "full-reload" : hmrUpdateType ?? "full-reload";
|
|
262
|
+
output += generateHmrCode(compiled.scopeId, effectiveHmrUpdateType);
|
|
263
|
+
}
|
|
245
264
|
return output;
|
|
246
265
|
}
|
|
247
266
|
|
|
@@ -491,6 +510,9 @@ function compileFile(filePath, cache, options, source) {
|
|
|
491
510
|
css: result.css,
|
|
492
511
|
scopeId,
|
|
493
512
|
hasScoped,
|
|
513
|
+
templateHash: result.templateHash,
|
|
514
|
+
styleHash: result.styleHash,
|
|
515
|
+
scriptHash: result.scriptHash,
|
|
494
516
|
styles
|
|
495
517
|
};
|
|
496
518
|
cache.set(filePath, compiled);
|
|
@@ -533,6 +555,23 @@ function compileBatch(files, cache, options) {
|
|
|
533
555
|
|
|
534
556
|
//#endregion
|
|
535
557
|
//#region src/plugin/state.ts
|
|
558
|
+
function hasFileMetadataChanged(previous, next) {
|
|
559
|
+
return previous === void 0 || previous.mtimeMs !== next.mtimeMs || previous.size !== next.size;
|
|
560
|
+
}
|
|
561
|
+
function diffPrecompileFiles(files, currentMetadata, previousMetadata) {
|
|
562
|
+
const changedFiles = [];
|
|
563
|
+
const seenFiles = new Set(files);
|
|
564
|
+
for (const file of files) {
|
|
565
|
+
const metadata = currentMetadata.get(file);
|
|
566
|
+
if (!metadata || hasFileMetadataChanged(previousMetadata.get(file), metadata)) changedFiles.push(file);
|
|
567
|
+
}
|
|
568
|
+
const deletedFiles = [];
|
|
569
|
+
for (const file of previousMetadata.keys()) if (!seenFiles.has(file)) deletedFiles.push(file);
|
|
570
|
+
return {
|
|
571
|
+
changedFiles,
|
|
572
|
+
deletedFiles
|
|
573
|
+
};
|
|
574
|
+
}
|
|
536
575
|
/**
|
|
537
576
|
* Pre-compile all Vue files matching scan patterns.
|
|
538
577
|
*/
|
|
@@ -543,9 +582,32 @@ async function compileAll(state) {
|
|
|
543
582
|
ignore: state.ignorePatterns,
|
|
544
583
|
absolute: true
|
|
545
584
|
});
|
|
546
|
-
|
|
547
|
-
const fileContents = [];
|
|
585
|
+
const currentMetadata = new Map();
|
|
548
586
|
for (const file of files) try {
|
|
587
|
+
const stat = fs.statSync(file);
|
|
588
|
+
currentMetadata.set(file, {
|
|
589
|
+
mtimeMs: stat.mtimeMs,
|
|
590
|
+
size: stat.size
|
|
591
|
+
});
|
|
592
|
+
} catch (e) {
|
|
593
|
+
state.logger.error(`Failed to stat ${file}:`, e);
|
|
594
|
+
}
|
|
595
|
+
const { changedFiles, deletedFiles } = diffPrecompileFiles(files, currentMetadata, state.precompileMetadata);
|
|
596
|
+
const cachedFileCount = files.length - changedFiles.length;
|
|
597
|
+
for (const file of deletedFiles) {
|
|
598
|
+
state.cache.delete(file);
|
|
599
|
+
state.collectedCss.delete(file);
|
|
600
|
+
state.precompileMetadata.delete(file);
|
|
601
|
+
state.pendingHmrUpdateTypes.delete(file);
|
|
602
|
+
}
|
|
603
|
+
state.logger.info(`Pre-compiling ${files.length} Vue files... (${changedFiles.length} changed, ${cachedFileCount} cached, ${deletedFiles.length} removed)`);
|
|
604
|
+
if (changedFiles.length === 0) {
|
|
605
|
+
const elapsed$1 = (performance.now() - startTime).toFixed(2);
|
|
606
|
+
state.logger.info(`Pre-compilation complete: cache reused (${elapsed$1}ms)`);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
const fileContents = [];
|
|
610
|
+
for (const file of changedFiles) try {
|
|
549
611
|
const source = fs.readFileSync(file, "utf-8");
|
|
550
612
|
fileContents.push({
|
|
551
613
|
path: file,
|
|
@@ -555,21 +617,26 @@ async function compileAll(state) {
|
|
|
555
617
|
state.logger.error(`Failed to read ${file}:`, e);
|
|
556
618
|
}
|
|
557
619
|
const result = compileBatch(fileContents, state.cache, { ssr: state.mergedOptions.ssr ?? false });
|
|
558
|
-
|
|
559
|
-
|
|
620
|
+
for (const file of changedFiles) {
|
|
621
|
+
state.collectedCss.delete(file);
|
|
622
|
+
state.pendingHmrUpdateTypes.delete(file);
|
|
623
|
+
}
|
|
624
|
+
for (const fileResult of result.results) {
|
|
625
|
+
const metadata = currentMetadata.get(fileResult.path);
|
|
626
|
+
if (fileResult.errors.length > 0) {
|
|
627
|
+
state.cache.delete(fileResult.path);
|
|
628
|
+
state.collectedCss.delete(fileResult.path);
|
|
629
|
+
state.precompileMetadata.delete(fileResult.path);
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (metadata) state.precompileMetadata.set(fileResult.path, metadata);
|
|
633
|
+
if (state.isProduction && fileResult.css) {
|
|
560
634
|
const cached = state.cache.get(fileResult.path);
|
|
561
|
-
|
|
562
|
-
"scss",
|
|
563
|
-
"sass",
|
|
564
|
-
"less",
|
|
565
|
-
"stylus",
|
|
566
|
-
"styl"
|
|
567
|
-
].includes(s.lang) || s.module !== false);
|
|
568
|
-
if (!hasDelegated) state.collectedCss.set(fileResult.path, resolveCssImports(fileResult.css, fileResult.path, state.cssAliasRules, false));
|
|
635
|
+
if (cached && !hasDelegatedStyles(cached)) state.collectedCss.set(fileResult.path, resolveCssImports(fileResult.css, fileResult.path, state.cssAliasRules, false));
|
|
569
636
|
}
|
|
570
637
|
}
|
|
571
638
|
const elapsed = (performance.now() - startTime).toFixed(2);
|
|
572
|
-
state.logger.info(`Pre-compilation complete: ${result.successCount}
|
|
639
|
+
state.logger.info(`Pre-compilation complete: ${result.successCount} recompiled, ${cachedFileCount} reused, ${result.failedCount} failed (${elapsed}ms, native batch: ${result.timeMs.toFixed(2)}ms)`);
|
|
573
640
|
}
|
|
574
641
|
|
|
575
642
|
//#endregion
|
|
@@ -741,6 +808,24 @@ async function resolveIdHook(ctx, state, id, importer) {
|
|
|
741
808
|
|
|
742
809
|
//#endregion
|
|
743
810
|
//#region src/plugin/load.ts
|
|
811
|
+
const SERVER_PLACEHOLDER_CODE = `import { createElementBlock, defineComponent } from "vue";
|
|
812
|
+
export default defineComponent({
|
|
813
|
+
name: "ServerPlaceholder",
|
|
814
|
+
render() {
|
|
815
|
+
return createElementBlock("div");
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
`;
|
|
819
|
+
function getBoundaryPlaceholderCode(realPath, ssr) {
|
|
820
|
+
if (ssr && realPath.endsWith(".client.vue")) return SERVER_PLACEHOLDER_CODE;
|
|
821
|
+
if (!ssr && realPath.endsWith(".server.vue")) return SERVER_PLACEHOLDER_CODE;
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
function getOxcDumpPath(root, realPath) {
|
|
825
|
+
const dumpDir = path.resolve(root || process.cwd(), "__agent_only", "oxc-dumps");
|
|
826
|
+
fs.mkdirSync(dumpDir, { recursive: true });
|
|
827
|
+
return path.join(dumpDir, `vize-oxc-error-${path.basename(realPath)}.ts`);
|
|
828
|
+
}
|
|
744
829
|
function loadHook(state, id, loadOptions) {
|
|
745
830
|
const currentBase = loadOptions?.ssr ? state.serverViteBase : state.clientViteBase;
|
|
746
831
|
if (id === RESOLVED_CSS_MODULE) {
|
|
@@ -807,6 +892,14 @@ function loadHook(state, id, loadOptions) {
|
|
|
807
892
|
state.logger.log(`load: skipping non-vue virtual module ${realPath}`);
|
|
808
893
|
return null;
|
|
809
894
|
}
|
|
895
|
+
const placeholderCode = getBoundaryPlaceholderCode(realPath, !!loadOptions?.ssr);
|
|
896
|
+
if (placeholderCode) {
|
|
897
|
+
state.logger.log(`load: using boundary placeholder for ${realPath}`);
|
|
898
|
+
return {
|
|
899
|
+
code: placeholderCode,
|
|
900
|
+
map: null
|
|
901
|
+
};
|
|
902
|
+
}
|
|
810
903
|
let compiled = state.cache.get(realPath);
|
|
811
904
|
if (!compiled && fs.existsSync(realPath)) {
|
|
812
905
|
state.logger.log(`load: on-demand compiling ${realPath}`);
|
|
@@ -816,13 +909,8 @@ function loadHook(state, id, loadOptions) {
|
|
|
816
909
|
});
|
|
817
910
|
}
|
|
818
911
|
if (compiled) {
|
|
819
|
-
const hasDelegated = compiled
|
|
820
|
-
|
|
821
|
-
"sass",
|
|
822
|
-
"less",
|
|
823
|
-
"stylus",
|
|
824
|
-
"styl"
|
|
825
|
-
].includes(s.lang) || s.module !== false);
|
|
912
|
+
const hasDelegated = hasDelegatedStyles(compiled);
|
|
913
|
+
const pendingHmrUpdateType = loadOptions?.ssr ? void 0 : state.pendingHmrUpdateTypes.get(realPath);
|
|
826
914
|
if (compiled.css && !hasDelegated) compiled = {
|
|
827
915
|
...compiled,
|
|
828
916
|
css: resolveCssImports(compiled.css, realPath, state.cssAliasRules, state.server !== null, currentBase)
|
|
@@ -830,9 +918,11 @@ function loadHook(state, id, loadOptions) {
|
|
|
830
918
|
const output = rewriteStaticAssetUrls(rewriteDynamicTemplateImports(generateOutput(compiled, {
|
|
831
919
|
isProduction: state.isProduction,
|
|
832
920
|
isDev: state.server !== null,
|
|
921
|
+
hmrUpdateType: pendingHmrUpdateType,
|
|
833
922
|
extractCss: state.extractCss,
|
|
834
923
|
filePath: realPath
|
|
835
924
|
}), state.dynamicImportAliasRules), state.dynamicImportAliasRules);
|
|
925
|
+
if (!loadOptions?.ssr) state.pendingHmrUpdateTypes.delete(realPath);
|
|
836
926
|
return {
|
|
837
927
|
code: output,
|
|
838
928
|
map: null
|
|
@@ -868,7 +958,7 @@ async function transformHook(state, code, id, options) {
|
|
|
868
958
|
};
|
|
869
959
|
} catch (e) {
|
|
870
960
|
state.logger.error(`transformWithOxc failed for ${realPath}:`, e);
|
|
871
|
-
const dumpPath =
|
|
961
|
+
const dumpPath = getOxcDumpPath(state.root, realPath);
|
|
872
962
|
fs.writeFileSync(dumpPath, code, "utf-8");
|
|
873
963
|
state.logger.error(`Dumped failing code to ${dumpPath}`);
|
|
874
964
|
return {
|
|
@@ -892,17 +982,25 @@ async function handleHotUpdateHook(state, ctx) {
|
|
|
892
982
|
ssr: state.mergedOptions?.ssr ?? false
|
|
893
983
|
}, source);
|
|
894
984
|
const newCompiled = state.cache.get(file);
|
|
985
|
+
try {
|
|
986
|
+
const stat = fs.statSync(file);
|
|
987
|
+
state.precompileMetadata.set(file, {
|
|
988
|
+
mtimeMs: stat.mtimeMs,
|
|
989
|
+
size: stat.size
|
|
990
|
+
});
|
|
991
|
+
} catch {
|
|
992
|
+
state.precompileMetadata.delete(file);
|
|
993
|
+
}
|
|
994
|
+
if (!hasHmrChanges(prevCompiled, newCompiled)) {
|
|
995
|
+
state.pendingHmrUpdateTypes.delete(file);
|
|
996
|
+
state.logger.log(`Re-compiled: ${path.relative(state.root, file)} (no-op)`);
|
|
997
|
+
return [];
|
|
998
|
+
}
|
|
895
999
|
const updateType = detectHmrUpdateType(prevCompiled, newCompiled);
|
|
896
1000
|
state.logger.log(`Re-compiled: ${path.relative(state.root, file)} (${updateType})`);
|
|
897
1001
|
const virtualId = toVirtualId(file);
|
|
898
1002
|
const modules = server.moduleGraph.getModulesByFile(virtualId) ?? server.moduleGraph.getModulesByFile(file);
|
|
899
|
-
const hasDelegated = newCompiled
|
|
900
|
-
"scss",
|
|
901
|
-
"sass",
|
|
902
|
-
"less",
|
|
903
|
-
"stylus",
|
|
904
|
-
"styl"
|
|
905
|
-
].includes(s.lang) || s.module !== false);
|
|
1003
|
+
const hasDelegated = hasDelegatedStyles(newCompiled);
|
|
906
1004
|
if (hasDelegated && updateType === "style-only") {
|
|
907
1005
|
const affectedModules = new Set();
|
|
908
1006
|
for (const block of newCompiled.styles ?? []) {
|
|
@@ -917,10 +1015,12 @@ async function handleHotUpdateHook(state, ctx) {
|
|
|
917
1015
|
const styleMods = server.moduleGraph.getModulesByFile(styleId);
|
|
918
1016
|
if (styleMods) for (const mod of styleMods) affectedModules.add(mod);
|
|
919
1017
|
}
|
|
920
|
-
if (modules) for (const mod of modules) affectedModules.add(mod);
|
|
921
1018
|
if (affectedModules.size > 0) return [...affectedModules];
|
|
1019
|
+
if (modules) return [...modules];
|
|
1020
|
+
return [];
|
|
922
1021
|
}
|
|
923
1022
|
if (updateType === "style-only" && newCompiled.css && !hasDelegated) {
|
|
1023
|
+
state.pendingHmrUpdateTypes.delete(file);
|
|
924
1024
|
server.ws.send({
|
|
925
1025
|
type: "custom",
|
|
926
1026
|
event: "vize:update",
|
|
@@ -932,7 +1032,11 @@ async function handleHotUpdateHook(state, ctx) {
|
|
|
932
1032
|
});
|
|
933
1033
|
return [];
|
|
934
1034
|
}
|
|
935
|
-
if (modules)
|
|
1035
|
+
if (modules) {
|
|
1036
|
+
state.pendingHmrUpdateTypes.set(file, updateType);
|
|
1037
|
+
return [...modules];
|
|
1038
|
+
}
|
|
1039
|
+
state.pendingHmrUpdateTypes.delete(file);
|
|
936
1040
|
} catch (e) {
|
|
937
1041
|
state.logger.error(`Re-compilation failed for ${file}:`, e);
|
|
938
1042
|
}
|
|
@@ -1019,6 +1123,8 @@ function vize(options = {}) {
|
|
|
1019
1123
|
const state = {
|
|
1020
1124
|
cache: new Map(),
|
|
1021
1125
|
collectedCss: new Map(),
|
|
1126
|
+
precompileMetadata: new Map(),
|
|
1127
|
+
pendingHmrUpdateTypes: new Map(),
|
|
1022
1128
|
isProduction: false,
|
|
1023
1129
|
root: "",
|
|
1024
1130
|
clientViteBase: "/",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizejs/vite-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.0",
|
|
4
4
|
"description": "High-performance native Vite plugin for Vue SFC compilation powered by Vize",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
45
|
"tinyglobby": "^0.2.0",
|
|
46
|
-
"@vizejs/native": "0.
|
|
46
|
+
"@vizejs/native": "0.23.0"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsdown",
|