@vigla/vite-plugin 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +12 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +1 -0
- package/dist/api.js.map +1 -0
- package/dist/compile.d.ts +16 -0
- package/dist/compile.d.ts.map +1 -0
- package/dist/compile.js +48 -0
- package/dist/compile.js.map +1 -0
- package/dist/compiler.d.ts +115 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +1 -0
- package/dist/compiler.js.map +1 -0
- package/dist/config-file.d.ts +10 -0
- package/dist/config-file.d.ts.map +1 -0
- package/dist/config-file.js +63 -0
- package/dist/config-file.js.map +1 -0
- package/dist/dependencies.d.ts +3 -0
- package/dist/dependencies.d.ts.map +1 -0
- package/dist/dependencies.js +40 -0
- package/dist/dependencies.js.map +1 -0
- package/dist/error.d.ts +7 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +49 -0
- package/dist/error.js.map +1 -0
- package/dist/front-end.d.ts +24 -0
- package/dist/front-end.d.ts.map +1 -0
- package/dist/front-end.js +120 -0
- package/dist/front-end.js.map +1 -0
- package/dist/id.d.ts +40 -0
- package/dist/id.d.ts.map +1 -0
- package/dist/id.js +144 -0
- package/dist/id.js.map +1 -0
- package/dist/identity.d.ts +41 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +31 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/log.d.ts +17 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +92 -0
- package/dist/log.js.map +1 -0
- package/dist/maps.d.ts +8 -0
- package/dist/maps.d.ts.map +1 -0
- package/dist/maps.js +37 -0
- package/dist/maps.js.map +1 -0
- package/dist/messages.d.ts +17 -0
- package/dist/messages.d.ts.map +1 -0
- package/dist/messages.js +16 -0
- package/dist/messages.js.map +1 -0
- package/dist/options.d.ts +26 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +187 -0
- package/dist/options.js.map +1 -0
- package/dist/plugins/compile-module.d.ts +5 -0
- package/dist/plugins/compile-module.d.ts.map +1 -0
- package/dist/plugins/compile-module.js +50 -0
- package/dist/plugins/compile-module.js.map +1 -0
- package/dist/plugins/compile.d.ts +5 -0
- package/dist/plugins/compile.d.ts.map +1 -0
- package/dist/plugins/compile.js +40 -0
- package/dist/plugins/compile.js.map +1 -0
- package/dist/plugins/configure.d.ts +6 -0
- package/dist/plugins/configure.d.ts.map +1 -0
- package/dist/plugins/configure.js +54 -0
- package/dist/plugins/configure.js.map +1 -0
- package/dist/plugins/hot-update.d.ts +5 -0
- package/dist/plugins/hot-update.d.ts.map +1 -0
- package/dist/plugins/hot-update.js +137 -0
- package/dist/plugins/hot-update.js.map +1 -0
- package/dist/plugins/load.d.ts +6 -0
- package/dist/plugins/load.d.ts.map +1 -0
- package/dist/plugins/load.js +79 -0
- package/dist/plugins/load.js.map +1 -0
- package/dist/plugins/optimizer.d.ts +5 -0
- package/dist/plugins/optimizer.d.ts.map +1 -0
- package/dist/plugins/optimizer.js +174 -0
- package/dist/plugins/optimizer.js.map +1 -0
- package/dist/plugins/preprocess.d.ts +11 -0
- package/dist/plugins/preprocess.d.ts.map +1 -0
- package/dist/plugins/preprocess.js +142 -0
- package/dist/plugins/preprocess.js.map +1 -0
- package/dist/plugins/runtime-scan.d.ts +4 -0
- package/dist/plugins/runtime-scan.d.ts.map +1 -0
- package/dist/plugins/runtime-scan.js +56 -0
- package/dist/plugins/runtime-scan.js.map +1 -0
- package/dist/plugins/scan.d.ts +5 -0
- package/dist/plugins/scan.d.ts.map +1 -0
- package/dist/plugins/scan.js +198 -0
- package/dist/plugins/scan.js.map +1 -0
- package/dist/preprocess.d.ts +6 -0
- package/dist/preprocess.d.ts.map +1 -0
- package/dist/preprocess.js +164 -0
- package/dist/preprocess.js.map +1 -0
- package/dist/public.d.ts +47 -0
- package/dist/public.d.ts.map +1 -0
- package/dist/public.js +1 -0
- package/dist/public.js.map +1 -0
- package/dist/vite-config.d.ts +14 -0
- package/dist/vite-config.d.ts.map +1 -0
- package/dist/vite-config.js +167 -0
- package/dist/vite-config.js.map +1 -0
- package/dist/warnings.d.ts +6 -0
- package/dist/warnings.d.ts.map +1 -0
- package/dist/warnings.js +57 -0
- package/dist/warnings.js.map +1 -0
- package/package.json +24 -5
- package/src/api.ts +12 -0
- package/src/compile.ts +96 -0
- package/src/compiler.ts +129 -0
- package/src/config-file.ts +75 -0
- package/src/dependencies.ts +47 -0
- package/src/error.ts +64 -0
- package/src/front-end.ts +146 -0
- package/src/id.ts +225 -0
- package/src/identity.ts +74 -0
- package/src/index.ts +61 -0
- package/src/log.ts +105 -0
- package/src/maps.ts +36 -0
- package/src/messages.ts +40 -0
- package/src/options.ts +257 -0
- package/src/plugins/compile-module.ts +65 -0
- package/src/plugins/compile.ts +49 -0
- package/src/plugins/configure.ts +63 -0
- package/src/plugins/hot-update.ts +159 -0
- package/src/plugins/load.ts +91 -0
- package/src/plugins/optimizer.ts +262 -0
- package/src/plugins/preprocess.ts +163 -0
- package/src/plugins/runtime-scan.ts +75 -0
- package/src/plugins/scan.ts +206 -0
- package/src/preprocess.ts +190 -0
- package/src/public.ts +56 -0
- package/src/vite-config.ts +252 -0
- package/src/warnings.ts +66 -0
- package/README.md +0 -3
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import {
|
|
5
|
+
createIdResolver,
|
|
6
|
+
type Environment,
|
|
7
|
+
type Plugin,
|
|
8
|
+
type ResolvedConfig,
|
|
9
|
+
type Rolldown,
|
|
10
|
+
type UserConfig
|
|
11
|
+
} from 'vite';
|
|
12
|
+
import { isDepExcluded } from 'vitefu';
|
|
13
|
+
import type { PluginAPI } from '../api.ts';
|
|
14
|
+
import type { CompileError, CompileOptions } from '../compiler.ts';
|
|
15
|
+
import { dynamicOptions } from '../compile.ts';
|
|
16
|
+
import { toRollupError } from '../error.ts';
|
|
17
|
+
import type { FrontEnd } from '../front-end.ts';
|
|
18
|
+
import type { Identity, Names } from '../identity.ts';
|
|
19
|
+
import type { Log } from '../log.ts';
|
|
20
|
+
import { arraify, type ResolvedOptions } from '../options.ts';
|
|
21
|
+
import { preprocessWith } from './preprocess.ts';
|
|
22
|
+
import { clientImports } from '../vite-config.ts';
|
|
23
|
+
import { runtimeScanPlugin, type RuntimeResolver } from './runtime-scan.ts';
|
|
24
|
+
import { scanning, scanPlugin } from './scan.ts';
|
|
25
|
+
|
|
26
|
+
const sensitive = [
|
|
27
|
+
'compilerOptions',
|
|
28
|
+
'configFile',
|
|
29
|
+
'experimental',
|
|
30
|
+
'extensions',
|
|
31
|
+
'preprocess'
|
|
32
|
+
] as const;
|
|
33
|
+
|
|
34
|
+
function escape(text: string): string {
|
|
35
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function bare(extension: string): string {
|
|
39
|
+
return escape(extension.startsWith('.') ? extension.slice(1) : extension);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function componentFilter(frontEnds: readonly FrontEnd[]): RegExp {
|
|
43
|
+
const extensions = frontEnds.flatMap((frontEnd) => frontEnd.components).map(bare);
|
|
44
|
+
return new RegExp(`^[^?#]+\\.(?:${extensions.join('|')})(?:[?#]|$)`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function moduleFilter(frontEnds: readonly FrontEnd[]): RegExp {
|
|
48
|
+
const alternatives = frontEnds.map(
|
|
49
|
+
(frontEnd) =>
|
|
50
|
+
`(?:${frontEnd.modules.infixes.map(escape).join('|')})(?:${frontEnd.modules.extensions.map(bare).join('|')})`
|
|
51
|
+
);
|
|
52
|
+
return new RegExp(`^[^?#]+(?:${alternatives.join('|')})(?:[?#]|$)`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function claiming(frontEnds: readonly FrontEnd[], filename: string, modules: boolean): FrontEnd {
|
|
56
|
+
const found = frontEnds.find((frontEnd) =>
|
|
57
|
+
modules
|
|
58
|
+
? frontEnd.modules.infixes.some((infix) => filename.includes(infix))
|
|
59
|
+
: frontEnd.components.some((extension) => filename.endsWith(extension))
|
|
60
|
+
);
|
|
61
|
+
return found ?? (frontEnds[0] as FrontEnd);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function compileComponent(
|
|
65
|
+
options: ResolvedOptions,
|
|
66
|
+
frontEnd: FrontEnd,
|
|
67
|
+
filename: string,
|
|
68
|
+
code: string,
|
|
69
|
+
generate: 'client' | 'server',
|
|
70
|
+
environment: Environment,
|
|
71
|
+
log: Log
|
|
72
|
+
) {
|
|
73
|
+
const compileOptions: CompileOptions = {
|
|
74
|
+
dev: true,
|
|
75
|
+
...options.compilerOptions,
|
|
76
|
+
css: 'injected',
|
|
77
|
+
filename,
|
|
78
|
+
generate
|
|
79
|
+
};
|
|
80
|
+
const groups = arraify(options.preprocess);
|
|
81
|
+
let source = code;
|
|
82
|
+
if (options.preprocess) {
|
|
83
|
+
const processed = await preprocessWith({ filename, frontEnd }, code, groups);
|
|
84
|
+
if (processed.map) compileOptions.sourcemap = processed.map as object;
|
|
85
|
+
source = processed.code;
|
|
86
|
+
}
|
|
87
|
+
const dynamic = await dynamicOptions(
|
|
88
|
+
options,
|
|
89
|
+
filename,
|
|
90
|
+
source,
|
|
91
|
+
compileOptions,
|
|
92
|
+
environment,
|
|
93
|
+
log,
|
|
94
|
+
'compile'
|
|
95
|
+
);
|
|
96
|
+
const compiled = frontEnd.compiler.compile(
|
|
97
|
+
source,
|
|
98
|
+
dynamic ? { ...compileOptions, ...dynamic } : compileOptions
|
|
99
|
+
);
|
|
100
|
+
return { code: compiled.js.code, map: compiled.js.map as never, moduleType: 'js' as const };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function compileLibraryModule(
|
|
104
|
+
options: CompileOptions,
|
|
105
|
+
frontEnd: FrontEnd,
|
|
106
|
+
filename: string,
|
|
107
|
+
code: string,
|
|
108
|
+
generate: 'client' | 'server'
|
|
109
|
+
) {
|
|
110
|
+
const compiled = frontEnd.compiler.compileModule(code, {
|
|
111
|
+
...options,
|
|
112
|
+
dev: options.dev ?? true,
|
|
113
|
+
filename,
|
|
114
|
+
generate
|
|
115
|
+
});
|
|
116
|
+
return { code: compiled.js.code, map: compiled.js.map as never, moduleType: 'js' as const };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function optimizerPlugin(
|
|
120
|
+
api: PluginAPI,
|
|
121
|
+
{ name, log }: Identity,
|
|
122
|
+
environmentName: string,
|
|
123
|
+
consumer: 'client' | 'server',
|
|
124
|
+
components: boolean
|
|
125
|
+
): Rolldown.Plugin {
|
|
126
|
+
const generate = consumer === 'server' ? 'server' : 'client';
|
|
127
|
+
const plugin: Rolldown.Plugin = {
|
|
128
|
+
name: components ? `${name}:optimize` : `${name}:optimize-module`
|
|
129
|
+
};
|
|
130
|
+
plugin.options = (input) => {
|
|
131
|
+
if (scanning(input)) {
|
|
132
|
+
delete plugin.transform;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
plugin.transform = {
|
|
136
|
+
filter: { id: components ? componentFilter(api.frontEnds) : moduleFilter(api.frontEnds) },
|
|
137
|
+
async handler(code: string, filename: string) {
|
|
138
|
+
const frontEnd = claiming(api.frontEnds, filename, !components);
|
|
139
|
+
try {
|
|
140
|
+
if (!components)
|
|
141
|
+
return compileLibraryModule(api.moduleOptions, frontEnd, filename, code, generate);
|
|
142
|
+
const environment = api.options.server?.environments[environmentName] as Environment;
|
|
143
|
+
return await compileComponent(
|
|
144
|
+
api.options,
|
|
145
|
+
frontEnd,
|
|
146
|
+
filename,
|
|
147
|
+
code,
|
|
148
|
+
generate,
|
|
149
|
+
environment,
|
|
150
|
+
log
|
|
151
|
+
);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
throw toRollupError(error as CompileError, api.options);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
} as Rolldown.Plugin['transform'];
|
|
157
|
+
};
|
|
158
|
+
return plugin;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function metadataChanged(
|
|
162
|
+
cacheDir: string,
|
|
163
|
+
options: ResolvedOptions,
|
|
164
|
+
{ metadataFile }: Names
|
|
165
|
+
): Promise<boolean> {
|
|
166
|
+
const metadata: Record<string, unknown> = {};
|
|
167
|
+
for (const key of sensitive) metadata[key] = options[key];
|
|
168
|
+
const current = JSON.stringify(metadata, (_, value) =>
|
|
169
|
+
typeof value === 'function' ? value.toString() : value
|
|
170
|
+
);
|
|
171
|
+
const file = path.resolve(cacheDir, metadataFile);
|
|
172
|
+
let existing: string | undefined;
|
|
173
|
+
try {
|
|
174
|
+
existing = await fs.readFile(file, 'utf8');
|
|
175
|
+
} catch {}
|
|
176
|
+
await fs.mkdir(cacheDir, { recursive: true });
|
|
177
|
+
await fs.writeFile(file, current);
|
|
178
|
+
return current !== existing;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function setupOptimizer(api: PluginAPI, identity: Identity): Plugin {
|
|
182
|
+
let viteConfig: ResolvedConfig;
|
|
183
|
+
return {
|
|
184
|
+
name: `${identity.name}:setup-optimizer`,
|
|
185
|
+
apply: 'serve',
|
|
186
|
+
configEnvironment(name, config) {
|
|
187
|
+
const consumer = config.consumer ?? (name === 'client' ? 'client' : 'server');
|
|
188
|
+
let resolve: ReturnType<typeof createIdResolver> | undefined;
|
|
189
|
+
let legacy: ReturnType<ResolvedConfig['createResolver']> | undefined;
|
|
190
|
+
const resolveRuntime: RuntimeResolver = async (source, importer) => {
|
|
191
|
+
const environment = api.options.server?.environments[name];
|
|
192
|
+
return environment
|
|
193
|
+
? await (resolve ??= createIdResolver(viteConfig, { scan: true }))(
|
|
194
|
+
environment,
|
|
195
|
+
source,
|
|
196
|
+
importer
|
|
197
|
+
)
|
|
198
|
+
: await (legacy ??= viteConfig.createResolver({ scan: true }))(
|
|
199
|
+
source,
|
|
200
|
+
importer,
|
|
201
|
+
false,
|
|
202
|
+
consumer === 'server'
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
const plugins: Rolldown.Plugin[] = [
|
|
206
|
+
{
|
|
207
|
+
name: `${identity.name}:shared-core`,
|
|
208
|
+
resolveId: {
|
|
209
|
+
order: 'pre',
|
|
210
|
+
filter: { id: /^vigla(?:\/[^?#]+)?$/ },
|
|
211
|
+
async handler(source, importer) {
|
|
212
|
+
const resolved = await resolveRuntime(source, importer);
|
|
213
|
+
if (resolved === undefined)
|
|
214
|
+
return this.error(
|
|
215
|
+
`Cannot resolve core runtime ${source} from ${importer ?? viteConfig.root}`
|
|
216
|
+
);
|
|
217
|
+
return {
|
|
218
|
+
id: resolved.startsWith('file://') ? resolved : pathToFileURL(resolved).href,
|
|
219
|
+
external: 'absolute'
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
];
|
|
225
|
+
const optimizeDeps: UserConfig['optimizeDeps'] = {
|
|
226
|
+
exclude: ['vigla'],
|
|
227
|
+
rolldownOptions: { plugins }
|
|
228
|
+
};
|
|
229
|
+
if (api.frontEnds.length === 0) return { optimizeDeps };
|
|
230
|
+
optimizeDeps.extensions = [
|
|
231
|
+
...new Set(api.frontEnds.flatMap((frontEnd) => frontEnd.components))
|
|
232
|
+
];
|
|
233
|
+
const exclude = config.optimizeDeps?.exclude ?? [];
|
|
234
|
+
const runtime = api.frontEnds.filter((frontEnd) => !isDepExcluded(frontEnd.name, exclude));
|
|
235
|
+
plugins.unshift(
|
|
236
|
+
runtimeScanPlugin(
|
|
237
|
+
identity.name,
|
|
238
|
+
runtime.flatMap(clientImports),
|
|
239
|
+
['vigla', ...runtime.map((frontEnd) => frontEnd.name)],
|
|
240
|
+
resolveRuntime
|
|
241
|
+
)
|
|
242
|
+
);
|
|
243
|
+
plugins.push(
|
|
244
|
+
scanPlugin(identity.name, api.frontEnds),
|
|
245
|
+
optimizerPlugin(api, identity, name, consumer, true),
|
|
246
|
+
optimizerPlugin(api, identity, name, consumer, false)
|
|
247
|
+
);
|
|
248
|
+
if (consumer === 'server') {
|
|
249
|
+
optimizeDeps.include = runtime.flatMap((frontEnd) => frontEnd.imports);
|
|
250
|
+
}
|
|
251
|
+
return { optimizeDeps };
|
|
252
|
+
},
|
|
253
|
+
configResolved(config) {
|
|
254
|
+
viteConfig = config;
|
|
255
|
+
},
|
|
256
|
+
async buildStart() {
|
|
257
|
+
if (api.frontEnds.length === 0 || !api.options.prebundleLibraries) return;
|
|
258
|
+
if (await metadataChanged(viteConfig.cacheDir, api.options, identity.names))
|
|
259
|
+
viteConfig.optimizeDeps.force = true;
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import type { Plugin, ResolvedConfig, Rollup, ViteDevServer } from 'vite';
|
|
4
|
+
import type { PluginAPI } from '../api.ts';
|
|
5
|
+
import type { CompileError, PreprocessorGroup, Processed } from '../compiler.ts';
|
|
6
|
+
import { toRollupError } from '../error.ts';
|
|
7
|
+
import type { ComponentRequest } from '../id.ts';
|
|
8
|
+
import type { Identity } from '../identity.ts';
|
|
9
|
+
import type { Log } from '../log.ts';
|
|
10
|
+
import { mapToRelative } from '../maps.ts';
|
|
11
|
+
import { arraify } from '../options.ts';
|
|
12
|
+
|
|
13
|
+
type Preprocess = (request: ComponentRequest, code: string) => Promise<Processed | undefined>;
|
|
14
|
+
|
|
15
|
+
interface Tagged {
|
|
16
|
+
__resolvedConfig?: ResolvedConfig | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function preprocessWith(
|
|
20
|
+
request: { filename: string; frontEnd: ComponentRequest['frontEnd'] },
|
|
21
|
+
code: string,
|
|
22
|
+
groups: PreprocessorGroup[]
|
|
23
|
+
): Promise<Processed> {
|
|
24
|
+
const { filename } = request;
|
|
25
|
+
try {
|
|
26
|
+
return await request.frontEnd.compiler.preprocess(code, groups, { filename });
|
|
27
|
+
} catch (error) {
|
|
28
|
+
const failure = error as Error;
|
|
29
|
+
failure.message = `Error while preprocessing ${filename}${failure.message ? ` - ${failure.message}` : ''}`;
|
|
30
|
+
throw failure;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function createPreprocess(groups: PreprocessorGroup[], config: ResolvedConfig): Preprocess {
|
|
35
|
+
for (const group of groups) {
|
|
36
|
+
const style = group.style as (PreprocessorGroup['style'] & Tagged) | undefined;
|
|
37
|
+
if (style && '__resolvedConfig' in style) style.__resolvedConfig = config;
|
|
38
|
+
}
|
|
39
|
+
return async (request, code) => {
|
|
40
|
+
const processed = await preprocessWith(request, code, groups);
|
|
41
|
+
if (typeof processed?.map === 'object') mapToRelative(processed.map, request.filename);
|
|
42
|
+
return processed;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function dependencyKey(file: string): string {
|
|
47
|
+
if (!/^[a-z]:/i.test(file) && !file.startsWith(path.win32.sep.repeat(2))) return file;
|
|
48
|
+
return file.replaceAll(path.win32.sep, path.posix.sep);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class Dependencies {
|
|
52
|
+
readonly #dependencies = new Map<string, Set<string>>();
|
|
53
|
+
readonly #dependants = new Map<string, Set<string>>();
|
|
54
|
+
readonly #server: ViteDevServer;
|
|
55
|
+
|
|
56
|
+
constructor(server: ViteDevServer, log: Log) {
|
|
57
|
+
this.#server = server;
|
|
58
|
+
const changed = (filename: string) => {
|
|
59
|
+
for (const dependant of this.#dependants.get(dependencyKey(filename)) ?? []) {
|
|
60
|
+
if (!fs.existsSync(dependant)) continue;
|
|
61
|
+
log.debug(
|
|
62
|
+
`emitting virtual change event for "${dependant}" because dependency "${filename}" changed`,
|
|
63
|
+
undefined,
|
|
64
|
+
'hmr'
|
|
65
|
+
);
|
|
66
|
+
server.watcher.emit('change', dependant);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
server.watcher.on('change', changed);
|
|
70
|
+
server.watcher.on('unlink', changed);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
#watch(file: string): void {
|
|
74
|
+
const root = this.#server.config.root;
|
|
75
|
+
if (file && !file.startsWith(`${root}/`) && !file.includes('\0') && fs.existsSync(file)) {
|
|
76
|
+
this.#server.watcher.add(path.resolve(file));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
clear(): void {
|
|
81
|
+
this.#dependencies.clear();
|
|
82
|
+
this.#dependants.clear();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
update(request: ComponentRequest, dependencies: string[]): void {
|
|
86
|
+
const id = request.normalizedFilename;
|
|
87
|
+
const previous = this.#dependencies.get(id) ?? new Set<string>();
|
|
88
|
+
const next = new Set<string>();
|
|
89
|
+
for (const dependency of dependencies) {
|
|
90
|
+
const key = dependencyKey(dependency);
|
|
91
|
+
if (next.has(key)) continue;
|
|
92
|
+
next.add(key);
|
|
93
|
+
if (previous.has(key)) continue;
|
|
94
|
+
this.#watch(dependency);
|
|
95
|
+
let dependants = this.#dependants.get(key);
|
|
96
|
+
if (dependants === undefined) {
|
|
97
|
+
dependants = new Set();
|
|
98
|
+
this.#dependants.set(key, dependants);
|
|
99
|
+
}
|
|
100
|
+
dependants.add(request.filename);
|
|
101
|
+
}
|
|
102
|
+
this.#dependencies.set(id, next);
|
|
103
|
+
for (const dependency of previous) {
|
|
104
|
+
if (!next.has(dependency)) {
|
|
105
|
+
this.#dependants.get(dependency)?.delete(request.filename);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function preprocess(api: PluginAPI, { name, log }: Identity): Plugin {
|
|
112
|
+
let run: Preprocess | undefined;
|
|
113
|
+
let dependencies: Dependencies | undefined;
|
|
114
|
+
const plugin: Plugin = {
|
|
115
|
+
name: `${name}:preprocess`,
|
|
116
|
+
enforce: 'pre',
|
|
117
|
+
configResolved(config) {
|
|
118
|
+
const groups = arraify(api.options.preprocess);
|
|
119
|
+
if (groups.length > 0) {
|
|
120
|
+
run = createPreprocess(groups, config);
|
|
121
|
+
Object.assign(plugin.transform as object, { filter: api.filter });
|
|
122
|
+
} else {
|
|
123
|
+
log.debug(
|
|
124
|
+
`disabling ${plugin.name} because no preprocessor is configured`,
|
|
125
|
+
undefined,
|
|
126
|
+
'preprocess'
|
|
127
|
+
);
|
|
128
|
+
run = undefined;
|
|
129
|
+
Object.assign(plugin.transform as object, { filter: { id: /$./ } });
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
configureServer(server) {
|
|
133
|
+
dependencies = new Dependencies(server, log);
|
|
134
|
+
},
|
|
135
|
+
buildStart() {
|
|
136
|
+
dependencies?.clear();
|
|
137
|
+
},
|
|
138
|
+
transform: {
|
|
139
|
+
async handler(code, id) {
|
|
140
|
+
const request = api.idParser(id, this.environment.config.consumer === 'server');
|
|
141
|
+
if (!request || run === undefined) return undefined;
|
|
142
|
+
try {
|
|
143
|
+
const processed = await run(request, code);
|
|
144
|
+
dependencies?.update(request, processed?.dependencies ?? []);
|
|
145
|
+
if (!processed) return undefined;
|
|
146
|
+
if (
|
|
147
|
+
api.options.isBuild &&
|
|
148
|
+
this.environment.config.build.watch &&
|
|
149
|
+
processed.dependencies
|
|
150
|
+
) {
|
|
151
|
+
for (const dependency of processed.dependencies) this.addWatchFile(dependency);
|
|
152
|
+
}
|
|
153
|
+
const result: Rollup.SourceDescription = { code: processed.code };
|
|
154
|
+
if (processed.map) result.map = processed.map as never;
|
|
155
|
+
return result;
|
|
156
|
+
} catch (error) {
|
|
157
|
+
throw toRollupError(error as CompileError, api.options);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
return plugin;
|
|
163
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Rolldown } from 'vite';
|
|
2
|
+
import { scanning } from './scan.ts';
|
|
3
|
+
|
|
4
|
+
export type RuntimeResolver = (
|
|
5
|
+
source: string,
|
|
6
|
+
importer: string | undefined
|
|
7
|
+
) => Promise<string | undefined>;
|
|
8
|
+
|
|
9
|
+
function escape(text: string): string {
|
|
10
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function withEntry(
|
|
14
|
+
input: Rolldown.InputOption | undefined,
|
|
15
|
+
runtimeId: string
|
|
16
|
+
): Rolldown.InputOption {
|
|
17
|
+
if (input === undefined) return [runtimeId];
|
|
18
|
+
if (typeof input === 'string') return [input, runtimeId];
|
|
19
|
+
if (Array.isArray(input)) return [...input, runtimeId];
|
|
20
|
+
return { ...input, [runtimeId]: runtimeId };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function crawler(
|
|
24
|
+
name: string,
|
|
25
|
+
runtimeId: string,
|
|
26
|
+
entries: readonly string[],
|
|
27
|
+
owned: RegExp,
|
|
28
|
+
resolve: RuntimeResolver
|
|
29
|
+
): Rolldown.Plugin {
|
|
30
|
+
return {
|
|
31
|
+
name,
|
|
32
|
+
resolveId: {
|
|
33
|
+
order: 'pre',
|
|
34
|
+
async handler(source, importer) {
|
|
35
|
+
if (source === runtimeId) return { id: source };
|
|
36
|
+
if (importer === undefined || !owned.test(source)) return null;
|
|
37
|
+
const file = await resolve(source, importer === runtimeId ? undefined : importer);
|
|
38
|
+
return file === undefined || /[\\/]node_modules[\\/]/.test(file) ? null : { id: file };
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
load: {
|
|
42
|
+
filter: { id: new RegExp(`^${escape(runtimeId)}$`) },
|
|
43
|
+
handler: () => ({
|
|
44
|
+
code: entries.map((entry) => `import ${JSON.stringify(entry)};\n`).join(''),
|
|
45
|
+
moduleType: 'js'
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function runtimeScanPlugin(
|
|
52
|
+
prefix: string,
|
|
53
|
+
entries: readonly string[],
|
|
54
|
+
packages: readonly string[],
|
|
55
|
+
resolve: RuntimeResolver
|
|
56
|
+
): Rolldown.Plugin {
|
|
57
|
+
const owned = new RegExp(`^(?:${packages.map(escape).join('|')})(?:/[^?#]+)?$`);
|
|
58
|
+
const name = `${prefix}:runtime-scan`;
|
|
59
|
+
const runtimeId = `\0${name}`;
|
|
60
|
+
const plugin: Rolldown.Plugin = {
|
|
61
|
+
name,
|
|
62
|
+
options(input) {
|
|
63
|
+
if (entries.length === 0 || !scanning(input)) return;
|
|
64
|
+
const plugins = (input.plugins ?? []) as Rolldown.Plugin[];
|
|
65
|
+
return {
|
|
66
|
+
...input,
|
|
67
|
+
input: withEntry(input.input, runtimeId),
|
|
68
|
+
plugins: plugins.map((entry) =>
|
|
69
|
+
entry === plugin ? crawler(name, runtimeId, entries, owned, resolve) : entry
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return plugin;
|
|
75
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import type { Rolldown } from 'vite';
|
|
3
|
+
import type { Compiler, Parse, ParsedComponent, Script } from '../compiler.ts';
|
|
4
|
+
import type { FrontEnd } from '../front-end.ts';
|
|
5
|
+
|
|
6
|
+
interface Node {
|
|
7
|
+
readonly type: string;
|
|
8
|
+
readonly [key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Part {
|
|
12
|
+
module: boolean;
|
|
13
|
+
lang: 'js' | 'ts';
|
|
14
|
+
code: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const partId = /\?scan=(\d+)&lang\.[jt]s$/;
|
|
18
|
+
|
|
19
|
+
export function scanning(input: Rolldown.InputOptions): boolean {
|
|
20
|
+
const plugins = (input.plugins ?? []) as { name?: string }[];
|
|
21
|
+
return plugins.some((entry) => entry?.name === 'vite:dep-scan:resolve');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function node(value: unknown): Node | undefined {
|
|
25
|
+
return value !== null && typeof value === 'object' && typeof (value as Node).type === 'string'
|
|
26
|
+
? (value as Node)
|
|
27
|
+
: undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function nodes(value: unknown): Node[] {
|
|
31
|
+
return Array.isArray(value) ? value.flatMap((item) => node(item) ?? []) : [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function identifier(value: unknown): string | undefined {
|
|
35
|
+
const found = node(value);
|
|
36
|
+
return found?.type === 'Identifier' ? (found.name as string) : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bind(pattern: Node | undefined, names: Set<string>): void {
|
|
40
|
+
if (pattern === undefined) return;
|
|
41
|
+
if (pattern.type === 'Identifier') names.add(pattern.name as string);
|
|
42
|
+
else if (pattern.type === 'ObjectPattern') {
|
|
43
|
+
for (const property of nodes(pattern.properties))
|
|
44
|
+
bind(node(property.type === 'RestElement' ? property.argument : property.value), names);
|
|
45
|
+
} else if (pattern.type === 'ArrayPattern') {
|
|
46
|
+
for (const element of nodes(pattern.elements)) bind(element, names);
|
|
47
|
+
} else if (pattern.type === 'RestElement') bind(node(pattern.argument), names);
|
|
48
|
+
else if (pattern.type === 'AssignmentPattern') bind(node(pattern.left), names);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function declared(body: readonly Node[]): Set<string> {
|
|
52
|
+
const names = new Set<string>();
|
|
53
|
+
for (const statement of body) {
|
|
54
|
+
const declaration = statement.type.startsWith('Export')
|
|
55
|
+
? node(statement.declaration)
|
|
56
|
+
: statement;
|
|
57
|
+
if (declaration === undefined) continue;
|
|
58
|
+
if (declaration.type === 'ImportDeclaration') {
|
|
59
|
+
for (const specifier of nodes(declaration.specifiers)) bind(node(specifier.local), names);
|
|
60
|
+
} else if (declaration.type === 'VariableDeclaration') {
|
|
61
|
+
for (const declarator of nodes(declaration.declarations)) bind(node(declarator.id), names);
|
|
62
|
+
} else {
|
|
63
|
+
const name = identifier(declaration.id);
|
|
64
|
+
if (name !== undefined) names.add(name);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return names;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function undeclaredExports(body: readonly Node[]): Set<string> {
|
|
71
|
+
const names = declared(body);
|
|
72
|
+
const missing = new Set<string>();
|
|
73
|
+
for (const statement of body) {
|
|
74
|
+
if (statement.type !== 'ExportNamedDeclaration' || statement.source) continue;
|
|
75
|
+
if (statement.exportKind === 'type') continue;
|
|
76
|
+
for (const specifier of nodes(statement.specifiers)) {
|
|
77
|
+
const local = identifier(specifier.local);
|
|
78
|
+
if (local !== undefined && specifier.exportKind !== 'type' && !names.has(local))
|
|
79
|
+
missing.add(local);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return missing;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function importedSources(body: readonly Node[]): string[] {
|
|
86
|
+
return body.flatMap((statement) =>
|
|
87
|
+
statement.type === 'ImportDeclaration' && statement.importKind !== 'type'
|
|
88
|
+
? [String(node(statement.source)?.value)]
|
|
89
|
+
: []
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function language(script: Script): 'js' | 'ts' | undefined {
|
|
94
|
+
let lang = 'js';
|
|
95
|
+
for (const attribute of script.attributes) {
|
|
96
|
+
if (attribute.name === 'src') return undefined;
|
|
97
|
+
if (attribute.name !== 'lang') continue;
|
|
98
|
+
if (!Array.isArray(attribute.value)) return undefined;
|
|
99
|
+
lang = (attribute.value as readonly { data?: string }[])
|
|
100
|
+
.map((part) => part.data ?? '')
|
|
101
|
+
.join('');
|
|
102
|
+
}
|
|
103
|
+
if (lang === 'js' || lang === 'javascript') return 'js';
|
|
104
|
+
if (lang === 'ts' || lang === 'typescript') return 'ts';
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function parse(
|
|
109
|
+
compiler: Compiler,
|
|
110
|
+
parser: Parse,
|
|
111
|
+
filename: string
|
|
112
|
+
): Promise<{ source: string; parsed: ParsedComponent }> {
|
|
113
|
+
const source = (await fs.readFile(filename, 'utf8')).replace(/^/, '');
|
|
114
|
+
try {
|
|
115
|
+
return { source, parsed: parser(source, { modern: true, filename }) };
|
|
116
|
+
} catch {
|
|
117
|
+
const blank = await compiler.preprocess(source, { style: () => ({ code: '' }) }, { filename });
|
|
118
|
+
return { source: blank.code, parsed: parser(blank.code, { modern: true, filename }) };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function split(compiler: Compiler, filename: string): Promise<Part[] | undefined> {
|
|
123
|
+
if (compiler.parse === undefined) return undefined;
|
|
124
|
+
try {
|
|
125
|
+
const { source, parsed } = await parse(compiler, compiler.parse, filename);
|
|
126
|
+
const parts: Part[] = [];
|
|
127
|
+
for (const [script, module] of [
|
|
128
|
+
[parsed.module, true],
|
|
129
|
+
[parsed.instance, false]
|
|
130
|
+
] as const) {
|
|
131
|
+
if (!script) continue;
|
|
132
|
+
const lang = language(script);
|
|
133
|
+
if (lang === undefined) return undefined;
|
|
134
|
+
const body = nodes(script.content.body);
|
|
135
|
+
let code = source.slice(script.content.start, script.content.end);
|
|
136
|
+
for (const local of undeclaredExports(body)) code += `\nvar ${local};`;
|
|
137
|
+
if (lang === 'ts') {
|
|
138
|
+
for (const imported of importedSources(body))
|
|
139
|
+
code += `\nimport ${JSON.stringify(imported)};`;
|
|
140
|
+
}
|
|
141
|
+
parts.push({ module, lang, code });
|
|
142
|
+
}
|
|
143
|
+
return parts;
|
|
144
|
+
} catch {
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function facade(filename: string, parts: readonly Part[]): string {
|
|
150
|
+
let code = '';
|
|
151
|
+
for (const [index, part] of parts.entries()) {
|
|
152
|
+
const specifier = JSON.stringify(`${filename}?scan=${index}&lang.${part.lang}`);
|
|
153
|
+
code += part.module ? `export * from ${specifier};\n` : `import ${specifier};\n`;
|
|
154
|
+
}
|
|
155
|
+
return `${code}export default {};\n`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function escape(text: string): string {
|
|
159
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function scanner(name: string, parsing: readonly FrontEnd[]): Rolldown.Plugin {
|
|
163
|
+
const extensions = parsing.flatMap((frontEnd) => frontEnd.components).map(escape);
|
|
164
|
+
const component = new RegExp(`^[^?#]+(?:${extensions.join('|')})$`);
|
|
165
|
+
const pending = new Map<string, Promise<Part[] | undefined>>();
|
|
166
|
+
return {
|
|
167
|
+
name,
|
|
168
|
+
resolveId: { filter: { id: partId }, handler: (source) => ({ id: source }) },
|
|
169
|
+
load: {
|
|
170
|
+
filter: { id: { include: [component, partId] } },
|
|
171
|
+
async handler(id) {
|
|
172
|
+
const part = partId.exec(id);
|
|
173
|
+
const filename = part === null ? id : id.slice(0, part.index);
|
|
174
|
+
let parts = pending.get(filename);
|
|
175
|
+
if (parts === undefined) {
|
|
176
|
+
const frontEnd = parsing.find((candidate) =>
|
|
177
|
+
candidate.components.some((extension) => filename.endsWith(extension))
|
|
178
|
+
) as FrontEnd;
|
|
179
|
+
parts = split(frontEnd.compiler, filename);
|
|
180
|
+
pending.set(filename, parts);
|
|
181
|
+
}
|
|
182
|
+
const found = await parts;
|
|
183
|
+
if (found === undefined) return null;
|
|
184
|
+
if (part === null) return { code: facade(filename, found), moduleType: 'js' };
|
|
185
|
+
const chosen = found[Number(part[1])] as Part;
|
|
186
|
+
return { code: chosen.code, moduleType: chosen.lang };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function scanPlugin(prefix: string, frontEnds: readonly FrontEnd[]): Rolldown.Plugin {
|
|
193
|
+
const parsing = frontEnds.filter((frontEnd) => frontEnd.compiler.parse !== undefined);
|
|
194
|
+
const plugin: Rolldown.Plugin = {
|
|
195
|
+
name: `${prefix}:scan`,
|
|
196
|
+
options(input) {
|
|
197
|
+
if (parsing.length === 0 || !scanning(input)) return;
|
|
198
|
+
const plugins = (input.plugins ?? []) as Rolldown.Plugin[];
|
|
199
|
+
return {
|
|
200
|
+
...input,
|
|
201
|
+
plugins: plugins.map((entry) => (entry === plugin ? scanner(plugin.name, parsing) : entry))
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return plugin;
|
|
206
|
+
}
|