@vizejs/vite-plugin 0.12.0 → 0.14.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +74 -63
  2. package/dist/index.js +782 -740
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
- import { transformWithOxc } from "vite";
2
- import path from "node:path";
3
1
  import fs from "node:fs";
4
- import { createRequire } from "node:module";
5
- import { pathToFileURL } from "node:url";
2
+ import { createHash } from "node:crypto";
3
+ import path from "node:path";
6
4
  import { glob } from "tinyglobby";
7
5
  import * as native from "@vizejs/native";
8
- import { createHash } from "node:crypto";
6
+ import { pathToFileURL } from "node:url";
7
+ import { transformWithOxc } from "vite";
8
+ import { createRequire } from "node:module";
9
9
 
10
10
  //#region src/hmr.ts
11
11
  /**
@@ -67,7 +67,73 @@ if (import.meta.hot) {
67
67
  }
68
68
 
69
69
  //#endregion
70
- //#region src/utils.ts
70
+ //#region src/utils/css.ts
71
+ /**
72
+ * Resolve CSS @import statements by inlining the imported files,
73
+ * then resolve @custom-media definitions within the combined CSS.
74
+ *
75
+ * This is necessary because Vize embeds CSS as a JS string via
76
+ * document.createElement('style'), bypassing Vite's CSS pipeline.
77
+ */
78
+ function resolveCssImports(css, importer, aliasRules, isDev, devUrlBase) {
79
+ const customMedia = new Map();
80
+ const importRegex = /^@import\s+(?:"([^"]+)"|'([^']+)');?\s*$/gm;
81
+ let result = css;
82
+ result = result.replace(importRegex, (_match, dqPath, sqPath) => {
83
+ const importPath = dqPath || sqPath;
84
+ if (!importPath) return _match;
85
+ const resolved = resolveCssPath(importPath, importer, aliasRules);
86
+ if (!resolved || !fs.existsSync(resolved)) return _match;
87
+ try {
88
+ const content = fs.readFileSync(resolved, "utf-8");
89
+ parseCustomMedia(content, customMedia);
90
+ return content;
91
+ } catch {
92
+ return _match;
93
+ }
94
+ });
95
+ parseCustomMedia(result, customMedia);
96
+ result = result.replace(/^@custom-media\s+[^;]+;\s*$/gm, "");
97
+ if (customMedia.size > 0) for (const [name, query] of customMedia) {
98
+ const escaped = name.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
99
+ result = result.replace(new RegExp(`\\(${escaped}\\)`, "g"), query);
100
+ }
101
+ if (isDev) result = result.replace(/url\(\s*(["']?)([^"')]+)\1\s*\)/g, (_match, quote, urlPath) => {
102
+ const trimmed = urlPath.trim();
103
+ if (trimmed.startsWith("data:") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("/@fs/")) return _match;
104
+ const resolved = resolveCssPath(trimmed, importer, aliasRules);
105
+ if (resolved && fs.existsSync(resolved)) {
106
+ const normalized = resolved.replace(/\\/g, "/");
107
+ const base = devUrlBase ?? "/";
108
+ const prefix = base.endsWith("/") ? base : base + "/";
109
+ return `url("${prefix}@fs${normalized}")`;
110
+ }
111
+ return _match;
112
+ });
113
+ result = result.replace(/:deep\(([^()]*(?:\([^()]*\))*[^()]*)\)/g, "$1");
114
+ result = result.replace(/\n{3,}/g, "\n\n");
115
+ return result;
116
+ }
117
+ function parseCustomMedia(css, map) {
118
+ const re = /@custom-media\s+(--[\w-]+)\s+(.+?)\s*;/g;
119
+ let m;
120
+ while ((m = re.exec(css)) !== null) map.set(m[1], m[2]);
121
+ }
122
+ function resolveCssPath(importPath, importer, aliasRules) {
123
+ for (const rule of aliasRules) if (importPath.startsWith(rule.find)) {
124
+ const resolved = importPath.replace(rule.find, rule.replacement);
125
+ return path.resolve(resolved);
126
+ }
127
+ if (importPath.startsWith(".")) {
128
+ const dir = path.dirname(importer);
129
+ return path.resolve(dir, importPath);
130
+ }
131
+ if (path.isAbsolute(importPath)) return importPath;
132
+ return null;
133
+ }
134
+
135
+ //#endregion
136
+ //#region src/utils/index.ts
71
137
  /** Known CSS preprocessor languages that must be delegated to Vite */
72
138
  const PREPROCESSOR_LANGS = new Set([
73
139
  "scss",
@@ -178,162 +244,122 @@ ${output}`;
178
244
  if (!isProduction && isDev && hasExportDefault) output += generateHmrCode(compiled.scopeId, hmrUpdateType ?? "full-reload");
179
245
  return output;
180
246
  }
181
- /**
182
- * Resolve CSS @import statements by inlining the imported files,
183
- * then resolve @custom-media definitions within the combined CSS.
184
- *
185
- * This is necessary because Vize embeds CSS as a JS string via
186
- * document.createElement('style'), bypassing Vite's CSS pipeline.
187
- */
188
- function resolveCssImports(css, importer, aliasRules, isDev, devUrlBase) {
189
- const customMedia = new Map();
190
- const importRegex = /^@import\s+(?:"([^"]+)"|'([^']+)');?\s*$/gm;
191
- let result = css;
192
- result = result.replace(importRegex, (_match, dqPath, sqPath) => {
193
- const importPath = dqPath || sqPath;
194
- if (!importPath) return _match;
195
- const resolved = resolveCssPath(importPath, importer, aliasRules);
196
- if (!resolved || !fs.existsSync(resolved)) return _match;
197
- try {
198
- const content = fs.readFileSync(resolved, "utf-8");
199
- parseCustomMedia(content, customMedia);
200
- return content;
201
- } catch {
202
- return _match;
203
- }
204
- });
205
- parseCustomMedia(result, customMedia);
206
- result = result.replace(/^@custom-media\s+[^;]+;\s*$/gm, "");
207
- if (customMedia.size > 0) for (const [name, query] of customMedia) {
208
- const escaped = name.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
209
- result = result.replace(new RegExp(`\\(${escaped}\\)`, "g"), query);
210
- }
211
- if (isDev) result = result.replace(/url\(\s*(["']?)([^"')]+)\1\s*\)/g, (_match, quote, urlPath) => {
212
- const trimmed = urlPath.trim();
213
- if (trimmed.startsWith("data:") || trimmed.startsWith("http://") || trimmed.startsWith("https://") || trimmed.startsWith("/@fs/")) return _match;
214
- const resolved = resolveCssPath(trimmed, importer, aliasRules);
215
- if (resolved && fs.existsSync(resolved)) {
216
- const normalized = resolved.replace(/\\/g, "/");
217
- const base = devUrlBase ?? "/";
218
- const prefix = base.endsWith("/") ? base : base + "/";
219
- return `url("${prefix}@fs${normalized}")`;
220
- }
221
- return _match;
222
- });
223
- result = result.replace(/:deep\(([^()]*(?:\([^()]*\))*[^()]*)\)/g, "$1");
224
- result = result.replace(/\n{3,}/g, "\n\n");
225
- return result;
247
+
248
+ //#endregion
249
+ //#region src/virtual.ts
250
+ const LEGACY_VIZE_PREFIX = "\0vize:";
251
+ const VIRTUAL_CSS_MODULE = "virtual:vize-styles";
252
+ const RESOLVED_CSS_MODULE = "\0vize:all-styles.css";
253
+ /** Check if a module ID is a vize-compiled virtual module */
254
+ function isVizeVirtual(id) {
255
+ return id.startsWith("\0") && id.endsWith(".vue.ts");
226
256
  }
227
- function parseCustomMedia(css, map) {
228
- const re = /@custom-media\s+(--[\w-]+)\s+(.+?)\s*;/g;
229
- let m;
230
- while ((m = re.exec(css)) !== null) map.set(m[1], m[2]);
257
+ /** Create a virtual module ID from a real .vue file path */
258
+ function toVirtualId(realPath) {
259
+ return "\0" + realPath + ".ts";
231
260
  }
232
- function resolveCssPath(importPath, importer, aliasRules) {
233
- for (const rule of aliasRules) if (importPath.startsWith(rule.find)) {
234
- const resolved = importPath.replace(rule.find, rule.replacement);
235
- return path.resolve(resolved);
236
- }
237
- if (importPath.startsWith(".")) {
238
- const dir = path.dirname(importer);
239
- return path.resolve(dir, importPath);
261
+ /** Extract the real .vue file path from a virtual module ID */
262
+ function fromVirtualId(virtualId) {
263
+ return virtualId.slice(1, -3);
264
+ }
265
+ function escapeRegExp(value) {
266
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
267
+ }
268
+ function toBrowserImportPrefix(replacement) {
269
+ const normalized = replacement.replace(/\\/g, "/");
270
+ if (normalized.startsWith("/@fs/")) return normalized;
271
+ if (path.isAbsolute(replacement) && fs.existsSync(replacement)) return `/@fs${normalized}`;
272
+ return normalized;
273
+ }
274
+ function normalizeFsIdForBuild(id) {
275
+ const [pathPart, queryPart] = id.split("?");
276
+ if (!pathPart.startsWith("/@fs/")) return id;
277
+ const normalizedPath = pathPart.slice(4);
278
+ return queryPart ? `${normalizedPath}?${queryPart}` : normalizedPath;
279
+ }
280
+ function rewriteDynamicTemplateImports(code, aliasRules) {
281
+ let rewritten = code;
282
+ for (const rule of aliasRules) {
283
+ const pattern = new RegExp(`\\bimport\\s*\\(\\s*\`${escapeRegExp(rule.fromPrefix)}`, "g");
284
+ rewritten = rewritten.replace(pattern, `import(/* @vite-ignore */ \`${rule.toPrefix}`);
240
285
  }
241
- if (path.isAbsolute(importPath)) return importPath;
242
- return null;
286
+ rewritten = rewritten.replace(/\bimport\s*\(\s*`/g, "import(/* @vite-ignore */ `");
287
+ return rewritten;
243
288
  }
244
289
 
245
290
  //#endregion
246
- //#region src/compiler.ts
247
- const { compileSfc, compileSfcBatchWithResults } = native;
291
+ //#region src/transform.ts
248
292
  /**
249
- * Extract style block metadata from a Vue SFC source string.
250
- * Parses `<style>` tags to determine lang, scoped, and module attributes.
293
+ * Rewrite static asset URLs in compiled template output.
294
+ *
295
+ * Transforms property values like `src: "@/assets/logo.svg"` into import
296
+ * statements hoisted to the top of the module, so Vite's module resolution
297
+ * pipeline handles alias expansion and asset hashing in both dev and build.
251
298
  */
252
- function extractStyleBlocks(source) {
253
- const blocks = [];
254
- const styleRegex = /<style([^>]*)>([\s\S]*?)<\/style>/gi;
255
- let match;
256
- let index = 0;
257
- while ((match = styleRegex.exec(source)) !== null) {
258
- const attrs = match[1];
259
- const content = match[2];
260
- const lang = attrs.match(/\blang=["']([^"']+)["']/)?.[1] ?? null;
261
- const scoped = /\bscoped\b/.test(attrs);
262
- const moduleMatch = attrs.match(/\bmodule(?:=["']([^"']+)["'])?\b/);
263
- const isModule = moduleMatch ? moduleMatch[1] || true : false;
264
- blocks.push({
265
- content,
266
- lang,
267
- scoped,
268
- module: isModule,
269
- index
299
+ const SCRIPT_EXTENSIONS = /\.(js|mjs|cjs|ts|mts|cts|jsx|tsx)$/i;
300
+ function rewriteStaticAssetUrls(code, aliasRules) {
301
+ let rewritten = code;
302
+ const imports = [];
303
+ let counter = 0;
304
+ for (const rule of aliasRules) {
305
+ const pattern = new RegExp(`("?src"?\\s*:\\s*)(?:"(${escapeRegExp(rule.fromPrefix)}[^"]+)"|'(${escapeRegExp(rule.fromPrefix)}[^']+)')`, "g");
306
+ rewritten = rewritten.replace(pattern, (match, prefix, dqPath, sqPath) => {
307
+ const fullPath = dqPath || sqPath;
308
+ if (fullPath && SCRIPT_EXTENSIONS.test(fullPath)) return match;
309
+ const varName = `__vize_static_${counter++}`;
310
+ imports.push(`import ${varName} from ${JSON.stringify(fullPath)};`);
311
+ return `${prefix}${varName}`;
270
312
  });
271
- index++;
272
- }
273
- return blocks;
274
- }
275
- function compileFile(filePath, cache, options, source) {
276
- const content = source ?? fs.readFileSync(filePath, "utf-8");
277
- const scopeId = generateScopeId(filePath);
278
- const hasScoped = /<style[^>]*\bscoped\b/.test(content);
279
- const result = compileSfc(content, {
280
- filename: filePath,
281
- sourceMap: options.sourceMap,
282
- ssr: options.ssr,
283
- scopeId: hasScoped ? `data-v-${scopeId}` : void 0
284
- });
285
- if (result.errors.length > 0) {
286
- const errorMsg = result.errors.join("\n");
287
- console.error(`[vize] Compilation error in ${filePath}:\n${errorMsg}`);
288
313
  }
289
- if (result.warnings.length > 0) result.warnings.forEach((warning) => {
290
- console.warn(`[vize] Warning in ${filePath}: ${warning}`);
291
- });
292
- const styles = extractStyleBlocks(content);
293
- const compiled = {
294
- code: result.code,
295
- css: result.css,
296
- scopeId,
297
- hasScoped,
298
- styles
299
- };
300
- cache.set(filePath, compiled);
301
- return compiled;
314
+ if (imports.length > 0) rewritten = imports.join("\n") + "\n" + rewritten;
315
+ return rewritten;
302
316
  }
303
317
  /**
304
- * Batch compile multiple files in parallel using native Rust multithreading.
305
- * Returns per-file results with content hashes for HMR.
318
+ * Built-in Vite/Vue/Nuxt define keys that are handled by Vite's own transform pipeline.
319
+ * These must NOT be replaced by the vize plugin because:
320
+ * 1. Nuxt runs both client and server Vite builds, each with different values
321
+ * (e.g., import.meta.server = true on server, false on client).
322
+ * 2. Vite's import.meta transform already handles these correctly per-environment.
306
323
  */
307
- function compileBatch(files, cache, options) {
308
- const inputs = files.map((f) => ({
309
- path: f.path,
310
- source: f.source
311
- }));
312
- const result = compileSfcBatchWithResults(inputs, { ssr: options.ssr });
313
- const sourceMap = new Map();
314
- for (const f of files) sourceMap.set(f.path, f.source);
315
- for (const fileResult of result.results) {
316
- if (fileResult.errors.length === 0) {
317
- const source = sourceMap.get(fileResult.path);
318
- const styles = source ? extractStyleBlocks(source) : void 0;
319
- cache.set(fileResult.path, {
320
- code: fileResult.code,
321
- css: fileResult.css,
322
- scopeId: fileResult.scopeId,
323
- hasScoped: fileResult.hasScoped,
324
- templateHash: fileResult.templateHash,
325
- styleHash: fileResult.styleHash,
326
- scriptHash: fileResult.scriptHash,
327
- styles
328
- });
329
- }
330
- if (fileResult.errors.length > 0) console.error(`[vize] Compilation error in ${fileResult.path}:\n${fileResult.errors.join("\n")}`);
331
- if (fileResult.warnings.length > 0) fileResult.warnings.forEach((warning) => {
332
- console.warn(`[vize] Warning in ${fileResult.path}: ${warning}`);
333
- });
324
+ const BUILTIN_DEFINE_PREFIXES = [
325
+ "import.meta.server",
326
+ "import.meta.client",
327
+ "import.meta.dev",
328
+ "import.meta.test",
329
+ "import.meta.prerender",
330
+ "import.meta.env",
331
+ "import.meta.hot",
332
+ "__VUE_",
333
+ "__NUXT_",
334
+ "process.env"
335
+ ];
336
+ function isBuiltinDefine(key) {
337
+ return BUILTIN_DEFINE_PREFIXES.some((prefix) => key === prefix || key.startsWith(prefix + ".") || key.startsWith(prefix + "_"));
338
+ }
339
+ /**
340
+ * Apply Vite define replacements to code.
341
+ * Replaces keys like `import.meta.vfFeatures.photoSection` with their values.
342
+ * Uses word-boundary-aware matching to avoid replacing inside strings or partial matches.
343
+ */
344
+ function applyDefineReplacements(code, defines) {
345
+ const sortedKeys = Object.keys(defines).sort((a, b) => b.length - a.length);
346
+ let result = code;
347
+ for (const key of sortedKeys) {
348
+ if (!result.includes(key)) continue;
349
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
350
+ const re = new RegExp(escaped + "(?![\\w$.])", "g");
351
+ result = result.replace(re, defines[key]);
334
352
  }
335
353
  return result;
336
354
  }
355
+ function createLogger(debug) {
356
+ return {
357
+ log: (...args) => debug && console.log("[vize]", ...args),
358
+ info: (...args) => console.log("[vize]", ...args),
359
+ warn: (...args) => console.warn("[vize]", ...args),
360
+ error: (...args) => console.error("[vize]", ...args)
361
+ };
362
+ }
337
363
 
338
364
  //#endregion
339
365
  //#region src/config.ts
@@ -413,189 +439,603 @@ async function loadConfigFile(configPath, env) {
413
439
  const vizeConfigStore = new Map();
414
440
 
415
441
  //#endregion
416
- //#region src/virtual.ts
417
- const LEGACY_VIZE_PREFIX = "\0vize:";
418
- const VIRTUAL_CSS_MODULE = "virtual:vize-styles";
419
- const RESOLVED_CSS_MODULE = "\0vize:all-styles.css";
420
- /** Check if a module ID is a vize-compiled virtual module */
421
- function isVizeVirtual(id) {
422
- return id.startsWith("\0") && id.endsWith(".vue.ts");
423
- }
424
- /** Create a virtual module ID from a real .vue file path */
425
- function toVirtualId(realPath) {
426
- return "\0" + realPath + ".ts";
427
- }
428
- /** Extract the real .vue file path from a virtual module ID */
429
- function fromVirtualId(virtualId) {
430
- return virtualId.slice(1, -3);
431
- }
432
- function escapeRegExp(value) {
433
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
434
- }
435
- function toBrowserImportPrefix(replacement) {
436
- const normalized = replacement.replace(/\\/g, "/");
437
- if (normalized.startsWith("/@fs/")) return normalized;
438
- if (path.isAbsolute(replacement) && fs.existsSync(replacement)) return `/@fs${normalized}`;
439
- return normalized;
442
+ //#region src/compiler.ts
443
+ const { compileSfc, compileSfcBatchWithResults } = native;
444
+ /**
445
+ * Extract style block metadata from a Vue SFC source string.
446
+ * Parses `<style>` tags to determine lang, scoped, and module attributes.
447
+ */
448
+ function extractStyleBlocks(source) {
449
+ const blocks = [];
450
+ const styleRegex = /<style([^>]*)>([\s\S]*?)<\/style>/gi;
451
+ let match;
452
+ let index = 0;
453
+ while ((match = styleRegex.exec(source)) !== null) {
454
+ const attrs = match[1];
455
+ const content = match[2];
456
+ const lang = attrs.match(/\blang=["']([^"']+)["']/)?.[1] ?? null;
457
+ const scoped = /\bscoped\b/.test(attrs);
458
+ const moduleMatch = attrs.match(/\bmodule(?:=["']([^"']+)["'])?\b/);
459
+ const isModule = moduleMatch ? moduleMatch[1] || true : false;
460
+ blocks.push({
461
+ content,
462
+ lang,
463
+ scoped,
464
+ module: isModule,
465
+ index
466
+ });
467
+ index++;
468
+ }
469
+ return blocks;
440
470
  }
441
- function normalizeFsIdForBuild(id) {
442
- const [pathPart, queryPart] = id.split("?");
443
- if (!pathPart.startsWith("/@fs/")) return id;
444
- const normalizedPath = pathPart.slice(4);
445
- return queryPart ? `${normalizedPath}?${queryPart}` : normalizedPath;
471
+ function compileFile(filePath, cache, options, source) {
472
+ const content = source ?? fs.readFileSync(filePath, "utf-8");
473
+ const scopeId = generateScopeId(filePath);
474
+ const hasScoped = /<style[^>]*\bscoped\b/.test(content);
475
+ const result = compileSfc(content, {
476
+ filename: filePath,
477
+ sourceMap: options.sourceMap,
478
+ ssr: options.ssr,
479
+ scopeId: hasScoped ? `data-v-${scopeId}` : void 0
480
+ });
481
+ if (result.errors.length > 0) {
482
+ const errorMsg = result.errors.join("\n");
483
+ console.error(`[vize] Compilation error in ${filePath}:\n${errorMsg}`);
484
+ }
485
+ if (result.warnings.length > 0) result.warnings.forEach((warning) => {
486
+ console.warn(`[vize] Warning in ${filePath}: ${warning}`);
487
+ });
488
+ const styles = extractStyleBlocks(content);
489
+ const compiled = {
490
+ code: result.code,
491
+ css: result.css,
492
+ scopeId,
493
+ hasScoped,
494
+ styles
495
+ };
496
+ cache.set(filePath, compiled);
497
+ return compiled;
446
498
  }
447
- function rewriteDynamicTemplateImports(code, aliasRules) {
448
- let rewritten = code;
449
- for (const rule of aliasRules) {
450
- const pattern = new RegExp(`\\bimport\\s*\\(\\s*\`${escapeRegExp(rule.fromPrefix)}`, "g");
451
- rewritten = rewritten.replace(pattern, `import(/* @vite-ignore */ \`${rule.toPrefix}`);
499
+ /**
500
+ * Batch compile multiple files in parallel using native Rust multithreading.
501
+ * Returns per-file results with content hashes for HMR.
502
+ */
503
+ function compileBatch(files, cache, options) {
504
+ const inputs = files.map((f) => ({
505
+ path: f.path,
506
+ source: f.source
507
+ }));
508
+ const result = compileSfcBatchWithResults(inputs, { ssr: options.ssr });
509
+ const sourceMap = new Map();
510
+ for (const f of files) sourceMap.set(f.path, f.source);
511
+ for (const fileResult of result.results) {
512
+ if (fileResult.errors.length === 0) {
513
+ const source = sourceMap.get(fileResult.path);
514
+ const styles = source ? extractStyleBlocks(source) : void 0;
515
+ cache.set(fileResult.path, {
516
+ code: fileResult.code,
517
+ css: fileResult.css,
518
+ scopeId: fileResult.scopeId,
519
+ hasScoped: fileResult.hasScoped,
520
+ templateHash: fileResult.templateHash,
521
+ styleHash: fileResult.styleHash,
522
+ scriptHash: fileResult.scriptHash,
523
+ styles
524
+ });
525
+ }
526
+ if (fileResult.errors.length > 0) console.error(`[vize] Compilation error in ${fileResult.path}:\n${fileResult.errors.join("\n")}`);
527
+ if (fileResult.warnings.length > 0) fileResult.warnings.forEach((warning) => {
528
+ console.warn(`[vize] Warning in ${fileResult.path}: ${warning}`);
529
+ });
452
530
  }
453
- rewritten = rewritten.replace(/\bimport\s*\(\s*`/g, "import(/* @vite-ignore */ `");
454
- return rewritten;
531
+ return result;
455
532
  }
456
533
 
457
534
  //#endregion
458
- //#region src/transform.ts
535
+ //#region src/plugin/state.ts
459
536
  /**
460
- * Rewrite static asset URLs in compiled template output.
461
- *
462
- * Transforms property values like `src: "@/assets/logo.svg"` into import
463
- * statements hoisted to the top of the module, so Vite's module resolution
464
- * pipeline handles alias expansion and asset hashing in both dev and build.
537
+ * Pre-compile all Vue files matching scan patterns.
465
538
  */
466
- const SCRIPT_EXTENSIONS = /\.(js|mjs|cjs|ts|mts|cts|jsx|tsx)$/i;
467
- function rewriteStaticAssetUrls(code, aliasRules) {
468
- let rewritten = code;
469
- const imports = [];
470
- let counter = 0;
471
- for (const rule of aliasRules) {
472
- const pattern = new RegExp(`("?src"?\\s*:\\s*)(?:"(${escapeRegExp(rule.fromPrefix)}[^"]+)"|'(${escapeRegExp(rule.fromPrefix)}[^']+)')`, "g");
473
- rewritten = rewritten.replace(pattern, (match, prefix, dqPath, sqPath) => {
474
- const fullPath = dqPath || sqPath;
475
- if (fullPath && SCRIPT_EXTENSIONS.test(fullPath)) return match;
476
- const varName = `__vize_static_${counter++}`;
477
- imports.push(`import ${varName} from ${JSON.stringify(fullPath)};`);
478
- return `${prefix}${varName}`;
539
+ async function compileAll(state) {
540
+ const startTime = performance.now();
541
+ const files = await glob(state.scanPatterns, {
542
+ cwd: state.root,
543
+ ignore: state.ignorePatterns,
544
+ absolute: true
545
+ });
546
+ state.logger.info(`Pre-compiling ${files.length} Vue files...`);
547
+ const fileContents = [];
548
+ for (const file of files) try {
549
+ const source = fs.readFileSync(file, "utf-8");
550
+ fileContents.push({
551
+ path: file,
552
+ source
479
553
  });
554
+ } catch (e) {
555
+ state.logger.error(`Failed to read ${file}:`, e);
480
556
  }
481
- if (imports.length > 0) rewritten = imports.join("\n") + "\n" + rewritten;
482
- return rewritten;
557
+ const result = compileBatch(fileContents, state.cache, { ssr: state.mergedOptions.ssr ?? false });
558
+ if (state.isProduction) {
559
+ for (const fileResult of result.results) if (fileResult.css) {
560
+ const cached = state.cache.get(fileResult.path);
561
+ const hasDelegated = cached?.styles?.some((s) => s.lang !== null && [
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));
569
+ }
570
+ }
571
+ const elapsed = (performance.now() - startTime).toFixed(2);
572
+ state.logger.info(`Pre-compilation complete: ${result.successCount} succeeded, ${result.failedCount} failed (${elapsed}ms, native batch: ${result.timeMs.toFixed(2)}ms)`);
483
573
  }
484
- /**
485
- * Built-in Vite/Vue/Nuxt define keys that are handled by Vite's own transform pipeline.
486
- * These must NOT be replaced by the vize plugin because:
487
- * 1. Nuxt runs both client and server Vite builds, each with different values
488
- * (e.g., import.meta.server = true on server, false on client).
489
- * 2. Vite's import.meta transform already handles these correctly per-environment.
490
- */
491
- const BUILTIN_DEFINE_PREFIXES = [
492
- "import.meta.server",
493
- "import.meta.client",
494
- "import.meta.dev",
495
- "import.meta.test",
496
- "import.meta.prerender",
497
- "import.meta.env",
498
- "import.meta.hot",
499
- "__VUE_",
500
- "__NUXT_",
501
- "process.env"
502
- ];
503
- function isBuiltinDefine(key) {
504
- return BUILTIN_DEFINE_PREFIXES.some((prefix) => key === prefix || key.startsWith(prefix + ".") || key.startsWith(prefix + "_"));
574
+
575
+ //#endregion
576
+ //#region src/plugin/resolve.ts
577
+ function resolveVuePath(state, id, importer) {
578
+ let resolved;
579
+ if (id.startsWith("/@fs/")) resolved = id.slice(4);
580
+ else if (id.startsWith("/") && !fs.existsSync(id)) resolved = path.resolve(state.root, id.slice(1));
581
+ else if (path.isAbsolute(id)) resolved = id;
582
+ else if (importer) {
583
+ const realImporter = isVizeVirtual(importer) ? fromVirtualId(importer) : importer;
584
+ resolved = path.resolve(path.dirname(realImporter), id);
585
+ } else resolved = path.resolve(state.root, id);
586
+ if (!path.isAbsolute(resolved)) resolved = path.resolve(state.root, resolved);
587
+ return path.normalize(resolved);
505
588
  }
506
- /**
507
- * Apply Vite define replacements to code.
508
- * Replaces keys like `import.meta.vfFeatures.photoSection` with their values.
509
- * Uses word-boundary-aware matching to avoid replacing inside strings or partial matches.
510
- */
511
- function applyDefineReplacements(code, defines) {
512
- const sortedKeys = Object.keys(defines).sort((a, b) => b.length - a.length);
513
- let result = code;
514
- for (const key of sortedKeys) {
515
- if (!result.includes(key)) continue;
516
- const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
517
- const re = new RegExp(escaped + "(?![\\w$.])", "g");
518
- result = result.replace(re, defines[key]);
589
+ async function resolveIdHook(ctx, state, id, importer) {
590
+ const isBuild = state.server === null;
591
+ if (id.startsWith("\0")) {
592
+ if (isVizeVirtual(id)) return null;
593
+ if (id.startsWith(LEGACY_VIZE_PREFIX)) {
594
+ const rawPath = id.slice(LEGACY_VIZE_PREFIX.length);
595
+ const cleanPath$1 = rawPath.endsWith(".ts") ? rawPath.slice(0, -3) : rawPath;
596
+ if (!cleanPath$1.endsWith(".vue")) {
597
+ state.logger.log(`resolveId: redirecting legacy virtual ID to ${cleanPath$1}`);
598
+ return cleanPath$1;
599
+ }
600
+ }
601
+ const cleanPath = id.slice(1);
602
+ if (cleanPath.startsWith("/") && !cleanPath.endsWith(".vue.ts")) {
603
+ const [pathPart, queryPart] = cleanPath.split("?");
604
+ const querySuffix = queryPart ? `?${queryPart}` : "";
605
+ state.logger.log(`resolveId: redirecting \0-prefixed non-vue ID to ${pathPart}${querySuffix}`);
606
+ const redirected = pathPart + querySuffix;
607
+ return isBuild ? normalizeFsIdForBuild(redirected) : redirected;
608
+ }
609
+ return null;
519
610
  }
520
- return result;
521
- }
522
- function createLogger(debug) {
523
- return {
524
- log: (...args) => debug && console.log("[vize]", ...args),
525
- info: (...args) => console.log("[vize]", ...args),
526
- warn: (...args) => console.warn("[vize]", ...args),
527
- error: (...args) => console.error("[vize]", ...args)
528
- };
611
+ if (id.startsWith("vize:")) {
612
+ let realPath = id.slice(5);
613
+ if (realPath.endsWith(".ts")) realPath = realPath.slice(0, -3);
614
+ state.logger.log(`resolveId: redirecting stale vize: ID to ${realPath}`);
615
+ const resolved = await ctx.resolve(realPath, importer, { skipSelf: true });
616
+ if (resolved && isBuild && resolved.id.startsWith("/@fs/")) return {
617
+ ...resolved,
618
+ id: normalizeFsIdForBuild(resolved.id)
619
+ };
620
+ return resolved;
621
+ }
622
+ if (id === VIRTUAL_CSS_MODULE) return RESOLVED_CSS_MODULE;
623
+ if (isBuild && id.startsWith("/@fs/")) return normalizeFsIdForBuild(id);
624
+ if (id.includes("?macro=true")) {
625
+ const filePath = id.split("?")[0];
626
+ const resolved = resolveVuePath(state, filePath, importer);
627
+ if (resolved && fs.existsSync(resolved)) return `\0${resolved}?macro=true`;
628
+ }
629
+ if (id.includes("?vue&type=style") || id.includes("?vue=&type=style")) {
630
+ const params = new URLSearchParams(id.split("?")[1]);
631
+ const lang = params.get("lang") || "css";
632
+ if (params.has("module")) return `\0${id}.module.${lang}`;
633
+ return `\0${id}.${lang}`;
634
+ }
635
+ const isMacroImporter = importer?.startsWith("\0") && importer?.endsWith("?macro=true");
636
+ if (importer && (isVizeVirtual(importer) || isMacroImporter)) {
637
+ const cleanImporter = isMacroImporter ? importer.slice(1).replace("?macro=true", "") : fromVirtualId(importer);
638
+ state.logger.log(`resolveId from virtual: id=${id}, cleanImporter=${cleanImporter}`);
639
+ if (id.startsWith("#")) try {
640
+ return await ctx.resolve(id, cleanImporter, { skipSelf: true });
641
+ } catch {
642
+ return null;
643
+ }
644
+ if (!id.endsWith(".vue")) {
645
+ if (!id.startsWith("./") && !id.startsWith("../") && !id.startsWith("/")) {
646
+ const matchesAlias = state.cssAliasRules.some((rule) => id === rule.find || id.startsWith(rule.find + "/"));
647
+ if (!matchesAlias) try {
648
+ const resolved = await ctx.resolve(id, cleanImporter, { skipSelf: true });
649
+ if (resolved) {
650
+ state.logger.log(`resolveId: resolved bare ${id} to ${resolved.id} via Vite resolver`);
651
+ if (isBuild && resolved.id.startsWith("/@fs/")) return {
652
+ ...resolved,
653
+ id: normalizeFsIdForBuild(resolved.id)
654
+ };
655
+ return resolved;
656
+ }
657
+ } catch {}
658
+ return null;
659
+ }
660
+ try {
661
+ const resolved = await ctx.resolve(id, cleanImporter, { skipSelf: true });
662
+ if (resolved) {
663
+ state.logger.log(`resolveId: resolved ${id} to ${resolved.id} via Vite resolver`);
664
+ if (isBuild && resolved.id.startsWith("/@fs/")) return {
665
+ ...resolved,
666
+ id: normalizeFsIdForBuild(resolved.id)
667
+ };
668
+ return resolved;
669
+ }
670
+ } catch {}
671
+ if (id.startsWith("./") || id.startsWith("../")) {
672
+ const [pathPart, queryPart] = id.split("?");
673
+ const querySuffix = queryPart ? `?${queryPart}` : "";
674
+ const resolved = path.resolve(path.dirname(cleanImporter), pathPart);
675
+ for (const ext of [
676
+ "",
677
+ ".ts",
678
+ ".tsx",
679
+ ".js",
680
+ ".jsx",
681
+ ".json"
682
+ ]) {
683
+ const candidate = resolved + ext;
684
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
685
+ const finalPath = candidate + querySuffix;
686
+ state.logger.log(`resolveId: resolved relative ${id} to ${finalPath}`);
687
+ return finalPath;
688
+ }
689
+ }
690
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) for (const indexFile of [
691
+ "/index.ts",
692
+ "/index.tsx",
693
+ "/index.js",
694
+ "/index.jsx"
695
+ ]) {
696
+ const candidate = resolved + indexFile;
697
+ if (fs.existsSync(candidate)) {
698
+ const finalPath = candidate + querySuffix;
699
+ state.logger.log(`resolveId: resolved directory ${id} to ${finalPath}`);
700
+ return finalPath;
701
+ }
702
+ }
703
+ }
704
+ return null;
705
+ }
706
+ }
707
+ if (id.endsWith(".vue")) {
708
+ const handleNodeModules = state.initialized ? state.mergedOptions.handleNodeModulesVue ?? true : true;
709
+ if (!handleNodeModules && id.includes("node_modules")) {
710
+ state.logger.log(`resolveId: skipping node_modules import ${id}`);
711
+ return null;
712
+ }
713
+ const resolved = resolveVuePath(state, id, importer);
714
+ const isNodeModulesPath = resolved.includes("node_modules");
715
+ if (!handleNodeModules && isNodeModulesPath) {
716
+ state.logger.log(`resolveId: skipping node_modules path ${resolved}`);
717
+ return null;
718
+ }
719
+ if (state.filter && !isNodeModulesPath && !state.filter(resolved)) {
720
+ state.logger.log(`resolveId: skipping filtered path ${resolved}`);
721
+ return null;
722
+ }
723
+ const hasCache = state.cache.has(resolved);
724
+ const fileExists = fs.existsSync(resolved);
725
+ state.logger.log(`resolveId: id=${id}, resolved=${resolved}, hasCache=${hasCache}, fileExists=${fileExists}, importer=${importer ?? "none"}`);
726
+ if (hasCache || fileExists) return toVirtualId(resolved);
727
+ if (!fileExists && !path.isAbsolute(id)) {
728
+ const viteResolved = await ctx.resolve(id, importer, { skipSelf: true });
729
+ if (viteResolved && viteResolved.id.endsWith(".vue")) {
730
+ const realPath = viteResolved.id;
731
+ const isResolvedNodeModules = realPath.includes("node_modules");
732
+ if ((isResolvedNodeModules ? handleNodeModules : state.filter(realPath)) && (state.cache.has(realPath) || fs.existsSync(realPath))) {
733
+ state.logger.log(`resolveId: resolved via Vite fallback ${id} to ${realPath}`);
734
+ return toVirtualId(realPath);
735
+ }
736
+ }
737
+ }
738
+ }
739
+ return null;
529
740
  }
530
741
 
531
742
  //#endregion
532
- //#region src/plugin.ts
533
- function vize(options = {}) {
534
- const cache = new Map();
535
- const collectedCss = new Map();
536
- let isProduction;
537
- let root;
538
- let clientViteBase = "/";
539
- let serverViteBase = "/";
540
- let server = null;
541
- let filter;
542
- let scanPatterns;
543
- let ignorePatterns;
544
- let mergedOptions;
545
- let initialized = false;
546
- let dynamicImportAliasRules = [];
547
- let cssAliasRules = [];
548
- let extractCss = false;
549
- let clientViteDefine = {};
550
- let serverViteDefine = {};
551
- const logger = createLogger(options.debug ?? false);
552
- async function compileAll() {
553
- const startTime = performance.now();
554
- const files = await glob(scanPatterns, {
555
- cwd: root,
556
- ignore: ignorePatterns,
557
- absolute: true
558
- });
559
- logger.info(`Pre-compiling ${files.length} Vue files...`);
560
- const fileContents = [];
561
- for (const file of files) try {
562
- const source = fs.readFileSync(file, "utf-8");
563
- fileContents.push({
564
- path: file,
565
- source
743
+ //#region src/plugin/load.ts
744
+ function loadHook(state, id, loadOptions) {
745
+ const currentBase = loadOptions?.ssr ? state.serverViteBase : state.clientViteBase;
746
+ if (id === RESOLVED_CSS_MODULE) {
747
+ const allCss = Array.from(state.collectedCss.values()).join("\n\n");
748
+ return allCss;
749
+ }
750
+ let styleId = id;
751
+ if (id.startsWith("\0") && id.includes("?vue")) styleId = id.slice(1).replace(/\.module\.\w+$/, "").replace(/\.\w+$/, "");
752
+ if (styleId.includes("?vue&type=style") || styleId.includes("?vue=&type=style")) {
753
+ const [filename, queryString] = styleId.split("?");
754
+ const realPath = isVizeVirtual(filename) ? fromVirtualId(filename) : filename;
755
+ const params = new URLSearchParams(queryString);
756
+ const indexStr = params.get("index");
757
+ const lang = params.get("lang");
758
+ const _hasModule = params.has("module");
759
+ const scoped = params.get("scoped");
760
+ const compiled = state.cache.get(realPath);
761
+ const blockIndex = indexStr !== null ? parseInt(indexStr, 10) : -1;
762
+ if (compiled?.styles && blockIndex >= 0 && blockIndex < compiled.styles.length) {
763
+ const block = compiled.styles[blockIndex];
764
+ let styleContent = block.content;
765
+ if (scoped && block.scoped && lang && lang !== "css") {
766
+ const lines = styleContent.split("\n");
767
+ const hoisted = [];
768
+ const body = [];
769
+ for (const line of lines) {
770
+ const trimmed = line.trimStart();
771
+ if (trimmed.startsWith("@use ") || trimmed.startsWith("@forward ") || trimmed.startsWith("@import ")) hoisted.push(line);
772
+ else body.push(line);
773
+ }
774
+ const bodyContent = body.join("\n");
775
+ const hoistedContent = hoisted.length > 0 ? hoisted.join("\n") + "\n\n" : "";
776
+ styleContent = `${hoistedContent}[${scoped}] {\n${bodyContent}\n}`;
777
+ }
778
+ return {
779
+ code: styleContent,
780
+ map: null
781
+ };
782
+ }
783
+ if (compiled?.css) return resolveCssImports(compiled.css, realPath, state.cssAliasRules, state.server !== null, currentBase);
784
+ return "";
785
+ }
786
+ if (id.startsWith("\0") && id.endsWith("?macro=true")) {
787
+ const realPath = id.slice(1).replace("?macro=true", "");
788
+ if (fs.existsSync(realPath)) {
789
+ const source = fs.readFileSync(realPath, "utf-8");
790
+ const setupMatch = source.match(/<script\s+setup[^>]*>([\s\S]*?)<\/script>/);
791
+ if (setupMatch) {
792
+ const scriptContent = setupMatch[1];
793
+ return {
794
+ code: `${scriptContent}\nexport default {}`,
795
+ map: null
796
+ };
797
+ }
798
+ }
799
+ return {
800
+ code: "export default {}",
801
+ map: null
802
+ };
803
+ }
804
+ if (isVizeVirtual(id)) {
805
+ const realPath = fromVirtualId(id);
806
+ if (!realPath.endsWith(".vue")) {
807
+ state.logger.log(`load: skipping non-vue virtual module ${realPath}`);
808
+ return null;
809
+ }
810
+ let compiled = state.cache.get(realPath);
811
+ if (!compiled && fs.existsSync(realPath)) {
812
+ state.logger.log(`load: on-demand compiling ${realPath}`);
813
+ compiled = compileFile(realPath, state.cache, {
814
+ sourceMap: state.mergedOptions?.sourceMap ?? !(state.isProduction ?? false),
815
+ ssr: state.mergedOptions?.ssr ?? false
566
816
  });
817
+ }
818
+ if (compiled) {
819
+ const hasDelegated = compiled.styles?.some((s) => s.lang !== null && [
820
+ "scss",
821
+ "sass",
822
+ "less",
823
+ "stylus",
824
+ "styl"
825
+ ].includes(s.lang) || s.module !== false);
826
+ if (compiled.css && !hasDelegated) compiled = {
827
+ ...compiled,
828
+ css: resolveCssImports(compiled.css, realPath, state.cssAliasRules, state.server !== null, currentBase)
829
+ };
830
+ const output = rewriteStaticAssetUrls(rewriteDynamicTemplateImports(generateOutput(compiled, {
831
+ isProduction: state.isProduction,
832
+ isDev: state.server !== null,
833
+ extractCss: state.extractCss,
834
+ filePath: realPath
835
+ }), state.dynamicImportAliasRules), state.dynamicImportAliasRules);
836
+ return {
837
+ code: output,
838
+ map: null
839
+ };
840
+ }
841
+ }
842
+ if (id.startsWith("\0")) {
843
+ const afterPrefix = id.startsWith(LEGACY_VIZE_PREFIX) ? id.slice(LEGACY_VIZE_PREFIX.length) : id.slice(1);
844
+ if (afterPrefix.includes("?commonjs-")) return null;
845
+ const [pathPart, queryPart] = afterPrefix.split("?");
846
+ const querySuffix = queryPart ? `?${queryPart}` : "";
847
+ const fsPath = pathPart.startsWith("/@fs/") ? pathPart.slice(4) : pathPart;
848
+ if (fsPath.startsWith("/") && fs.existsSync(fsPath) && fs.statSync(fsPath).isFile()) {
849
+ const importPath = state.server === null ? `${pathToFileURL(fsPath).href}${querySuffix}` : "/@fs" + fsPath + querySuffix;
850
+ state.logger.log(`load: proxying \0-prefixed file ${id} -> re-export from ${importPath}`);
851
+ return `export { default } from ${JSON.stringify(importPath)};\nexport * from ${JSON.stringify(importPath)};`;
852
+ }
853
+ }
854
+ return null;
855
+ }
856
+ async function transformHook(state, code, id, options) {
857
+ const isMacro = id.startsWith("\0") && id.endsWith("?macro=true");
858
+ if (isVizeVirtual(id) || isMacro) {
859
+ const realPath = isMacro ? id.slice(1).replace("?macro=true", "") : fromVirtualId(id);
860
+ try {
861
+ const result = await transformWithOxc(code, realPath, { lang: "ts" });
862
+ const defines = options?.ssr ? state.serverViteDefine : state.clientViteDefine;
863
+ let transformed = result.code;
864
+ if (Object.keys(defines).length > 0) transformed = applyDefineReplacements(transformed, defines);
865
+ return {
866
+ code: transformed,
867
+ map: result.map
868
+ };
567
869
  } catch (e) {
568
- logger.error(`Failed to read ${file}:`, e);
870
+ state.logger.error(`transformWithOxc failed for ${realPath}:`, e);
871
+ const dumpPath = `/tmp/vize-oxc-error-${path.basename(realPath)}.ts`;
872
+ fs.writeFileSync(dumpPath, code, "utf-8");
873
+ state.logger.error(`Dumped failing code to ${dumpPath}`);
874
+ return {
875
+ code: "export default {}",
876
+ map: null
877
+ };
569
878
  }
570
- const result = compileBatch(fileContents, cache, { ssr: mergedOptions.ssr ?? false });
571
- if (isProduction) {
572
- for (const fileResult of result.results) if (fileResult.css) {
573
- const cached = cache.get(fileResult.path);
574
- const hasDelegated = cached?.styles?.some((s) => s.lang !== null && [
575
- "scss",
576
- "sass",
577
- "less",
578
- "stylus",
579
- "styl"
580
- ].includes(s.lang) || s.module !== false);
581
- if (!hasDelegated) collectedCss.set(fileResult.path, resolveCssImports(fileResult.css, fileResult.path, cssAliasRules, false));
879
+ }
880
+ return null;
881
+ }
882
+
883
+ //#endregion
884
+ //#region src/plugin/hmr.ts
885
+ async function handleHotUpdateHook(state, ctx) {
886
+ const { file, server, read } = ctx;
887
+ if (file.endsWith(".vue") && state.filter(file)) try {
888
+ const source = await read();
889
+ const prevCompiled = state.cache.get(file);
890
+ compileFile(file, state.cache, {
891
+ sourceMap: state.mergedOptions?.sourceMap ?? !state.isProduction,
892
+ ssr: state.mergedOptions?.ssr ?? false
893
+ }, source);
894
+ const newCompiled = state.cache.get(file);
895
+ const updateType = detectHmrUpdateType(prevCompiled, newCompiled);
896
+ state.logger.log(`Re-compiled: ${path.relative(state.root, file)} (${updateType})`);
897
+ const virtualId = toVirtualId(file);
898
+ const modules = server.moduleGraph.getModulesByFile(virtualId) ?? server.moduleGraph.getModulesByFile(file);
899
+ const hasDelegated = newCompiled.styles?.some((s) => s.lang !== null && [
900
+ "scss",
901
+ "sass",
902
+ "less",
903
+ "stylus",
904
+ "styl"
905
+ ].includes(s.lang) || s.module !== false);
906
+ if (hasDelegated && updateType === "style-only") {
907
+ const affectedModules = new Set();
908
+ for (const block of newCompiled.styles ?? []) {
909
+ const params = new URLSearchParams();
910
+ params.set("vue", "");
911
+ params.set("type", "style");
912
+ params.set("index", String(block.index));
913
+ if (block.scoped) params.set("scoped", `data-v-${newCompiled.scopeId}`);
914
+ params.set("lang", block.lang ?? "css");
915
+ if (block.module !== false) params.set("module", typeof block.module === "string" ? block.module : "");
916
+ const styleId = `${file}?${params.toString()}`;
917
+ const styleMods = server.moduleGraph.getModulesByFile(styleId);
918
+ if (styleMods) for (const mod of styleMods) affectedModules.add(mod);
582
919
  }
920
+ if (modules) for (const mod of modules) affectedModules.add(mod);
921
+ if (affectedModules.size > 0) return [...affectedModules];
922
+ }
923
+ if (updateType === "style-only" && newCompiled.css && !hasDelegated) {
924
+ server.ws.send({
925
+ type: "custom",
926
+ event: "vize:update",
927
+ data: {
928
+ id: newCompiled.scopeId,
929
+ type: "style-only",
930
+ css: resolveCssImports(newCompiled.css, file, state.cssAliasRules, true, state.clientViteBase)
931
+ }
932
+ });
933
+ return [];
583
934
  }
584
- const elapsed = (performance.now() - startTime).toFixed(2);
585
- logger.info(`Pre-compilation complete: ${result.successCount} succeeded, ${result.failedCount} failed (${elapsed}ms, native batch: ${result.timeMs.toFixed(2)}ms)`);
935
+ if (modules) return [...modules];
936
+ } catch (e) {
937
+ state.logger.error(`Re-compilation failed for ${file}:`, e);
586
938
  }
587
- function resolveVuePath(id, importer) {
588
- let resolved;
589
- if (id.startsWith("/@fs/")) resolved = id.slice(4);
590
- else if (id.startsWith("/") && !fs.existsSync(id)) resolved = path.resolve(root, id.slice(1));
591
- else if (path.isAbsolute(id)) resolved = id;
592
- else if (importer) {
593
- const realImporter = isVizeVirtual(importer) ? fromVirtualId(importer) : importer;
594
- resolved = path.resolve(path.dirname(realImporter), id);
595
- } else resolved = path.resolve(root, id);
596
- if (!path.isAbsolute(resolved)) resolved = path.resolve(root, resolved);
597
- return path.normalize(resolved);
939
+ }
940
+ function handleGenerateBundleHook(state, emitFile) {
941
+ if (!state.extractCss || state.collectedCss.size === 0) return;
942
+ const allCss = Array.from(state.collectedCss.values()).join("\n\n");
943
+ if (allCss.trim()) {
944
+ emitFile({
945
+ type: "asset",
946
+ fileName: "assets/vize-components.css",
947
+ source: allCss
948
+ });
949
+ state.logger.log(`Extracted CSS to assets/vize-components.css (${state.collectedCss.size} components)`);
598
950
  }
951
+ }
952
+
953
+ //#endregion
954
+ //#region src/plugin/compat.ts
955
+ function createVueCompatPlugin(state) {
956
+ let compilerSfc = null;
957
+ const loadCompilerSfc = () => {
958
+ if (!compilerSfc) try {
959
+ const require = createRequire(import.meta.url);
960
+ compilerSfc = require("@vue/compiler-sfc");
961
+ } catch {
962
+ compilerSfc = { parse: () => ({
963
+ descriptor: {},
964
+ errors: []
965
+ }) };
966
+ }
967
+ return compilerSfc;
968
+ };
969
+ return {
970
+ name: "vite:vue",
971
+ api: { get options() {
972
+ return {
973
+ compiler: loadCompilerSfc(),
974
+ isProduction: state.isProduction ?? false,
975
+ root: state.root ?? process.cwd(),
976
+ template: {}
977
+ };
978
+ } }
979
+ };
980
+ }
981
+ function createPostTransformPlugin(state) {
982
+ return {
983
+ name: "vize:post-transform",
984
+ enforce: "post",
985
+ async transform(code, id, transformOptions) {
986
+ if (!id.endsWith(".vue") && !id.endsWith(".vue.ts") && !id.includes("node_modules") && id.endsWith(".setup.ts") && /<script\s+setup[\s>]/.test(code)) {
987
+ state.logger.log(`post-transform: compiling virtual SFC content from ${id}`);
988
+ try {
989
+ const compiled = compileFile(id, state.cache, {
990
+ sourceMap: state.mergedOptions?.sourceMap ?? !(state.isProduction ?? false),
991
+ ssr: state.mergedOptions?.ssr ?? false
992
+ }, code);
993
+ const output = generateOutput(compiled, {
994
+ isProduction: state.isProduction,
995
+ isDev: state.server !== null,
996
+ extractCss: state.extractCss,
997
+ filePath: id
998
+ });
999
+ const result = await transformWithOxc(output, id, { lang: "ts" });
1000
+ const defines = transformOptions?.ssr ? state.serverViteDefine : state.clientViteDefine;
1001
+ let transformed = result.code;
1002
+ if (Object.keys(defines).length > 0) transformed = applyDefineReplacements(transformed, defines);
1003
+ return {
1004
+ code: transformed,
1005
+ map: result.map
1006
+ };
1007
+ } catch (e) {
1008
+ state.logger.error(`Virtual SFC compilation failed for ${id}:`, e);
1009
+ }
1010
+ }
1011
+ return null;
1012
+ }
1013
+ };
1014
+ }
1015
+
1016
+ //#endregion
1017
+ //#region src/plugin/index.ts
1018
+ function vize(options = {}) {
1019
+ const state = {
1020
+ cache: new Map(),
1021
+ collectedCss: new Map(),
1022
+ isProduction: false,
1023
+ root: "",
1024
+ clientViteBase: "/",
1025
+ serverViteBase: "/",
1026
+ server: null,
1027
+ filter: () => true,
1028
+ scanPatterns: null,
1029
+ ignorePatterns: [],
1030
+ mergedOptions: options,
1031
+ initialized: false,
1032
+ dynamicImportAliasRules: [],
1033
+ cssAliasRules: [],
1034
+ extractCss: false,
1035
+ clientViteDefine: {},
1036
+ serverViteDefine: {},
1037
+ logger: createLogger(options.debug ?? false)
1038
+ };
599
1039
  const mainPlugin = {
600
1040
  name: "vite-plugin-vize",
601
1041
  enforce: "pre",
@@ -610,12 +1050,12 @@ function vize(options = {}) {
610
1050
  };
611
1051
  },
612
1052
  async configResolved(resolvedConfig) {
613
- root = options.root ?? resolvedConfig.root;
614
- isProduction = options.isProduction ?? resolvedConfig.isProduction;
1053
+ state.root = options.root ?? resolvedConfig.root;
1054
+ state.isProduction = options.isProduction ?? resolvedConfig.isProduction;
615
1055
  const isSsrBuild = !!resolvedConfig.build?.ssr;
616
- if (isSsrBuild) serverViteBase = resolvedConfig.base ?? "/";
617
- else clientViteBase = resolvedConfig.base ?? "/";
618
- extractCss = isProduction;
1056
+ if (isSsrBuild) state.serverViteBase = resolvedConfig.base ?? "/";
1057
+ else state.clientViteBase = resolvedConfig.base ?? "/";
1058
+ state.extractCss = state.isProduction;
619
1059
  const isSsr = !!resolvedConfig.build?.ssr;
620
1060
  const envDefine = {};
621
1061
  if (resolvedConfig.define) for (const [key, value] of Object.entries(resolvedConfig.define)) {
@@ -623,8 +1063,8 @@ function vize(options = {}) {
623
1063
  if (typeof value === "string") envDefine[key] = value;
624
1064
  else envDefine[key] = JSON.stringify(value);
625
1065
  }
626
- if (isSsr) serverViteDefine = envDefine;
627
- else clientViteDefine = envDefine;
1066
+ if (isSsr) state.serverViteDefine = envDefine;
1067
+ else state.clientViteDefine = envDefine;
628
1068
  const configEnv = {
629
1069
  mode: resolvedConfig.mode,
630
1070
  command: resolvedConfig.command === "build" ? "build" : "serve",
@@ -632,19 +1072,19 @@ function vize(options = {}) {
632
1072
  };
633
1073
  let fileConfig = null;
634
1074
  if (options.configMode !== false) {
635
- fileConfig = await loadConfig(root, {
1075
+ fileConfig = await loadConfig(state.root, {
636
1076
  mode: options.configMode ?? "root",
637
1077
  configFile: options.configFile,
638
1078
  env: configEnv
639
1079
  });
640
1080
  if (fileConfig) {
641
- logger.log("Loaded config from vize.config file");
642
- vizeConfigStore.set(root, fileConfig);
1081
+ state.logger.log("Loaded config from vize.config file");
1082
+ vizeConfigStore.set(state.root, fileConfig);
643
1083
  }
644
1084
  }
645
1085
  const viteConfig = fileConfig?.vite ?? {};
646
1086
  const compilerConfig = fileConfig?.compiler ?? {};
647
- mergedOptions = {
1087
+ state.mergedOptions = {
648
1088
  ...options,
649
1089
  ssr: options.ssr ?? compilerConfig.ssr ?? false,
650
1090
  sourceMap: options.sourceMap ?? compilerConfig.sourceMap,
@@ -654,38 +1094,38 @@ function vize(options = {}) {
654
1094
  scanPatterns: options.scanPatterns ?? viteConfig.scanPatterns,
655
1095
  ignorePatterns: options.ignorePatterns ?? viteConfig.ignorePatterns
656
1096
  };
657
- dynamicImportAliasRules = [];
1097
+ state.dynamicImportAliasRules = [];
658
1098
  for (const alias of resolvedConfig.resolve.alias) {
659
1099
  if (typeof alias.find !== "string" || typeof alias.replacement !== "string") continue;
660
1100
  const fromPrefix = alias.find.endsWith("/") ? alias.find : `${alias.find}/`;
661
1101
  const replacement = toBrowserImportPrefix(alias.replacement);
662
1102
  const toPrefix = replacement.endsWith("/") ? replacement : `${replacement}/`;
663
- dynamicImportAliasRules.push({
1103
+ state.dynamicImportAliasRules.push({
664
1104
  fromPrefix,
665
1105
  toPrefix
666
1106
  });
667
1107
  }
668
- dynamicImportAliasRules.sort((a, b) => b.fromPrefix.length - a.fromPrefix.length);
669
- cssAliasRules = [];
1108
+ state.dynamicImportAliasRules.sort((a, b) => b.fromPrefix.length - a.fromPrefix.length);
1109
+ state.cssAliasRules = [];
670
1110
  for (const alias of resolvedConfig.resolve.alias) {
671
1111
  if (typeof alias.find !== "string" || typeof alias.replacement !== "string") continue;
672
- cssAliasRules.push({
1112
+ state.cssAliasRules.push({
673
1113
  find: alias.find,
674
1114
  replacement: alias.replacement
675
1115
  });
676
1116
  }
677
- cssAliasRules.sort((a, b) => b.find.length - a.find.length);
678
- filter = createFilter(mergedOptions.include, mergedOptions.exclude);
679
- scanPatterns = mergedOptions.scanPatterns ?? ["**/*.vue"];
680
- ignorePatterns = mergedOptions.ignorePatterns ?? [
1117
+ state.cssAliasRules.sort((a, b) => b.find.length - a.find.length);
1118
+ state.filter = createFilter(state.mergedOptions.include, state.mergedOptions.exclude);
1119
+ state.scanPatterns = state.mergedOptions.scanPatterns ?? ["**/*.vue"];
1120
+ state.ignorePatterns = state.mergedOptions.ignorePatterns ?? [
681
1121
  "node_modules/**",
682
1122
  "dist/**",
683
1123
  ".git/**"
684
1124
  ];
685
- initialized = true;
1125
+ state.initialized = true;
686
1126
  },
687
1127
  configureServer(devServer) {
688
- server = devServer;
1128
+ state.server = devServer;
689
1129
  devServer.middlewares.use((req, _res, next) => {
690
1130
  if (req.url && req.url.includes("__x00__")) {
691
1131
  const [urlPath, queryPart] = req.url.split("?");
@@ -696,7 +1136,7 @@ function vize(options = {}) {
696
1136
  if (fsPath.startsWith("/") && fs.existsSync(fsPath) && fs.statSync(fsPath).isFile() && !fsPath.endsWith(".vue.ts")) {
697
1137
  const cleaned = queryPart ? `${cleanedPath}?${queryPart}` : cleanedPath;
698
1138
  if (cleaned !== req.url) {
699
- logger.log(`middleware: rewriting ${req.url} -> ${cleaned}`);
1139
+ state.logger.log(`middleware: rewriting ${req.url} -> ${cleaned}`);
700
1140
  req.url = cleaned;
701
1141
  }
702
1142
  }
@@ -706,428 +1146,30 @@ function vize(options = {}) {
706
1146
  });
707
1147
  },
708
1148
  async buildStart() {
709
- if (!scanPatterns) return;
710
- await compileAll();
711
- logger.log("Cache keys:", [...cache.keys()].slice(0, 3));
1149
+ if (!state.scanPatterns) return;
1150
+ await compileAll(state);
1151
+ state.logger.log("Cache keys:", [...state.cache.keys()].slice(0, 3));
712
1152
  },
713
- async resolveId(id, importer) {
714
- const isBuild = server === null;
715
- if (id.startsWith("\0")) {
716
- if (isVizeVirtual(id)) return null;
717
- if (id.startsWith(LEGACY_VIZE_PREFIX)) {
718
- const rawPath = id.slice(LEGACY_VIZE_PREFIX.length);
719
- const cleanPath$1 = rawPath.endsWith(".ts") ? rawPath.slice(0, -3) : rawPath;
720
- if (!cleanPath$1.endsWith(".vue")) {
721
- logger.log(`resolveId: redirecting legacy virtual ID to ${cleanPath$1}`);
722
- return cleanPath$1;
723
- }
724
- }
725
- const cleanPath = id.slice(1);
726
- if (cleanPath.startsWith("/") && !cleanPath.endsWith(".vue.ts")) {
727
- const [pathPart, queryPart] = cleanPath.split("?");
728
- const querySuffix = queryPart ? `?${queryPart}` : "";
729
- logger.log(`resolveId: redirecting \0-prefixed non-vue ID to ${pathPart}${querySuffix}`);
730
- const redirected = pathPart + querySuffix;
731
- return isBuild ? normalizeFsIdForBuild(redirected) : redirected;
732
- }
733
- return null;
734
- }
735
- if (id.startsWith("vize:")) {
736
- let realPath = id.slice(5);
737
- if (realPath.endsWith(".ts")) realPath = realPath.slice(0, -3);
738
- logger.log(`resolveId: redirecting stale vize: ID to ${realPath}`);
739
- const resolved = await this.resolve(realPath, importer, { skipSelf: true });
740
- if (resolved && isBuild && resolved.id.startsWith("/@fs/")) return {
741
- ...resolved,
742
- id: normalizeFsIdForBuild(resolved.id)
743
- };
744
- return resolved;
745
- }
746
- if (id === VIRTUAL_CSS_MODULE) return RESOLVED_CSS_MODULE;
747
- if (isBuild && id.startsWith("/@fs/")) return normalizeFsIdForBuild(id);
748
- if (id.includes("?macro=true")) {
749
- const filePath = id.split("?")[0];
750
- const resolved = resolveVuePath(filePath, importer);
751
- if (resolved && fs.existsSync(resolved)) return `\0${resolved}?macro=true`;
752
- }
753
- if (id.includes("?vue&type=style") || id.includes("?vue=&type=style")) {
754
- const params = new URLSearchParams(id.split("?")[1]);
755
- const lang = params.get("lang") || "css";
756
- if (params.has("module")) return `\0${id}.module.${lang}`;
757
- return `\0${id}.${lang}`;
758
- }
759
- const isMacroImporter = importer?.startsWith("\0") && importer?.endsWith("?macro=true");
760
- if (importer && (isVizeVirtual(importer) || isMacroImporter)) {
761
- const cleanImporter = isMacroImporter ? importer.slice(1).replace("?macro=true", "") : fromVirtualId(importer);
762
- logger.log(`resolveId from virtual: id=${id}, cleanImporter=${cleanImporter}`);
763
- if (id.startsWith("#")) try {
764
- return await this.resolve(id, cleanImporter, { skipSelf: true });
765
- } catch {
766
- return null;
767
- }
768
- if (!id.endsWith(".vue")) {
769
- if (!id.startsWith("./") && !id.startsWith("../") && !id.startsWith("/")) {
770
- const matchesAlias = cssAliasRules.some((rule) => id === rule.find || id.startsWith(rule.find + "/"));
771
- if (!matchesAlias) try {
772
- const resolved = await this.resolve(id, cleanImporter, { skipSelf: true });
773
- if (resolved) {
774
- logger.log(`resolveId: resolved bare ${id} to ${resolved.id} via Vite resolver`);
775
- if (isBuild && resolved.id.startsWith("/@fs/")) return {
776
- ...resolved,
777
- id: normalizeFsIdForBuild(resolved.id)
778
- };
779
- return resolved;
780
- }
781
- } catch {}
782
- return null;
783
- }
784
- try {
785
- const resolved = await this.resolve(id, cleanImporter, { skipSelf: true });
786
- if (resolved) {
787
- logger.log(`resolveId: resolved ${id} to ${resolved.id} via Vite resolver`);
788
- if (isBuild && resolved.id.startsWith("/@fs/")) return {
789
- ...resolved,
790
- id: normalizeFsIdForBuild(resolved.id)
791
- };
792
- return resolved;
793
- }
794
- } catch {}
795
- if (id.startsWith("./") || id.startsWith("../")) {
796
- const [pathPart, queryPart] = id.split("?");
797
- const querySuffix = queryPart ? `?${queryPart}` : "";
798
- const resolved = path.resolve(path.dirname(cleanImporter), pathPart);
799
- for (const ext of [
800
- "",
801
- ".ts",
802
- ".tsx",
803
- ".js",
804
- ".jsx",
805
- ".json"
806
- ]) {
807
- const candidate = resolved + ext;
808
- if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) {
809
- const finalPath = candidate + querySuffix;
810
- logger.log(`resolveId: resolved relative ${id} to ${finalPath}`);
811
- return finalPath;
812
- }
813
- }
814
- if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) for (const indexFile of [
815
- "/index.ts",
816
- "/index.tsx",
817
- "/index.js",
818
- "/index.jsx"
819
- ]) {
820
- const candidate = resolved + indexFile;
821
- if (fs.existsSync(candidate)) {
822
- const finalPath = candidate + querySuffix;
823
- logger.log(`resolveId: resolved directory ${id} to ${finalPath}`);
824
- return finalPath;
825
- }
826
- }
827
- }
828
- return null;
829
- }
830
- }
831
- if (id.endsWith(".vue")) {
832
- const handleNodeModules = initialized ? mergedOptions.handleNodeModulesVue ?? true : true;
833
- if (!handleNodeModules && id.includes("node_modules")) {
834
- logger.log(`resolveId: skipping node_modules import ${id}`);
835
- return null;
836
- }
837
- const resolved = resolveVuePath(id, importer);
838
- const isNodeModulesPath = resolved.includes("node_modules");
839
- if (!handleNodeModules && isNodeModulesPath) {
840
- logger.log(`resolveId: skipping node_modules path ${resolved}`);
841
- return null;
842
- }
843
- if (filter && !isNodeModulesPath && !filter(resolved)) {
844
- logger.log(`resolveId: skipping filtered path ${resolved}`);
845
- return null;
846
- }
847
- const hasCache = cache.has(resolved);
848
- const fileExists = fs.existsSync(resolved);
849
- logger.log(`resolveId: id=${id}, resolved=${resolved}, hasCache=${hasCache}, fileExists=${fileExists}, importer=${importer ?? "none"}`);
850
- if (hasCache || fileExists) return toVirtualId(resolved);
851
- if (!fileExists && !path.isAbsolute(id)) {
852
- const viteResolved = await this.resolve(id, importer, { skipSelf: true });
853
- if (viteResolved && viteResolved.id.endsWith(".vue")) {
854
- const realPath = viteResolved.id;
855
- const isResolvedNodeModules = realPath.includes("node_modules");
856
- if ((isResolvedNodeModules ? handleNodeModules : filter(realPath)) && (cache.has(realPath) || fs.existsSync(realPath))) {
857
- logger.log(`resolveId: resolved via Vite fallback ${id} to ${realPath}`);
858
- return toVirtualId(realPath);
859
- }
860
- }
861
- }
862
- }
863
- return null;
1153
+ resolveId(id, importer) {
1154
+ return resolveIdHook(this, state, id, importer);
864
1155
  },
865
1156
  load(id, loadOptions) {
866
- const currentBase = loadOptions?.ssr ? serverViteBase : clientViteBase;
867
- if (id === RESOLVED_CSS_MODULE) {
868
- const allCss = Array.from(collectedCss.values()).join("\n\n");
869
- return allCss;
870
- }
871
- let styleId = id;
872
- if (id.startsWith("\0") && id.includes("?vue")) styleId = id.slice(1).replace(/\.module\.\w+$/, "").replace(/\.\w+$/, "");
873
- if (styleId.includes("?vue&type=style") || styleId.includes("?vue=&type=style")) {
874
- const [filename, queryString] = styleId.split("?");
875
- const realPath = isVizeVirtual(filename) ? fromVirtualId(filename) : filename;
876
- const params = new URLSearchParams(queryString);
877
- const indexStr = params.get("index");
878
- const lang = params.get("lang");
879
- const _hasModule = params.has("module");
880
- const scoped = params.get("scoped");
881
- const compiled = cache.get(realPath);
882
- const blockIndex = indexStr !== null ? parseInt(indexStr, 10) : -1;
883
- if (compiled?.styles && blockIndex >= 0 && blockIndex < compiled.styles.length) {
884
- const block = compiled.styles[blockIndex];
885
- let styleContent = block.content;
886
- if (scoped && block.scoped && lang && lang !== "css") {
887
- const lines = styleContent.split("\n");
888
- const hoisted = [];
889
- const body = [];
890
- for (const line of lines) {
891
- const trimmed = line.trimStart();
892
- if (trimmed.startsWith("@use ") || trimmed.startsWith("@forward ") || trimmed.startsWith("@import ")) hoisted.push(line);
893
- else body.push(line);
894
- }
895
- const bodyContent = body.join("\n");
896
- const hoistedContent = hoisted.length > 0 ? hoisted.join("\n") + "\n\n" : "";
897
- styleContent = `${hoistedContent}[${scoped}] {\n${bodyContent}\n}`;
898
- }
899
- return {
900
- code: styleContent,
901
- map: null
902
- };
903
- }
904
- if (compiled?.css) return resolveCssImports(compiled.css, realPath, cssAliasRules, server !== null, currentBase);
905
- return "";
906
- }
907
- if (id.startsWith("\0") && id.endsWith("?macro=true")) {
908
- const realPath = id.slice(1).replace("?macro=true", "");
909
- if (fs.existsSync(realPath)) {
910
- const source = fs.readFileSync(realPath, "utf-8");
911
- const setupMatch = source.match(/<script\s+setup[^>]*>([\s\S]*?)<\/script>/);
912
- if (setupMatch) {
913
- const scriptContent = setupMatch[1];
914
- return {
915
- code: `${scriptContent}\nexport default {}`,
916
- map: null
917
- };
918
- }
919
- }
920
- return {
921
- code: "export default {}",
922
- map: null
923
- };
924
- }
925
- if (isVizeVirtual(id)) {
926
- const realPath = fromVirtualId(id);
927
- if (!realPath.endsWith(".vue")) {
928
- logger.log(`load: skipping non-vue virtual module ${realPath}`);
929
- return null;
930
- }
931
- let compiled = cache.get(realPath);
932
- if (!compiled && fs.existsSync(realPath)) {
933
- logger.log(`load: on-demand compiling ${realPath}`);
934
- compiled = compileFile(realPath, cache, {
935
- sourceMap: mergedOptions?.sourceMap ?? !(isProduction ?? false),
936
- ssr: mergedOptions?.ssr ?? false
937
- });
938
- }
939
- if (compiled) {
940
- const hasDelegated = compiled.styles?.some((s) => s.lang !== null && [
941
- "scss",
942
- "sass",
943
- "less",
944
- "stylus",
945
- "styl"
946
- ].includes(s.lang) || s.module !== false);
947
- if (compiled.css && !hasDelegated) compiled = {
948
- ...compiled,
949
- css: resolveCssImports(compiled.css, realPath, cssAliasRules, server !== null, currentBase)
950
- };
951
- const output = rewriteStaticAssetUrls(rewriteDynamicTemplateImports(generateOutput(compiled, {
952
- isProduction,
953
- isDev: server !== null,
954
- extractCss,
955
- filePath: realPath
956
- }), dynamicImportAliasRules), dynamicImportAliasRules);
957
- return {
958
- code: output,
959
- map: null
960
- };
961
- }
962
- }
963
- if (id.startsWith("\0")) {
964
- const afterPrefix = id.startsWith(LEGACY_VIZE_PREFIX) ? id.slice(LEGACY_VIZE_PREFIX.length) : id.slice(1);
965
- if (afterPrefix.includes("?commonjs-")) return null;
966
- const [pathPart, queryPart] = afterPrefix.split("?");
967
- const querySuffix = queryPart ? `?${queryPart}` : "";
968
- const fsPath = pathPart.startsWith("/@fs/") ? pathPart.slice(4) : pathPart;
969
- if (fsPath.startsWith("/") && fs.existsSync(fsPath) && fs.statSync(fsPath).isFile()) {
970
- const importPath = server === null ? `${pathToFileURL(fsPath).href}${querySuffix}` : "/@fs" + fsPath + querySuffix;
971
- logger.log(`load: proxying \0-prefixed file ${id} -> re-export from ${importPath}`);
972
- return `export { default } from ${JSON.stringify(importPath)};\nexport * from ${JSON.stringify(importPath)};`;
973
- }
974
- }
975
- return null;
1157
+ return loadHook(state, id, loadOptions);
976
1158
  },
977
- async transform(code, id, options$1) {
978
- const isMacro = id.startsWith("\0") && id.endsWith("?macro=true");
979
- if (isVizeVirtual(id) || isMacro) {
980
- const realPath = isMacro ? id.slice(1).replace("?macro=true", "") : fromVirtualId(id);
981
- try {
982
- const result = await transformWithOxc(code, realPath, { lang: "ts" });
983
- const defines = options$1?.ssr ? serverViteDefine : clientViteDefine;
984
- let transformed = result.code;
985
- if (Object.keys(defines).length > 0) transformed = applyDefineReplacements(transformed, defines);
986
- return {
987
- code: transformed,
988
- map: result.map
989
- };
990
- } catch (e) {
991
- logger.error(`transformWithOxc failed for ${realPath}:`, e);
992
- const dumpPath = `/tmp/vize-oxc-error-${path.basename(realPath)}.ts`;
993
- fs.writeFileSync(dumpPath, code, "utf-8");
994
- logger.error(`Dumped failing code to ${dumpPath}`);
995
- return {
996
- code: "export default {}",
997
- map: null
998
- };
999
- }
1000
- }
1001
- return null;
1159
+ async transform(code, id, transformOptions) {
1160
+ return transformHook(state, code, id, transformOptions);
1002
1161
  },
1003
1162
  async handleHotUpdate(ctx) {
1004
- const { file, server: server$1, read } = ctx;
1005
- if (file.endsWith(".vue") && filter(file)) try {
1006
- const source = await read();
1007
- const prevCompiled = cache.get(file);
1008
- compileFile(file, cache, {
1009
- sourceMap: mergedOptions?.sourceMap ?? !isProduction,
1010
- ssr: mergedOptions?.ssr ?? false
1011
- }, source);
1012
- const newCompiled = cache.get(file);
1013
- const updateType = detectHmrUpdateType(prevCompiled, newCompiled);
1014
- logger.log(`Re-compiled: ${path.relative(root, file)} (${updateType})`);
1015
- const virtualId = toVirtualId(file);
1016
- const modules = server$1.moduleGraph.getModulesByFile(virtualId) ?? server$1.moduleGraph.getModulesByFile(file);
1017
- const hasDelegated = newCompiled.styles?.some((s) => s.lang !== null && [
1018
- "scss",
1019
- "sass",
1020
- "less",
1021
- "stylus",
1022
- "styl"
1023
- ].includes(s.lang) || s.module !== false);
1024
- if (hasDelegated && updateType === "style-only") {
1025
- const affectedModules = new Set();
1026
- for (const block of newCompiled.styles ?? []) {
1027
- const params = new URLSearchParams();
1028
- params.set("vue", "");
1029
- params.set("type", "style");
1030
- params.set("index", String(block.index));
1031
- if (block.scoped) params.set("scoped", `data-v-${newCompiled.scopeId}`);
1032
- params.set("lang", block.lang ?? "css");
1033
- if (block.module !== false) params.set("module", typeof block.module === "string" ? block.module : "");
1034
- const styleId = `${file}?${params.toString()}`;
1035
- const styleMods = server$1.moduleGraph.getModulesByFile(styleId);
1036
- if (styleMods) for (const mod of styleMods) affectedModules.add(mod);
1037
- }
1038
- if (modules) for (const mod of modules) affectedModules.add(mod);
1039
- if (affectedModules.size > 0) return [...affectedModules];
1040
- }
1041
- if (updateType === "style-only" && newCompiled.css && !hasDelegated) {
1042
- server$1.ws.send({
1043
- type: "custom",
1044
- event: "vize:update",
1045
- data: {
1046
- id: newCompiled.scopeId,
1047
- type: "style-only",
1048
- css: resolveCssImports(newCompiled.css, file, cssAliasRules, true, clientViteBase)
1049
- }
1050
- });
1051
- return [];
1052
- }
1053
- if (modules) return [...modules];
1054
- } catch (e) {
1055
- logger.error(`Re-compilation failed for ${file}:`, e);
1056
- }
1163
+ return handleHotUpdateHook(state, ctx);
1057
1164
  },
1058
- generateBundle(_, _bundle) {
1059
- if (!extractCss || collectedCss.size === 0) return;
1060
- const allCss = Array.from(collectedCss.values()).join("\n\n");
1061
- if (allCss.trim()) {
1062
- this.emitFile({
1063
- type: "asset",
1064
- fileName: "assets/vize-components.css",
1065
- source: allCss
1066
- });
1067
- logger.log(`Extracted CSS to assets/vize-components.css (${collectedCss.size} components)`);
1068
- }
1069
- }
1070
- };
1071
- let compilerSfc = null;
1072
- const loadCompilerSfc = () => {
1073
- if (!compilerSfc) try {
1074
- const require = createRequire(import.meta.url);
1075
- compilerSfc = require("@vue/compiler-sfc");
1076
- } catch {
1077
- compilerSfc = { parse: () => ({
1078
- descriptor: {},
1079
- errors: []
1080
- }) };
1081
- }
1082
- return compilerSfc;
1083
- };
1084
- const vueCompatPlugin = {
1085
- name: "vite:vue",
1086
- api: { get options() {
1087
- return {
1088
- compiler: loadCompilerSfc(),
1089
- isProduction: isProduction ?? false,
1090
- root: root ?? process.cwd(),
1091
- template: {}
1092
- };
1093
- } }
1094
- };
1095
- const postTransformPlugin = {
1096
- name: "vize:post-transform",
1097
- enforce: "post",
1098
- async transform(code, id, transformOptions) {
1099
- if (!id.endsWith(".vue") && !id.endsWith(".vue.ts") && !id.includes("node_modules") && id.endsWith(".setup.ts") && /<script\s+setup[\s>]/.test(code)) {
1100
- logger.log(`post-transform: compiling virtual SFC content from ${id}`);
1101
- try {
1102
- const compiled = compileFile(id, cache, {
1103
- sourceMap: mergedOptions?.sourceMap ?? !(isProduction ?? false),
1104
- ssr: mergedOptions?.ssr ?? false
1105
- }, code);
1106
- const output = generateOutput(compiled, {
1107
- isProduction,
1108
- isDev: server !== null,
1109
- extractCss,
1110
- filePath: id
1111
- });
1112
- const result = await transformWithOxc(output, id, { lang: "ts" });
1113
- const defines = transformOptions?.ssr ? serverViteDefine : clientViteDefine;
1114
- let transformed = result.code;
1115
- if (Object.keys(defines).length > 0) transformed = applyDefineReplacements(transformed, defines);
1116
- return {
1117
- code: transformed,
1118
- map: result.map
1119
- };
1120
- } catch (e) {
1121
- logger.error(`Virtual SFC compilation failed for ${id}:`, e);
1122
- }
1123
- }
1124
- return null;
1165
+ generateBundle() {
1166
+ handleGenerateBundleHook(state, this.emitFile.bind(this));
1125
1167
  }
1126
1168
  };
1127
1169
  return [
1128
- vueCompatPlugin,
1170
+ createVueCompatPlugin(state),
1129
1171
  mainPlugin,
1130
- postTransformPlugin
1172
+ createPostTransformPlugin(state)
1131
1173
  ];
1132
1174
  }
1133
1175