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