@vizejs/vite-plugin 0.0.1-alpha.44
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 +214 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +447 -0
- package/package.json +58 -0
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 { vizeNative } from '@vizejs/vite-plugin'
|
|
34
|
+
|
|
35
|
+
export default defineConfig({
|
|
36
|
+
plugins: [
|
|
37
|
+
vizeNative({
|
|
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 { vizeNative } from '@vizejs/vite-plugin'
|
|
51
|
+
|
|
52
|
+
export default defineNuxtConfig({
|
|
53
|
+
vite: {
|
|
54
|
+
plugins: [
|
|
55
|
+
vizeNative({
|
|
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
|
+
vizeNative()
|
|
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
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { LoadConfigOptions, VizeConfig, defineConfig, loadConfig } from "vizejs";
|
|
2
|
+
import { Plugin } from "vite";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
|
|
6
|
+
interface VizeOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Files to include in compilation
|
|
9
|
+
* @default /\.vue$/
|
|
10
|
+
*/
|
|
11
|
+
include?: string | RegExp | (string | RegExp)[];
|
|
12
|
+
/**
|
|
13
|
+
* Files to exclude from compilation
|
|
14
|
+
* @default /node_modules/
|
|
15
|
+
*/
|
|
16
|
+
exclude?: string | RegExp | (string | RegExp)[];
|
|
17
|
+
/**
|
|
18
|
+
* Force production mode
|
|
19
|
+
* @default auto-detected from Vite config
|
|
20
|
+
*/
|
|
21
|
+
isProduction?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Enable SSR mode
|
|
24
|
+
* @default false
|
|
25
|
+
*/
|
|
26
|
+
ssr?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Enable source map generation
|
|
29
|
+
* @default true in development, false in production
|
|
30
|
+
*/
|
|
31
|
+
sourceMap?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Enable Vapor mode compilation
|
|
34
|
+
* @default false
|
|
35
|
+
*/
|
|
36
|
+
vapor?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Root directory to scan for .vue files
|
|
39
|
+
* @default Vite's root
|
|
40
|
+
*/
|
|
41
|
+
root?: string;
|
|
42
|
+
/**
|
|
43
|
+
* Glob patterns to scan for .vue files during pre-compilation
|
|
44
|
+
* @default ['**\/*.vue']
|
|
45
|
+
*/
|
|
46
|
+
scanPatterns?: string[];
|
|
47
|
+
/**
|
|
48
|
+
* Glob patterns to ignore during pre-compilation
|
|
49
|
+
* @default ['node_modules/**', 'dist/**', '.git/**']
|
|
50
|
+
*/
|
|
51
|
+
ignorePatterns?: string[];
|
|
52
|
+
/**
|
|
53
|
+
* Config file search mode
|
|
54
|
+
* - 'root': Search only in the project root directory
|
|
55
|
+
* - 'auto': Search from cwd upward until finding a config file
|
|
56
|
+
* - false: Disable config file loading
|
|
57
|
+
* @default 'root'
|
|
58
|
+
*/
|
|
59
|
+
configMode?: "root" | "auto" | false;
|
|
60
|
+
/**
|
|
61
|
+
* Custom config file path (overrides automatic search)
|
|
62
|
+
*/
|
|
63
|
+
configFile?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Enable debug logging
|
|
66
|
+
* @default false
|
|
67
|
+
*/
|
|
68
|
+
debug?: boolean;
|
|
69
|
+
}
|
|
70
|
+
interface CompiledModule {
|
|
71
|
+
code: string;
|
|
72
|
+
css?: string;
|
|
73
|
+
scopeId: string;
|
|
74
|
+
hasScoped: boolean;
|
|
75
|
+
templateHash?: string;
|
|
76
|
+
styleHash?: string;
|
|
77
|
+
scriptHash?: string;
|
|
78
|
+
} //#endregion
|
|
79
|
+
//#region src/index.d.ts
|
|
80
|
+
declare function vize(options?: VizeOptions): Plugin;
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
export { CompiledModule, LoadConfigOptions, VizeConfig, VizeOptions, vize as default, defineConfig, loadConfig, vize };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { glob } from "tinyglobby";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { defineConfig, loadConfig } from "vizejs";
|
|
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 require = createRequire(import.meta.url);
|
|
123
|
+
let compileSfc = null;
|
|
124
|
+
let compileSfcBatchWithResults = null;
|
|
125
|
+
function loadNative() {
|
|
126
|
+
if (compileSfc) return compileSfc;
|
|
127
|
+
try {
|
|
128
|
+
const native = require("@vizejs/native");
|
|
129
|
+
compileSfc = native.compileSfc;
|
|
130
|
+
return compileSfc;
|
|
131
|
+
} catch (e) {
|
|
132
|
+
throw new Error(`Failed to load @vizejs/native. Make sure it's installed and built:\n${String(e)}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function loadNativeBatch() {
|
|
136
|
+
if (compileSfcBatchWithResults) return compileSfcBatchWithResults;
|
|
137
|
+
try {
|
|
138
|
+
const native = require("@vizejs/native");
|
|
139
|
+
compileSfcBatchWithResults = native.compileSfcBatchWithResults;
|
|
140
|
+
return compileSfcBatchWithResults;
|
|
141
|
+
} catch (e) {
|
|
142
|
+
throw new Error(`Failed to load @vizejs/native. Make sure it's installed and built:\n${String(e)}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function compileFile(filePath, cache, options, source) {
|
|
146
|
+
const compile = loadNative();
|
|
147
|
+
const content = source ?? fs.readFileSync(filePath, "utf-8");
|
|
148
|
+
const scopeId = generateScopeId(filePath);
|
|
149
|
+
const hasScoped = /<style[^>]*\bscoped\b/.test(content);
|
|
150
|
+
const result = compile(content, {
|
|
151
|
+
filename: filePath,
|
|
152
|
+
sourceMap: options.sourceMap,
|
|
153
|
+
ssr: options.ssr,
|
|
154
|
+
scopeId: hasScoped ? `data-v-${scopeId}` : void 0
|
|
155
|
+
});
|
|
156
|
+
if (result.errors.length > 0) {
|
|
157
|
+
const errorMsg = result.errors.join("\n");
|
|
158
|
+
console.error(`[vize] Compilation error in ${filePath}:\n${errorMsg}`);
|
|
159
|
+
}
|
|
160
|
+
if (result.warnings.length > 0) result.warnings.forEach((warning) => {
|
|
161
|
+
console.warn(`[vize] Warning in ${filePath}: ${warning}`);
|
|
162
|
+
});
|
|
163
|
+
const compiled = {
|
|
164
|
+
code: result.code,
|
|
165
|
+
css: result.css,
|
|
166
|
+
scopeId,
|
|
167
|
+
hasScoped
|
|
168
|
+
};
|
|
169
|
+
cache.set(filePath, compiled);
|
|
170
|
+
return compiled;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Batch compile multiple files in parallel using native Rust multithreading.
|
|
174
|
+
* Returns per-file results with content hashes for HMR.
|
|
175
|
+
*/
|
|
176
|
+
function compileBatch(files, cache, options) {
|
|
177
|
+
const compile = loadNativeBatch();
|
|
178
|
+
const inputs = files.map((f) => ({
|
|
179
|
+
path: f.path,
|
|
180
|
+
source: f.source
|
|
181
|
+
}));
|
|
182
|
+
const result = compile(inputs, { ssr: options.ssr });
|
|
183
|
+
for (const fileResult of result.results) {
|
|
184
|
+
if (fileResult.errors.length === 0) cache.set(fileResult.path, {
|
|
185
|
+
code: fileResult.code,
|
|
186
|
+
css: fileResult.css,
|
|
187
|
+
scopeId: fileResult.scopeId,
|
|
188
|
+
hasScoped: fileResult.hasScoped,
|
|
189
|
+
templateHash: fileResult.templateHash,
|
|
190
|
+
styleHash: fileResult.styleHash,
|
|
191
|
+
scriptHash: fileResult.scriptHash
|
|
192
|
+
});
|
|
193
|
+
if (fileResult.errors.length > 0) console.error(`[vize] Compilation error in ${fileResult.path}:\n${fileResult.errors.join("\n")}`);
|
|
194
|
+
if (fileResult.warnings.length > 0) fileResult.warnings.forEach((warning) => {
|
|
195
|
+
console.warn(`[vize] Warning in ${fileResult.path}: ${warning}`);
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return result;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/index.ts
|
|
203
|
+
const VIRTUAL_PREFIX = "\0vize:";
|
|
204
|
+
const VIRTUAL_CSS_MODULE = "virtual:vize-styles";
|
|
205
|
+
const RESOLVED_CSS_MODULE = "\0vize:all-styles.css";
|
|
206
|
+
function createLogger(debug) {
|
|
207
|
+
return {
|
|
208
|
+
log: (...args) => debug && console.log("[vize]", ...args),
|
|
209
|
+
info: (...args) => console.log("[vize]", ...args),
|
|
210
|
+
warn: (...args) => console.warn("[vize]", ...args),
|
|
211
|
+
error: (...args) => console.error("[vize]", ...args)
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function vize(options = {}) {
|
|
215
|
+
const cache = new Map();
|
|
216
|
+
const virtualToReal = new Map();
|
|
217
|
+
const collectedCss = new Map();
|
|
218
|
+
let isProduction;
|
|
219
|
+
let root;
|
|
220
|
+
let server = null;
|
|
221
|
+
let filter;
|
|
222
|
+
let scanPatterns;
|
|
223
|
+
let ignorePatterns;
|
|
224
|
+
let mergedOptions;
|
|
225
|
+
let extractCss = false;
|
|
226
|
+
const logger = createLogger(options.debug ?? false);
|
|
227
|
+
async function compileAll() {
|
|
228
|
+
const startTime = performance.now();
|
|
229
|
+
const files = await glob(scanPatterns, {
|
|
230
|
+
cwd: root,
|
|
231
|
+
ignore: ignorePatterns,
|
|
232
|
+
absolute: true
|
|
233
|
+
});
|
|
234
|
+
logger.info(`Pre-compiling ${files.length} Vue files...`);
|
|
235
|
+
const fileContents = [];
|
|
236
|
+
for (const file of files) try {
|
|
237
|
+
const source = fs.readFileSync(file, "utf-8");
|
|
238
|
+
fileContents.push({
|
|
239
|
+
path: file,
|
|
240
|
+
source
|
|
241
|
+
});
|
|
242
|
+
} catch (e) {
|
|
243
|
+
logger.error(`Failed to read ${file}:`, e);
|
|
244
|
+
}
|
|
245
|
+
const result = compileBatch(fileContents, cache, { ssr: mergedOptions.ssr ?? false });
|
|
246
|
+
if (isProduction) {
|
|
247
|
+
for (const fileResult of result.results) if (fileResult.css) collectedCss.set(fileResult.path, fileResult.css);
|
|
248
|
+
}
|
|
249
|
+
const elapsed = (performance.now() - startTime).toFixed(2);
|
|
250
|
+
logger.info(`Pre-compilation complete: ${result.successCount} succeeded, ${result.failedCount} failed (${elapsed}ms, native batch: ${result.timeMs.toFixed(2)}ms)`);
|
|
251
|
+
}
|
|
252
|
+
function resolveVuePath(id, importer) {
|
|
253
|
+
let resolved;
|
|
254
|
+
if (id.startsWith("/@fs/")) resolved = id.slice(4);
|
|
255
|
+
else if (id.startsWith("/") && !fs.existsSync(id)) resolved = path.resolve(root, id.slice(1));
|
|
256
|
+
else if (path.isAbsolute(id)) resolved = id;
|
|
257
|
+
else if (importer) {
|
|
258
|
+
const realImporter = importer.startsWith(VIRTUAL_PREFIX) ? virtualToReal.get(importer) ?? importer.slice(VIRTUAL_PREFIX.length) : importer;
|
|
259
|
+
resolved = path.resolve(path.dirname(realImporter), id);
|
|
260
|
+
} else resolved = path.resolve(root, id);
|
|
261
|
+
return path.normalize(resolved);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
name: "vite-plugin-vize",
|
|
265
|
+
enforce: "pre",
|
|
266
|
+
config() {
|
|
267
|
+
return { optimizeDeps: {
|
|
268
|
+
include: ["vue"],
|
|
269
|
+
exclude: ["virtual:vize-styles"],
|
|
270
|
+
esbuildOptions: { plugins: [{
|
|
271
|
+
name: "vize-externalize-vue",
|
|
272
|
+
setup(build) {
|
|
273
|
+
build.onResolve({ filter: /\.vue$/ }, (args) => ({
|
|
274
|
+
path: args.path,
|
|
275
|
+
external: true
|
|
276
|
+
}));
|
|
277
|
+
}
|
|
278
|
+
}] },
|
|
279
|
+
rolldownOptions: { external: [/\.vue$/] }
|
|
280
|
+
} };
|
|
281
|
+
},
|
|
282
|
+
async configResolved(resolvedConfig) {
|
|
283
|
+
root = options.root ?? resolvedConfig.root;
|
|
284
|
+
isProduction = options.isProduction ?? resolvedConfig.isProduction;
|
|
285
|
+
extractCss = isProduction;
|
|
286
|
+
let fileConfig = null;
|
|
287
|
+
if (options.configMode !== false) {
|
|
288
|
+
const { loadConfig: loadConfig$1 } = await import("vizejs");
|
|
289
|
+
fileConfig = await loadConfig$1(root, {
|
|
290
|
+
mode: options.configMode ?? "root",
|
|
291
|
+
configFile: options.configFile
|
|
292
|
+
});
|
|
293
|
+
if (fileConfig) logger.log("Loaded config from vize.config file");
|
|
294
|
+
}
|
|
295
|
+
const viteConfig = fileConfig?.vite ?? {};
|
|
296
|
+
const compilerConfig = fileConfig?.compiler ?? {};
|
|
297
|
+
mergedOptions = {
|
|
298
|
+
...options,
|
|
299
|
+
ssr: options.ssr ?? compilerConfig.ssr ?? false,
|
|
300
|
+
sourceMap: options.sourceMap ?? compilerConfig.sourceMap,
|
|
301
|
+
vapor: options.vapor ?? compilerConfig.vapor ?? false,
|
|
302
|
+
include: options.include ?? viteConfig.include,
|
|
303
|
+
exclude: options.exclude ?? viteConfig.exclude,
|
|
304
|
+
scanPatterns: options.scanPatterns ?? viteConfig.scanPatterns,
|
|
305
|
+
ignorePatterns: options.ignorePatterns ?? viteConfig.ignorePatterns
|
|
306
|
+
};
|
|
307
|
+
filter = createFilter(mergedOptions.include, mergedOptions.exclude);
|
|
308
|
+
scanPatterns = mergedOptions.scanPatterns ?? ["**/*.vue"];
|
|
309
|
+
ignorePatterns = mergedOptions.ignorePatterns ?? [
|
|
310
|
+
"node_modules/**",
|
|
311
|
+
"dist/**",
|
|
312
|
+
".git/**"
|
|
313
|
+
];
|
|
314
|
+
},
|
|
315
|
+
configureServer(devServer) {
|
|
316
|
+
server = devServer;
|
|
317
|
+
},
|
|
318
|
+
async buildStart() {
|
|
319
|
+
await compileAll();
|
|
320
|
+
logger.log("Cache keys:", [...cache.keys()].slice(0, 3));
|
|
321
|
+
},
|
|
322
|
+
async resolveId(id, importer) {
|
|
323
|
+
if (id === VIRTUAL_CSS_MODULE) return RESOLVED_CSS_MODULE;
|
|
324
|
+
if (id.includes("?vue&type=style")) return id;
|
|
325
|
+
if (importer?.startsWith(VIRTUAL_PREFIX)) {
|
|
326
|
+
const realImporter = virtualToReal.get(importer) ?? importer.slice(VIRTUAL_PREFIX.length);
|
|
327
|
+
const cleanImporter = realImporter.endsWith(".ts") ? realImporter.slice(0, -3) : realImporter;
|
|
328
|
+
logger.log(`resolveId from virtual: id=${id}, cleanImporter=${cleanImporter}`);
|
|
329
|
+
if (!id.endsWith(".vue")) if (id.startsWith("./") || id.startsWith("../")) {
|
|
330
|
+
const [pathPart, queryPart] = id.split("?");
|
|
331
|
+
const querySuffix = queryPart ? `?${queryPart}` : "";
|
|
332
|
+
const resolved = path.resolve(path.dirname(cleanImporter), pathPart);
|
|
333
|
+
for (const ext of [
|
|
334
|
+
"",
|
|
335
|
+
".ts",
|
|
336
|
+
".tsx",
|
|
337
|
+
".js",
|
|
338
|
+
".jsx",
|
|
339
|
+
".json"
|
|
340
|
+
]) if (fs.existsSync(resolved + ext)) {
|
|
341
|
+
const finalPath = resolved + ext + querySuffix;
|
|
342
|
+
logger.log(`resolveId: resolved relative ${id} to ${finalPath}`);
|
|
343
|
+
return finalPath;
|
|
344
|
+
}
|
|
345
|
+
} else {
|
|
346
|
+
if (id.includes("/dist/") || id.includes("/lib/") || id.includes("/es/")) {
|
|
347
|
+
logger.log(`resolveId: skipping already-resolved path ${id}`);
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
logger.log(`resolveId: resolving external ${id} from ${cleanImporter}`);
|
|
351
|
+
const resolved = await this.resolve(id, cleanImporter, { skipSelf: true });
|
|
352
|
+
logger.log(`resolveId: resolved external ${id} to`, resolved?.id ?? "null");
|
|
353
|
+
return resolved;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (id.endsWith(".vue")) {
|
|
357
|
+
const resolved = resolveVuePath(id, importer);
|
|
358
|
+
const hasCache = cache.has(resolved);
|
|
359
|
+
const fileExists = fs.existsSync(resolved);
|
|
360
|
+
logger.log(`resolveId: id=${id}, resolved=${resolved}, hasCache=${hasCache}, fileExists=${fileExists}, importer=${importer ?? "none"}`);
|
|
361
|
+
if (hasCache || fileExists) {
|
|
362
|
+
const virtualId = VIRTUAL_PREFIX + resolved + ".ts";
|
|
363
|
+
virtualToReal.set(virtualId, resolved);
|
|
364
|
+
return virtualId;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return null;
|
|
368
|
+
},
|
|
369
|
+
load(id) {
|
|
370
|
+
if (id === RESOLVED_CSS_MODULE) {
|
|
371
|
+
const allCss = Array.from(collectedCss.values()).join("\n\n");
|
|
372
|
+
return allCss;
|
|
373
|
+
}
|
|
374
|
+
if (id.includes("?vue&type=style")) {
|
|
375
|
+
const [filename] = id.split("?");
|
|
376
|
+
const realPath = filename.startsWith(VIRTUAL_PREFIX) ? virtualToReal.get(filename) ?? filename.slice(VIRTUAL_PREFIX.length) : filename;
|
|
377
|
+
const compiled = cache.get(realPath);
|
|
378
|
+
if (compiled?.css) return compiled.css;
|
|
379
|
+
return "";
|
|
380
|
+
}
|
|
381
|
+
if (id.startsWith(VIRTUAL_PREFIX)) {
|
|
382
|
+
const lookupId = id.endsWith(".ts") ? id.slice(0, -3) : id;
|
|
383
|
+
const realPath = virtualToReal.get(id) ?? lookupId.slice(VIRTUAL_PREFIX.length);
|
|
384
|
+
const compiled = cache.get(realPath);
|
|
385
|
+
if (compiled) {
|
|
386
|
+
const output = generateOutput(compiled, {
|
|
387
|
+
isProduction,
|
|
388
|
+
isDev: server !== null,
|
|
389
|
+
extractCss
|
|
390
|
+
});
|
|
391
|
+
return {
|
|
392
|
+
code: output,
|
|
393
|
+
map: null
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return null;
|
|
398
|
+
},
|
|
399
|
+
async handleHotUpdate(ctx) {
|
|
400
|
+
const { file, server: server$1, read } = ctx;
|
|
401
|
+
if (file.endsWith(".vue") && filter(file)) try {
|
|
402
|
+
const source = await read();
|
|
403
|
+
const prevCompiled = cache.get(file);
|
|
404
|
+
compileFile(file, cache, {
|
|
405
|
+
sourceMap: mergedOptions.sourceMap ?? !isProduction,
|
|
406
|
+
ssr: mergedOptions.ssr ?? false
|
|
407
|
+
}, source);
|
|
408
|
+
const newCompiled = cache.get(file);
|
|
409
|
+
const updateType = detectHmrUpdateType(prevCompiled, newCompiled);
|
|
410
|
+
logger.log(`Re-compiled: ${path.relative(root, file)} (${updateType})`);
|
|
411
|
+
const virtualId = VIRTUAL_PREFIX + file + ".ts";
|
|
412
|
+
const modules = server$1.moduleGraph.getModulesByFile(virtualId) ?? server$1.moduleGraph.getModulesByFile(file);
|
|
413
|
+
if (updateType === "style-only" && newCompiled.css) {
|
|
414
|
+
server$1.ws.send({
|
|
415
|
+
type: "custom",
|
|
416
|
+
event: "vize:update",
|
|
417
|
+
data: {
|
|
418
|
+
id: newCompiled.scopeId,
|
|
419
|
+
type: "style-only",
|
|
420
|
+
css: newCompiled.css
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
if (modules) return [...modules];
|
|
426
|
+
} catch (e) {
|
|
427
|
+
logger.error(`Re-compilation failed for ${file}:`, e);
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
generateBundle(_, _bundle) {
|
|
431
|
+
if (!extractCss || collectedCss.size === 0) return;
|
|
432
|
+
const allCss = Array.from(collectedCss.values()).join("\n\n");
|
|
433
|
+
if (allCss.trim()) {
|
|
434
|
+
this.emitFile({
|
|
435
|
+
type: "asset",
|
|
436
|
+
fileName: "assets/vize-components.css",
|
|
437
|
+
source: allCss
|
|
438
|
+
});
|
|
439
|
+
logger.log(`Extracted CSS to assets/vize-components.css (${collectedCss.size} components)`);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
var src_default = vize;
|
|
445
|
+
|
|
446
|
+
//#endregion
|
|
447
|
+
export { src_default as default, defineConfig, loadConfig, vize };
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vizejs/vite-plugin",
|
|
3
|
+
"version": "0.0.1-alpha.44",
|
|
4
|
+
"description": "High-performance native Vite plugin for Vue SFC compilation powered by Vize",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"provenance": true,
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"vite",
|
|
23
|
+
"vue",
|
|
24
|
+
"plugin",
|
|
25
|
+
"sfc",
|
|
26
|
+
"compiler",
|
|
27
|
+
"native",
|
|
28
|
+
"fast"
|
|
29
|
+
],
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/ubugeeei/vize",
|
|
33
|
+
"directory": "npm/vite-plugin-vize"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.0.0",
|
|
38
|
+
"tsdown": "^0.9.0",
|
|
39
|
+
"typescript": "~5.6.0",
|
|
40
|
+
"vite": "^8.0.0-beta.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"tinyglobby": "^0.2.0",
|
|
47
|
+
"vizejs": "0.0.1-alpha.20",
|
|
48
|
+
"@vizejs/native": "0.0.1-alpha.44"
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsdown",
|
|
52
|
+
"dev": "tsdown --watch",
|
|
53
|
+
"lint": "oxlint --deny-warnings --type-aware --tsconfig tsconfig.json",
|
|
54
|
+
"lint:fix": "oxlint --type-aware --tsconfig tsconfig.json --fix",
|
|
55
|
+
"fmt": "oxfmt --write src",
|
|
56
|
+
"fmt:check": "oxfmt src"
|
|
57
|
+
}
|
|
58
|
+
}
|