@analogjs/vite-plugin-angular 2.5.0-beta.4 → 2.5.0-beta.40
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/package.json +2 -2
- package/src/lib/analog-compiler-plugin.d.ts +14 -0
- package/src/lib/analog-compiler-plugin.js +386 -0
- package/src/lib/analog-compiler-plugin.js.map +1 -0
- package/src/lib/angular-vite-plugin.d.ts +6 -0
- package/src/lib/angular-vite-plugin.js +124 -281
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/angular-vitest-plugin.js +2 -2
- package/src/lib/angular-vitest-plugin.js.map +1 -1
- package/src/lib/utils/plugin-config.d.ts +38 -0
- package/src/lib/utils/plugin-config.js +82 -0
- package/src/lib/utils/plugin-config.js.map +1 -0
- package/src/lib/utils/virtual-ids.d.ts +8 -0
- package/src/lib/utils/virtual-ids.js +45 -0
- package/src/lib/utils/virtual-ids.js.map +1 -0
- package/src/lib/utils/virtual-resources.d.ts +31 -0
- package/src/lib/utils/virtual-resources.js +69 -0
- package/src/lib/utils/virtual-resources.js.map +1 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, writeFileSync, promises as fsPromises } from 'node:fs';
|
|
2
2
|
import { basename, dirname, isAbsolute, join, relative, resolve, } from 'node:path';
|
|
3
3
|
import * as vite from 'vite';
|
|
4
4
|
import * as compilerCli from '@angular/compiler-cli';
|
|
@@ -8,7 +8,6 @@ import { globSync } from 'tinyglobby';
|
|
|
8
8
|
import { defaultClientConditions, normalizePath, preprocessCSS, } from 'vite';
|
|
9
9
|
import { buildOptimizerPlugin } from './angular-build-optimizer-plugin.js';
|
|
10
10
|
import { jitPlugin } from './angular-jit-plugin.js';
|
|
11
|
-
import { createCompilerPlugin, createRolldownCompilerPlugin, } from './compiler-plugin.js';
|
|
12
11
|
import { StyleUrlsResolver, TemplateUrlsResolver, } from './component-resolvers.js';
|
|
13
12
|
import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers, } from './host.js';
|
|
14
13
|
import { angularVitestPlugins } from './angular-vitest-plugin.js';
|
|
@@ -20,7 +19,10 @@ import { nxFolderPlugin } from './nx-folder-plugin.js';
|
|
|
20
19
|
import { replaceFiles, } from './plugins/file-replacements.plugin.js';
|
|
21
20
|
import { routerPlugin } from './router-plugin.js';
|
|
22
21
|
import { createHash } from 'node:crypto';
|
|
23
|
-
import {
|
|
22
|
+
import { analogCompilerPlugin } from './analog-compiler-plugin.js';
|
|
23
|
+
import { TS_EXT_REGEX, createTsConfigGetter, getTsConfigPath, createDepOptimizerConfig, } from './utils/plugin-config.js';
|
|
24
|
+
import { VIRTUAL_RAW_PREFIX, VIRTUAL_STYLE_PREFIX, toVirtualRawId, toVirtualStyleId, } from './utils/virtual-ids.js';
|
|
25
|
+
import { loadVirtualRawModule, loadVirtualStyleModule, rewriteHtmlRawImport, rewriteInlineStyleImport, } from './utils/virtual-resources.js';
|
|
24
26
|
export var DiagnosticModes;
|
|
25
27
|
(function (DiagnosticModes) {
|
|
26
28
|
DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
|
|
@@ -29,12 +31,6 @@ export var DiagnosticModes;
|
|
|
29
31
|
DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
|
|
30
32
|
DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
|
|
31
33
|
})(DiagnosticModes || (DiagnosticModes = {}));
|
|
32
|
-
/**
|
|
33
|
-
* TypeScript file extension regex
|
|
34
|
-
* Match .(c or m)ts, .ts extensions with an optional ? for query params
|
|
35
|
-
* Ignore .tsx extensions
|
|
36
|
-
*/
|
|
37
|
-
const TS_EXT_REGEX = /\.[cm]?(ts)[^x]?\??/;
|
|
38
34
|
const classNames = new Map();
|
|
39
35
|
export function angular(options) {
|
|
40
36
|
/**
|
|
@@ -61,9 +57,9 @@ export function angular(options) {
|
|
|
61
57
|
fileReplacements: options?.fileReplacements ?? [],
|
|
62
58
|
useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false,
|
|
63
59
|
useAnalogCompiler: options?.experimental?.useAnalogCompiler ?? false,
|
|
60
|
+
analogCompilationMode: options?.experimental?.analogCompilationMode ?? 'full',
|
|
64
61
|
};
|
|
65
62
|
let resolvedConfig;
|
|
66
|
-
// Store config context needed for getTsConfigPath resolution
|
|
67
63
|
let tsConfigResolutionContext = null;
|
|
68
64
|
const ts = require('typescript');
|
|
69
65
|
let builder;
|
|
@@ -113,126 +109,6 @@ export function angular(options) {
|
|
|
113
109
|
// Previously the compilation was recreated on every pass, which meant Angular
|
|
114
110
|
// never had prior state and could never produce HMR payloads.
|
|
115
111
|
let angularCompilation;
|
|
116
|
-
// Analog compiler state (used when experimental.useAnalogCompiler is true)
|
|
117
|
-
const analogRegistry = new Map();
|
|
118
|
-
const analogResourceToSource = new Map();
|
|
119
|
-
/** Tracks which npm packages have already had their .d.ts files scanned. */
|
|
120
|
-
const scannedDtsPackages = new Set();
|
|
121
|
-
let analogProjectRoot = '';
|
|
122
|
-
async function initAnalogCompiler() {
|
|
123
|
-
if (jit)
|
|
124
|
-
return; // JIT: no registry scan needed
|
|
125
|
-
// Scan all source files to build the registry
|
|
126
|
-
analogRegistry.clear();
|
|
127
|
-
scannedDtsPackages.clear();
|
|
128
|
-
const resolvedTsConfigPath = resolveTsConfigPath();
|
|
129
|
-
analogProjectRoot = dirname(resolvedTsConfigPath);
|
|
130
|
-
const config = compilerCli.readConfiguration(resolvedTsConfigPath);
|
|
131
|
-
const results = await Promise.all(config.rootNames.map(async (file) => {
|
|
132
|
-
try {
|
|
133
|
-
const code = await fsPromises.readFile(file, 'utf-8');
|
|
134
|
-
return analogScanFile(code, file);
|
|
135
|
-
}
|
|
136
|
-
catch {
|
|
137
|
-
return []; // Skip unreadable files
|
|
138
|
-
}
|
|
139
|
-
}));
|
|
140
|
-
for (const entries of results) {
|
|
141
|
-
for (const entry of entries) {
|
|
142
|
-
analogRegistry.set(entry.className, entry);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Lazily scan .d.ts files for external packages imported by the given source.
|
|
148
|
-
* Each package is scanned at most once and the results are cached in the registry.
|
|
149
|
-
*/
|
|
150
|
-
function ensureDtsRegistryForSource(code, id) {
|
|
151
|
-
for (const pkg of analogCollectImportedPackages(code, id)) {
|
|
152
|
-
if (scannedDtsPackages.has(pkg))
|
|
153
|
-
continue;
|
|
154
|
-
scannedDtsPackages.add(pkg);
|
|
155
|
-
try {
|
|
156
|
-
const dtsEntries = analogScanPackageDts(pkg, analogProjectRoot);
|
|
157
|
-
for (const entry of dtsEntries) {
|
|
158
|
-
if (!analogRegistry.has(entry.className)) {
|
|
159
|
-
analogRegistry.set(entry.className, entry);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
catch {
|
|
164
|
-
// Package may not have .d.ts files or may not be Angular
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
async function handleAnalogCompilerTransform(code, id) {
|
|
169
|
-
if (!/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code)) {
|
|
170
|
-
return undefined;
|
|
171
|
-
}
|
|
172
|
-
// JIT mode
|
|
173
|
-
if (jit) {
|
|
174
|
-
const result = analogJitTransform(code, id);
|
|
175
|
-
return { code: result.code, map: result.map };
|
|
176
|
-
}
|
|
177
|
-
// Inline external templateUrl/styleUrl(s) into the source before compilation
|
|
178
|
-
// using OXC parser for precise AST-based rewriting.
|
|
179
|
-
code = inlineResourceUrls(code, id);
|
|
180
|
-
// Pre-resolve inline styles that need preprocessing (SCSS/Sass/Less)
|
|
181
|
-
let resolvedStyles;
|
|
182
|
-
let resolvedInlineStyles;
|
|
183
|
-
if (pluginOptions.inlineStylesExtension !== 'css') {
|
|
184
|
-
const styleStrings = extractInlineStylesOxc(code, id);
|
|
185
|
-
if (styleStrings.length > 0) {
|
|
186
|
-
resolvedInlineStyles = new Map();
|
|
187
|
-
for (let i = 0; i < styleStrings.length; i++) {
|
|
188
|
-
try {
|
|
189
|
-
const fakePath = id.replace(/\.ts$/, `.inline-${i}.${pluginOptions.inlineStylesExtension}`);
|
|
190
|
-
const processed = await preprocessCSS(styleStrings[i], fakePath, resolvedConfig);
|
|
191
|
-
resolvedInlineStyles.set(i, processed.code);
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
194
|
-
// Skip styles that can't be preprocessed
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
if (resolvedInlineStyles.size === 0)
|
|
198
|
-
resolvedInlineStyles = undefined;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
// Lazily scan .d.ts files for any external packages this file imports,
|
|
202
|
-
// so pre-compiled directives (e.g. RouterLinkActive) are in the registry
|
|
203
|
-
// before template compilation needs them.
|
|
204
|
-
ensureDtsRegistryForSource(code, id);
|
|
205
|
-
const result = analogCompile(code, id, {
|
|
206
|
-
registry: analogRegistry,
|
|
207
|
-
resolvedStyles,
|
|
208
|
-
resolvedInlineStyles,
|
|
209
|
-
});
|
|
210
|
-
// Track resource dependencies for HMR
|
|
211
|
-
for (const dep of result.resourceDependencies) {
|
|
212
|
-
analogResourceToSource.set(dep, id);
|
|
213
|
-
}
|
|
214
|
-
// Strip TypeScript-only syntax that the analog compiler preserves.
|
|
215
|
-
// Use OXC to reliably strip all TS syntax (type annotations, generics,
|
|
216
|
-
// interfaces, etc.) so the output is valid JavaScript for both client
|
|
217
|
-
// and SSR environments.
|
|
218
|
-
const stripped = await vite.transformWithOxc(result.code, id, {
|
|
219
|
-
lang: 'ts',
|
|
220
|
-
sourcemap: false,
|
|
221
|
-
decorator: { legacy: false, emitDecoratorMetadata: false },
|
|
222
|
-
});
|
|
223
|
-
let outputCode = stripped.code;
|
|
224
|
-
// Append HMR code in dev mode
|
|
225
|
-
if (watchMode && pluginOptions.liveReload) {
|
|
226
|
-
const fileDeclarations = [...analogRegistry.values()].filter((e) => e.fileName === id);
|
|
227
|
-
if (fileDeclarations.length > 0) {
|
|
228
|
-
// Local deps: other Angular classes in the same file that components
|
|
229
|
-
// may reference as template dependencies.
|
|
230
|
-
const localDepClassNames = fileDeclarations.map((e) => e.className);
|
|
231
|
-
outputCode += generateHmrCode(fileDeclarations, localDepClassNames);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
return { code: outputCode, map: result.map };
|
|
235
|
-
}
|
|
236
112
|
function angularPlugin() {
|
|
237
113
|
let isProd = false;
|
|
238
114
|
if (angularFullVersion < 190000 || isTest) {
|
|
@@ -254,7 +130,6 @@ export function angular(options) {
|
|
|
254
130
|
}
|
|
255
131
|
return {
|
|
256
132
|
name: '@analogjs/vite-plugin-angular',
|
|
257
|
-
...(pluginOptions.useAnalogCompiler ? { enforce: 'pre' } : {}),
|
|
258
133
|
async config(config, { command }) {
|
|
259
134
|
watchMode = command === 'serve';
|
|
260
135
|
isProd =
|
|
@@ -268,62 +143,26 @@ export function angular(options) {
|
|
|
268
143
|
};
|
|
269
144
|
// Do a preliminary resolution for esbuild plugin (before configResolved)
|
|
270
145
|
const preliminaryTsConfigPath = resolveTsConfigPath();
|
|
271
|
-
// When useAnalogCompiler is true, configure the built-in OXC transform
|
|
272
|
-
// to strip TypeScript but NOT lower decorators. The analog compiler
|
|
273
|
-
// handles decorator processing in its transform hook. This avoids the
|
|
274
|
-
// ordering conflict where lowered decorators can't be found by the
|
|
275
|
-
// compiler, while still ensuring all .ts files get TypeScript stripped
|
|
276
|
-
// (including those excluded from the analog compiler's transform filter).
|
|
277
146
|
const esbuild = pluginOptions.useAngularCompilationAPI
|
|
278
147
|
? undefined
|
|
279
|
-
:
|
|
280
|
-
? false
|
|
281
|
-
: (config.esbuild ?? false);
|
|
148
|
+
: (config.esbuild ?? false);
|
|
282
149
|
const oxc = pluginOptions.useAngularCompilationAPI
|
|
283
150
|
? undefined
|
|
284
|
-
:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
plugins: [
|
|
294
|
-
createRolldownCompilerPlugin({
|
|
295
|
-
tsconfig: preliminaryTsConfigPath,
|
|
296
|
-
sourcemap: !isProd,
|
|
297
|
-
advancedOptimizations: isProd,
|
|
298
|
-
jit,
|
|
299
|
-
incremental: watchMode,
|
|
300
|
-
}),
|
|
301
|
-
],
|
|
302
|
-
};
|
|
303
|
-
const esbuildOptions = {
|
|
304
|
-
plugins: [
|
|
305
|
-
createCompilerPlugin({
|
|
306
|
-
tsconfig: preliminaryTsConfigPath,
|
|
307
|
-
sourcemap: !isProd,
|
|
308
|
-
advancedOptimizations: isProd,
|
|
309
|
-
jit,
|
|
310
|
-
incremental: watchMode,
|
|
311
|
-
}, isTest, !isAstroIntegration),
|
|
312
|
-
],
|
|
313
|
-
define: defineOptions,
|
|
314
|
-
};
|
|
151
|
+
: (config.oxc ?? false);
|
|
152
|
+
const depOptimizer = createDepOptimizerConfig({
|
|
153
|
+
tsconfig: preliminaryTsConfigPath,
|
|
154
|
+
isProd,
|
|
155
|
+
jit,
|
|
156
|
+
watchMode,
|
|
157
|
+
isTest,
|
|
158
|
+
isAstroIntegration,
|
|
159
|
+
});
|
|
315
160
|
return {
|
|
316
161
|
...(vite.rolldownVersion ? { oxc } : { esbuild }),
|
|
317
|
-
|
|
318
|
-
include: ['rxjs/operators', 'rxjs'],
|
|
319
|
-
exclude: ['@angular/platform-server'],
|
|
320
|
-
...(vite.rolldownVersion
|
|
321
|
-
? { rolldownOptions }
|
|
322
|
-
: { esbuildOptions }),
|
|
323
|
-
},
|
|
162
|
+
...depOptimizer,
|
|
324
163
|
resolve: {
|
|
325
164
|
conditions: [
|
|
326
|
-
|
|
165
|
+
...depOptimizer.resolve.conditions,
|
|
327
166
|
...(config.resolve?.conditions || defaultClientConditions),
|
|
328
167
|
],
|
|
329
168
|
},
|
|
@@ -352,26 +191,6 @@ export function angular(options) {
|
|
|
352
191
|
},
|
|
353
192
|
configureServer(server) {
|
|
354
193
|
viteServer = server;
|
|
355
|
-
if (pluginOptions.useAnalogCompiler) {
|
|
356
|
-
// Watch for new .ts files and scan them into the registry
|
|
357
|
-
server.watcher.on('add', (filePath) => {
|
|
358
|
-
if (filePath.endsWith('.ts') &&
|
|
359
|
-
!filePath.endsWith('.spec.ts') &&
|
|
360
|
-
!filePath.endsWith('.d.ts')) {
|
|
361
|
-
try {
|
|
362
|
-
const code = require('fs').readFileSync(filePath, 'utf-8');
|
|
363
|
-
const entries = analogScanFile(code, filePath);
|
|
364
|
-
for (const entry of entries) {
|
|
365
|
-
analogRegistry.set(entry.className, entry);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
catch {
|
|
369
|
-
// Skip unreadable files
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
});
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
194
|
// Add/unlink changes the TypeScript program shape, not just file
|
|
376
195
|
// contents, so we need to invalidate both include discovery and the
|
|
377
196
|
// cached tsconfig root names before recompiling.
|
|
@@ -385,10 +204,6 @@ export function angular(options) {
|
|
|
385
204
|
});
|
|
386
205
|
},
|
|
387
206
|
async buildStart() {
|
|
388
|
-
if (pluginOptions.useAnalogCompiler) {
|
|
389
|
-
await initAnalogCompiler();
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
207
|
// Defer the first compilation in test mode
|
|
393
208
|
if (!isVitestVscode) {
|
|
394
209
|
await performCompilation(resolvedConfig);
|
|
@@ -397,35 +212,6 @@ export function angular(options) {
|
|
|
397
212
|
}
|
|
398
213
|
},
|
|
399
214
|
async handleHotUpdate(ctx) {
|
|
400
|
-
// Analog compiler HMR path
|
|
401
|
-
if (pluginOptions.useAnalogCompiler) {
|
|
402
|
-
// Resource file changes → invalidate parent .ts module
|
|
403
|
-
if (analogResourceToSource.has(ctx.file)) {
|
|
404
|
-
const parentSource = analogResourceToSource.get(ctx.file);
|
|
405
|
-
const parentModule = ctx.server.moduleGraph.getModuleById(parentSource);
|
|
406
|
-
if (parentModule) {
|
|
407
|
-
return [parentModule];
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
if (TS_EXT_REGEX.test(ctx.file)) {
|
|
411
|
-
const [fileId] = ctx.file.split('?');
|
|
412
|
-
const code = require('fs').readFileSync(fileId, 'utf-8');
|
|
413
|
-
// Remove old entries from this file
|
|
414
|
-
const oldEntries = [...analogRegistry.entries()]
|
|
415
|
-
.filter(([_, v]) => v.fileName === fileId)
|
|
416
|
-
.map(([k]) => k);
|
|
417
|
-
for (const key of oldEntries) {
|
|
418
|
-
analogRegistry.delete(key);
|
|
419
|
-
}
|
|
420
|
-
// Rescan the changed file
|
|
421
|
-
const newEntries = analogScanFile(code, fileId);
|
|
422
|
-
for (const entry of newEntries) {
|
|
423
|
-
analogRegistry.set(entry.className, entry);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
// Let Vite handle the rest — the transform hook will recompile
|
|
427
|
-
return ctx.modules;
|
|
428
|
-
}
|
|
429
215
|
if (TS_EXT_REGEX.test(ctx.file)) {
|
|
430
216
|
let [fileId] = ctx.file.split('?');
|
|
431
217
|
pendingCompilation = performCompilation(resolvedConfig, [fileId]);
|
|
@@ -450,6 +236,16 @@ export function angular(options) {
|
|
|
450
236
|
}
|
|
451
237
|
if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
|
|
452
238
|
fileTransformMap.delete(ctx.file.split('?')[0]);
|
|
239
|
+
// Virtual style modules (used for JIT external styleUrls) are not
|
|
240
|
+
// found by moduleGraph.getModulesByFile because their module id is
|
|
241
|
+
// a virtual prefix, not the real file path. Look up the virtual
|
|
242
|
+
// module manually and include it so HMR propagation works.
|
|
243
|
+
const virtualStyleId = `\0${toVirtualStyleId(normalizePath(ctx.file))}`;
|
|
244
|
+
const virtualMod = ctx.server.moduleGraph.getModuleById(virtualStyleId);
|
|
245
|
+
if (virtualMod && !ctx.modules.includes(virtualMod)) {
|
|
246
|
+
ctx.server.moduleGraph.invalidateModule(virtualMod);
|
|
247
|
+
ctx.modules.push(virtualMod);
|
|
248
|
+
}
|
|
453
249
|
/**
|
|
454
250
|
* Check to see if this was a direct request
|
|
455
251
|
* for an external resource (styles, html).
|
|
@@ -531,10 +327,26 @@ export function angular(options) {
|
|
|
531
327
|
return ctx.modules;
|
|
532
328
|
},
|
|
533
329
|
resolveId(id, importer) {
|
|
330
|
+
if (id.startsWith(VIRTUAL_STYLE_PREFIX) ||
|
|
331
|
+
id.startsWith(VIRTUAL_RAW_PREFIX)) {
|
|
332
|
+
return `\0${id}`;
|
|
333
|
+
}
|
|
534
334
|
if (jit && id.startsWith('angular:jit:')) {
|
|
535
|
-
const
|
|
536
|
-
return
|
|
335
|
+
const filePath = normalizePath(resolve(dirname(importer), id.split(';')[1]));
|
|
336
|
+
return id.includes(':style')
|
|
337
|
+
? toVirtualStyleId(filePath)
|
|
338
|
+
: toVirtualRawId(filePath);
|
|
537
339
|
}
|
|
340
|
+
// User `.html?raw` and `.scss?inline` imports get rewritten to
|
|
341
|
+
// virtual ids so Vite's server.fs Denied ID check (which fires on
|
|
342
|
+
// /[?&](raw|inline)\b/ before the load hook runs) and asset/CSS
|
|
343
|
+
// matchers (which key on file extension) stay out of the way.
|
|
344
|
+
const rawRewrite = rewriteHtmlRawImport(id, importer);
|
|
345
|
+
if (rawRewrite)
|
|
346
|
+
return rawRewrite;
|
|
347
|
+
const inlineRewrite = rewriteInlineStyleImport(id, importer);
|
|
348
|
+
if (inlineRewrite)
|
|
349
|
+
return inlineRewrite;
|
|
538
350
|
// Map angular external styleUrls to the source file
|
|
539
351
|
if (isComponentStyleSheet(id)) {
|
|
540
352
|
const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
|
|
@@ -545,6 +357,29 @@ export function angular(options) {
|
|
|
545
357
|
return undefined;
|
|
546
358
|
},
|
|
547
359
|
async load(id) {
|
|
360
|
+
// Both virtual raw (templates) and virtual style (external styles)
|
|
361
|
+
// ids come in from two paths: the transform-time substitution below
|
|
362
|
+
// (dev + production) and the resolveId rewrite for user `.html?raw`
|
|
363
|
+
// / `.scss?inline` imports. The virtual ids carry no file extension,
|
|
364
|
+
// so Vite's built-in asset/CSS plugins never pick them up and we
|
|
365
|
+
// never see the Denied ID check that blocks `?raw`/`?inline`.
|
|
366
|
+
// (#2263, #2283)
|
|
367
|
+
const styleModule = await loadVirtualStyleModule(this, id, resolvedConfig);
|
|
368
|
+
if (styleModule !== undefined)
|
|
369
|
+
return styleModule;
|
|
370
|
+
const rawModule = await loadVirtualRawModule(this, id);
|
|
371
|
+
if (rawModule !== undefined)
|
|
372
|
+
return rawModule;
|
|
373
|
+
// Vitest fallback: the module-runner calls ensureEntryFromUrl before
|
|
374
|
+
// transformRequest, which skips pluginContainer.resolveId entirely,
|
|
375
|
+
// so a user `import foo from './a.scss?inline'` reaches load as the
|
|
376
|
+
// bare query form. Handle it here so tests still resolve.
|
|
377
|
+
if (/\.(css|scss|sass|less)\?inline$/.test(id)) {
|
|
378
|
+
const filePath = id.split('?')[0];
|
|
379
|
+
const code = await fsPromises.readFile(filePath, 'utf-8');
|
|
380
|
+
const result = await preprocessCSS(code, filePath, resolvedConfig);
|
|
381
|
+
return `export default ${JSON.stringify(result.code)}`;
|
|
382
|
+
}
|
|
548
383
|
// Map angular inline styles to the source text
|
|
549
384
|
if (isComponentStyleSheet(id)) {
|
|
550
385
|
const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
|
|
@@ -569,13 +404,6 @@ export function angular(options) {
|
|
|
569
404
|
!(options?.transformFilter(code, id) ?? true)) {
|
|
570
405
|
return;
|
|
571
406
|
}
|
|
572
|
-
// Analog compiler transform path
|
|
573
|
-
if (pluginOptions.useAnalogCompiler) {
|
|
574
|
-
if (id.includes('.ts?')) {
|
|
575
|
-
id = id.replace(/\?(.*)/, '');
|
|
576
|
-
}
|
|
577
|
-
return handleAnalogCompilerTransform(code, id);
|
|
578
|
-
}
|
|
579
407
|
if (pluginOptions.useAngularCompilationAPI) {
|
|
580
408
|
const isAngular = /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
|
|
581
409
|
if (!isAngular) {
|
|
@@ -646,24 +474,54 @@ export function angular(options) {
|
|
|
646
474
|
pendingCompilation = null;
|
|
647
475
|
}
|
|
648
476
|
const typescriptResult = fileEmitter(id);
|
|
649
|
-
|
|
650
|
-
|
|
477
|
+
// File not in the Angular program — skip and let other plugins
|
|
478
|
+
// or Vite's built-in transform handle it. Warn if it looks like
|
|
479
|
+
// an Angular file that should have been compiled.
|
|
480
|
+
if (!typescriptResult) {
|
|
481
|
+
const isAngular = !id.includes('@ng/component') &&
|
|
482
|
+
/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
|
|
483
|
+
if (isAngular) {
|
|
484
|
+
this.warn(`[@analogjs/vite-plugin-angular]: "${id}" contains Angular decorators but is not in the TypeScript program. ` +
|
|
485
|
+
`Ensure it is included in your tsconfig.`);
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
if (typescriptResult.warnings &&
|
|
490
|
+
typescriptResult.warnings.length > 0) {
|
|
651
491
|
this.warn(`${typescriptResult.warnings.join('\n')}`);
|
|
652
492
|
}
|
|
653
|
-
if (typescriptResult
|
|
493
|
+
if (typescriptResult.errors && typescriptResult.errors.length > 0) {
|
|
654
494
|
this.error(`${typescriptResult.errors.join('\n')}`);
|
|
655
495
|
}
|
|
656
|
-
|
|
657
|
-
let data = typescriptResult?.content ?? '';
|
|
496
|
+
let data = typescriptResult.content ?? '';
|
|
658
497
|
if (jit && data.includes('angular:jit:')) {
|
|
659
498
|
data = data.replace(/angular:jit:style:inline;/g, 'virtual:angular:jit:style:inline;');
|
|
499
|
+
// Emit safe resource ids directly in the transformed JS so Vite
|
|
500
|
+
// never sees the dangerous ?raw / ?inline form during
|
|
501
|
+
// loadAndTransform. Both templates and external styles use
|
|
502
|
+
// virtual module ids with no file extension so neither the
|
|
503
|
+
// vite:css plugin nor the vite:asset plugin picks them up
|
|
504
|
+
// based on the extension.
|
|
505
|
+
//
|
|
506
|
+
// Why this matters: Vite's Denied ID check fires for any id
|
|
507
|
+
// matching that regex whose path is outside server.fs.allow,
|
|
508
|
+
// and it runs *before* pluginContainer.load. Vitest's worker
|
|
509
|
+
// fetchModule path also bypasses pluginContainer.resolveId
|
|
510
|
+
// (it calls moduleGraph.ensureEntryFromUrl first, which makes
|
|
511
|
+
// the resolveId chain a no-op for the module-runner). So
|
|
512
|
+
// neither the resolveId-based rewrites nor the load-hook
|
|
513
|
+
// fallback (added in 2.4.4) get a chance to run for
|
|
514
|
+
// cross-library imports — the security check has already thrown
|
|
515
|
+
// by then. Emitting the safe ids directly in transform is the
|
|
516
|
+
// only place we can guarantee Vite never sees the dangerous
|
|
517
|
+
// form. (#2263)
|
|
660
518
|
templateUrls.forEach((templateUrlSet) => {
|
|
661
519
|
const [templateFile, resolvedTemplateUrl] = templateUrlSet.split('|');
|
|
662
|
-
data = data.replace(`angular:jit:template:file;${templateFile}`,
|
|
520
|
+
data = data.replace(`angular:jit:template:file;${templateFile}`, toVirtualRawId(resolvedTemplateUrl));
|
|
663
521
|
});
|
|
664
522
|
styleUrls.forEach((styleUrlSet) => {
|
|
665
523
|
const [styleFile, resolvedStyleUrl] = styleUrlSet.split('|');
|
|
666
|
-
data = data.replace(`angular:jit:style:file;${styleFile}`,
|
|
524
|
+
data = data.replace(`angular:jit:style:file;${styleFile}`, toVirtualStyleId(resolvedStyleUrl));
|
|
667
525
|
});
|
|
668
526
|
}
|
|
669
527
|
return {
|
|
@@ -684,10 +542,26 @@ export function angular(options) {
|
|
|
684
542
|
},
|
|
685
543
|
};
|
|
686
544
|
}
|
|
545
|
+
const compilationPlugin = pluginOptions.useAnalogCompiler
|
|
546
|
+
? analogCompilerPlugin({
|
|
547
|
+
tsconfigGetter: pluginOptions.tsconfigGetter,
|
|
548
|
+
workspaceRoot: pluginOptions.workspaceRoot,
|
|
549
|
+
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
550
|
+
jit,
|
|
551
|
+
liveReload: pluginOptions.liveReload,
|
|
552
|
+
supportedBrowsers: pluginOptions.supportedBrowsers,
|
|
553
|
+
transformFilter: options?.transformFilter,
|
|
554
|
+
isTest,
|
|
555
|
+
isAstroIntegration,
|
|
556
|
+
analogCompilationMode: pluginOptions.analogCompilationMode,
|
|
557
|
+
})
|
|
558
|
+
: angularPlugin();
|
|
687
559
|
return [
|
|
688
560
|
replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
|
|
689
|
-
|
|
690
|
-
pluginOptions.
|
|
561
|
+
compilationPlugin,
|
|
562
|
+
!pluginOptions.useAnalogCompiler &&
|
|
563
|
+
pluginOptions.liveReload &&
|
|
564
|
+
liveReloadPlugin({ classNames, fileEmitter }),
|
|
691
565
|
...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
|
|
692
566
|
(jit &&
|
|
693
567
|
jitPlugin({
|
|
@@ -713,37 +587,6 @@ export function angular(options) {
|
|
|
713
587
|
absolute: true,
|
|
714
588
|
});
|
|
715
589
|
}
|
|
716
|
-
function createTsConfigGetter(tsconfigOrGetter) {
|
|
717
|
-
if (typeof tsconfigOrGetter === 'function') {
|
|
718
|
-
return tsconfigOrGetter;
|
|
719
|
-
}
|
|
720
|
-
return () => tsconfigOrGetter || '';
|
|
721
|
-
}
|
|
722
|
-
function getTsConfigPath(root, tsconfig, isProd, isTest, isLib) {
|
|
723
|
-
if (tsconfig && isAbsolute(tsconfig)) {
|
|
724
|
-
if (!existsSync(tsconfig)) {
|
|
725
|
-
console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${tsconfig}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
|
|
726
|
-
}
|
|
727
|
-
return tsconfig;
|
|
728
|
-
}
|
|
729
|
-
let tsconfigFilePath = './tsconfig.app.json';
|
|
730
|
-
if (isLib) {
|
|
731
|
-
tsconfigFilePath = isProd
|
|
732
|
-
? './tsconfig.lib.prod.json'
|
|
733
|
-
: './tsconfig.lib.json';
|
|
734
|
-
}
|
|
735
|
-
if (isTest) {
|
|
736
|
-
tsconfigFilePath = './tsconfig.spec.json';
|
|
737
|
-
}
|
|
738
|
-
if (tsconfig) {
|
|
739
|
-
tsconfigFilePath = tsconfig;
|
|
740
|
-
}
|
|
741
|
-
const resolvedPath = resolve(root, tsconfigFilePath);
|
|
742
|
-
if (!existsSync(resolvedPath)) {
|
|
743
|
-
console.error(`[@analogjs/vite-plugin-angular]: Unable to resolve tsconfig at ${resolvedPath}. This causes compilation issues. Check the path or set the "tsconfig" property with an absolute path.`);
|
|
744
|
-
}
|
|
745
|
-
return resolvedPath;
|
|
746
|
-
}
|
|
747
590
|
function resolveTsConfigPath() {
|
|
748
591
|
const tsconfigValue = pluginOptions.tsconfigGetter();
|
|
749
592
|
return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
|