@analogjs/vite-plugin-angular 2.5.0-beta.3 → 2.5.0-beta.30
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 +395 -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 -280
- 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
|
@@ -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,8 @@ 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
24
|
export var DiagnosticModes;
|
|
25
25
|
(function (DiagnosticModes) {
|
|
26
26
|
DiagnosticModes[DiagnosticModes["None"] = 0] = "None";
|
|
@@ -29,12 +29,6 @@ export var DiagnosticModes;
|
|
|
29
29
|
DiagnosticModes[DiagnosticModes["Semantic"] = 4] = "Semantic";
|
|
30
30
|
DiagnosticModes[DiagnosticModes["All"] = 7] = "All";
|
|
31
31
|
})(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
32
|
const classNames = new Map();
|
|
39
33
|
export function angular(options) {
|
|
40
34
|
/**
|
|
@@ -61,9 +55,9 @@ export function angular(options) {
|
|
|
61
55
|
fileReplacements: options?.fileReplacements ?? [],
|
|
62
56
|
useAngularCompilationAPI: options?.experimental?.useAngularCompilationAPI ?? false,
|
|
63
57
|
useAnalogCompiler: options?.experimental?.useAnalogCompiler ?? false,
|
|
58
|
+
analogCompilationMode: options?.experimental?.analogCompilationMode ?? 'full',
|
|
64
59
|
};
|
|
65
60
|
let resolvedConfig;
|
|
66
|
-
// Store config context needed for getTsConfigPath resolution
|
|
67
61
|
let tsConfigResolutionContext = null;
|
|
68
62
|
const ts = require('typescript');
|
|
69
63
|
let builder;
|
|
@@ -113,126 +107,6 @@ export function angular(options) {
|
|
|
113
107
|
// Previously the compilation was recreated on every pass, which meant Angular
|
|
114
108
|
// never had prior state and could never produce HMR payloads.
|
|
115
109
|
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
110
|
function angularPlugin() {
|
|
237
111
|
let isProd = false;
|
|
238
112
|
if (angularFullVersion < 190000 || isTest) {
|
|
@@ -254,7 +128,6 @@ export function angular(options) {
|
|
|
254
128
|
}
|
|
255
129
|
return {
|
|
256
130
|
name: '@analogjs/vite-plugin-angular',
|
|
257
|
-
...(pluginOptions.useAnalogCompiler ? { enforce: 'pre' } : {}),
|
|
258
131
|
async config(config, { command }) {
|
|
259
132
|
watchMode = command === 'serve';
|
|
260
133
|
isProd =
|
|
@@ -268,62 +141,26 @@ export function angular(options) {
|
|
|
268
141
|
};
|
|
269
142
|
// Do a preliminary resolution for esbuild plugin (before configResolved)
|
|
270
143
|
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
144
|
const esbuild = pluginOptions.useAngularCompilationAPI
|
|
278
145
|
? undefined
|
|
279
|
-
:
|
|
280
|
-
? false
|
|
281
|
-
: (config.esbuild ?? false);
|
|
146
|
+
: (config.esbuild ?? false);
|
|
282
147
|
const oxc = pluginOptions.useAngularCompilationAPI
|
|
283
148
|
? 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
|
-
};
|
|
149
|
+
: (config.oxc ?? false);
|
|
150
|
+
const depOptimizer = createDepOptimizerConfig({
|
|
151
|
+
tsconfig: preliminaryTsConfigPath,
|
|
152
|
+
isProd,
|
|
153
|
+
jit,
|
|
154
|
+
watchMode,
|
|
155
|
+
isTest,
|
|
156
|
+
isAstroIntegration,
|
|
157
|
+
});
|
|
315
158
|
return {
|
|
316
159
|
...(vite.rolldownVersion ? { oxc } : { esbuild }),
|
|
317
|
-
|
|
318
|
-
include: ['rxjs/operators', 'rxjs'],
|
|
319
|
-
exclude: ['@angular/platform-server'],
|
|
320
|
-
...(vite.rolldownVersion
|
|
321
|
-
? { rolldownOptions }
|
|
322
|
-
: { esbuildOptions }),
|
|
323
|
-
},
|
|
160
|
+
...depOptimizer,
|
|
324
161
|
resolve: {
|
|
325
162
|
conditions: [
|
|
326
|
-
|
|
163
|
+
...depOptimizer.resolve.conditions,
|
|
327
164
|
...(config.resolve?.conditions || defaultClientConditions),
|
|
328
165
|
],
|
|
329
166
|
},
|
|
@@ -352,26 +189,6 @@ export function angular(options) {
|
|
|
352
189
|
},
|
|
353
190
|
configureServer(server) {
|
|
354
191
|
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
192
|
// Add/unlink changes the TypeScript program shape, not just file
|
|
376
193
|
// contents, so we need to invalidate both include discovery and the
|
|
377
194
|
// cached tsconfig root names before recompiling.
|
|
@@ -385,10 +202,6 @@ export function angular(options) {
|
|
|
385
202
|
});
|
|
386
203
|
},
|
|
387
204
|
async buildStart() {
|
|
388
|
-
if (pluginOptions.useAnalogCompiler) {
|
|
389
|
-
await initAnalogCompiler();
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
205
|
// Defer the first compilation in test mode
|
|
393
206
|
if (!isVitestVscode) {
|
|
394
207
|
await performCompilation(resolvedConfig);
|
|
@@ -397,35 +210,6 @@ export function angular(options) {
|
|
|
397
210
|
}
|
|
398
211
|
},
|
|
399
212
|
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
213
|
if (TS_EXT_REGEX.test(ctx.file)) {
|
|
430
214
|
let [fileId] = ctx.file.split('?');
|
|
431
215
|
pendingCompilation = performCompilation(resolvedConfig, [fileId]);
|
|
@@ -533,7 +317,36 @@ export function angular(options) {
|
|
|
533
317
|
resolveId(id, importer) {
|
|
534
318
|
if (jit && id.startsWith('angular:jit:')) {
|
|
535
319
|
const path = id.split(';')[1];
|
|
536
|
-
return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(':style') ? 'inline' : 'raw'}`;
|
|
320
|
+
return `${normalizePath(resolve(dirname(importer), path))}?${id.includes(':style') ? 'analog-inline' : 'analog-raw'}`;
|
|
321
|
+
}
|
|
322
|
+
// Intercept .html?raw imports to bypass Vite 7.3.2+ server.fs restrictions
|
|
323
|
+
// These are generated by JIT template transforms and would otherwise be
|
|
324
|
+
// blocked by Vite's stricter ?raw query parameter security checks
|
|
325
|
+
if (id.includes('.html?raw')) {
|
|
326
|
+
const filePath = id.split('?')[0];
|
|
327
|
+
const resolved = isAbsolute(filePath)
|
|
328
|
+
? normalizePath(filePath)
|
|
329
|
+
: importer
|
|
330
|
+
? normalizePath(resolve(dirname(importer), filePath))
|
|
331
|
+
: undefined;
|
|
332
|
+
if (resolved) {
|
|
333
|
+
return resolved + '?analog-raw';
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Intercept style ?inline imports to bypass Vite 8.0.5+ server.fs
|
|
337
|
+
// restrictions. Vite's security check matches /[?&]inline\b/ so we
|
|
338
|
+
// use ?analog-inline which avoids the regex while we handle CSS
|
|
339
|
+
// preprocessing and inline export ourselves in the load hook.
|
|
340
|
+
if (/\.(css|scss|sass|less)\?inline$/.test(id)) {
|
|
341
|
+
const filePath = id.split('?')[0];
|
|
342
|
+
const resolved = isAbsolute(filePath)
|
|
343
|
+
? normalizePath(filePath)
|
|
344
|
+
: importer
|
|
345
|
+
? normalizePath(resolve(dirname(importer), filePath))
|
|
346
|
+
: undefined;
|
|
347
|
+
if (resolved) {
|
|
348
|
+
return resolved + '?analog-inline';
|
|
349
|
+
}
|
|
537
350
|
}
|
|
538
351
|
// Map angular external styleUrls to the source file
|
|
539
352
|
if (isComponentStyleSheet(id)) {
|
|
@@ -545,6 +358,31 @@ export function angular(options) {
|
|
|
545
358
|
return undefined;
|
|
546
359
|
},
|
|
547
360
|
async load(id) {
|
|
361
|
+
// Handle Angular template raw imports directly to bypass Vite server.fs
|
|
362
|
+
// restrictions on ?raw query parameters (Vite 7.3.2+)
|
|
363
|
+
if (id.endsWith('?analog-raw')) {
|
|
364
|
+
const filePath = id.slice(0, -'?analog-raw'.length);
|
|
365
|
+
const content = await fsPromises.readFile(filePath, 'utf-8');
|
|
366
|
+
return `export default ${JSON.stringify(content)}`;
|
|
367
|
+
}
|
|
368
|
+
// Handle Angular style imports directly, bypassing Vite 8.0.5+
|
|
369
|
+
// server.fs security check which blocks IDs matching /[?&]inline\b/.
|
|
370
|
+
// Compile via preprocessCSS and return as inline string export.
|
|
371
|
+
//
|
|
372
|
+
// We accept both ?analog-inline (rewritten by resolveId for the
|
|
373
|
+
// browser dev-server path) and ?inline (the original query) because
|
|
374
|
+
// Vitest's fetchModule path calls moduleGraph.ensureEntryFromUrl
|
|
375
|
+
// before transformRequest, which means pluginContainer.resolveId is
|
|
376
|
+
// never invoked for module-runner imports — so the resolveId-based
|
|
377
|
+
// rewrite never runs in the test path. Handling ?inline here covers
|
|
378
|
+
// both paths.
|
|
379
|
+
if (id.includes('?analog-inline') ||
|
|
380
|
+
/\.(css|scss|sass|less)\?inline$/.test(id)) {
|
|
381
|
+
const filePath = id.split('?')[0];
|
|
382
|
+
const code = await fsPromises.readFile(filePath, 'utf-8');
|
|
383
|
+
const result = await preprocessCSS(code, filePath, resolvedConfig);
|
|
384
|
+
return `export default ${JSON.stringify(result.code)}`;
|
|
385
|
+
}
|
|
548
386
|
// Map angular inline styles to the source text
|
|
549
387
|
if (isComponentStyleSheet(id)) {
|
|
550
388
|
const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
|
|
@@ -569,13 +407,6 @@ export function angular(options) {
|
|
|
569
407
|
!(options?.transformFilter(code, id) ?? true)) {
|
|
570
408
|
return;
|
|
571
409
|
}
|
|
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
410
|
if (pluginOptions.useAngularCompilationAPI) {
|
|
580
411
|
const isAngular = /(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
|
|
581
412
|
if (!isAngular) {
|
|
@@ -646,24 +477,52 @@ export function angular(options) {
|
|
|
646
477
|
pendingCompilation = null;
|
|
647
478
|
}
|
|
648
479
|
const typescriptResult = fileEmitter(id);
|
|
649
|
-
|
|
650
|
-
|
|
480
|
+
// File not in the Angular program — skip and let other plugins
|
|
481
|
+
// or Vite's built-in transform handle it. Warn if it looks like
|
|
482
|
+
// an Angular file that should have been compiled.
|
|
483
|
+
if (!typescriptResult) {
|
|
484
|
+
const isAngular = !id.includes('@ng/component') &&
|
|
485
|
+
/(Component|Directive|Pipe|Injectable|NgModule)\(/.test(code);
|
|
486
|
+
if (isAngular) {
|
|
487
|
+
this.warn(`[@analogjs/vite-plugin-angular]: "${id}" contains Angular decorators but is not in the TypeScript program. ` +
|
|
488
|
+
`Ensure it is included in your tsconfig.`);
|
|
489
|
+
}
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (typescriptResult.warnings &&
|
|
493
|
+
typescriptResult.warnings.length > 0) {
|
|
651
494
|
this.warn(`${typescriptResult.warnings.join('\n')}`);
|
|
652
495
|
}
|
|
653
|
-
if (typescriptResult
|
|
496
|
+
if (typescriptResult.errors && typescriptResult.errors.length > 0) {
|
|
654
497
|
this.error(`${typescriptResult.errors.join('\n')}`);
|
|
655
498
|
}
|
|
656
|
-
|
|
657
|
-
let data = typescriptResult?.content ?? '';
|
|
499
|
+
let data = typescriptResult.content ?? '';
|
|
658
500
|
if (jit && data.includes('angular:jit:')) {
|
|
659
501
|
data = data.replace(/angular:jit:style:inline;/g, 'virtual:angular:jit:style:inline;');
|
|
502
|
+
// Emit ?analog-raw and ?analog-inline directly (instead of
|
|
503
|
+
// ?raw / ?inline) so the import ids in the compiled JS never
|
|
504
|
+
// match Vite 8.0.5+'s [?&](raw|inline)\b security regex during
|
|
505
|
+
// loadAndTransform.
|
|
506
|
+
//
|
|
507
|
+
// Why this matters: Vite's Denied ID check fires for any id
|
|
508
|
+
// matching that regex whose path is outside server.fs.allow,
|
|
509
|
+
// and it runs *before* pluginContainer.load. Vitest's worker
|
|
510
|
+
// fetchModule path also bypasses pluginContainer.resolveId
|
|
511
|
+
// (it calls moduleGraph.ensureEntryFromUrl first, which makes
|
|
512
|
+
// the resolveId chain a no-op for the module-runner). So
|
|
513
|
+
// neither the resolveId-based ?inline -> ?analog-inline rewrite
|
|
514
|
+
// nor the load-hook fallback (added in 2.4.4) gets a chance to
|
|
515
|
+
// run for cross-library imports — the security check has
|
|
516
|
+
// already thrown by then. Emitting the safe query directly in
|
|
517
|
+
// the transform output is the only place we can guarantee Vite
|
|
518
|
+
// never sees the dangerous form. (#2263)
|
|
660
519
|
templateUrls.forEach((templateUrlSet) => {
|
|
661
520
|
const [templateFile, resolvedTemplateUrl] = templateUrlSet.split('|');
|
|
662
|
-
data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?raw`);
|
|
521
|
+
data = data.replace(`angular:jit:template:file;${templateFile}`, `${resolvedTemplateUrl}?analog-raw`);
|
|
663
522
|
});
|
|
664
523
|
styleUrls.forEach((styleUrlSet) => {
|
|
665
524
|
const [styleFile, resolvedStyleUrl] = styleUrlSet.split('|');
|
|
666
|
-
data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?inline`);
|
|
525
|
+
data = data.replace(`angular:jit:style:file;${styleFile}`, `${resolvedStyleUrl}?analog-inline`);
|
|
667
526
|
});
|
|
668
527
|
}
|
|
669
528
|
return {
|
|
@@ -684,10 +543,26 @@ export function angular(options) {
|
|
|
684
543
|
},
|
|
685
544
|
};
|
|
686
545
|
}
|
|
546
|
+
const compilationPlugin = pluginOptions.useAnalogCompiler
|
|
547
|
+
? analogCompilerPlugin({
|
|
548
|
+
tsconfigGetter: pluginOptions.tsconfigGetter,
|
|
549
|
+
workspaceRoot: pluginOptions.workspaceRoot,
|
|
550
|
+
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
551
|
+
jit,
|
|
552
|
+
liveReload: pluginOptions.liveReload,
|
|
553
|
+
supportedBrowsers: pluginOptions.supportedBrowsers,
|
|
554
|
+
transformFilter: options?.transformFilter,
|
|
555
|
+
isTest,
|
|
556
|
+
isAstroIntegration,
|
|
557
|
+
analogCompilationMode: pluginOptions.analogCompilationMode,
|
|
558
|
+
})
|
|
559
|
+
: angularPlugin();
|
|
687
560
|
return [
|
|
688
561
|
replaceFiles(pluginOptions.fileReplacements, pluginOptions.workspaceRoot),
|
|
689
|
-
|
|
690
|
-
pluginOptions.
|
|
562
|
+
compilationPlugin,
|
|
563
|
+
!pluginOptions.useAnalogCompiler &&
|
|
564
|
+
pluginOptions.liveReload &&
|
|
565
|
+
liveReloadPlugin({ classNames, fileEmitter }),
|
|
691
566
|
...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
|
|
692
567
|
(jit &&
|
|
693
568
|
jitPlugin({
|
|
@@ -713,37 +588,6 @@ export function angular(options) {
|
|
|
713
588
|
absolute: true,
|
|
714
589
|
});
|
|
715
590
|
}
|
|
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
591
|
function resolveTsConfigPath() {
|
|
748
592
|
const tsconfigValue = pluginOptions.tsconfigGetter();
|
|
749
593
|
return getTsConfigPath(tsConfigResolutionContext.root, tsconfigValue, tsConfigResolutionContext.isProd, isTest, tsConfigResolutionContext.isLib);
|