@vizejs/vite-plugin 0.97.0 → 0.101.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.d.mts CHANGED
@@ -546,6 +546,7 @@ interface VizeOptions {
546
546
  root?: string;
547
547
  /**
548
548
  * Glob patterns to scan for .vue files during pre-compilation
549
+ * Use an empty array to disable startup pre-compilation and compile on demand.
549
550
  * @default ['**\/*.vue']
550
551
  */
551
552
  scanPatterns?: string[];
@@ -619,6 +620,13 @@ interface DynamicImportAliasRule {
619
620
  }
620
621
  //#endregion
621
622
  //#region src/transform.d.ts
623
+ /**
624
+ * Rewrite static asset URLs in compiled template output.
625
+ *
626
+ * Transforms property values like `src: "@/assets/logo.svg"` into import
627
+ * statements hoisted to the top of the module, so Vite's module resolution
628
+ * pipeline handles alias expansion and asset hashing in both dev and build.
629
+ */
622
630
  declare function rewriteStaticAssetUrls(code: string, aliasRules: DynamicImportAliasRule[]): string;
623
631
  //#endregion
624
632
  //#region src/plugin/index.d.ts
package/dist/index.mjs CHANGED
@@ -1,20 +1,16 @@
1
1
  import { createRequire } from "node:module";
2
2
  import fs from "node:fs";
3
3
  import { createHash } from "node:crypto";
4
- import path from "node:path";
5
4
  import * as native from "@vizejs/native";
6
- import { classifyVitePluginRequest } from "@vizejs/native";
5
+ import { applyViteDefineReplacements, classifyVitePluginRequest, createViteBareImportBases, createViteBareImportCandidates, createViteVirtualId, detectViteHmrUpdateType, generateViteHmrCode, hasViteHmrChanges, isViteBareSpecifier, normalizeViteRequireBase, normalizeViteResolvedVuePath, resolveViteAliasRequest, resolveViteCssImports, resolveViteRelativeImport, resolveViteVuePath, rewriteViteDynamicTemplateImports, rewriteViteStaticAssetUrls, scopeViteCssForPipeline, shouldApplyViteDefineInVirtualModule, splitViteIdQuery, toViteBrowserImportPrefix } from "@vizejs/native";
7
6
  import { CONFIG_FILE_NAMES, defineConfig, loadConfig } from "vize";
8
7
  import { glob } from "tinyglobby";
8
+ import path from "node:path";
9
9
  import { pathToFileURL } from "node:url";
10
10
  import { transformWithOxc } from "vite";
11
11
  //#region src/hmr.ts
12
- function didHashChange(prevHash, nextHash) {
13
- return prevHash !== nextHash;
14
- }
15
12
  function hasHmrChanges(prev, next) {
16
- if (!prev) return true;
17
- return didHashChange(prev.scriptHash, next.scriptHash) || didHashChange(prev.templateHash, next.templateHash) || didHashChange(prev.styleHash, next.styleHash);
13
+ return hasViteHmrChanges(toHmrHashes(prev), toHmrHashes(next));
18
14
  }
19
15
  /**
20
16
  * Detect the type of HMR update needed based on content hash changes.
@@ -24,362 +20,44 @@ function hasHmrChanges(prev, next) {
24
20
  * @returns The type of HMR update needed
25
21
  */
26
22
  function detectHmrUpdateType(prev, next) {
27
- if (!prev) return "full-reload";
28
- if (didHashChange(prev.scriptHash, next.scriptHash)) return "full-reload";
29
- const templateChanged = didHashChange(prev.templateHash, next.templateHash);
30
- if (didHashChange(prev.styleHash, next.styleHash) && !templateChanged) return "style-only";
31
- if (templateChanged) return "template-only";
32
- return "full-reload";
23
+ return detectViteHmrUpdateType(toHmrHashes(prev), toHmrHashes(next));
33
24
  }
34
25
  /**
35
26
  * Generate HMR-aware code output based on update type.
36
27
  */
37
28
  function generateHmrCode(scopeId, updateType) {
38
- return `
39
- if (import.meta.hot) {
40
- _sfc_main.__hmrId = ${JSON.stringify(scopeId)};
41
- _sfc_main.__hmrUpdateType = ${JSON.stringify(updateType)};
42
-
43
- import.meta.hot.accept((mod) => {
44
- if (!mod) return;
45
- const { default: updated } = mod;
46
- if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
47
- const updateType = updated.__hmrUpdateType || 'full-reload';
48
- if (updateType === 'template-only') {
49
- __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render);
50
- } else {
51
- __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated);
52
- }
53
- }
54
- });
55
-
56
- import.meta.hot.on('vize:update', (data) => {
57
- if (data.id !== _sfc_main.__hmrId) return;
58
-
59
- if (data.type === 'style-only') {
60
- // Update styles without remounting component
61
- const styleId = 'vize-style-' + _sfc_main.__hmrId;
62
- const styleEl = document.getElementById(styleId);
63
- if (styleEl && data.css) {
64
- styleEl.textContent = data.css;
65
- }
66
- }
67
- });
68
-
69
- if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
70
- __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main);
71
- }
72
- }`;
29
+ return generateViteHmrCode(scopeId, updateType);
30
+ }
31
+ function toHmrHashes(module) {
32
+ return module ? {
33
+ scriptHash: module.scriptHash,
34
+ templateHash: module.templateHash,
35
+ styleHash: module.styleHash
36
+ } : void 0;
73
37
  }
74
38
  //#endregion
75
39
  //#region src/utils/css.ts
76
- const deepSelectorPattern = /:deep\(([^()]*(?:\([^()]*\))*[^()]*)\)/;
77
- const globalSelectorPattern = /:global\(([^()]*(?:\([^()]*\))*[^()]*)\)/g;
78
- const recursiveAtRules = new Set([
79
- "@container",
80
- "@layer",
81
- "@media",
82
- "@supports"
83
- ]);
84
40
  function scopeCssForPipeline(css, scopeId) {
85
- return transformCssBlock(css, scopeId);
41
+ return scopeViteCssForPipeline(css, scopeId);
86
42
  }
87
43
  /**
88
44
  * Resolve CSS @import statements by inlining the imported files,
89
45
  * then resolve @custom-media definitions within the combined CSS.
90
- *
91
- * This is necessary because Vize embeds CSS as a JS string via
92
- * document.createElement('style'), bypassing Vite's CSS pipeline.
93
46
  */
94
47
  function resolveCssImports(css, importer, aliasRules, isDev, devUrlBase) {
95
- const customMedia = /* @__PURE__ */ new Map();
96
- const importRegex = /^@import\s+(?:"([^"]+)"|'([^']+)');?\s*$/gm;
97
- let result = css;
98
- result = result.replace(importRegex, (_match, dqPath, sqPath) => {
99
- const importPath = dqPath || sqPath;
100
- if (!importPath) return _match;
101
- const resolved = resolveCssPath(importPath, importer, aliasRules);
102
- if (!resolved || !fs.existsSync(resolved)) return _match;
103
- try {
104
- const content = fs.readFileSync(resolved, "utf-8");
105
- parseCustomMedia(content, customMedia);
106
- return content;
107
- } catch {
108
- return _match;
109
- }
110
- });
111
- parseCustomMedia(result, customMedia);
112
- result = result.replace(/^@custom-media\s+[^;]+;\s*$/gm, "");
113
- if (customMedia.size > 0) for (const [name, query] of customMedia) {
114
- const escaped = name.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
115
- result = result.replace(new RegExp(`\\(${escaped}\\)`, "g"), query);
116
- }
117
- if (isDev) result = result.replace(/url\(\s*(["']?)([^"')]+)\1\s*\)/g, (_match, quote, urlPath) => {
118
- const trimmed = urlPath.trim();
119
- if (trimmed.startsWith("data:") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("/@fs/")) return _match;
120
- const resolved = resolveCssPath(trimmed, importer, aliasRules);
121
- if (resolved && fs.existsSync(resolved)) {
122
- const normalized = resolved.replace(/\\/g, "/");
123
- const base = devUrlBase ?? "/";
124
- return `url("${base.endsWith("/") ? base : base + "/"}@fs${normalized}")`;
125
- }
126
- return _match;
127
- });
128
- result = result.replace(new RegExp(deepSelectorPattern.source, "g"), "$1");
129
- result = result.replace(/\n{3,}/g, "\n\n");
130
- return result;
131
- }
132
- function transformCssBlock(css, scopeId) {
133
- let output = "";
134
- let cursor = 0;
135
- while (cursor < css.length) {
136
- const brace = findNextTopLevelBrace(css, cursor);
137
- if (brace === -1) {
138
- output += css.slice(cursor);
139
- break;
140
- }
141
- const end = findMatchingBrace(css, brace);
142
- if (end === -1) {
143
- output += css.slice(cursor);
144
- break;
145
- }
146
- const header = css.slice(cursor, brace);
147
- const body = css.slice(brace + 1, end);
148
- const leadingLength = header.search(/\S/);
149
- const leading = leadingLength === -1 ? header : header.slice(0, leadingLength);
150
- const statement = leadingLength === -1 ? "" : header.slice(leadingLength);
151
- output += leading;
152
- if (statement.trimStart().startsWith("@")) {
153
- output += statement;
154
- output += "{";
155
- output += shouldRecurseAtRule(statement) ? transformCssBlock(body, scopeId) : body;
156
- output += "}";
157
- } else {
158
- output += scopeSelectorList(statement, scopeId);
159
- output += "{";
160
- output += body;
161
- output += "}";
162
- }
163
- cursor = end + 1;
164
- }
165
- return output;
166
- }
167
- function shouldRecurseAtRule(statement) {
168
- const name = statement.trimStart().split(/\s+/, 1)[0];
169
- return name !== void 0 && recursiveAtRules.has(name);
170
- }
171
- function findNextTopLevelBrace(css, start) {
172
- let parenDepth = 0;
173
- let bracketDepth = 0;
174
- let quote = null;
175
- let inComment = false;
176
- for (let index = start; index < css.length; index += 1) {
177
- const char = css[index];
178
- const next = css[index + 1];
179
- if (inComment) {
180
- if (char === "*" && next === "/") {
181
- inComment = false;
182
- index += 1;
183
- }
184
- continue;
185
- }
186
- if (quote !== null) {
187
- if (char === "\\") index += 1;
188
- else if (char === quote) quote = null;
189
- continue;
190
- }
191
- if (char === "/" && next === "*") {
192
- inComment = true;
193
- index += 1;
194
- continue;
195
- }
196
- if (char === "'" || char === "\"") {
197
- quote = char;
198
- continue;
199
- }
200
- if (char === "(") parenDepth += 1;
201
- else if (char === ")" && parenDepth > 0) parenDepth -= 1;
202
- else if (char === "[") bracketDepth += 1;
203
- else if (char === "]" && bracketDepth > 0) bracketDepth -= 1;
204
- else if (char === "{" && parenDepth === 0 && bracketDepth === 0) return index;
205
- }
206
- return -1;
207
- }
208
- function findMatchingBrace(css, start) {
209
- let depth = 0;
210
- let quote = null;
211
- let inComment = false;
212
- for (let index = start; index < css.length; index += 1) {
213
- const char = css[index];
214
- const next = css[index + 1];
215
- if (inComment) {
216
- if (char === "*" && next === "/") {
217
- inComment = false;
218
- index += 1;
219
- }
220
- continue;
221
- }
222
- if (quote !== null) {
223
- if (char === "\\") index += 1;
224
- else if (char === quote) quote = null;
225
- continue;
226
- }
227
- if (char === "/" && next === "*") {
228
- inComment = true;
229
- index += 1;
230
- continue;
231
- }
232
- if (char === "'" || char === "\"") {
233
- quote = char;
234
- continue;
235
- }
236
- if (char === "{") depth += 1;
237
- else if (char === "}") {
238
- depth -= 1;
239
- if (depth === 0) return index;
240
- }
241
- }
242
- return -1;
243
- }
244
- function scopeSelectorList(selectorList, scopeId) {
245
- return splitSelectorList(selectorList).map((selector) => scopeSelector(selector, scopeId)).join(",");
246
- }
247
- function splitSelectorList(selectorList) {
248
- const selectors = [];
249
- let start = 0;
250
- let parenDepth = 0;
251
- let bracketDepth = 0;
252
- let quote = null;
253
- for (let index = 0; index < selectorList.length; index += 1) {
254
- const char = selectorList[index];
255
- if (quote !== null) {
256
- if (char === "\\") index += 1;
257
- else if (char === quote) quote = null;
258
- continue;
259
- }
260
- if (char === "'" || char === "\"") {
261
- quote = char;
262
- continue;
263
- }
264
- if (char === "(") parenDepth += 1;
265
- else if (char === ")" && parenDepth > 0) parenDepth -= 1;
266
- else if (char === "[") bracketDepth += 1;
267
- else if (char === "]" && bracketDepth > 0) bracketDepth -= 1;
268
- else if (char === "," && parenDepth === 0 && bracketDepth === 0) {
269
- selectors.push(selectorList.slice(start, index));
270
- start = index + 1;
271
- }
272
- }
273
- selectors.push(selectorList.slice(start));
274
- return selectors;
275
- }
276
- function scopeSelector(selector, scopeId) {
277
- const leadingLength = selector.search(/\S/);
278
- if (leadingLength === -1) return selector;
279
- const leading = selector.slice(0, leadingLength);
280
- const trailingLength = selector.match(/\s*$/)?.[0].length ?? 0;
281
- const bodyEnd = trailingLength === 0 ? selector.length : selector.length - trailingLength;
282
- const trailing = selector.slice(bodyEnd);
283
- let body = selector.slice(leadingLength, bodyEnd).replace(globalSelectorPattern, "$1");
284
- const deep = body.match(deepSelectorPattern);
285
- if (deep?.index !== void 0) {
286
- const before = body.slice(0, deep.index).trimEnd();
287
- const inner = deep[1] ?? "";
288
- const after = body.slice(deep.index + deep[0].length);
289
- body = `${before.length === 0 ? `[${scopeId}]` : addScopeToSelectorEnd(before, scopeId)} ${inner}${after}`;
290
- } else body = addScopeToSelectorEnd(body, scopeId);
291
- return leading + body + trailing;
292
- }
293
- function addScopeToSelectorEnd(selector, scopeId) {
294
- const targetStart = findLastCompoundStart(selector);
295
- const beforeTarget = selector.slice(0, targetStart);
296
- const target = selector.slice(targetStart);
297
- const insertAt = findScopeInsertPosition(target);
298
- return `${beforeTarget}${target.slice(0, insertAt)}[${scopeId}]${target.slice(insertAt)}`;
299
- }
300
- function findLastCompoundStart(selector) {
301
- let parenDepth = 0;
302
- let bracketDepth = 0;
303
- let quote = null;
304
- for (let index = selector.length - 1; index >= 0; index -= 1) {
305
- const char = selector[index];
306
- if (quote !== null) {
307
- if (char === quote) quote = null;
308
- continue;
309
- }
310
- if (char === "'" || char === "\"") {
311
- quote = char;
312
- continue;
313
- }
314
- if (char === ")") parenDepth += 1;
315
- else if (char === "(" && parenDepth > 0) parenDepth -= 1;
316
- else if (char === "]") bracketDepth += 1;
317
- else if (char === "[" && bracketDepth > 0) bracketDepth -= 1;
318
- else if (parenDepth === 0 && bracketDepth === 0 && (char === ">" || char === "+" || char === "~")) return index + 1;
319
- else if (parenDepth === 0 && bracketDepth === 0 && /\s/.test(char)) {
320
- while (index > 0 && /\s/.test(selector[index - 1])) index -= 1;
321
- return index + 1;
322
- }
323
- }
324
- return 0;
325
- }
326
- function findScopeInsertPosition(target) {
327
- let parenDepth = 0;
328
- let bracketDepth = 0;
329
- let quote = null;
330
- for (let index = 0; index < target.length; index += 1) {
331
- const char = target[index];
332
- if (quote !== null) {
333
- if (char === "\\") index += 1;
334
- else if (char === quote) quote = null;
335
- continue;
336
- }
337
- if (char === "'" || char === "\"") {
338
- quote = char;
339
- continue;
340
- }
341
- if (char === "(") parenDepth += 1;
342
- else if (char === ")" && parenDepth > 0) parenDepth -= 1;
343
- else if (char === "[") bracketDepth += 1;
344
- else if (char === "]" && bracketDepth > 0) bracketDepth -= 1;
345
- else if (char === ":" && parenDepth === 0 && bracketDepth === 0) return index;
346
- }
347
- return target.length;
348
- }
349
- function parseCustomMedia(css, map) {
350
- const re = /@custom-media\s+(--[\w-]+)\s+(.+?)\s*;/g;
351
- let m;
352
- while ((m = re.exec(css)) !== null) map.set(m[1], m[2]);
353
- }
354
- function resolveCssPath(importPath, importer, aliasRules) {
355
- for (const rule of aliasRules) {
356
- const resolved = resolveAliasPath(importPath, rule);
357
- if (resolved !== null) return path.resolve(resolved);
358
- }
359
- if (importPath.startsWith(".")) {
360
- const dir = path.dirname(importer);
361
- return path.resolve(dir, importPath);
362
- }
363
- if (path.isAbsolute(importPath)) return importPath;
364
- return null;
365
- }
366
- function resolveAliasPath(importPath, rule) {
367
- if (typeof rule.find !== "string") {
368
- const pattern = stableAliasPattern$1(rule.find);
369
- return pattern.test(importPath) ? importPath.replace(pattern, rule.replacement) : null;
370
- }
371
- const suffix = matchedAliasSuffix(importPath, rule.find);
372
- if (suffix !== null) return path.join(rule.replacement, suffix);
373
- return null;
374
- }
375
- function stableAliasPattern$1(pattern) {
376
- return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
377
- }
378
- function matchedAliasSuffix(importPath, find) {
379
- if (importPath === find) return "";
380
- const prefix = find.endsWith("/") ? find : `${find}/`;
381
- if (!importPath.startsWith(prefix)) return null;
382
- return importPath.slice(prefix.length);
48
+ return resolveViteCssImports(css, importer, aliasRules.map(toNativeCssAliasRule), isDev, devUrlBase);
49
+ }
50
+ function toNativeCssAliasRule(rule) {
51
+ return typeof rule.find === "string" ? {
52
+ find: rule.find,
53
+ replacement: rule.replacement,
54
+ isRegex: false
55
+ } : {
56
+ find: rule.find.source,
57
+ replacement: rule.replacement,
58
+ isRegex: true,
59
+ flags: rule.find.flags.replace(/[gy]/g, "")
60
+ };
383
61
  }
384
62
  //#endregion
385
63
  //#region src/utils/index.ts
@@ -508,29 +186,16 @@ ${output}`;
508
186
  }
509
187
  return output;
510
188
  }
511
- const VIZE_SSR_PREFIX = "\0vize-ssr:";
512
189
  const RESOLVED_CSS_MODULE = "\0vize:all-styles.css";
513
190
  /** Create a virtual module ID from a real .vue file path */
514
191
  function toVirtualId(realPath, ssr = false) {
515
- return ssr ? `${VIZE_SSR_PREFIX}${realPath}.ts` : "\0" + realPath + ".ts";
516
- }
517
- function escapeRegExp(value) {
518
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
192
+ return createViteVirtualId(realPath, ssr);
519
193
  }
520
194
  function toBrowserImportPrefix(replacement) {
521
- const normalized = replacement.replace(/\\/g, "/");
522
- if (normalized.startsWith("/@fs/")) return normalized;
523
- if (path.isAbsolute(replacement) && fs.existsSync(replacement)) return `/@fs${normalized}`;
524
- return normalized;
195
+ return toViteBrowserImportPrefix(replacement);
525
196
  }
526
197
  function rewriteDynamicTemplateImports(code, aliasRules) {
527
- let rewritten = code;
528
- for (const rule of aliasRules) {
529
- const pattern = new RegExp(`\\bimport\\s*\\(\\s*\`${escapeRegExp(rule.fromPrefix)}`, "g");
530
- rewritten = rewritten.replace(pattern, `import(/* @vite-ignore */ \`${rule.toPrefix}`);
531
- }
532
- rewritten = rewritten.replace(/\bimport\s*\(\s*`/g, "import(/* @vite-ignore */ `");
533
- return rewritten;
198
+ return rewriteViteDynamicTemplateImports(code, aliasRules);
534
199
  }
535
200
  //#endregion
536
201
  //#region src/transform.ts
@@ -547,53 +212,11 @@ function rewriteDynamicTemplateImports(code, aliasRules) {
547
212
  * statements hoisted to the top of the module, so Vite's module resolution
548
213
  * pipeline handles alias expansion and asset hashing in both dev and build.
549
214
  */
550
- const SCRIPT_EXTENSIONS = /\.(js|mjs|cjs|ts|mts|cts|jsx|tsx)$/i;
551
215
  function rewriteStaticAssetUrls(code, aliasRules) {
552
- let rewritten = code;
553
- const imports = [];
554
- let counter = 0;
555
- for (const rule of aliasRules) {
556
- const pattern = new RegExp(`("?src"?\\s*:\\s*)(?:"(${escapeRegExp(rule.fromPrefix)}[^"]+)"|'(${escapeRegExp(rule.fromPrefix)}[^']+)')`, "g");
557
- rewritten = rewritten.replace(pattern, (match, prefix, dqPath, sqPath) => {
558
- const fullPath = dqPath || sqPath;
559
- if (fullPath && SCRIPT_EXTENSIONS.test(fullPath)) return match;
560
- const varName = `__vize_static_${counter++}`;
561
- imports.push(`import ${varName} from ${JSON.stringify(fullPath)};`);
562
- return `${prefix}${varName}`;
563
- });
564
- }
565
- if (imports.length > 0) rewritten = imports.join("\n") + "\n" + rewritten;
566
- return rewritten;
567
- }
568
- /**
569
- * Built-in Vite/Vue/Nuxt define keys that are normally handled by Vite's own
570
- * transform pipeline.
571
- */
572
- const BUILTIN_DEFINE_PREFIXES = [
573
- "import.meta.server",
574
- "import.meta.client",
575
- "import.meta.dev",
576
- "import.meta.test",
577
- "import.meta.prerender",
578
- "import.meta.env",
579
- "import.meta.hot",
580
- "__VUE_",
581
- "__NUXT_",
582
- "process.env"
583
- ];
584
- const VIRTUAL_MODULE_DEFINE_KEYS = new Set([
585
- "import.meta.server",
586
- "import.meta.client",
587
- "import.meta.dev",
588
- "import.meta.test",
589
- "import.meta.prerender"
590
- ]);
591
- function isBuiltinDefine(key) {
592
- return BUILTIN_DEFINE_PREFIXES.some((prefix) => key === prefix || key.startsWith(prefix + ".") || key.startsWith(prefix + "_"));
216
+ return rewriteViteStaticAssetUrls(code, aliasRules);
593
217
  }
594
218
  function shouldApplyDefineInVirtualModule(key) {
595
- if (VIRTUAL_MODULE_DEFINE_KEYS.has(key)) return true;
596
- return !isBuiltinDefine(key);
219
+ return shouldApplyViteDefineInVirtualModule(key);
597
220
  }
598
221
  /**
599
222
  * Apply Vite define replacements to code.
@@ -601,15 +224,10 @@ function shouldApplyDefineInVirtualModule(key) {
601
224
  * Uses word-boundary-aware matching to avoid replacing inside strings or partial matches.
602
225
  */
603
226
  function applyDefineReplacements(code, defines) {
604
- const sortedKeys = Object.keys(defines).sort((a, b) => b.length - a.length);
605
- let result = code;
606
- for (const key of sortedKeys) {
607
- if (!result.includes(key)) continue;
608
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
609
- const re = new RegExp(escaped + "(?![\\w$.])", "g");
610
- result = result.replace(re, defines[key]);
611
- }
612
- return result;
227
+ return applyViteDefineReplacements(code, Object.entries(defines).map(([key, value]) => ({
228
+ key,
229
+ value
230
+ })));
613
231
  }
614
232
  function createLogger(debug) {
615
233
  return {
@@ -688,10 +306,7 @@ function compileFile(filePath, cache, options, source) {
688
306
  * Returns per-file results with content hashes for HMR.
689
307
  */
690
308
  function compileBatch(files, cache, options) {
691
- const result = compileSfcBatchWithResults(files.map((f) => ({
692
- path: f.path,
693
- source: f.source
694
- })), buildCompileBatchOptions(options));
309
+ const result = compileSfcBatchWithResults(files, buildCompileBatchOptions(options));
695
310
  for (const fileResult of result.results) {
696
311
  if (fileResult.errors.length === 0) cache.set(fileResult.path, {
697
312
  code: fileResult.code,
@@ -741,10 +356,23 @@ function normalizePrecompileBatchSize(value) {
741
356
  if (value === void 0 || !Number.isFinite(value) || value <= 0) return 128;
742
357
  return Math.max(1, Math.floor(value));
743
358
  }
744
- function chunkPrecompileFiles(files, batchSize) {
359
+ function chunkPrecompileFiles(files, batchSize, options = {}) {
745
360
  const normalizedBatchSize = normalizePrecompileBatchSize(batchSize);
361
+ const maxBytes = Math.max(1, Math.floor(options.maxBytes ?? 33554432));
746
362
  const chunks = [];
747
- for (let start = 0; start < files.length; start += normalizedBatchSize) chunks.push(files.slice(start, start + normalizedBatchSize));
363
+ let current = [];
364
+ let currentBytes = 0;
365
+ for (const file of files) {
366
+ const fileBytes = Math.max(0, options.metadata?.get(file)?.size ?? 0);
367
+ if (current.length > 0 && (current.length >= normalizedBatchSize || currentBytes + fileBytes > maxBytes)) {
368
+ chunks.push(current);
369
+ current = [];
370
+ currentBytes = 0;
371
+ }
372
+ current.push(file);
373
+ currentBytes += fileBytes;
374
+ }
375
+ if (current.length > 0) chunks.push(current);
748
376
  return chunks;
749
377
  }
750
378
  function getEnvironmentCache(state, ssr) {
@@ -805,7 +433,7 @@ async function compileAll(state) {
805
433
  let successCount = 0;
806
434
  let failedCount = 0;
807
435
  let nativeTimeMs = 0;
808
- const chunks = chunkPrecompileFiles(changedFiles, state.precompileBatchSize);
436
+ const chunks = chunkPrecompileFiles(changedFiles, state.precompileBatchSize, { metadata: currentMetadata });
809
437
  for (const chunk of chunks) {
810
438
  const fileContents = [];
811
439
  for (const file of chunk) try {
@@ -849,104 +477,35 @@ async function compileAll(state) {
849
477
  //#endregion
850
478
  //#region src/plugin/resolve.ts
851
479
  function resolveVuePath(state, id, importer) {
852
- let resolved;
853
- if (id.startsWith("/@fs/")) resolved = id.slice(4);
854
- else if (id.startsWith("/") && !fs.existsSync(id)) resolved = path.resolve(state.root, id.slice(1));
855
- else if (path.isAbsolute(id)) resolved = id;
856
- else if (importer) {
857
- const importerRequest = classifyVitePluginRequest(importer);
858
- const realImporter = importerRequest.vizeVirtualPath ?? importerRequest.strippedVirtualPath ?? importer;
859
- resolved = path.resolve(path.dirname(realImporter), id);
860
- } else resolved = path.resolve(state.root, id);
861
- if (!path.isAbsolute(resolved)) resolved = path.resolve(state.root, resolved);
862
- return path.normalize(resolved);
863
- }
864
- function normalizeRequireBase(importer) {
865
- if (!importer) return null;
866
- let normalized = importer;
867
- const request = classifyVitePluginRequest(normalized);
868
- if (request.vizeVirtualPath) normalized = request.vizeVirtualPath;
869
- else if (request.isMacroVirtualId) normalized = request.strippedVirtualPath ?? "";
870
- return normalized.split("?")[0] ?? null;
871
- }
872
- function splitIdQuery(id) {
873
- const queryStart = id.indexOf("?");
874
- if (queryStart === -1) return [id, ""];
875
- return [id.slice(0, queryStart), id.slice(queryStart)];
876
- }
877
- function isBareSpecifier(id) {
878
- const [request] = splitIdQuery(id);
879
- return request !== "" && !request.startsWith("./") && !request.startsWith("../") && !request.startsWith("/") && !request.startsWith("\0") && !request.includes(":");
480
+ return resolveViteVuePath(state.root, id, importer);
880
481
  }
482
+ const EMPTY_NATIVE_ALIAS_RULES = [];
881
483
  function resolveAliasRequest(state, id) {
882
- const [request, querySuffix] = splitIdQuery(id);
883
- for (const rule of state.cssAliasRules) {
884
- if (rule.find instanceof RegExp) {
885
- const pattern = stableAliasPattern(rule.find);
886
- if (pattern.test(request)) return request.replace(pattern, rule.replacement) + querySuffix;
887
- continue;
888
- }
889
- if (request === rule.find) return rule.replacement + querySuffix;
890
- const findPrefix = rule.find.endsWith("/") ? rule.find : rule.find + "/";
891
- if (request.startsWith(findPrefix)) return (rule.replacement.endsWith("/") ? rule.replacement : rule.replacement + "/") + request.slice(findPrefix.length) + querySuffix;
892
- }
893
- return null;
894
- }
895
- function stableAliasPattern(pattern) {
896
- return new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, ""));
897
- }
898
- function pushPnpmHoistBases(candidates, start, isDirectory) {
899
- if (!start) return;
900
- let dir = isDirectory ? start : path.dirname(start);
901
- for (;;) {
902
- const pnpmHoist = path.join(dir, "node_modules", ".pnpm", "node_modules");
903
- if (fs.existsSync(pnpmHoist)) {
904
- candidates.push(path.join(pnpmHoist, "package.json"));
905
- break;
906
- }
907
- const parent = path.dirname(dir);
908
- if (parent === dir) break;
909
- dir = parent;
910
- }
484
+ return resolveViteAliasRequest(id, nativeCssAliasRules(state));
911
485
  }
912
486
  function resolveBareImportWithNode(state, id, importer) {
913
- const [request, querySuffix] = splitIdQuery(id);
914
- const candidates = [normalizeRequireBase(importer), path.join(state.root, "package.json")].filter((candidate) => candidate != null);
915
- pushPnpmHoistBases(candidates, importer ?? null, false);
916
- pushPnpmHoistBases(candidates, state.root, true);
917
- const seen = /* @__PURE__ */ new Set();
918
- for (const candidate of candidates) {
919
- if (seen.has(candidate)) continue;
920
- seen.add(candidate);
921
- try {
922
- return `${createRequire(candidate).resolve(request)}${querySuffix}`;
923
- } catch {}
924
- }
487
+ const { request, querySuffix } = splitViteIdQuery(id);
488
+ for (const candidate of createViteBareImportBases(state.root, importer)) try {
489
+ return `${createRequire(candidate).resolve(request)}${querySuffix}`;
490
+ } catch {}
925
491
  return null;
926
492
  }
927
493
  function resolveBareImportCandidatesWithNode(state, id, importer, resolvedId) {
928
- const candidates = [
929
- resolvedId,
930
- resolveAliasRequest(state, id),
931
- id
932
- ].filter((candidate) => candidate != null && isBareSpecifier(candidate));
933
- const seen = /* @__PURE__ */ new Set();
934
- for (const candidate of candidates) {
935
- if (seen.has(candidate)) continue;
936
- seen.add(candidate);
494
+ for (const candidate of createViteBareImportCandidates(id, nativeCssAliasRules(state), resolvedId)) {
937
495
  const resolved = resolveBareImportWithNode(state, candidate, importer);
938
496
  if (resolved) return resolved;
939
497
  }
940
498
  return null;
941
499
  }
942
500
  function normalizeResolvedVuePath(id) {
943
- const [pathPart] = splitIdQuery(id);
944
- if (!pathPart?.endsWith(".vue")) return null;
945
- return pathPart.startsWith("/@fs/") ? pathPart.slice(4) : pathPart;
501
+ return normalizeViteResolvedVuePath(id);
502
+ }
503
+ function nativeCssAliasRules(state) {
504
+ return state.cssAliasRules.length === 0 ? EMPTY_NATIVE_ALIAS_RULES : state.cssAliasRules.map(toNativeCssAliasRule);
946
505
  }
947
506
  async function resolveAliasedVueImport(ctx, state, id, importer, isSsrRequest, handleNodeModules) {
948
507
  if (path.isAbsolute(id)) return null;
949
- const viteImporter = normalizeRequireBase(importer) ?? importer;
508
+ const viteImporter = normalizeViteRequireBase(importer) ?? importer;
950
509
  const viteResolved = await ctx.resolve(id, viteImporter, { skipSelf: true });
951
510
  const realPath = viteResolved ? normalizeResolvedVuePath(viteResolved.id) : null;
952
511
  if (!realPath) return null;
@@ -985,8 +544,7 @@ async function resolveIdHook(ctx, state, id, importer, options) {
985
544
  }
986
545
  const cleanPath = id.slice(1);
987
546
  if (cleanPath.startsWith("/") && !cleanPath.endsWith(".vue.ts")) {
988
- const [pathPart, queryPart] = cleanPath.split("?");
989
- const querySuffix = queryPart ? `?${queryPart}` : "";
547
+ const { request: pathPart, querySuffix } = splitViteIdQuery(cleanPath);
990
548
  state.logger.log(`resolveId: redirecting \0-prefixed non-vue ID to ${pathPart}${querySuffix}`);
991
549
  const redirected = pathPart + querySuffix;
992
550
  return isBuild ? classifyVitePluginRequest(redirected).normalizedFsId ?? redirected : redirected;
@@ -1025,7 +583,7 @@ async function resolveIdHook(ctx, state, id, importer, options) {
1025
583
  if (!id.endsWith(".vue")) {
1026
584
  if (!id.startsWith("./") && !id.startsWith("../") && !id.startsWith("/")) {
1027
585
  const aliasRequest = resolveAliasRequest(state, id);
1028
- if (aliasRequest && isBareSpecifier(aliasRequest)) {
586
+ if (aliasRequest && isViteBareSpecifier(aliasRequest)) {
1029
587
  const nodeResolved = resolveBareImportCandidatesWithNode(state, id, cleanImporter);
1030
588
  if (nodeResolved) {
1031
589
  state.logger.log(`resolveId: resolved aliased bare ${id} to ${nodeResolved} via Node fallback`);
@@ -1046,7 +604,7 @@ async function resolveIdHook(ctx, state, id, importer, options) {
1046
604
  state.logger.log(`resolveId: normalized bare ${id} to ${nodeResolved} via Node fallback`);
1047
605
  return nodeResolved;
1048
606
  }
1049
- if (isBareSpecifier(resolved.id)) return null;
607
+ if (isViteBareSpecifier(resolved.id)) return null;
1050
608
  return resolved;
1051
609
  }
1052
610
  } catch {}
@@ -1055,7 +613,7 @@ async function resolveIdHook(ctx, state, id, importer, options) {
1055
613
  state.logger.log(`resolveId: resolved bare ${id} to ${nodeResolved} via Node fallback`);
1056
614
  return nodeResolved;
1057
615
  }
1058
- if (aliasRequest && aliasRequest !== id && !isBareSpecifier(aliasRequest)) {
616
+ if (aliasRequest && aliasRequest !== id && !isViteBareSpecifier(aliasRequest)) {
1059
617
  try {
1060
618
  const resolved = await ctx.resolve(aliasRequest, cleanImporter, { skipSelf: true });
1061
619
  if (resolved) {
@@ -1070,7 +628,7 @@ async function resolveIdHook(ctx, state, id, importer, options) {
1070
628
  state.logger.log(`resolveId: normalized aliased bare ${id} to ${nodeResolved} via Node fallback`);
1071
629
  return nodeResolved;
1072
630
  }
1073
- if (isBareSpecifier(resolved.id)) return null;
631
+ if (isViteBareSpecifier(resolved.id)) return null;
1074
632
  return resolved;
1075
633
  }
1076
634
  } catch {}
@@ -1095,36 +653,10 @@ async function resolveIdHook(ctx, state, id, importer, options) {
1095
653
  }
1096
654
  } catch {}
1097
655
  if (id.startsWith("./") || id.startsWith("../")) {
1098
- const [pathPart, queryPart] = id.split("?");
1099
- const querySuffix = queryPart ? `?${queryPart}` : "";
1100
- const resolved = path.resolve(path.dirname(cleanImporter), pathPart);
1101
- for (const ext of [
1102
- "",
1103
- ".ts",
1104
- ".tsx",
1105
- ".js",
1106
- ".jsx",
1107
- ".json"
1108
- ]) {
1109
- const candidate = resolved + ext;
1110
- if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
1111
- const finalPath = candidate + querySuffix;
1112
- state.logger.log(`resolveId: resolved relative ${id} to ${finalPath}`);
1113
- return finalPath;
1114
- }
1115
- }
1116
- if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) for (const indexFile of [
1117
- "/index.ts",
1118
- "/index.tsx",
1119
- "/index.js",
1120
- "/index.jsx"
1121
- ]) {
1122
- const candidate = resolved + indexFile;
1123
- if (fs.existsSync(candidate)) {
1124
- const finalPath = candidate + querySuffix;
1125
- state.logger.log(`resolveId: resolved directory ${id} to ${finalPath}`);
1126
- return finalPath;
1127
- }
656
+ const resolved = resolveViteRelativeImport(id, cleanImporter);
657
+ if (resolved) {
658
+ state.logger.log(`resolveId: resolved relative ${id} to ${resolved}`);
659
+ return resolved;
1128
660
  }
1129
661
  }
1130
662
  return null;
@@ -1215,7 +747,11 @@ function loadDefinePageMetaArtifact(state, realPath, ssr) {
1215
747
  function loadHook(state, id, loadOptions) {
1216
748
  const currentBase = loadOptions?.ssr ? state.serverViteBase : state.clientViteBase;
1217
749
  const request = classifyVitePluginRequest(id);
1218
- if (id === "\0vize:all-styles.css") return Array.from(state.collectedCss.values()).join("\n\n");
750
+ if (id === "\0vize:all-styles.css") {
751
+ let allCss = "";
752
+ for (const css of state.collectedCss.values()) allCss += allCss ? `\n\n${css}` : css;
753
+ return allCss;
754
+ }
1219
755
  let styleId = id;
1220
756
  if (id.startsWith("\0") && id.includes("?vue")) styleId = id.slice(1).replace(/\.module\.\w+$/, "").replace(/\.\w+$/, "");
1221
757
  const styleRequest = classifyVitePluginRequest(styleId);
@@ -1429,7 +965,8 @@ async function handleHotUpdateHook(state, ctx) {
1429
965
  }
1430
966
  function handleGenerateBundleHook(state, emitFile) {
1431
967
  if (!state.extractCss || state.collectedCss.size === 0) return;
1432
- const allCss = Array.from(state.collectedCss.values()).join("\n\n");
968
+ let allCss = "";
969
+ for (const css of state.collectedCss.values()) allCss += allCss ? `\n\n${css}` : css;
1433
970
  if (allCss.trim()) {
1434
971
  emitFile({
1435
972
  type: "asset",
@@ -1505,9 +1042,10 @@ function createPostTransformPlugin(state) {
1505
1042
  //#endregion
1506
1043
  //#region src/plugin/unocss.ts
1507
1044
  const bridgePatched = Symbol("vize.unocssBridgePatched");
1045
+ const VIZE_SSR_PREFIX = "\0vize-ssr:";
1508
1046
  const plainSsrPrefix = VIZE_SSR_PREFIX.slice(1);
1509
1047
  function stripBridgePrefix(id) {
1510
- if (id.startsWith("\0vize-ssr:")) return id.slice(10);
1048
+ if (id.startsWith(VIZE_SSR_PREFIX)) return id.slice(10);
1511
1049
  if (id.startsWith(plainSsrPrefix)) return id.slice(plainSsrPrefix.length);
1512
1050
  if (id.startsWith("\0")) return id.slice(1);
1513
1051
  return id;
@@ -1518,6 +1056,20 @@ function isUnoCssBridgeModuleId(id) {
1518
1056
  function normalizeUnoCssBridgeModuleId(id) {
1519
1057
  return stripBridgePrefix(id).replace(/\.ts(?=\?|$)/, "");
1520
1058
  }
1059
+ function appendOriginalVueSourceForUnoCss(code, normalizedId) {
1060
+ const sourcePath = normalizedId.split("?")[0];
1061
+ if (!sourcePath) return code;
1062
+ try {
1063
+ if (fs.statSync(sourcePath).size > 2097152) return code;
1064
+ } catch {
1065
+ return code;
1066
+ }
1067
+ try {
1068
+ return `${code}\n${fs.readFileSync(sourcePath, "utf-8")}`;
1069
+ } catch {
1070
+ return code;
1071
+ }
1072
+ }
1521
1073
  function patchUnoCssBridge(plugins) {
1522
1074
  for (const plugin of plugins) {
1523
1075
  if (!plugin.name?.startsWith("unocss:") || typeof plugin.transform !== "function" || plugin[bridgePatched]) continue;
@@ -1527,9 +1079,7 @@ function patchUnoCssBridge(plugins) {
1527
1079
  if (!isUnoCssBridgeModuleId(id)) return originalTransform.call(this, code, id, ...args);
1528
1080
  const normalizedId = normalizeUnoCssBridgeModuleId(id);
1529
1081
  let effectiveCode = code;
1530
- if (isExtractionOnly) try {
1531
- effectiveCode = `${code}\n${fs.readFileSync(normalizedId.split("?")[0], "utf-8")}`;
1532
- } catch {}
1082
+ if (isExtractionOnly) effectiveCode = appendOriginalVueSourceForUnoCss(code, normalizedId);
1533
1083
  return originalTransform.call(this, effectiveCode, normalizedId, ...args);
1534
1084
  };
1535
1085
  plugin[bridgePatched] = true;
@@ -1693,7 +1243,7 @@ function vize(options = {}) {
1693
1243
  });
1694
1244
  },
1695
1245
  async buildStart() {
1696
- if (!state.scanPatterns) return;
1246
+ if (!state.scanPatterns || state.scanPatterns.length === 0) return;
1697
1247
  await compileAll(state);
1698
1248
  state.logger.log("Cache keys:", [...state.cache.keys()].slice(0, 3));
1699
1249
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizejs/vite-plugin",
3
- "version": "0.97.0",
3
+ "version": "0.101.0",
4
4
  "description": "High-performance native Vite plugin for Vue SFC compilation powered by Vize",
5
5
  "keywords": [
6
6
  "compiler",
@@ -33,9 +33,9 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@vizejs/native": "0.97.0",
36
+ "@vizejs/native": "0.101.0",
37
37
  "tinyglobby": "0.2.16",
38
- "vize": "0.97.0"
38
+ "vize": "0.101.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/node": "25.7.0",