@vizejs/vite-plugin 0.0.1-alpha.100

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/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # @vizejs/vite-plugin
2
+
3
+ High-performance native Vite plugin for Vue SFC compilation powered by [Vize](https://github.com/ubugeeei/vize).
4
+
5
+ ## Features
6
+
7
+ - **Native Performance**: Uses Rust-based compiler via Node.js native bindings (NAPI)
8
+ - **Pre-compilation**: All `.vue` files are compiled at server startup for instant module resolution
9
+ - **Virtual Modules**: Compiled code is served from memory as virtual modules
10
+ - **HMR Support**: Hot Module Replacement with automatic re-compilation on file changes
11
+ - **Vapor Mode**: Optional support for Vue Vapor mode compilation
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ # npm
17
+ npm install @vizejs/vite-plugin
18
+
19
+ # pnpm
20
+ pnpm add @vizejs/vite-plugin
21
+
22
+ # yarn
23
+ yarn add @vizejs/vite-plugin
24
+ ```
25
+
26
+ ## Usage
27
+
28
+ ### Vite
29
+
30
+ ```ts
31
+ // vite.config.ts
32
+ import { defineConfig } from 'vite'
33
+ import vize from '@vizejs/vite-plugin'
34
+
35
+ export default defineConfig({
36
+ plugins: [
37
+ vize({
38
+ // options
39
+ })
40
+ ]
41
+ })
42
+ ```
43
+
44
+ ### Nuxt
45
+
46
+ For Nuxt 3, add the plugin to your `nuxt.config.ts`:
47
+
48
+ ```ts
49
+ // nuxt.config.ts
50
+ import vize from '@vizejs/vite-plugin'
51
+
52
+ export default defineNuxtConfig({
53
+ vite: {
54
+ plugins: [
55
+ vize({
56
+ // Exclude Nuxt's internal .vue files if needed
57
+ exclude: [/node_modules/, /#/, /\.nuxt/]
58
+ })
59
+ ]
60
+ },
61
+
62
+ // Disable the default Vue plugin
63
+ vue: {
64
+ propsDestructure: false
65
+ }
66
+ })
67
+ ```
68
+
69
+ **Note**: When using with Nuxt, you may need to disable Nuxt's built-in Vue plugin to avoid conflicts:
70
+
71
+ ```ts
72
+ // nuxt.config.ts
73
+ export default defineNuxtConfig({
74
+ hooks: {
75
+ 'vite:extendConfig': (config) => {
76
+ // Remove @vitejs/plugin-vue from plugins
77
+ config.plugins = config.plugins?.filter(
78
+ (p) => p && (Array.isArray(p) ? p[0] : p).name !== 'vite:vue'
79
+ )
80
+ }
81
+ },
82
+ vite: {
83
+ plugins: [
84
+ vize()
85
+ ]
86
+ }
87
+ })
88
+ ```
89
+
90
+ ## Options
91
+
92
+ ```ts
93
+ interface VizeNativeOptions {
94
+ /**
95
+ * Files to include in compilation
96
+ * @default /\.vue$/
97
+ */
98
+ include?: string | RegExp | (string | RegExp)[]
99
+
100
+ /**
101
+ * Files to exclude from compilation
102
+ * @default /node_modules/
103
+ */
104
+ exclude?: string | RegExp | (string | RegExp)[]
105
+
106
+ /**
107
+ * Force production mode
108
+ * @default auto-detected from Vite config
109
+ */
110
+ isProduction?: boolean
111
+
112
+ /**
113
+ * Enable SSR mode
114
+ * @default false
115
+ */
116
+ ssr?: boolean
117
+
118
+ /**
119
+ * Enable source map generation
120
+ * @default true in development, false in production
121
+ */
122
+ sourceMap?: boolean
123
+
124
+ /**
125
+ * Enable Vapor mode compilation
126
+ * @default false
127
+ */
128
+ vapor?: boolean
129
+
130
+ /**
131
+ * Root directory to scan for .vue files
132
+ * @default Vite's root
133
+ */
134
+ root?: string
135
+
136
+ /**
137
+ * Glob patterns to scan for .vue files during pre-compilation
138
+ * @default ['**\/*.vue']
139
+ */
140
+ scanPatterns?: string[]
141
+
142
+ /**
143
+ * Glob patterns to ignore during pre-compilation
144
+ * @default ['node_modules/**', 'dist/**', '.git/**']
145
+ */
146
+ ignorePatterns?: string[]
147
+ }
148
+ ```
149
+
150
+ ## How It Works
151
+
152
+ ### Pre-compilation at Startup
153
+
154
+ When the Vite dev server starts (or build begins), the plugin:
155
+
156
+ 1. Scans the project root for all `.vue` files matching the configured patterns
157
+ 2. Compiles each file using the native Vize compiler
158
+ 3. Stores the compiled JavaScript and CSS in an in-memory cache
159
+
160
+ This approach leverages Vize's exceptional performance - compiling 15,000 SFC files in under 500ms with multi-threading.
161
+
162
+ ### Virtual Module Resolution
163
+
164
+ When Vite requests a `.vue` file:
165
+
166
+ 1. The plugin intercepts the module resolution
167
+ 2. Returns the pre-compiled code from cache (or compiles on-demand if not cached)
168
+ 3. CSS is injected inline with deduplication support
169
+
170
+ ### HMR (Hot Module Replacement)
171
+
172
+ When a `.vue` file changes:
173
+
174
+ 1. The plugin detects the change via `handleHotUpdate`
175
+ 2. Re-compiles only the changed file
176
+ 3. Updates the cache
177
+ 4. Vite handles the rest of the HMR flow
178
+
179
+ ## Performance
180
+
181
+ Vize's native compiler is significantly faster than the official Vue compiler:
182
+
183
+ | Benchmark (15,000 SFCs) | @vue/compiler-sfc | Vize | Speedup |
184
+ |-------------------------|-------------------|------|---------|
185
+ | Single-threaded | 16.21s | 6.65s | **2.4x** |
186
+ | Multi-threaded | 4.13s | 498ms | **8.3x** |
187
+
188
+ ## Comparison with vite-plugin-vize
189
+
190
+ | Feature | vite-plugin-vize | vite-plugin-vize |
191
+ |---------|------------------|-------------------------|
192
+ | Compiler | WASM | Native (NAPI) |
193
+ | Pre-compilation | No | Yes |
194
+ | Module Loading | Transform | Virtual Module (Load) |
195
+ | Performance | Fast | Fastest |
196
+ | Platform | Any | Node.js only |
197
+
198
+ Use `vite-plugin-vize` (WASM-based) when you need:
199
+ - Browser compatibility (e.g., StackBlitz, WebContainers)
200
+ - Platform-independent deployment
201
+
202
+ Use `vite-plugin-vize` when you need:
203
+ - Maximum performance
204
+ - Server-side only (standard Node.js environment)
205
+
206
+ ## Requirements
207
+
208
+ - Node.js 18+
209
+ - Vite 5.0+ / 6.0+ / 7.0+
210
+ - Vue 3.x
211
+
212
+ ## License
213
+
214
+ MIT
@@ -0,0 +1,498 @@
1
+ import { Plugin } from "vite";
2
+
3
+ //#region ../vize/src/types.d.ts
4
+ type MaybePromise<T> = T | Promise<T>;
5
+ interface ConfigEnv {
6
+ mode: string;
7
+ command: "serve" | "build" | "check" | "lint" | "fmt";
8
+ isSsrBuild?: boolean;
9
+ }
10
+ type UserConfigExport = VizeConfig | ((env: ConfigEnv) => MaybePromise<VizeConfig>);
11
+ type RuleSeverity = "off" | "warn" | "error";
12
+ type RuleCategory = "correctness" | "suspicious" | "style" | "perf" | "a11y" | "security";
13
+ /**
14
+ * Vize configuration options
15
+ */
16
+ interface VizeConfig {
17
+ /**
18
+ * Vue compiler options
19
+ */
20
+ compiler?: CompilerConfig;
21
+ /**
22
+ * Vite plugin options
23
+ */
24
+ vite?: VitePluginConfig;
25
+ /**
26
+ * Linter options
27
+ */
28
+ linter?: LinterConfig;
29
+ /**
30
+ * Type checker options
31
+ */
32
+ typeChecker?: TypeCheckerConfig;
33
+ /**
34
+ * Formatter options
35
+ */
36
+ formatter?: FormatterConfig;
37
+ /**
38
+ * LSP options
39
+ */
40
+ lsp?: LspConfig;
41
+ /**
42
+ * Musea component gallery options
43
+ */
44
+ musea?: MuseaConfig;
45
+ /**
46
+ * Global type declarations
47
+ */
48
+ globalTypes?: GlobalTypesConfig;
49
+ }
50
+ /**
51
+ * Compiler configuration
52
+ */
53
+ interface CompilerConfig {
54
+ /**
55
+ * Compilation mode
56
+ * @default 'module'
57
+ */
58
+ mode?: "module" | "function";
59
+ /**
60
+ * Enable Vapor mode compilation
61
+ * @default false
62
+ */
63
+ vapor?: boolean;
64
+ /**
65
+ * Enable SSR mode
66
+ * @default false
67
+ */
68
+ ssr?: boolean;
69
+ /**
70
+ * Enable source map generation
71
+ * @default true in development, false in production
72
+ */
73
+ sourceMap?: boolean;
74
+ /**
75
+ * Prefix template identifiers with _ctx
76
+ * @default false
77
+ */
78
+ prefixIdentifiers?: boolean;
79
+ /**
80
+ * Hoist static nodes
81
+ * @default true
82
+ */
83
+ hoistStatic?: boolean;
84
+ /**
85
+ * Cache v-on handlers
86
+ * @default true
87
+ */
88
+ cacheHandlers?: boolean;
89
+ /**
90
+ * Enable TypeScript parsing in <script> blocks
91
+ * @default true
92
+ */
93
+ isTs?: boolean;
94
+ /**
95
+ * Script file extension for generated output
96
+ * @default 'ts'
97
+ */
98
+ scriptExt?: "ts" | "js";
99
+ /**
100
+ * Module name for runtime imports
101
+ * @default 'vue'
102
+ */
103
+ runtimeModuleName?: string;
104
+ /**
105
+ * Global variable name for runtime (IIFE builds)
106
+ * @default 'Vue'
107
+ */
108
+ runtimeGlobalName?: string;
109
+ }
110
+ /**
111
+ * Vite plugin configuration
112
+ */
113
+ interface VitePluginConfig {
114
+ /**
115
+ * Files to include in compilation
116
+ * @default /\.vue$/
117
+ */
118
+ include?: string | RegExp | (string | RegExp)[];
119
+ /**
120
+ * Files to exclude from compilation
121
+ * @default /node_modules/
122
+ */
123
+ exclude?: string | RegExp | (string | RegExp)[];
124
+ /**
125
+ * Glob patterns to scan for .vue files during pre-compilation
126
+ * @default ['**\/*.vue']
127
+ */
128
+ scanPatterns?: string[];
129
+ /**
130
+ * Glob patterns to ignore during pre-compilation
131
+ * @default ['node_modules/**', 'dist/**', '.git/**']
132
+ */
133
+ ignorePatterns?: string[];
134
+ }
135
+ /**
136
+ * Linter configuration
137
+ */
138
+ interface LinterConfig {
139
+ /**
140
+ * Enable linting
141
+ */
142
+ enabled?: boolean;
143
+ /**
144
+ * Rules to enable/disable
145
+ */
146
+ rules?: Record<string, RuleSeverity>;
147
+ /**
148
+ * Category-level severity overrides
149
+ */
150
+ categories?: Partial<Record<RuleCategory, RuleSeverity>>;
151
+ }
152
+ /**
153
+ * Type checker configuration
154
+ */
155
+ interface TypeCheckerConfig {
156
+ /**
157
+ * Enable type checking
158
+ * @default false
159
+ */
160
+ enabled?: boolean;
161
+ /**
162
+ * Enable strict mode
163
+ * @default false
164
+ */
165
+ strict?: boolean;
166
+ /**
167
+ * Check component props
168
+ * @default true
169
+ */
170
+ checkProps?: boolean;
171
+ /**
172
+ * Check component emits
173
+ * @default true
174
+ */
175
+ checkEmits?: boolean;
176
+ /**
177
+ * Check template bindings
178
+ * @default true
179
+ */
180
+ checkTemplateBindings?: boolean;
181
+ /**
182
+ * Path to tsconfig.json
183
+ * @default auto-detected
184
+ */
185
+ tsconfig?: string;
186
+ /**
187
+ * Path to tsgo binary
188
+ */
189
+ tsgoPath?: string;
190
+ }
191
+ /**
192
+ * Formatter configuration
193
+ */
194
+ interface FormatterConfig {
195
+ /**
196
+ * Max line width
197
+ * @default 80
198
+ */
199
+ printWidth?: number;
200
+ /**
201
+ * Indentation width
202
+ * @default 2
203
+ */
204
+ tabWidth?: number;
205
+ /**
206
+ * Use tabs for indentation
207
+ * @default false
208
+ */
209
+ useTabs?: boolean;
210
+ /**
211
+ * Print semicolons
212
+ * @default true
213
+ */
214
+ semi?: boolean;
215
+ /**
216
+ * Use single quotes
217
+ * @default false
218
+ */
219
+ singleQuote?: boolean;
220
+ /**
221
+ * Trailing commas
222
+ * @default 'all'
223
+ */
224
+ trailingComma?: "all" | "none" | "es5";
225
+ }
226
+ /**
227
+ * LSP configuration
228
+ */
229
+ interface LspConfig {
230
+ /**
231
+ * Enable LSP
232
+ * @default true
233
+ */
234
+ enabled?: boolean;
235
+ /**
236
+ * Enable diagnostics
237
+ * @default true
238
+ */
239
+ diagnostics?: boolean;
240
+ /**
241
+ * Enable completions
242
+ * @default true
243
+ */
244
+ completion?: boolean;
245
+ /**
246
+ * Enable hover information
247
+ * @default true
248
+ */
249
+ hover?: boolean;
250
+ /**
251
+ * Enable go-to-definition
252
+ * @default true
253
+ */
254
+ definition?: boolean;
255
+ /**
256
+ * Enable formatting via LSP
257
+ * @default true
258
+ */
259
+ formatting?: boolean;
260
+ /**
261
+ * Enable code actions
262
+ * @default true
263
+ */
264
+ codeActions?: boolean;
265
+ /**
266
+ * Use tsgo for type checking in LSP
267
+ * @default false
268
+ */
269
+ tsgo?: boolean;
270
+ }
271
+ /**
272
+ * VRT (Visual Regression Testing) configuration for Musea
273
+ */
274
+ interface MuseaVrtConfig {
275
+ /**
276
+ * Threshold for pixel comparison (0-1)
277
+ * @default 0.1
278
+ */
279
+ threshold?: number;
280
+ /**
281
+ * Output directory for screenshots
282
+ * @default '__musea_snapshots__'
283
+ */
284
+ outDir?: string;
285
+ /**
286
+ * Viewport sizes
287
+ */
288
+ viewports?: Array<{
289
+ width: number;
290
+ height: number;
291
+ name?: string;
292
+ }>;
293
+ }
294
+ /**
295
+ * A11y configuration for Musea
296
+ */
297
+ interface MuseaA11yConfig {
298
+ /**
299
+ * Enable a11y checking
300
+ * @default false
301
+ */
302
+ enabled?: boolean;
303
+ /**
304
+ * Axe-core rules to enable/disable
305
+ */
306
+ rules?: Record<string, boolean>;
307
+ }
308
+ /**
309
+ * Autogen configuration for Musea
310
+ */
311
+ interface MuseaAutogenConfig {
312
+ /**
313
+ * Enable auto-generation of variants
314
+ * @default false
315
+ */
316
+ enabled?: boolean;
317
+ /**
318
+ * Max variants to generate per component
319
+ * @default 10
320
+ */
321
+ maxVariants?: number;
322
+ }
323
+ /**
324
+ * Musea component gallery configuration
325
+ */
326
+ interface MuseaConfig {
327
+ /**
328
+ * Glob patterns for art files
329
+ * @default ['**\/*.art.vue']
330
+ */
331
+ include?: string[];
332
+ /**
333
+ * Glob patterns to exclude
334
+ * @default ['node_modules/**', 'dist/**']
335
+ */
336
+ exclude?: string[];
337
+ /**
338
+ * Base path for gallery
339
+ * @default '/__musea__'
340
+ */
341
+ basePath?: string;
342
+ /**
343
+ * Enable Storybook compatibility
344
+ * @default false
345
+ */
346
+ storybookCompat?: boolean;
347
+ /**
348
+ * Enable inline art detection in .vue files
349
+ * @default false
350
+ */
351
+ inlineArt?: boolean;
352
+ /**
353
+ * VRT configuration
354
+ */
355
+ vrt?: MuseaVrtConfig;
356
+ /**
357
+ * A11y configuration
358
+ */
359
+ a11y?: MuseaA11yConfig;
360
+ /**
361
+ * Autogen configuration
362
+ */
363
+ autogen?: MuseaAutogenConfig;
364
+ }
365
+ /**
366
+ * Global type declaration
367
+ */
368
+ interface GlobalTypeDeclaration {
369
+ /**
370
+ * TypeScript type string
371
+ */
372
+ type: string;
373
+ /**
374
+ * Default value
375
+ */
376
+ defaultValue?: string;
377
+ }
378
+ /**
379
+ * Global types configuration
380
+ */
381
+ type GlobalTypesConfig = Record<string, GlobalTypeDeclaration | string>;
382
+ /**
383
+ * Options for loading vize.config file
384
+ */
385
+ interface LoadConfigOptions {
386
+ /**
387
+ * Config file search mode
388
+ * - 'root': Search only in the specified root directory
389
+ * - 'auto': Search from cwd upward until finding a config file
390
+ * - 'none': Don't load config file
391
+ * @default 'root'
392
+ */
393
+ mode?: "root" | "auto" | "none";
394
+ /**
395
+ * Custom config file path (overrides automatic search)
396
+ */
397
+ configFile?: string;
398
+ /**
399
+ * Config environment for dynamic config resolution
400
+ */
401
+ env?: ConfigEnv;
402
+ } //#endregion
403
+ //#region src/types.d.ts
404
+ interface VizeOptions {
405
+ /**
406
+ * Files to include in compilation
407
+ * @default /\.vue$/
408
+ */
409
+ include?: string | RegExp | (string | RegExp)[];
410
+ /**
411
+ * Files to exclude from compilation
412
+ * @default /node_modules/
413
+ */
414
+ exclude?: string | RegExp | (string | RegExp)[];
415
+ /**
416
+ * Force production mode
417
+ * @default auto-detected from Vite config
418
+ */
419
+ isProduction?: boolean;
420
+ /**
421
+ * Enable SSR mode
422
+ * @default false
423
+ */
424
+ ssr?: boolean;
425
+ /**
426
+ * Enable source map generation
427
+ * @default true in development, false in production
428
+ */
429
+ sourceMap?: boolean;
430
+ /**
431
+ * Enable Vapor mode compilation
432
+ * @default false
433
+ */
434
+ vapor?: boolean;
435
+ /**
436
+ * Root directory to scan for .vue files
437
+ * @default Vite's root
438
+ */
439
+ root?: string;
440
+ /**
441
+ * Glob patterns to scan for .vue files during pre-compilation
442
+ * @default ['**\/*.vue']
443
+ */
444
+ scanPatterns?: string[];
445
+ /**
446
+ * Glob patterns to ignore during pre-compilation
447
+ * @default ['node_modules/**', 'dist/**', '.git/**']
448
+ */
449
+ ignorePatterns?: string[];
450
+ /**
451
+ * Config file search mode
452
+ * - 'root': Search only in the project root directory
453
+ * - 'auto': Search from cwd upward until finding a config file
454
+ * - false: Disable config file loading
455
+ * @default 'root'
456
+ */
457
+ configMode?: "root" | "auto" | false;
458
+ /**
459
+ * Custom config file path (overrides automatic search)
460
+ */
461
+ configFile?: string;
462
+ /**
463
+ * Enable debug logging
464
+ * @default false
465
+ */
466
+ debug?: boolean;
467
+ }
468
+ interface CompiledModule {
469
+ code: string;
470
+ css?: string;
471
+ scopeId: string;
472
+ hasScoped: boolean;
473
+ templateHash?: string;
474
+ styleHash?: string;
475
+ scriptHash?: string;
476
+ }
477
+
478
+ //#endregion
479
+ //#region src/index.d.ts
480
+ /**
481
+ * Define a Vize configuration with type checking.
482
+ * Accepts a plain object or a function that receives ConfigEnv.
483
+ */
484
+ declare function defineConfig(config: UserConfigExport): UserConfigExport;
485
+ /**
486
+ * Load Vize configuration from file
487
+ */
488
+ declare function loadConfig(root: string, options?: LoadConfigOptions): Promise<VizeConfig | null>;
489
+ /**
490
+ * Shared config store for inter-plugin communication.
491
+ * Key = project root, Value = resolved VizeConfig.
492
+ * Used by musea() and other plugins to access the unified config.
493
+ */
494
+ declare const vizeConfigStore: Map<string, VizeConfig>;
495
+ declare function vize(options?: VizeOptions): Plugin;
496
+
497
+ //#endregion
498
+ export { CompiledModule, LoadConfigOptions, VizeConfig, VizeOptions, vize as default, defineConfig, loadConfig, vize, vizeConfigStore };
package/dist/index.js ADDED
@@ -0,0 +1,542 @@
1
+ import { transformWithOxc } from "vite";
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import { glob } from "tinyglobby";
5
+ import * as native from "@vizejs/native";
6
+ import { createHash } from "node:crypto";
7
+
8
+ //#region src/hmr.ts
9
+ /**
10
+ * Detect the type of HMR update needed based on content hash changes.
11
+ *
12
+ * @param prev - Previously compiled module (undefined if first compile)
13
+ * @param next - Newly compiled module
14
+ * @returns The type of HMR update needed
15
+ */
16
+ function detectHmrUpdateType(prev, next) {
17
+ if (!prev) return "full-reload";
18
+ const scriptChanged = prev.scriptHash !== next.scriptHash;
19
+ if (scriptChanged) return "full-reload";
20
+ const templateChanged = prev.templateHash !== next.templateHash;
21
+ const styleChanged = prev.styleHash !== next.styleHash;
22
+ if (styleChanged && !templateChanged) return "style-only";
23
+ if (templateChanged) return "template-only";
24
+ return "full-reload";
25
+ }
26
+ /**
27
+ * Generate HMR-aware code output based on update type.
28
+ */
29
+ function generateHmrCode(scopeId, updateType) {
30
+ return `
31
+ if (import.meta.hot) {
32
+ _sfc_main.__hmrId = ${JSON.stringify(scopeId)};
33
+ _sfc_main.__hmrUpdateType = ${JSON.stringify(updateType)};
34
+
35
+ import.meta.hot.accept((mod) => {
36
+ if (!mod) return;
37
+ const { default: updated } = mod;
38
+ if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
39
+ const updateType = updated.__hmrUpdateType || 'full-reload';
40
+ if (updateType === 'template-only') {
41
+ __VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render);
42
+ } else {
43
+ __VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated);
44
+ }
45
+ }
46
+ });
47
+
48
+ import.meta.hot.on('vize:update', (data) => {
49
+ if (data.id !== _sfc_main.__hmrId) return;
50
+
51
+ if (data.type === 'style-only') {
52
+ // Update styles without remounting component
53
+ const styleId = 'vize-style-' + _sfc_main.__hmrId;
54
+ const styleEl = document.getElementById(styleId);
55
+ if (styleEl && data.css) {
56
+ styleEl.textContent = data.css;
57
+ }
58
+ }
59
+ });
60
+
61
+ if (typeof __VUE_HMR_RUNTIME__ !== 'undefined') {
62
+ __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main);
63
+ }
64
+ }`;
65
+ }
66
+
67
+ //#endregion
68
+ //#region src/utils.ts
69
+ function generateScopeId(filename) {
70
+ const hash = createHash("sha256").update(filename).digest("hex");
71
+ return hash.slice(0, 8);
72
+ }
73
+ function createFilter(include, exclude) {
74
+ const includePatterns = include ? Array.isArray(include) ? include : [include] : [/\.vue$/];
75
+ const excludePatterns = exclude ? Array.isArray(exclude) ? exclude : [exclude] : [/node_modules/];
76
+ return (id) => {
77
+ const matchInclude = includePatterns.some((pattern) => typeof pattern === "string" ? id.includes(pattern) : pattern.test(id));
78
+ const matchExclude = excludePatterns.some((pattern) => typeof pattern === "string" ? id.includes(pattern) : pattern.test(id));
79
+ return matchInclude && !matchExclude;
80
+ };
81
+ }
82
+ function generateOutput(compiled, options) {
83
+ const { isProduction, isDev, hmrUpdateType, extractCss } = options;
84
+ let output = compiled.code;
85
+ const exportDefaultRegex = /^export default /m;
86
+ const hasExportDefault = exportDefaultRegex.test(output);
87
+ const hasSfcMainDefined = /\bconst\s+_sfc_main\s*=/.test(output);
88
+ if (hasExportDefault && !hasSfcMainDefined) {
89
+ output = output.replace(exportDefaultRegex, "const _sfc_main = ");
90
+ if (compiled.hasScoped && compiled.scopeId) output += `\n_sfc_main.__scopeId = "data-v-${compiled.scopeId}";`;
91
+ output += "\nexport default _sfc_main;";
92
+ } else if (hasExportDefault && hasSfcMainDefined) {
93
+ if (compiled.hasScoped && compiled.scopeId) output = output.replace(/^export default _sfc_main/m, `_sfc_main.__scopeId = "data-v-${compiled.scopeId}";\nexport default _sfc_main`);
94
+ }
95
+ if (compiled.css && !(isProduction && extractCss)) {
96
+ const cssCode = JSON.stringify(compiled.css);
97
+ const cssId = JSON.stringify(`vize-style-${compiled.scopeId}`);
98
+ output = `
99
+ const __vize_css__ = ${cssCode};
100
+ const __vize_css_id__ = ${cssId};
101
+ (function() {
102
+ if (typeof document !== 'undefined') {
103
+ let style = document.getElementById(__vize_css_id__);
104
+ if (!style) {
105
+ style = document.createElement('style');
106
+ style.id = __vize_css_id__;
107
+ style.textContent = __vize_css__;
108
+ document.head.appendChild(style);
109
+ } else {
110
+ style.textContent = __vize_css__;
111
+ }
112
+ }
113
+ })();
114
+ ${output}`;
115
+ }
116
+ if (!isProduction && isDev && hasExportDefault) output += generateHmrCode(compiled.scopeId, hmrUpdateType ?? "full-reload");
117
+ return output;
118
+ }
119
+
120
+ //#endregion
121
+ //#region src/compiler.ts
122
+ const { compileSfc, compileSfcBatchWithResults } = native;
123
+ function compileFile(filePath, cache, options, source) {
124
+ const content = source ?? fs.readFileSync(filePath, "utf-8");
125
+ const scopeId = generateScopeId(filePath);
126
+ const hasScoped = /<style[^>]*\bscoped\b/.test(content);
127
+ const result = compileSfc(content, {
128
+ filename: filePath,
129
+ sourceMap: options.sourceMap,
130
+ ssr: options.ssr,
131
+ scopeId: hasScoped ? `data-v-${scopeId}` : void 0
132
+ });
133
+ if (result.errors.length > 0) {
134
+ const errorMsg = result.errors.join("\n");
135
+ console.error(`[vize] Compilation error in ${filePath}:\n${errorMsg}`);
136
+ }
137
+ if (result.warnings.length > 0) result.warnings.forEach((warning) => {
138
+ console.warn(`[vize] Warning in ${filePath}: ${warning}`);
139
+ });
140
+ const compiled = {
141
+ code: result.code,
142
+ css: result.css,
143
+ scopeId,
144
+ hasScoped
145
+ };
146
+ cache.set(filePath, compiled);
147
+ return compiled;
148
+ }
149
+ /**
150
+ * Batch compile multiple files in parallel using native Rust multithreading.
151
+ * Returns per-file results with content hashes for HMR.
152
+ */
153
+ function compileBatch(files, cache, options) {
154
+ const inputs = files.map((f) => ({
155
+ path: f.path,
156
+ source: f.source
157
+ }));
158
+ const result = compileSfcBatchWithResults(inputs, { ssr: options.ssr });
159
+ for (const fileResult of result.results) {
160
+ if (fileResult.errors.length === 0) cache.set(fileResult.path, {
161
+ code: fileResult.code,
162
+ css: fileResult.css,
163
+ scopeId: fileResult.scopeId,
164
+ hasScoped: fileResult.hasScoped,
165
+ templateHash: fileResult.templateHash,
166
+ styleHash: fileResult.styleHash,
167
+ scriptHash: fileResult.scriptHash
168
+ });
169
+ if (fileResult.errors.length > 0) console.error(`[vize] Compilation error in ${fileResult.path}:\n${fileResult.errors.join("\n")}`);
170
+ if (fileResult.warnings.length > 0) fileResult.warnings.forEach((warning) => {
171
+ console.warn(`[vize] Warning in ${fileResult.path}: ${warning}`);
172
+ });
173
+ }
174
+ return result;
175
+ }
176
+
177
+ //#endregion
178
+ //#region src/index.ts
179
+ const CONFIG_FILES = [
180
+ "vize.config.ts",
181
+ "vize.config.js",
182
+ "vize.config.mjs",
183
+ "vize.config.json"
184
+ ];
185
+ const DEFAULT_CONFIG_ENV = {
186
+ mode: "development",
187
+ command: "serve"
188
+ };
189
+ /**
190
+ * Define a Vize configuration with type checking.
191
+ * Accepts a plain object or a function that receives ConfigEnv.
192
+ */
193
+ function defineConfig(config) {
194
+ return config;
195
+ }
196
+ /**
197
+ * Load Vize configuration from file
198
+ */
199
+ async function loadConfig(root, options = {}) {
200
+ const { mode = "root", configFile, env } = options;
201
+ if (mode === "none") return null;
202
+ if (configFile) {
203
+ const configPath = path.isAbsolute(configFile) ? configFile : path.resolve(root, configFile);
204
+ return loadConfigFile(configPath, env);
205
+ }
206
+ if (mode === "auto") {
207
+ let searchDir = root;
208
+ while (true) {
209
+ const found$1 = findConfigInDir(searchDir);
210
+ if (found$1) return loadConfigFile(found$1, env);
211
+ const parentDir = path.dirname(searchDir);
212
+ if (parentDir === searchDir) break;
213
+ searchDir = parentDir;
214
+ }
215
+ return null;
216
+ }
217
+ const found = findConfigInDir(root);
218
+ return found ? loadConfigFile(found, env) : null;
219
+ }
220
+ function findConfigInDir(dir) {
221
+ for (const filename of CONFIG_FILES) {
222
+ const configPath = path.join(dir, filename);
223
+ if (fs.existsSync(configPath)) return configPath;
224
+ }
225
+ return null;
226
+ }
227
+ async function resolveConfigExport(exported, env) {
228
+ if (typeof exported === "function") return exported(env ?? DEFAULT_CONFIG_ENV);
229
+ return exported;
230
+ }
231
+ async function loadConfigFile(configPath, env) {
232
+ if (!fs.existsSync(configPath)) return null;
233
+ const ext = path.extname(configPath);
234
+ if (ext === ".json") {
235
+ const content = fs.readFileSync(configPath, "utf-8");
236
+ return JSON.parse(content);
237
+ }
238
+ try {
239
+ const module = await import(configPath);
240
+ const exported = module.default ?? module;
241
+ return resolveConfigExport(exported, env);
242
+ } catch (e) {
243
+ console.warn(`[vize] Failed to load config from ${configPath}:`, e);
244
+ return null;
245
+ }
246
+ }
247
+ /**
248
+ * Shared config store for inter-plugin communication.
249
+ * Key = project root, Value = resolved VizeConfig.
250
+ * Used by musea() and other plugins to access the unified config.
251
+ */
252
+ const vizeConfigStore = new Map();
253
+ const VIRTUAL_PREFIX = "\0vize:";
254
+ const VIRTUAL_CSS_MODULE = "virtual:vize-styles";
255
+ const RESOLVED_CSS_MODULE = "\0vize:all-styles.css";
256
+ function createLogger(debug) {
257
+ return {
258
+ log: (...args) => debug && console.log("[vize]", ...args),
259
+ info: (...args) => console.log("[vize]", ...args),
260
+ warn: (...args) => console.warn("[vize]", ...args),
261
+ error: (...args) => console.error("[vize]", ...args)
262
+ };
263
+ }
264
+ function vize(options = {}) {
265
+ const cache = new Map();
266
+ const virtualToReal = new Map();
267
+ const collectedCss = new Map();
268
+ let isProduction;
269
+ let root;
270
+ let server = null;
271
+ let filter;
272
+ let scanPatterns;
273
+ let ignorePatterns;
274
+ let mergedOptions;
275
+ let extractCss = false;
276
+ const logger = createLogger(options.debug ?? false);
277
+ async function compileAll() {
278
+ const startTime = performance.now();
279
+ const files = await glob(scanPatterns, {
280
+ cwd: root,
281
+ ignore: ignorePatterns,
282
+ absolute: true
283
+ });
284
+ logger.info(`Pre-compiling ${files.length} Vue files...`);
285
+ const fileContents = [];
286
+ for (const file of files) try {
287
+ const source = fs.readFileSync(file, "utf-8");
288
+ fileContents.push({
289
+ path: file,
290
+ source
291
+ });
292
+ } catch (e) {
293
+ logger.error(`Failed to read ${file}:`, e);
294
+ }
295
+ const result = compileBatch(fileContents, cache, { ssr: mergedOptions.ssr ?? false });
296
+ if (isProduction) {
297
+ for (const fileResult of result.results) if (fileResult.css) collectedCss.set(fileResult.path, fileResult.css);
298
+ }
299
+ const elapsed = (performance.now() - startTime).toFixed(2);
300
+ logger.info(`Pre-compilation complete: ${result.successCount} succeeded, ${result.failedCount} failed (${elapsed}ms, native batch: ${result.timeMs.toFixed(2)}ms)`);
301
+ }
302
+ function resolveVuePath(id, importer) {
303
+ let resolved;
304
+ if (id.startsWith("/@fs/")) resolved = id.slice(4);
305
+ else if (id.startsWith("/") && !fs.existsSync(id)) resolved = path.resolve(root, id.slice(1));
306
+ else if (path.isAbsolute(id)) resolved = id;
307
+ else if (importer) {
308
+ let realImporter = importer.startsWith(VIRTUAL_PREFIX) ? virtualToReal.get(importer) ?? importer.slice(VIRTUAL_PREFIX.length) : importer;
309
+ if (realImporter.endsWith(".vue.ts")) realImporter = realImporter.slice(0, -3);
310
+ resolved = path.resolve(path.dirname(realImporter), id);
311
+ } else resolved = path.resolve(root, id);
312
+ if (!path.isAbsolute(resolved)) resolved = path.resolve(root, resolved);
313
+ return path.normalize(resolved);
314
+ }
315
+ return {
316
+ name: "vite-plugin-vize",
317
+ enforce: "pre",
318
+ config() {
319
+ return { optimizeDeps: {
320
+ include: ["vue"],
321
+ exclude: ["virtual:vize-styles"],
322
+ esbuildOptions: { plugins: [{
323
+ name: "vize-externalize-vue",
324
+ setup(build) {
325
+ build.onResolve({ filter: /\.vue$/ }, (args) => ({
326
+ path: args.path,
327
+ external: true
328
+ }));
329
+ }
330
+ }] },
331
+ rolldownOptions: { external: [/\.vue$/] }
332
+ } };
333
+ },
334
+ async configResolved(resolvedConfig) {
335
+ root = options.root ?? resolvedConfig.root;
336
+ isProduction = options.isProduction ?? resolvedConfig.isProduction;
337
+ extractCss = isProduction;
338
+ const configEnv = {
339
+ mode: resolvedConfig.mode,
340
+ command: resolvedConfig.command === "build" ? "build" : "serve",
341
+ isSsrBuild: !!resolvedConfig.build?.ssr
342
+ };
343
+ let fileConfig = null;
344
+ if (options.configMode !== false) {
345
+ fileConfig = await loadConfig(root, {
346
+ mode: options.configMode ?? "root",
347
+ configFile: options.configFile,
348
+ env: configEnv
349
+ });
350
+ if (fileConfig) {
351
+ logger.log("Loaded config from vize.config file");
352
+ vizeConfigStore.set(root, fileConfig);
353
+ }
354
+ }
355
+ const viteConfig = fileConfig?.vite ?? {};
356
+ const compilerConfig = fileConfig?.compiler ?? {};
357
+ mergedOptions = {
358
+ ...options,
359
+ ssr: options.ssr ?? compilerConfig.ssr ?? false,
360
+ sourceMap: options.sourceMap ?? compilerConfig.sourceMap,
361
+ vapor: options.vapor ?? compilerConfig.vapor ?? false,
362
+ include: options.include ?? viteConfig.include,
363
+ exclude: options.exclude ?? viteConfig.exclude,
364
+ scanPatterns: options.scanPatterns ?? viteConfig.scanPatterns,
365
+ ignorePatterns: options.ignorePatterns ?? viteConfig.ignorePatterns
366
+ };
367
+ filter = createFilter(mergedOptions.include, mergedOptions.exclude);
368
+ scanPatterns = mergedOptions.scanPatterns ?? ["**/*.vue"];
369
+ ignorePatterns = mergedOptions.ignorePatterns ?? [
370
+ "node_modules/**",
371
+ "dist/**",
372
+ ".git/**"
373
+ ];
374
+ },
375
+ configureServer(devServer) {
376
+ server = devServer;
377
+ },
378
+ async buildStart() {
379
+ await compileAll();
380
+ logger.log("Cache keys:", [...cache.keys()].slice(0, 3));
381
+ },
382
+ async resolveId(id, importer) {
383
+ if (id.startsWith("\0")) return null;
384
+ if (id.startsWith("vize:")) {
385
+ let realPath = id.slice(5);
386
+ if (realPath.endsWith(".ts")) realPath = realPath.slice(0, -3);
387
+ logger.log(`resolveId: redirecting stale vize: ID to ${realPath}`);
388
+ if (realPath.includes("node_modules")) return this.resolve(realPath, importer, { skipSelf: true });
389
+ return this.resolve(realPath, importer, { skipSelf: true });
390
+ }
391
+ if (id === VIRTUAL_CSS_MODULE) return RESOLVED_CSS_MODULE;
392
+ if (id.includes("?vue&type=style")) return id;
393
+ if (importer?.startsWith(VIRTUAL_PREFIX)) {
394
+ const realImporter = virtualToReal.get(importer) ?? importer.slice(VIRTUAL_PREFIX.length);
395
+ const cleanImporter = realImporter.endsWith(".ts") ? realImporter.slice(0, -3) : realImporter;
396
+ logger.log(`resolveId from virtual: id=${id}, cleanImporter=${cleanImporter}`);
397
+ if (id.startsWith("#")) try {
398
+ return await this.resolve(id, cleanImporter, { skipSelf: true });
399
+ } catch {
400
+ return null;
401
+ }
402
+ if (!id.endsWith(".vue")) if (id.startsWith("./") || id.startsWith("../")) {
403
+ const [pathPart, queryPart] = id.split("?");
404
+ const querySuffix = queryPart ? `?${queryPart}` : "";
405
+ const resolved = path.resolve(path.dirname(cleanImporter), pathPart);
406
+ for (const ext of [
407
+ "",
408
+ ".ts",
409
+ ".tsx",
410
+ ".js",
411
+ ".jsx",
412
+ ".json"
413
+ ]) if (fs.existsSync(resolved + ext)) {
414
+ const finalPath = resolved + ext + querySuffix;
415
+ logger.log(`resolveId: resolved relative ${id} to ${finalPath}`);
416
+ return finalPath;
417
+ }
418
+ } else {
419
+ if (id.includes("/dist/") || id.includes("/lib/") || id.includes("/es/")) {
420
+ logger.log(`resolveId: skipping already-resolved path ${id}`);
421
+ return null;
422
+ }
423
+ logger.log(`resolveId: resolving external ${id} from ${cleanImporter}`);
424
+ const resolved = await this.resolve(id, cleanImporter, { skipSelf: true });
425
+ logger.log(`resolveId: resolved external ${id} to`, resolved?.id ?? "null");
426
+ return resolved;
427
+ }
428
+ }
429
+ if (id.endsWith(".vue")) {
430
+ if (id.includes("node_modules")) {
431
+ logger.log(`resolveId: skipping node_modules import ${id}`);
432
+ return null;
433
+ }
434
+ const resolved = resolveVuePath(id, importer);
435
+ if (resolved.includes("node_modules")) {
436
+ logger.log(`resolveId: skipping node_modules path ${resolved}`);
437
+ return null;
438
+ }
439
+ if (!filter(resolved)) {
440
+ logger.log(`resolveId: skipping filtered path ${resolved}`);
441
+ return null;
442
+ }
443
+ const hasCache = cache.has(resolved);
444
+ const fileExists = fs.existsSync(resolved);
445
+ logger.log(`resolveId: id=${id}, resolved=${resolved}, hasCache=${hasCache}, fileExists=${fileExists}, importer=${importer ?? "none"}`);
446
+ if (hasCache || fileExists) {
447
+ const virtualId = VIRTUAL_PREFIX + resolved + ".ts";
448
+ virtualToReal.set(virtualId, resolved);
449
+ return virtualId;
450
+ }
451
+ }
452
+ return null;
453
+ },
454
+ load(id) {
455
+ if (id === RESOLVED_CSS_MODULE) {
456
+ const allCss = Array.from(collectedCss.values()).join("\n\n");
457
+ return allCss;
458
+ }
459
+ if (id.includes("?vue&type=style")) {
460
+ const [filename] = id.split("?");
461
+ const realPath = filename.startsWith(VIRTUAL_PREFIX) ? virtualToReal.get(filename) ?? filename.slice(VIRTUAL_PREFIX.length) : filename;
462
+ const compiled = cache.get(realPath);
463
+ if (compiled?.css) return compiled.css;
464
+ return "";
465
+ }
466
+ if (id.startsWith(VIRTUAL_PREFIX)) {
467
+ const lookupId = id.endsWith(".ts") ? id.slice(0, -3) : id;
468
+ const realPath = virtualToReal.get(id) ?? lookupId.slice(VIRTUAL_PREFIX.length);
469
+ const compiled = cache.get(realPath);
470
+ if (compiled) {
471
+ const output = generateOutput(compiled, {
472
+ isProduction,
473
+ isDev: server !== null,
474
+ extractCss
475
+ });
476
+ return {
477
+ code: output,
478
+ map: null
479
+ };
480
+ }
481
+ }
482
+ return null;
483
+ },
484
+ async transform(code, id) {
485
+ if (id.startsWith(VIRTUAL_PREFIX) && id.endsWith(".ts")) {
486
+ const result = await transformWithOxc(code, id.slice(VIRTUAL_PREFIX.length), { lang: "ts" });
487
+ return {
488
+ code: result.code,
489
+ map: result.map
490
+ };
491
+ }
492
+ return null;
493
+ },
494
+ async handleHotUpdate(ctx) {
495
+ const { file, server: server$1, read } = ctx;
496
+ if (file.endsWith(".vue") && filter(file)) try {
497
+ const source = await read();
498
+ const prevCompiled = cache.get(file);
499
+ compileFile(file, cache, {
500
+ sourceMap: mergedOptions.sourceMap ?? !isProduction,
501
+ ssr: mergedOptions.ssr ?? false
502
+ }, source);
503
+ const newCompiled = cache.get(file);
504
+ const updateType = detectHmrUpdateType(prevCompiled, newCompiled);
505
+ logger.log(`Re-compiled: ${path.relative(root, file)} (${updateType})`);
506
+ const virtualId = VIRTUAL_PREFIX + file + ".ts";
507
+ const modules = server$1.moduleGraph.getModulesByFile(virtualId) ?? server$1.moduleGraph.getModulesByFile(file);
508
+ if (updateType === "style-only" && newCompiled.css) {
509
+ server$1.ws.send({
510
+ type: "custom",
511
+ event: "vize:update",
512
+ data: {
513
+ id: newCompiled.scopeId,
514
+ type: "style-only",
515
+ css: newCompiled.css
516
+ }
517
+ });
518
+ return [];
519
+ }
520
+ if (modules) return [...modules];
521
+ } catch (e) {
522
+ logger.error(`Re-compilation failed for ${file}:`, e);
523
+ }
524
+ },
525
+ generateBundle(_, _bundle) {
526
+ if (!extractCss || collectedCss.size === 0) return;
527
+ const allCss = Array.from(collectedCss.values()).join("\n\n");
528
+ if (allCss.trim()) {
529
+ this.emitFile({
530
+ type: "asset",
531
+ fileName: "assets/vize-components.css",
532
+ source: allCss
533
+ });
534
+ logger.log(`Extracted CSS to assets/vize-components.css (${collectedCss.size} components)`);
535
+ }
536
+ }
537
+ };
538
+ }
539
+ var src_default = vize;
540
+
541
+ //#endregion
542
+ export { src_default as default, defineConfig, loadConfig, vize, vizeConfigStore };
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@vizejs/vite-plugin",
3
+ "version": "0.0.1-alpha.100",
4
+ "description": "High-performance native Vite plugin for Vue SFC compilation powered by Vize",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist"
19
+ ],
20
+ "keywords": [
21
+ "vite",
22
+ "vue",
23
+ "plugin",
24
+ "sfc",
25
+ "compiler",
26
+ "native",
27
+ "fast"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/ubugeeei/vize",
32
+ "directory": "npm/vite-plugin-vize"
33
+ },
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "@types/node": "^22.0.0",
37
+ "tsdown": "^0.9.0",
38
+ "typescript": "~5.6.0",
39
+ "vite": "^8.0.0-beta.0"
40
+ },
41
+ "peerDependencies": {
42
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
43
+ },
44
+ "dependencies": {
45
+ "tinyglobby": "^0.2.0",
46
+ "@vizejs/native": "0.0.1-alpha.100"
47
+ },
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "dev": "tsdown --watch",
51
+ "lint": "oxlint --deny-warnings --type-aware --tsconfig tsconfig.json",
52
+ "lint:fix": "oxlint --type-aware --tsconfig tsconfig.json --fix",
53
+ "fmt": "oxfmt --write src",
54
+ "fmt:check": "oxfmt src"
55
+ }
56
+ }