@analogjs/vite-plugin-angular 2.0.0-alpha.1 → 2.0.0-alpha.11
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 +2 -0
- package/package.json +5 -2
- package/src/lib/angular-pending-tasks.plugin.js +3 -2
- package/src/lib/angular-pending-tasks.plugin.js.map +1 -1
- package/src/lib/angular-storybook-plugin.js +2 -2
- package/src/lib/angular-storybook-plugin.js.map +1 -1
- package/src/lib/angular-vite-plugin.d.ts +18 -11
- package/src/lib/angular-vite-plugin.js +405 -105
- package/src/lib/angular-vite-plugin.js.map +1 -1
- package/src/lib/angular-vitest-plugin.js +1 -6
- package/src/lib/angular-vitest-plugin.js.map +1 -1
- package/src/lib/authoring/analog.js +1 -1
- package/src/lib/authoring/analog.js.map +1 -1
- package/src/lib/authoring/markdown-transform.js +7 -2
- package/src/lib/authoring/markdown-transform.js.map +1 -1
- package/src/lib/authoring/marked-setup.service.js +5 -6
- package/src/lib/authoring/marked-setup.service.js.map +1 -1
- package/src/lib/component-resolvers.js.map +1 -1
- package/src/lib/host.d.ts +4 -2
- package/src/lib/host.js +64 -22
- package/src/lib/host.js.map +1 -1
- package/src/lib/live-reload-plugin.d.ts +6 -0
- package/src/lib/live-reload-plugin.js +63 -0
- package/src/lib/live-reload-plugin.js.map +1 -0
- package/src/lib/models.d.ts +11 -0
- package/src/lib/models.js +2 -0
- package/src/lib/models.js.map +1 -0
- package/src/lib/utils/hmr-candidates.d.ts +32 -0
- package/src/lib/utils/hmr-candidates.js +272 -0
- package/src/lib/utils/hmr-candidates.js.map +1 -0
- package/src/lib/utils/source-file-cache.d.ts +2 -2
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { dirname, resolve } from 'node:path';
|
|
1
|
+
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
2
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
3
|
import * as compilerCli from '@angular/compiler-cli';
|
|
3
|
-
import * as ts from 'typescript';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
5
5
|
import { normalizePath, preprocessCSS, } from 'vite';
|
|
6
|
+
import * as ngCompiler from '@angular/compiler';
|
|
6
7
|
import { createCompilerPlugin } from './compiler-plugin.js';
|
|
7
8
|
import { StyleUrlsResolver, TemplateUrlsResolver, } from './component-resolvers.js';
|
|
8
9
|
import { augmentHostWithCaching, augmentHostWithResources, augmentProgramWithVersioning, mergeTransformers, } from './host.js';
|
|
9
10
|
import { jitPlugin } from './angular-jit-plugin.js';
|
|
10
11
|
import { buildOptimizerPlugin } from './angular-build-optimizer-plugin.js';
|
|
11
|
-
import { createJitResourceTransformer, SourceFileCache, } from './utils/devkit.js';
|
|
12
|
+
import { createJitResourceTransformer, SourceFileCache, angularMajor, } from './utils/devkit.js';
|
|
12
13
|
import { angularVitestPlugins } from './angular-vitest-plugin.js';
|
|
13
14
|
import { angularStorybookPlugin } from './angular-storybook-plugin.js';
|
|
14
15
|
const require = createRequire(import.meta.url);
|
|
@@ -16,22 +17,21 @@ import { getFrontmatterMetadata } from './authoring/frontmatter.js';
|
|
|
16
17
|
import { defaultMarkdownTemplateTransforms, } from './authoring/markdown-transform.js';
|
|
17
18
|
import { routerPlugin } from './router-plugin.js';
|
|
18
19
|
import { pendingTasksPlugin } from './angular-pending-tasks.plugin.js';
|
|
20
|
+
import { liveReloadPlugin } from './live-reload-plugin.js';
|
|
19
21
|
/**
|
|
20
22
|
* TypeScript file extension regex
|
|
21
23
|
* Match .(c or m)ts, .ts extensions with an optional ? for query params
|
|
22
24
|
* Ignore .tsx extensions
|
|
23
25
|
*/
|
|
24
26
|
const TS_EXT_REGEX = /\.[cm]?(ts|analog|ag)[^x]?\??/;
|
|
27
|
+
const classNames = new Map();
|
|
25
28
|
export function angular(options) {
|
|
26
29
|
/**
|
|
27
30
|
* Normalize plugin options so defaults
|
|
28
31
|
* are used for values not provided.
|
|
29
32
|
*/
|
|
30
33
|
const pluginOptions = {
|
|
31
|
-
tsconfig: options?.tsconfig
|
|
32
|
-
(process.env['NODE_ENV'] === 'test'
|
|
33
|
-
? './tsconfig.spec.json'
|
|
34
|
-
: './tsconfig.app.json'),
|
|
34
|
+
tsconfig: options?.tsconfig || '',
|
|
35
35
|
workspaceRoot: options?.workspaceRoot ?? process.cwd(),
|
|
36
36
|
inlineStylesExtension: options?.inlineStylesExtension ?? 'css',
|
|
37
37
|
advanced: {
|
|
@@ -50,20 +50,19 @@ export function angular(options) {
|
|
|
50
50
|
: defaultMarkdownTemplateTransforms,
|
|
51
51
|
include: options?.include ?? [],
|
|
52
52
|
additionalContentDirs: options?.additionalContentDirs ?? [],
|
|
53
|
+
liveReload: options?.liveReload ?? false,
|
|
54
|
+
disableTypeChecking: options?.disableTypeChecking ?? true,
|
|
53
55
|
};
|
|
54
|
-
// The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files
|
|
55
|
-
let fileEmitter;
|
|
56
|
-
let compilerOptions = {};
|
|
57
|
-
const ts = require('typescript');
|
|
58
56
|
let resolvedConfig;
|
|
59
|
-
let rootNames;
|
|
60
|
-
let host;
|
|
61
57
|
let nextProgram;
|
|
62
58
|
let builderProgram;
|
|
63
59
|
let watchMode = false;
|
|
64
|
-
let testWatchMode =
|
|
60
|
+
let testWatchMode = isTestWatchMode();
|
|
61
|
+
let inlineComponentStyles;
|
|
62
|
+
let externalComponentStyles;
|
|
65
63
|
const sourceFileCache = new SourceFileCache();
|
|
66
64
|
const isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];
|
|
65
|
+
const isVitestVscode = !!process.env['VITEST_VSCODE'];
|
|
67
66
|
const isStackBlitz = !!process.versions['webcontainer'];
|
|
68
67
|
const isAstroIntegration = process.env['ANALOG_ASTRO'] === 'true';
|
|
69
68
|
const isStorybook = process.env['npm_lifecycle_script']?.includes('storybook') ||
|
|
@@ -72,28 +71,27 @@ export function angular(options) {
|
|
|
72
71
|
process.env['ANALOG_STORYBOOK'] === 'true';
|
|
73
72
|
const jit = typeof pluginOptions?.jit !== 'undefined' ? pluginOptions.jit : isTest;
|
|
74
73
|
let viteServer;
|
|
75
|
-
let styleTransform;
|
|
76
74
|
const styleUrlsResolver = new StyleUrlsResolver();
|
|
77
75
|
const templateUrlsResolver = new TemplateUrlsResolver();
|
|
76
|
+
const outputFiles = new Map();
|
|
77
|
+
const fileEmitter = (file) => {
|
|
78
|
+
return outputFiles.get(normalizePath(file));
|
|
79
|
+
};
|
|
80
|
+
let initialCompilation = false;
|
|
81
|
+
const declarationFiles = [];
|
|
78
82
|
function angularPlugin() {
|
|
79
83
|
let isProd = false;
|
|
84
|
+
if (angularMajor < 19 || isTest) {
|
|
85
|
+
pluginOptions.liveReload = false;
|
|
86
|
+
}
|
|
80
87
|
return {
|
|
81
88
|
name: '@analogjs/vite-plugin-angular',
|
|
82
|
-
async watchChange() {
|
|
83
|
-
if (isTest) {
|
|
84
|
-
await buildAndAnalyze();
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
89
|
async config(config, { command }) {
|
|
88
90
|
watchMode = command === 'serve';
|
|
89
91
|
isProd =
|
|
90
92
|
config.mode === 'production' ||
|
|
91
93
|
process.env['NODE_ENV'] === 'production';
|
|
92
|
-
pluginOptions.tsconfig =
|
|
93
|
-
options?.tsconfig ??
|
|
94
|
-
resolve(config.root || '.', process.env['NODE_ENV'] === 'test'
|
|
95
|
-
? './tsconfig.spec.json'
|
|
96
|
-
: './tsconfig.app.json');
|
|
94
|
+
pluginOptions.tsconfig = getTsConfigPath(config.root || '.', pluginOptions, isProd, isTest, !!config?.build?.lib);
|
|
97
95
|
return {
|
|
98
96
|
esbuild: config.esbuild ?? false,
|
|
99
97
|
optimizeDeps: {
|
|
@@ -123,44 +121,59 @@ export function angular(options) {
|
|
|
123
121
|
},
|
|
124
122
|
configResolved(config) {
|
|
125
123
|
resolvedConfig = config;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
124
|
+
if (isTest) {
|
|
125
|
+
// set test watch mode
|
|
126
|
+
// - vite override from vitest-angular
|
|
127
|
+
// - @nx/vite executor set server.watch explicitly to undefined (watch)/null (watch=false)
|
|
128
|
+
// - vite config for test.watch variable
|
|
129
|
+
// - vitest watch mode detected from the command line
|
|
130
|
+
testWatchMode =
|
|
131
|
+
!(config.server.watch === null) ||
|
|
132
|
+
config.test?.watch === true ||
|
|
133
|
+
testWatchMode;
|
|
134
|
+
}
|
|
133
135
|
},
|
|
134
136
|
configureServer(server) {
|
|
135
137
|
viteServer = server;
|
|
136
138
|
server.watcher.on('add', async () => {
|
|
137
|
-
|
|
138
|
-
await buildAndAnalyze();
|
|
139
|
+
await performCompilation(resolvedConfig);
|
|
139
140
|
});
|
|
140
141
|
server.watcher.on('unlink', async () => {
|
|
141
|
-
|
|
142
|
-
await buildAndAnalyze();
|
|
142
|
+
await performCompilation(resolvedConfig);
|
|
143
143
|
});
|
|
144
144
|
},
|
|
145
145
|
async buildStart() {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
// Defer the first compilation in test mode
|
|
147
|
+
if (!isVitestVscode) {
|
|
148
|
+
const { host } = await performCompilation(resolvedConfig);
|
|
149
|
+
initialCompilation = true;
|
|
150
|
+
// Only store cache if in watch mode
|
|
151
|
+
if (watchMode) {
|
|
152
|
+
augmentHostWithCaching(host, sourceFileCache);
|
|
153
|
+
}
|
|
150
154
|
}
|
|
151
|
-
await buildAndAnalyze();
|
|
152
155
|
},
|
|
153
156
|
async handleHotUpdate(ctx) {
|
|
154
|
-
// The `handleHotUpdate` hook may be called before the `buildStart`,
|
|
155
|
-
// which sets the compilation. As a result, the `host` may not be available
|
|
156
|
-
// yet for use, leading to build errors such as "cannot read properties of undefined"
|
|
157
|
-
// (because `host` is undefined).
|
|
158
|
-
if (!host) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
157
|
if (TS_EXT_REGEX.test(ctx.file)) {
|
|
162
|
-
|
|
163
|
-
|
|
158
|
+
let [fileId] = ctx.file.split('?');
|
|
159
|
+
if (pluginOptions.supportAnalogFormat &&
|
|
160
|
+
['ag', 'analog', 'agx'].some((ext) => fileId.endsWith(ext))) {
|
|
161
|
+
fileId += '.ts';
|
|
162
|
+
}
|
|
163
|
+
await performCompilation(resolvedConfig, [fileId]);
|
|
164
|
+
const result = fileEmitter(fileId);
|
|
165
|
+
if (pluginOptions.liveReload &&
|
|
166
|
+
result?.hmrEligible &&
|
|
167
|
+
classNames.get(fileId)) {
|
|
168
|
+
const relativeFileId = `${relative(process.cwd(), fileId)}@${classNames.get(fileId)}`;
|
|
169
|
+
sendHMRComponentUpdate(ctx.server, relativeFileId);
|
|
170
|
+
return ctx.modules.map((mod) => {
|
|
171
|
+
if (mod.id === ctx.file) {
|
|
172
|
+
return markModuleSelfAccepting(mod);
|
|
173
|
+
}
|
|
174
|
+
return mod;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
164
177
|
}
|
|
165
178
|
if (/\.(html|htm|css|less|sass|scss)$/.test(ctx.file)) {
|
|
166
179
|
/**
|
|
@@ -169,19 +182,73 @@ export function angular(options) {
|
|
|
169
182
|
*/
|
|
170
183
|
const isDirect = ctx.modules.find((mod) => ctx.file === mod.file && mod.id?.includes('?direct'));
|
|
171
184
|
if (isDirect) {
|
|
185
|
+
if (pluginOptions.liveReload && isDirect?.id && isDirect.file) {
|
|
186
|
+
const isComponentStyle = isDirect.type === 'css' && isComponentStyleSheet(isDirect.id);
|
|
187
|
+
if (isComponentStyle) {
|
|
188
|
+
const { encapsulation } = getComponentStyleSheetMeta(isDirect.id);
|
|
189
|
+
// Track if the component uses ShadowDOM encapsulation
|
|
190
|
+
// Shadow DOM components currently require a full reload.
|
|
191
|
+
// Vite's CSS hot replacement does not support shadow root searching.
|
|
192
|
+
if (encapsulation !== 'shadow') {
|
|
193
|
+
ctx.server.ws.send({
|
|
194
|
+
type: 'update',
|
|
195
|
+
updates: [
|
|
196
|
+
{
|
|
197
|
+
type: 'css-update',
|
|
198
|
+
timestamp: Date.now(),
|
|
199
|
+
path: isDirect.url,
|
|
200
|
+
acceptedPath: isDirect.file,
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
});
|
|
204
|
+
return ctx.modules
|
|
205
|
+
.filter((mod) => {
|
|
206
|
+
// Component stylesheets will have 2 modules (*.component.scss and *.component.scss?direct&ngcomp=xyz&e=x)
|
|
207
|
+
// We remove the module with the query params to prevent vite double logging the stylesheet name "hmr update *.component.scss, *.component.scss?direct&ngcomp=xyz&e=x"
|
|
208
|
+
return mod.file !== ctx.file || mod.id !== isDirect.id;
|
|
209
|
+
})
|
|
210
|
+
.map((mod) => {
|
|
211
|
+
if (mod.file === ctx.file) {
|
|
212
|
+
return markModuleSelfAccepting(mod);
|
|
213
|
+
}
|
|
214
|
+
return mod;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
172
219
|
return ctx.modules;
|
|
173
220
|
}
|
|
174
221
|
const mods = [];
|
|
222
|
+
const updates = [];
|
|
175
223
|
ctx.modules.forEach((mod) => {
|
|
176
224
|
mod.importers.forEach((imp) => {
|
|
177
225
|
sourceFileCache.invalidate([imp.id]);
|
|
178
226
|
ctx.server.moduleGraph.invalidateModule(imp);
|
|
179
|
-
|
|
227
|
+
if (pluginOptions.liveReload && classNames.get(imp.id)) {
|
|
228
|
+
updates.push(imp.id);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
mods.push(imp);
|
|
232
|
+
}
|
|
180
233
|
});
|
|
181
234
|
});
|
|
182
|
-
await
|
|
235
|
+
await performCompilation(resolvedConfig, updates);
|
|
236
|
+
if (updates.length > 0) {
|
|
237
|
+
updates.forEach((updateId) => {
|
|
238
|
+
const impRelativeFileId = `${relative(process.cwd(), updateId)}@${classNames.get(updateId)}`;
|
|
239
|
+
sendHMRComponentUpdate(ctx.server, impRelativeFileId);
|
|
240
|
+
});
|
|
241
|
+
return ctx.modules.map((mod) => {
|
|
242
|
+
if (mod.id === ctx.file) {
|
|
243
|
+
return markModuleSelfAccepting(mod);
|
|
244
|
+
}
|
|
245
|
+
return mod;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
183
248
|
return mods;
|
|
184
249
|
}
|
|
250
|
+
// clear HMR updates with a full reload
|
|
251
|
+
classNames.clear();
|
|
185
252
|
return ctx.modules;
|
|
186
253
|
},
|
|
187
254
|
resolveId(id, importer) {
|
|
@@ -189,8 +256,25 @@ export function angular(options) {
|
|
|
189
256
|
const path = id.split(';')[1];
|
|
190
257
|
return `${normalizePath(resolve(dirname(importer), path))}?raw`;
|
|
191
258
|
}
|
|
259
|
+
// Map angular external styleUrls to the source file
|
|
260
|
+
if (isComponentStyleSheet(id)) {
|
|
261
|
+
const componentStyles = externalComponentStyles?.get(getFilenameFromPath(id));
|
|
262
|
+
if (componentStyles) {
|
|
263
|
+
return componentStyles + new URL(id, 'http://localhost').search;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
192
266
|
return undefined;
|
|
193
267
|
},
|
|
268
|
+
async load(id) {
|
|
269
|
+
// Map angular inline styles to the source text
|
|
270
|
+
if (isComponentStyleSheet(id)) {
|
|
271
|
+
const componentStyles = inlineComponentStyles?.get(getFilenameFromPath(id));
|
|
272
|
+
if (componentStyles) {
|
|
273
|
+
return componentStyles;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
},
|
|
194
278
|
async transform(code, id) {
|
|
195
279
|
// Skip transforming node_modules
|
|
196
280
|
if (id.includes('node_modules')) {
|
|
@@ -220,6 +304,19 @@ export function angular(options) {
|
|
|
220
304
|
if (id.includes('analog-content-')) {
|
|
221
305
|
return;
|
|
222
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* Encapsulate component stylesheets that use emulated encapsulation
|
|
309
|
+
*/
|
|
310
|
+
if (pluginOptions.liveReload && isComponentStyleSheet(id)) {
|
|
311
|
+
const { encapsulation, componentId } = getComponentStyleSheetMeta(id);
|
|
312
|
+
if (encapsulation === 'emulated' && componentId) {
|
|
313
|
+
const encapsulated = ngCompiler.encapsulateStyle(code, componentId);
|
|
314
|
+
return {
|
|
315
|
+
code: encapsulated,
|
|
316
|
+
map: null,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
223
320
|
if (TS_EXT_REGEX.test(id)) {
|
|
224
321
|
if (id.includes('.ts?')) {
|
|
225
322
|
// Strip the query string off the ID
|
|
@@ -231,11 +328,17 @@ export function angular(options) {
|
|
|
231
328
|
* for test(Vitest)
|
|
232
329
|
*/
|
|
233
330
|
if (isTest) {
|
|
331
|
+
if (isVitestVscode && !initialCompilation) {
|
|
332
|
+
// Do full initial compilation
|
|
333
|
+
await performCompilation(resolvedConfig);
|
|
334
|
+
initialCompilation = true;
|
|
335
|
+
}
|
|
234
336
|
const tsMod = viteServer?.moduleGraph.getModuleById(id);
|
|
235
337
|
if (tsMod) {
|
|
236
|
-
|
|
237
|
-
if (testWatchMode) {
|
|
238
|
-
|
|
338
|
+
const invalidated = tsMod.lastInvalidationTimestamp;
|
|
339
|
+
if (testWatchMode && invalidated) {
|
|
340
|
+
sourceFileCache.invalidate([id]);
|
|
341
|
+
await performCompilation(resolvedConfig, [id]);
|
|
239
342
|
}
|
|
240
343
|
}
|
|
241
344
|
}
|
|
@@ -250,7 +353,7 @@ export function angular(options) {
|
|
|
250
353
|
this.addWatchFile(absoluteFileUrl);
|
|
251
354
|
}
|
|
252
355
|
}
|
|
253
|
-
const typescriptResult =
|
|
356
|
+
const typescriptResult = fileEmitter(id);
|
|
254
357
|
if (typescriptResult?.warnings &&
|
|
255
358
|
typescriptResult?.warnings.length > 0) {
|
|
256
359
|
this.warn(`${typescriptResult.warnings.join('\n')}`);
|
|
@@ -283,7 +386,7 @@ export function angular(options) {
|
|
|
283
386
|
pluginOptions.supportAnalogFormat &&
|
|
284
387
|
fileEmitter) {
|
|
285
388
|
sourceFileCache.invalidate([`${id}.ts`]);
|
|
286
|
-
const ngFileResult =
|
|
389
|
+
const ngFileResult = fileEmitter(`${id}.ts`);
|
|
287
390
|
data = ngFileResult?.content || '';
|
|
288
391
|
if (id.includes('.agx')) {
|
|
289
392
|
const metadata = await getFrontmatterMetadata(code, id, pluginOptions.markdownTemplateTransforms || []);
|
|
@@ -297,10 +400,17 @@ export function angular(options) {
|
|
|
297
400
|
}
|
|
298
401
|
return undefined;
|
|
299
402
|
},
|
|
403
|
+
closeBundle() {
|
|
404
|
+
declarationFiles.forEach(({ declarationFileDir, declarationPath, data }) => {
|
|
405
|
+
mkdirSync(declarationFileDir, { recursive: true });
|
|
406
|
+
writeFileSync(declarationPath, data, 'utf-8');
|
|
407
|
+
});
|
|
408
|
+
},
|
|
300
409
|
};
|
|
301
410
|
}
|
|
302
411
|
return [
|
|
303
412
|
angularPlugin(),
|
|
413
|
+
pluginOptions.liveReload && liveReloadPlugin({ classNames, fileEmitter }),
|
|
304
414
|
...(isTest && !isStackBlitz ? angularVitestPlugins() : []),
|
|
305
415
|
(jit &&
|
|
306
416
|
jitPlugin({
|
|
@@ -331,7 +441,7 @@ export function angular(options) {
|
|
|
331
441
|
const globs = [
|
|
332
442
|
`${appRoot}/**/*.{analog,agx,ag}`,
|
|
333
443
|
...extraGlobs.map((glob) => `${workspaceRoot}${glob}.{analog,agx,ag}`),
|
|
334
|
-
...(pluginOptions.additionalContentDirs || [])
|
|
444
|
+
...(pluginOptions.additionalContentDirs || []).map((glob) => `${workspaceRoot}${glob}/**/*.agx`),
|
|
335
445
|
...pluginOptions.include.map((glob) => `${workspaceRoot}${glob}`.replace(/\.ts$/, '.analog')),
|
|
336
446
|
];
|
|
337
447
|
return fg
|
|
@@ -350,11 +460,29 @@ export function angular(options) {
|
|
|
350
460
|
dot: true,
|
|
351
461
|
});
|
|
352
462
|
}
|
|
353
|
-
function
|
|
463
|
+
function getTsConfigPath(root, options, isProd, isTest, isLib) {
|
|
464
|
+
if (options.tsconfig && isAbsolute(options.tsconfig)) {
|
|
465
|
+
return options.tsconfig;
|
|
466
|
+
}
|
|
467
|
+
let tsconfigFilePath = './tsconfig.app.json';
|
|
468
|
+
if (isLib) {
|
|
469
|
+
tsconfigFilePath = isProd
|
|
470
|
+
? './tsconfig.lib.prod.json'
|
|
471
|
+
: './tsconfig.lib.json';
|
|
472
|
+
}
|
|
473
|
+
if (isTest) {
|
|
474
|
+
tsconfigFilePath = './tsconfig.spec.json';
|
|
475
|
+
}
|
|
476
|
+
if (options.tsconfig) {
|
|
477
|
+
tsconfigFilePath = options.tsconfig;
|
|
478
|
+
}
|
|
479
|
+
return resolve(root, tsconfigFilePath);
|
|
480
|
+
}
|
|
481
|
+
async function performCompilation(config, ids) {
|
|
354
482
|
const isProd = config.mode === 'production';
|
|
355
483
|
const analogFiles = findAnalogFiles(config);
|
|
356
484
|
const includeFiles = findIncludes();
|
|
357
|
-
|
|
485
|
+
let { options: tsCompilerOptions, rootNames } = compilerCli.readConfiguration(pluginOptions.tsconfig, {
|
|
358
486
|
suppressOutputPathCheck: true,
|
|
359
487
|
outDir: undefined,
|
|
360
488
|
sourceMap: false,
|
|
@@ -377,89 +505,261 @@ export function angular(options) {
|
|
|
377
505
|
// AOT and virtually compiled .analog files.
|
|
378
506
|
tsCompilerOptions.compilationMode = 'experimental-local';
|
|
379
507
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
508
|
+
if (pluginOptions.liveReload && watchMode) {
|
|
509
|
+
tsCompilerOptions['_enableHmr'] = true;
|
|
510
|
+
tsCompilerOptions['externalRuntimeStyles'] = true;
|
|
511
|
+
// Workaround for https://github.com/angular/angular/issues/59310
|
|
512
|
+
// Force extra instructions to be generated for HMR w/defer
|
|
513
|
+
tsCompilerOptions['supportTestBed'] = true;
|
|
514
|
+
}
|
|
515
|
+
if (tsCompilerOptions.compilationMode === 'partial') {
|
|
516
|
+
// These options can't be false in partial mode
|
|
517
|
+
tsCompilerOptions['supportTestBed'] = true;
|
|
518
|
+
tsCompilerOptions['supportJitMode'] = true;
|
|
519
|
+
}
|
|
520
|
+
if (!isTest && config.build?.lib) {
|
|
521
|
+
tsCompilerOptions['declaration'] = true;
|
|
522
|
+
tsCompilerOptions['declarationMap'] = watchMode;
|
|
523
|
+
tsCompilerOptions['inlineSources'] = true;
|
|
524
|
+
}
|
|
525
|
+
rootNames = rootNames.concat(analogFiles, includeFiles);
|
|
526
|
+
const ts = require('typescript');
|
|
527
|
+
const host = ts.createIncrementalCompilerHost(tsCompilerOptions);
|
|
384
528
|
if (!jit) {
|
|
529
|
+
const styleTransform = (code, filename) => preprocessCSS(code, filename, config);
|
|
530
|
+
inlineComponentStyles = tsCompilerOptions['externalRuntimeStyles']
|
|
531
|
+
? new Map()
|
|
532
|
+
: undefined;
|
|
533
|
+
externalComponentStyles = tsCompilerOptions['externalRuntimeStyles']
|
|
534
|
+
? new Map()
|
|
535
|
+
: undefined;
|
|
385
536
|
augmentHostWithResources(host, styleTransform, {
|
|
386
537
|
inlineStylesExtension: pluginOptions.inlineStylesExtension,
|
|
387
538
|
supportAnalogFormat: pluginOptions.supportAnalogFormat,
|
|
388
539
|
isProd,
|
|
389
540
|
markdownTemplateTransforms: pluginOptions.markdownTemplateTransforms,
|
|
541
|
+
inlineComponentStyles,
|
|
542
|
+
externalComponentStyles,
|
|
390
543
|
});
|
|
391
544
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
*/
|
|
398
|
-
async function buildAndAnalyze() {
|
|
545
|
+
/**
|
|
546
|
+
* Creates a new NgtscProgram to analyze/re-analyze
|
|
547
|
+
* the source files and create a file emitter.
|
|
548
|
+
* This is shared between an initial build and a hot update.
|
|
549
|
+
*/
|
|
399
550
|
let builder;
|
|
400
551
|
let typeScriptProgram;
|
|
401
552
|
let angularCompiler;
|
|
402
553
|
if (!jit) {
|
|
403
554
|
// Create the Angular specific program that contains the Angular compiler
|
|
404
|
-
const angularProgram = new compilerCli.NgtscProgram(rootNames,
|
|
555
|
+
const angularProgram = new compilerCli.NgtscProgram(ids && ids.length > 0 ? ids : rootNames, tsCompilerOptions, host, nextProgram);
|
|
405
556
|
angularCompiler = angularProgram.compiler;
|
|
406
557
|
typeScriptProgram = angularProgram.getTsProgram();
|
|
407
558
|
augmentProgramWithVersioning(typeScriptProgram);
|
|
408
|
-
builder = builderProgram
|
|
409
|
-
ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, builderProgram);
|
|
559
|
+
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, builderProgram);
|
|
410
560
|
await angularCompiler.analyzeAsync();
|
|
411
561
|
nextProgram = angularProgram;
|
|
562
|
+
builderProgram =
|
|
563
|
+
builder;
|
|
412
564
|
}
|
|
413
565
|
else {
|
|
414
|
-
builder =
|
|
415
|
-
ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, compilerOptions, host, nextProgram);
|
|
566
|
+
builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, tsCompilerOptions, host, nextProgram);
|
|
416
567
|
typeScriptProgram = builder.getProgram();
|
|
417
|
-
nextProgram = builderProgram;
|
|
418
568
|
}
|
|
419
569
|
if (!watchMode) {
|
|
420
570
|
// When not in watch mode, the startup cost of the incremental analysis can be avoided by
|
|
421
571
|
// using an abstract builder that only wraps a TypeScript program.
|
|
422
572
|
builder = ts.createAbstractBuilder(typeScriptProgram, host);
|
|
423
573
|
}
|
|
424
|
-
const
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
574
|
+
const beforeTransformers = jit
|
|
575
|
+
? [
|
|
576
|
+
compilerCli.constructorParametersDownlevelTransform(builder.getProgram()),
|
|
577
|
+
createJitResourceTransformer(() => builder.getProgram().getTypeChecker()),
|
|
578
|
+
]
|
|
579
|
+
: [];
|
|
580
|
+
const transformers = mergeTransformers({ before: beforeTransformers }, jit ? {} : angularCompiler.prepareEmit().transformers);
|
|
581
|
+
const fileMetadata = getFileMetadata(builder, angularCompiler, pluginOptions.liveReload, pluginOptions.disableTypeChecking);
|
|
582
|
+
const writeFileCallback = (_filename, content, _a, _b, sourceFiles) => {
|
|
583
|
+
if (!sourceFiles?.length) {
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const filename = normalizePath(sourceFiles[0].fileName);
|
|
587
|
+
if (filename.includes('ngtypecheck.ts') || filename.includes('.d.')) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const metadata = watchMode ? fileMetadata(filename) : {};
|
|
591
|
+
outputFiles.set(filename, {
|
|
592
|
+
content,
|
|
593
|
+
dependencies: [],
|
|
594
|
+
errors: metadata.errors,
|
|
595
|
+
warnings: metadata.warnings,
|
|
596
|
+
hmrUpdateCode: metadata.hmrUpdateCode,
|
|
597
|
+
hmrEligible: metadata.hmrEligible,
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
const writeOutputFile = (id) => {
|
|
601
|
+
const sourceFile = builder.getSourceFile(id);
|
|
602
|
+
if (!sourceFile) {
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
let content = '';
|
|
606
|
+
builder.emit(sourceFile, (filename, data) => {
|
|
607
|
+
if (/\.[cm]?js$/.test(filename)) {
|
|
608
|
+
content = data;
|
|
609
|
+
}
|
|
610
|
+
if (!watchMode &&
|
|
611
|
+
!isTest &&
|
|
612
|
+
/\.d\.ts/.test(filename) &&
|
|
613
|
+
!filename.includes('.ngtypecheck.')) {
|
|
614
|
+
// output to library root instead /src
|
|
615
|
+
const declarationPath = resolve(config.root, config.build.outDir, relative(config.root, filename)).replace('/src/', '/');
|
|
616
|
+
const declarationFileDir = declarationPath
|
|
617
|
+
.replace(basename(filename), '')
|
|
618
|
+
.replace('/src/', '/');
|
|
619
|
+
declarationFiles.push({
|
|
620
|
+
declarationFileDir,
|
|
621
|
+
declarationPath,
|
|
622
|
+
data,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
|
|
626
|
+
writeFileCallback(id, content, false, undefined, [sourceFile]);
|
|
627
|
+
};
|
|
628
|
+
if (!watchMode) {
|
|
629
|
+
for (const sf of builder.getSourceFiles()) {
|
|
630
|
+
const id = sf.fileName;
|
|
631
|
+
writeOutputFile(id);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
if (ids && ids.length > 0) {
|
|
636
|
+
ids.forEach((id) => writeOutputFile(id));
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
// TypeScript will loop until there are no more affected files in the program
|
|
640
|
+
while (builder.emitNextAffectedFile(writeFileCallback, undefined, undefined, transformers)) {
|
|
641
|
+
/* empty */
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return { host };
|
|
438
646
|
}
|
|
439
647
|
}
|
|
440
|
-
|
|
441
|
-
|
|
648
|
+
function sendHMRComponentUpdate(server, id) {
|
|
649
|
+
server.ws.send('angular:component-update', {
|
|
650
|
+
id: encodeURIComponent(id),
|
|
651
|
+
timestamp: Date.now(),
|
|
652
|
+
});
|
|
653
|
+
classNames.delete(id);
|
|
654
|
+
}
|
|
655
|
+
export function getFileMetadata(program, angularCompiler, liveReload, disableTypeChecking) {
|
|
656
|
+
const ts = require('typescript');
|
|
657
|
+
return (file) => {
|
|
442
658
|
const sourceFile = program.getSourceFile(file);
|
|
443
659
|
if (!sourceFile) {
|
|
444
|
-
return
|
|
660
|
+
return {};
|
|
445
661
|
}
|
|
446
|
-
const diagnostics = angularCompiler
|
|
447
|
-
? angularCompiler.getDiagnosticsForFile(sourceFile, 1)
|
|
448
|
-
: [];
|
|
662
|
+
const diagnostics = getDiagnosticsForSourceFile(sourceFile, !!disableTypeChecking, program, angularCompiler);
|
|
449
663
|
const errors = diagnostics
|
|
450
664
|
.filter((d) => d.category === ts.DiagnosticCategory?.Error)
|
|
451
|
-
.map((d) => d.messageText
|
|
665
|
+
.map((d) => typeof d.messageText === 'object'
|
|
666
|
+
? d.messageText.messageText
|
|
667
|
+
: d.messageText);
|
|
452
668
|
const warnings = diagnostics
|
|
453
669
|
.filter((d) => d.category === ts.DiagnosticCategory?.Warning)
|
|
454
670
|
.map((d) => d.messageText);
|
|
455
|
-
let
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
671
|
+
let hmrUpdateCode = undefined;
|
|
672
|
+
let hmrEligible = false;
|
|
673
|
+
if (liveReload) {
|
|
674
|
+
for (const node of sourceFile.statements) {
|
|
675
|
+
if (ts.isClassDeclaration(node) && node.name != null) {
|
|
676
|
+
hmrUpdateCode = angularCompiler?.emitHmrUpdateModule(node);
|
|
677
|
+
if (!!hmrUpdateCode) {
|
|
678
|
+
classNames.set(file, node.name.getText());
|
|
679
|
+
hmrEligible = true;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
459
682
|
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
|
|
683
|
+
}
|
|
684
|
+
return { errors, warnings, hmrUpdateCode, hmrEligible };
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
function getDiagnosticsForSourceFile(sourceFile, disableTypeChecking, program, angularCompiler) {
|
|
688
|
+
const syntacticDiagnostics = program.getSyntacticDiagnostics(sourceFile);
|
|
689
|
+
if (disableTypeChecking) {
|
|
690
|
+
// Syntax errors are cheap to compute and the app will not run if there are any
|
|
691
|
+
// So always show these types of errors regardless if type checking is disabled
|
|
692
|
+
return syntacticDiagnostics;
|
|
693
|
+
}
|
|
694
|
+
const semanticDiagnostics = program.getSemanticDiagnostics(sourceFile);
|
|
695
|
+
const angularDiagnostics = angularCompiler
|
|
696
|
+
? angularCompiler.getDiagnosticsForFile(sourceFile, 1)
|
|
697
|
+
: [];
|
|
698
|
+
return [
|
|
699
|
+
...syntacticDiagnostics,
|
|
700
|
+
...semanticDiagnostics,
|
|
701
|
+
...angularDiagnostics,
|
|
702
|
+
];
|
|
703
|
+
}
|
|
704
|
+
function markModuleSelfAccepting(mod) {
|
|
705
|
+
// support Vite 6
|
|
706
|
+
if ('_clientModule' in mod) {
|
|
707
|
+
mod['_clientModule'].isSelfAccepting = true;
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
...mod,
|
|
711
|
+
isSelfAccepting: true,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
function isComponentStyleSheet(id) {
|
|
715
|
+
return id.includes('ngcomp=');
|
|
716
|
+
}
|
|
717
|
+
function getComponentStyleSheetMeta(id) {
|
|
718
|
+
const params = new URL(id, 'http://localhost').searchParams;
|
|
719
|
+
const encapsulationMapping = {
|
|
720
|
+
'0': 'emulated',
|
|
721
|
+
'2': 'none',
|
|
722
|
+
'3': 'shadow',
|
|
463
723
|
};
|
|
724
|
+
return {
|
|
725
|
+
componentId: params.get('ngcomp'),
|
|
726
|
+
encapsulation: encapsulationMapping[params.get('e')],
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Removes leading / and query string from a url path
|
|
731
|
+
* e.g. /foo.scss?direct&ngcomp=ng-c3153525609&e=0 returns foo.scss
|
|
732
|
+
* @param id
|
|
733
|
+
*/
|
|
734
|
+
function getFilenameFromPath(id) {
|
|
735
|
+
return new URL(id, 'http://localhost').pathname.replace(/^\//, '');
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Checks for vitest run from the command line
|
|
739
|
+
* @returns boolean
|
|
740
|
+
*/
|
|
741
|
+
export function isTestWatchMode(args = process.argv) {
|
|
742
|
+
// vitest --run
|
|
743
|
+
const hasRun = args.find((arg) => arg.includes('--run'));
|
|
744
|
+
if (hasRun) {
|
|
745
|
+
return false;
|
|
746
|
+
}
|
|
747
|
+
// vitest --no-run
|
|
748
|
+
const hasNoRun = args.find((arg) => arg.includes('--no-run'));
|
|
749
|
+
if (hasNoRun) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
// check for --watch=false or --no-watch
|
|
753
|
+
const hasWatch = args.find((arg) => arg.includes('watch'));
|
|
754
|
+
if (hasWatch && ['false', 'no'].some((neg) => hasWatch.includes(neg))) {
|
|
755
|
+
return false;
|
|
756
|
+
}
|
|
757
|
+
// check for --watch false
|
|
758
|
+
const watchIndex = args.findIndex((arg) => arg.includes('watch'));
|
|
759
|
+
const watchArg = args[watchIndex + 1];
|
|
760
|
+
if (watchArg && watchArg === 'false') {
|
|
761
|
+
return false;
|
|
762
|
+
}
|
|
763
|
+
return true;
|
|
464
764
|
}
|
|
465
765
|
//# sourceMappingURL=angular-vite-plugin.js.map
|