@angular/build 19.0.0-next.9 → 19.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +21 -19
- package/src/builders/application/build-action.js +22 -10
- package/src/builders/application/chunk-optimizer.js +1 -4
- package/src/builders/application/execute-build.js +59 -24
- package/src/builders/application/execute-post-bundle.js +28 -5
- package/src/builders/application/index.d.ts +0 -16
- package/src/builders/application/index.js +15 -10
- package/src/builders/application/options.d.ts +14 -2
- package/src/builders/application/options.js +25 -10
- package/src/builders/application/results.d.ts +5 -3
- package/src/builders/application/schema.d.ts +86 -0
- package/src/builders/application/schema.js +19 -1
- package/src/builders/application/schema.json +73 -4
- package/src/builders/application/setup-bundling.d.ts +6 -1
- package/src/builders/application/setup-bundling.js +47 -13
- package/src/builders/dev-server/options.d.ts +2 -2
- package/src/builders/dev-server/options.js +2 -2
- package/src/builders/dev-server/schema.d.ts +2 -1
- package/src/builders/dev-server/schema.json +1 -2
- package/src/builders/dev-server/vite-server.d.ts +3 -2
- package/src/builders/dev-server/vite-server.js +123 -61
- package/src/index.d.ts +1 -0
- package/src/tools/angular/angular-host.d.ts +1 -1
- package/src/tools/angular/angular-host.js +14 -6
- package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
- package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
- package/src/tools/angular/compilation/aot-compilation.js +39 -0
- package/src/tools/angular/compilation/parallel-compilation.js +2 -2
- package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
- package/src/tools/angular/compilation/parallel-worker.js +5 -2
- package/src/tools/esbuild/angular/compiler-plugin.d.ts +3 -4
- package/src/tools/esbuild/angular/compiler-plugin.js +58 -33
- package/src/tools/esbuild/angular/component-stylesheets.d.ts +18 -18
- package/src/tools/esbuild/angular/component-stylesheets.js +66 -38
- package/src/tools/esbuild/angular/jit-plugin-callbacks.d.ts +1 -1
- package/src/tools/esbuild/angular/jit-plugin-callbacks.js +11 -3
- package/src/tools/esbuild/angular/source-file-cache.d.ts +1 -1
- package/src/tools/esbuild/angular/source-file-cache.js +6 -2
- package/src/tools/esbuild/application-code-bundle.d.ts +7 -5
- package/src/tools/esbuild/application-code-bundle.js +280 -249
- package/src/tools/esbuild/bundler-context.d.ts +2 -1
- package/src/tools/esbuild/bundler-context.js +10 -12
- package/src/tools/esbuild/bundler-execution-result.d.ts +14 -3
- package/src/tools/esbuild/bundler-execution-result.js +15 -8
- package/src/tools/esbuild/commonjs-checker.js +2 -2
- package/src/tools/esbuild/compiler-plugin-options.d.ts +2 -4
- package/src/tools/esbuild/compiler-plugin-options.js +15 -37
- package/src/tools/esbuild/global-scripts.js +1 -1
- package/src/tools/esbuild/global-styles.js +4 -1
- package/src/tools/esbuild/index-html-generator.js +8 -0
- package/src/tools/esbuild/javascript-transformer.js +3 -0
- package/src/tools/esbuild/server-bundle-metadata-plugin.d.ts +22 -0
- package/src/tools/esbuild/server-bundle-metadata-plugin.js +36 -0
- package/src/tools/esbuild/stylesheets/bundle-options.d.ts +2 -0
- package/src/tools/esbuild/stylesheets/bundle-options.js +2 -1
- package/src/tools/esbuild/stylesheets/sass-language.js +4 -0
- package/src/tools/esbuild/stylesheets/stylesheet-plugin-factory.d.ts +9 -0
- package/src/tools/esbuild/utils.js +13 -31
- package/src/tools/sass/worker.js +19 -0
- package/src/tools/vite/middlewares/assets-middleware.d.ts +6 -1
- package/src/tools/vite/middlewares/assets-middleware.js +42 -22
- package/src/tools/vite/middlewares/component-middleware.d.ts +9 -0
- package/src/tools/vite/middlewares/component-middleware.js +33 -0
- package/src/tools/vite/middlewares/index.d.ts +2 -1
- package/src/tools/vite/middlewares/index.js +3 -1
- package/src/tools/vite/middlewares/ssr-middleware.js +11 -8
- package/src/tools/vite/plugins/angular-memory-plugin.d.ts +1 -0
- package/src/tools/vite/plugins/angular-memory-plugin.js +5 -13
- package/src/tools/vite/plugins/setup-middlewares-plugin.d.ts +3 -1
- package/src/tools/vite/plugins/setup-middlewares-plugin.js +12 -3
- package/src/tools/vite/utils.d.ts +1 -0
- package/src/typings.d.ts +1 -1
- package/src/utils/environment-options.d.ts +1 -0
- package/src/utils/environment-options.js +4 -2
- package/src/utils/index-file/auto-csp.d.ts +23 -0
- package/src/utils/index-file/auto-csp.js +283 -0
- package/src/utils/index-file/html-rewriting-stream.d.ts +5 -1
- package/src/utils/index-file/index-html-generator.d.ts +4 -0
- package/src/utils/index-file/index-html-generator.js +11 -0
- package/src/utils/index-file/inline-critical-css.js +17 -18
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/server-rendering/esm-in-memory-loader/utils.d.ts +8 -0
- package/src/utils/server-rendering/esm-in-memory-loader/utils.js +13 -0
- package/src/utils/server-rendering/launch-server.js +5 -5
- package/src/utils/server-rendering/load-esm-from-memory.d.ts +1 -1
- package/src/utils/server-rendering/manifest.d.ts +9 -8
- package/src/utils/server-rendering/manifest.js +17 -23
- package/src/utils/server-rendering/prerender.js +30 -19
- package/src/utils/server-rendering/render-worker.js +4 -2
- package/src/utils/supported-browsers.js +1 -0
|
@@ -65,10 +65,10 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
65
65
|
}
|
|
66
66
|
// TODO: Adjust architect to not force a JsonObject derived return type
|
|
67
67
|
const browserOptions = (await context.validateOptions(rawBrowserOptions, builderName));
|
|
68
|
-
if (browserOptions.prerender) {
|
|
68
|
+
if (browserOptions.prerender || (browserOptions.outputMode && browserOptions.server)) {
|
|
69
69
|
// Disable prerendering if enabled and force SSR.
|
|
70
70
|
// This is so instead of prerendering all the routes for every change, the page is "prerendered" when it is requested.
|
|
71
|
-
browserOptions.prerender =
|
|
71
|
+
browserOptions.prerender = undefined;
|
|
72
72
|
browserOptions.ssr ||= true;
|
|
73
73
|
}
|
|
74
74
|
// Set all packages as external to support Vite's prebundle caching
|
|
@@ -92,8 +92,14 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
92
92
|
// https://nodejs.org/api/process.html#processsetsourcemapsenabledval
|
|
93
93
|
process.setSourceMapsEnabled(true);
|
|
94
94
|
}
|
|
95
|
-
//
|
|
96
|
-
browserOptions.externalRuntimeStyles =
|
|
95
|
+
// Enable to support component style hot reloading (`NG_HMR_CSTYLES=0` can be used to disable selectively)
|
|
96
|
+
browserOptions.externalRuntimeStyles =
|
|
97
|
+
serverOptions.liveReload && serverOptions.hmr && environment_options_1.useComponentStyleHmr;
|
|
98
|
+
// Enable to support component template hot replacement (`NG_HMR_TEMPLATE=1` can be used to enable)
|
|
99
|
+
browserOptions.templateUpdates = !!serverOptions.liveReload && environment_options_1.useComponentTemplateHmr;
|
|
100
|
+
if (browserOptions.templateUpdates) {
|
|
101
|
+
context.logger.warn('Experimental support for component template hot replacement has been enabled via the "NG_HMR_TEMPLATE" environment variable.');
|
|
102
|
+
}
|
|
97
103
|
// Setup the prebundling transformer that will be shared across Vite prebundling requests
|
|
98
104
|
const prebundleTransformer = new internal_1.JavaScriptTransformer(
|
|
99
105
|
// Always enable JIT linking to support applications built with and without AOT.
|
|
@@ -115,7 +121,8 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
115
121
|
explicitBrowser: [],
|
|
116
122
|
explicitServer: [],
|
|
117
123
|
};
|
|
118
|
-
const
|
|
124
|
+
const componentStyles = new Map();
|
|
125
|
+
const templateUpdates = new Map();
|
|
119
126
|
// Add cleanup logic via a builder teardown.
|
|
120
127
|
let deferred;
|
|
121
128
|
context.addTeardown(async () => {
|
|
@@ -125,20 +132,30 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
125
132
|
});
|
|
126
133
|
// TODO: Switch this to an architect schedule call when infrastructure settings are supported
|
|
127
134
|
for await (const result of builderAction(browserOptions, context, extensions?.buildPlugins)) {
|
|
135
|
+
if (result.kind === results_1.ResultKind.Failure) {
|
|
136
|
+
if (result.errors.length && server) {
|
|
137
|
+
hadError = true;
|
|
138
|
+
server.ws.send({
|
|
139
|
+
type: 'error',
|
|
140
|
+
err: {
|
|
141
|
+
message: result.errors[0].text,
|
|
142
|
+
stack: '',
|
|
143
|
+
loc: result.errors[0].location ?? undefined,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
// Clear existing error overlay on successful result
|
|
150
|
+
if (hadError && server) {
|
|
151
|
+
hadError = false;
|
|
152
|
+
// Send an empty update to clear the error overlay
|
|
153
|
+
server.ws.send({
|
|
154
|
+
'type': 'update',
|
|
155
|
+
updates: [],
|
|
156
|
+
});
|
|
157
|
+
}
|
|
128
158
|
switch (result.kind) {
|
|
129
|
-
case results_1.ResultKind.Failure:
|
|
130
|
-
if (result.errors.length && server) {
|
|
131
|
-
hadError = true;
|
|
132
|
-
server.ws.send({
|
|
133
|
-
type: 'error',
|
|
134
|
-
err: {
|
|
135
|
-
message: result.errors[0].text,
|
|
136
|
-
stack: '',
|
|
137
|
-
loc: result.errors[0].location ?? undefined,
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
continue;
|
|
142
159
|
case results_1.ResultKind.Full:
|
|
143
160
|
if (result.detail?.['htmlIndexPath']) {
|
|
144
161
|
htmlIndexPath = result.detail['htmlIndexPath'];
|
|
@@ -157,8 +174,10 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
157
174
|
assetFiles.set('/' + normalizePath(outputPath), normalizePath(file.inputPath));
|
|
158
175
|
}
|
|
159
176
|
}
|
|
177
|
+
// Clear stale template updates on code rebuilds
|
|
178
|
+
templateUpdates.clear();
|
|
160
179
|
// Analyze result files for changes
|
|
161
|
-
analyzeResultFiles(normalizePath, htmlIndexPath, result.files, generatedFiles);
|
|
180
|
+
analyzeResultFiles(normalizePath, htmlIndexPath, result.files, generatedFiles, componentStyles);
|
|
162
181
|
break;
|
|
163
182
|
case results_1.ResultKind.Incremental:
|
|
164
183
|
(0, node_assert_1.default)(server, 'Builder must provide an initial full build before incremental results.');
|
|
@@ -166,21 +185,22 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
166
185
|
break;
|
|
167
186
|
case results_1.ResultKind.ComponentUpdate:
|
|
168
187
|
(0, node_assert_1.default)(serverOptions.hmr, 'Component updates are only supported with HMR enabled.');
|
|
169
|
-
|
|
170
|
-
|
|
188
|
+
(0, node_assert_1.default)(server, 'Builder must provide an initial full build before component update results.');
|
|
189
|
+
for (const componentUpdate of result.updates) {
|
|
190
|
+
if (componentUpdate.type === 'template') {
|
|
191
|
+
templateUpdates.set(componentUpdate.id, componentUpdate.content);
|
|
192
|
+
server.ws.send('angular:component-update', {
|
|
193
|
+
id: componentUpdate.id,
|
|
194
|
+
timestamp: Date.now(),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
context.logger.info('Component update sent to client(s).');
|
|
199
|
+
continue;
|
|
171
200
|
default:
|
|
172
201
|
context.logger.warn(`Unknown result kind [${result.kind}] provided by build.`);
|
|
173
202
|
continue;
|
|
174
203
|
}
|
|
175
|
-
// Clear existing error overlay on successful result
|
|
176
|
-
if (hadError && server) {
|
|
177
|
-
hadError = false;
|
|
178
|
-
// Send an empty update to clear the error overlay
|
|
179
|
-
server.ws.send({
|
|
180
|
-
'type': 'update',
|
|
181
|
-
updates: [],
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
204
|
// To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
|
|
185
205
|
let requiresServerRestart = false;
|
|
186
206
|
if (result.detail?.['externalMetadata']) {
|
|
@@ -220,7 +240,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
220
240
|
await server.restart();
|
|
221
241
|
}
|
|
222
242
|
else {
|
|
223
|
-
await handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger,
|
|
243
|
+
await handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger, componentStyles);
|
|
224
244
|
}
|
|
225
245
|
}
|
|
226
246
|
else {
|
|
@@ -249,11 +269,17 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
249
269
|
browserOptions.ssr.entry) {
|
|
250
270
|
ssrMode = plugins_1.ServerSsrMode.ExternalSsrMiddleware;
|
|
251
271
|
}
|
|
252
|
-
else if (browserOptions.
|
|
272
|
+
else if (browserOptions.ssr) {
|
|
253
273
|
ssrMode = plugins_1.ServerSsrMode.InternalSsrMiddleware;
|
|
254
274
|
}
|
|
275
|
+
if (browserOptions.progress !== false && ssrMode !== plugins_1.ServerSsrMode.NoSsr) {
|
|
276
|
+
// This is a workaround for https://github.com/angular/angular-cli/issues/28336, which is caused by the interaction between `zone.js` and `listr2`.
|
|
277
|
+
process.once('SIGINT', () => {
|
|
278
|
+
process.kill(process.pid);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
255
281
|
// Setup server and start listening
|
|
256
|
-
const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills),
|
|
282
|
+
const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), componentStyles, templateUpdates, browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
|
|
257
283
|
server = await createServer(serverConfiguration);
|
|
258
284
|
await server.listen();
|
|
259
285
|
const urls = server.resolvedUrls;
|
|
@@ -269,6 +295,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
269
295
|
key: 'r',
|
|
270
296
|
description: 'force reload browser',
|
|
271
297
|
action(server) {
|
|
298
|
+
componentStyles.forEach((record) => record.used?.clear());
|
|
272
299
|
server.ws.send({
|
|
273
300
|
type: 'full-reload',
|
|
274
301
|
path: '*',
|
|
@@ -287,7 +314,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
287
314
|
}
|
|
288
315
|
await new Promise((resolve) => (deferred = resolve));
|
|
289
316
|
}
|
|
290
|
-
async function handleUpdate(normalizePath, generatedFiles, server, serverOptions, logger,
|
|
317
|
+
async function handleUpdate(normalizePath, generatedFiles, server, serverOptions, logger, componentStyles) {
|
|
291
318
|
const updatedFiles = [];
|
|
292
319
|
let destroyAngularServerAppCalled = false;
|
|
293
320
|
// Invalidate any updated files
|
|
@@ -309,40 +336,54 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
|
|
|
309
336
|
if (!updatedFiles.length) {
|
|
310
337
|
return;
|
|
311
338
|
}
|
|
312
|
-
if (serverOptions.
|
|
339
|
+
if (serverOptions.hmr) {
|
|
313
340
|
if (updatedFiles.every((f) => f.endsWith('.css'))) {
|
|
341
|
+
let requiresReload = false;
|
|
314
342
|
const timestamp = Date.now();
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
343
|
+
const updates = updatedFiles.flatMap((filePath) => {
|
|
344
|
+
// For component styles, an HMR update must be sent for each one with the corresponding
|
|
345
|
+
// component identifier search parameter (`ngcomp`). The Vite client code will not keep
|
|
346
|
+
// the existing search parameters when it performs an update and each one must be
|
|
347
|
+
// specified explicitly. Typically, there is only one each though as specific style files
|
|
348
|
+
// are not typically reused across components.
|
|
349
|
+
const record = componentStyles.get(filePath);
|
|
350
|
+
if (record) {
|
|
351
|
+
if (record.reload) {
|
|
352
|
+
// Shadow DOM components currently require a full reload.
|
|
353
|
+
// Vite's CSS hot replacement does not support shadow root searching.
|
|
354
|
+
requiresReload = true;
|
|
355
|
+
return [];
|
|
356
|
+
}
|
|
357
|
+
return Array.from(record.used ?? []).map((id) => {
|
|
358
|
+
return {
|
|
326
359
|
type: 'css-update',
|
|
327
360
|
timestamp,
|
|
328
|
-
path: `${filePath}?ngcomp` + (id ? `=${id}` : ''),
|
|
361
|
+
path: `${filePath}?ngcomp` + (typeof id === 'string' ? `=${id}` : ''),
|
|
329
362
|
acceptedPath: filePath,
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
type: 'css-update',
|
|
368
|
+
timestamp,
|
|
369
|
+
path: filePath,
|
|
370
|
+
acceptedPath: filePath,
|
|
371
|
+
};
|
|
339
372
|
});
|
|
340
|
-
|
|
341
|
-
|
|
373
|
+
if (!requiresReload) {
|
|
374
|
+
server.ws.send({
|
|
375
|
+
type: 'update',
|
|
376
|
+
updates,
|
|
377
|
+
});
|
|
378
|
+
logger.info('HMR update sent to client(s).');
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
342
381
|
}
|
|
343
382
|
}
|
|
344
383
|
// Send reload command to clients
|
|
345
384
|
if (serverOptions.liveReload) {
|
|
385
|
+
// Clear used component tracking on full reload
|
|
386
|
+
componentStyles.forEach((record) => record.used?.clear());
|
|
346
387
|
server.ws.send({
|
|
347
388
|
type: 'full-reload',
|
|
348
389
|
path: '*',
|
|
@@ -350,7 +391,7 @@ async function handleUpdate(normalizePath, generatedFiles, server, serverOptions
|
|
|
350
391
|
logger.info('Page reload sent to client(s).');
|
|
351
392
|
}
|
|
352
393
|
}
|
|
353
|
-
function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generatedFiles) {
|
|
394
|
+
function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generatedFiles, componentStyles) {
|
|
354
395
|
const seen = new Set(['/index.html']);
|
|
355
396
|
for (const [outputPath, file] of Object.entries(resultFiles)) {
|
|
356
397
|
if (file.origin === 'disk') {
|
|
@@ -373,6 +414,7 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
|
|
|
373
414
|
contents: file.contents,
|
|
374
415
|
servable,
|
|
375
416
|
size: file.contents.byteLength,
|
|
417
|
+
hash: file.hash,
|
|
376
418
|
type: file.type,
|
|
377
419
|
updated: false,
|
|
378
420
|
});
|
|
@@ -395,15 +437,28 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
|
|
|
395
437
|
type: file.type,
|
|
396
438
|
servable,
|
|
397
439
|
});
|
|
440
|
+
// Record any external component styles
|
|
441
|
+
if (filePath.endsWith('.css') && /^\/[a-f0-9]{64}\.css$/.test(filePath)) {
|
|
442
|
+
const componentStyle = componentStyles.get(filePath);
|
|
443
|
+
if (componentStyle) {
|
|
444
|
+
componentStyle.rawContent = file.contents;
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
componentStyles.set(filePath, {
|
|
448
|
+
rawContent: file.contents,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
}
|
|
398
452
|
}
|
|
399
453
|
// Clear stale output files
|
|
400
454
|
for (const file of generatedFiles.keys()) {
|
|
401
455
|
if (!seen.has(file)) {
|
|
402
456
|
generatedFiles.delete(file);
|
|
457
|
+
componentStyles.delete(file);
|
|
403
458
|
}
|
|
404
459
|
}
|
|
405
460
|
}
|
|
406
|
-
async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless,
|
|
461
|
+
async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless, componentStyles, templateUpdates, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
|
|
407
462
|
const proxy = await (0, utils_1.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
|
|
408
463
|
// dynamically import Vite for ESM compatibility
|
|
409
464
|
const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
|
|
@@ -446,6 +501,8 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
446
501
|
host: serverOptions.host,
|
|
447
502
|
open: serverOptions.open,
|
|
448
503
|
headers: serverOptions.headers,
|
|
504
|
+
// Disable the websocket if live reload is disabled (false/undefined are the only valid values)
|
|
505
|
+
ws: serverOptions.liveReload === false && serverOptions.hmr === false ? false : undefined,
|
|
449
506
|
proxy,
|
|
450
507
|
cors: {
|
|
451
508
|
// Allow preflight requests to be proxied.
|
|
@@ -495,7 +552,8 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
495
552
|
assets,
|
|
496
553
|
indexHtmlTransformer,
|
|
497
554
|
extensionMiddleware,
|
|
498
|
-
|
|
555
|
+
componentStyles,
|
|
556
|
+
templateUpdates,
|
|
499
557
|
ssrMode,
|
|
500
558
|
}),
|
|
501
559
|
(0, plugins_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser),
|
|
@@ -504,6 +562,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
504
562
|
virtualProjectRoot,
|
|
505
563
|
outputFiles,
|
|
506
564
|
external: externalMetadata.explicitBrowser,
|
|
565
|
+
skipViteClient: serverOptions.liveReload === false && serverOptions.hmr === false,
|
|
507
566
|
}),
|
|
508
567
|
],
|
|
509
568
|
// Browser only optimizeDeps. (This does not run for SSR dependencies).
|
|
@@ -568,6 +627,9 @@ function getDepOptimizationConfig({ disabled, exclude, include, target, zoneless
|
|
|
568
627
|
supported: (0, internal_1.getFeatureSupport)(target, zoneless),
|
|
569
628
|
plugins,
|
|
570
629
|
loader,
|
|
630
|
+
define: {
|
|
631
|
+
'ngServerMode': `${ssr}`,
|
|
632
|
+
},
|
|
571
633
|
resolveExtensions: ['.mjs', '.js', '.cjs'],
|
|
572
634
|
},
|
|
573
635
|
};
|
package/src/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
8
|
export { buildApplication, type ApplicationBuilderOptions, type ApplicationBuilderOutput, } from './builders/application';
|
|
9
|
+
export type { ApplicationBuilderExtensions } from './builders/application/options';
|
|
9
10
|
export { type BuildOutputFile, BuildOutputFileType } from './tools/esbuild/bundler-context';
|
|
10
11
|
export type { BuildOutputAsset } from './tools/esbuild/bundler-execution-result';
|
|
11
12
|
export { executeDevServerBuilder, type DevServerBuilderOptions, type DevServerBuilderOutput, } from './builders/dev-server';
|
|
@@ -14,7 +14,7 @@ export interface AngularHostOptions {
|
|
|
14
14
|
sourceFileCache?: Map<string, ts.SourceFile>;
|
|
15
15
|
modifiedFiles?: Set<string>;
|
|
16
16
|
externalStylesheets?: Map<string, string>;
|
|
17
|
-
transformStylesheet(data: string, containingFile: string, stylesheetFile?: string, order?: number): Promise<string | null>;
|
|
17
|
+
transformStylesheet(data: string, containingFile: string, stylesheetFile?: string, order?: number, className?: string): Promise<string | null>;
|
|
18
18
|
processWebWorker(workerFile: string, containingFile: string): string;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
@@ -113,16 +113,14 @@ function createAngularCompilerHost(typescript, compilerOptions, hostOptions) {
|
|
|
113
113
|
if (data.trim().length === 0) {
|
|
114
114
|
return { content: '' };
|
|
115
115
|
}
|
|
116
|
-
const result = await hostOptions.transformStylesheet(data, context.containingFile, context.resourceFile ?? undefined,
|
|
117
|
-
// TODO: Remove once available in compiler-cli types
|
|
118
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
119
|
-
context.order);
|
|
116
|
+
const result = await hostOptions.transformStylesheet(data, context.containingFile, context.resourceFile ?? undefined, context.order, context.className);
|
|
120
117
|
return typeof result === 'string' ? { content: result } : null;
|
|
121
118
|
};
|
|
122
119
|
host.resourceNameToFileName = function (resourceName, containingFile) {
|
|
123
120
|
const resolvedPath = node_path_1.default.join(node_path_1.default.dirname(containingFile), resourceName);
|
|
124
|
-
// All resource names that have
|
|
125
|
-
|
|
121
|
+
// All resource names that have template file extensions are assumed to be templates
|
|
122
|
+
// TODO: Update compiler to provide the resource type to avoid extension matching here.
|
|
123
|
+
if (!hostOptions.externalStylesheets || hasTemplateExtension(resolvedPath)) {
|
|
126
124
|
return resolvedPath;
|
|
127
125
|
}
|
|
128
126
|
// For external stylesheets, create a unique identifier and store the mapping
|
|
@@ -150,3 +148,13 @@ function createAngularCompilerHost(typescript, compilerOptions, hostOptions) {
|
|
|
150
148
|
}
|
|
151
149
|
return host;
|
|
152
150
|
}
|
|
151
|
+
function hasTemplateExtension(file) {
|
|
152
|
+
const extension = node_path_1.default.extname(file).toLowerCase();
|
|
153
|
+
switch (extension) {
|
|
154
|
+
case '.htm':
|
|
155
|
+
case '.html':
|
|
156
|
+
case '.svg':
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
@@ -31,6 +31,7 @@ export declare abstract class AngularCompilation {
|
|
|
31
31
|
compilerOptions: ng.CompilerOptions;
|
|
32
32
|
referencedFiles: readonly string[];
|
|
33
33
|
externalStylesheets?: ReadonlyMap<string, string>;
|
|
34
|
+
templateUpdates?: ReadonlyMap<string, string>;
|
|
34
35
|
}>;
|
|
35
36
|
abstract emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>>;
|
|
36
37
|
protected abstract collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;
|
|
@@ -16,6 +16,7 @@ export declare class AotCompilation extends AngularCompilation {
|
|
|
16
16
|
compilerOptions: ng.CompilerOptions;
|
|
17
17
|
referencedFiles: readonly string[];
|
|
18
18
|
externalStylesheets?: ReadonlyMap<string, string>;
|
|
19
|
+
templateUpdates?: ReadonlyMap<string, string>;
|
|
19
20
|
}>;
|
|
20
21
|
collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic>;
|
|
21
22
|
emitAffectedFiles(): Iterable<EmitFileResult>;
|
|
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.AotCompilation = void 0;
|
|
14
14
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
15
16
|
const typescript_1 = __importDefault(require("typescript"));
|
|
16
17
|
const profiling_1 = require("../../esbuild/profiling");
|
|
17
18
|
const angular_host_1 = require("../angular-host");
|
|
@@ -65,6 +66,33 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
|
|
|
65
66
|
}
|
|
66
67
|
const typeScriptProgram = typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram(angularTypeScriptProgram, host, oldProgram, configurationDiagnostics);
|
|
67
68
|
await (0, profiling_1.profileAsync)('NG_ANALYZE_PROGRAM', () => angularCompiler.analyzeAsync());
|
|
69
|
+
let templateUpdates;
|
|
70
|
+
if (compilerOptions['_enableHmr'] &&
|
|
71
|
+
hostOptions.modifiedFiles &&
|
|
72
|
+
hasOnlyTemplates(hostOptions.modifiedFiles)) {
|
|
73
|
+
const componentNodes = [...hostOptions.modifiedFiles].flatMap((file) => [
|
|
74
|
+
...angularCompiler.getComponentsWithTemplateFile(file),
|
|
75
|
+
]);
|
|
76
|
+
for (const node of componentNodes) {
|
|
77
|
+
if (!typescript_1.default.isClassDeclaration(node)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const componentFilename = node.getSourceFile().fileName;
|
|
81
|
+
let relativePath = (0, node_path_1.relative)(host.getCurrentDirectory(), componentFilename);
|
|
82
|
+
if (relativePath.startsWith('..')) {
|
|
83
|
+
relativePath = componentFilename;
|
|
84
|
+
}
|
|
85
|
+
const updateId = encodeURIComponent(`${host.getCanonicalFileName(relativePath)}@${node.name?.text}`);
|
|
86
|
+
const updateText = angularCompiler.emitHmrUpdateModule(node);
|
|
87
|
+
if (updateText === null) {
|
|
88
|
+
// Build is needed if a template cannot be updated
|
|
89
|
+
templateUpdates = undefined;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
templateUpdates ??= new Map();
|
|
93
|
+
templateUpdates.set(updateId, updateText);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
68
96
|
const affectedFiles = (0, profiling_1.profileSync)('NG_FIND_AFFECTED', () => findAffectedFiles(typeScriptProgram, angularCompiler, usingBuildInfo));
|
|
69
97
|
// Get all files referenced in the TypeScript/Angular program including component resources
|
|
70
98
|
const referencedFiles = typeScriptProgram
|
|
@@ -90,6 +118,7 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
|
|
|
90
118
|
compilerOptions,
|
|
91
119
|
referencedFiles,
|
|
92
120
|
externalStylesheets: hostOptions.externalStylesheets,
|
|
121
|
+
templateUpdates,
|
|
93
122
|
};
|
|
94
123
|
}
|
|
95
124
|
*collectDiagnostics(modes) {
|
|
@@ -267,3 +296,13 @@ function findAffectedFiles(builder, { ignoreForDiagnostics }, includeTTC) {
|
|
|
267
296
|
}
|
|
268
297
|
return affectedFiles;
|
|
269
298
|
}
|
|
299
|
+
function hasOnlyTemplates(modifiedFiles) {
|
|
300
|
+
for (const file of modifiedFiles) {
|
|
301
|
+
const lowerFile = file.toLowerCase();
|
|
302
|
+
if (lowerFile.endsWith('.html') || lowerFile.endsWith('.svg')) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
@@ -37,9 +37,9 @@ class ParallelCompilation extends angular_compilation_1.AngularCompilation {
|
|
|
37
37
|
initialize(tsconfig, hostOptions, compilerOptionsTransformer) {
|
|
38
38
|
const stylesheetChannel = new node_worker_threads_1.MessageChannel();
|
|
39
39
|
// The request identifier is required because Angular can issue multiple concurrent requests
|
|
40
|
-
stylesheetChannel.port1.on('message', ({ requestId, data, containingFile, stylesheetFile }) => {
|
|
40
|
+
stylesheetChannel.port1.on('message', ({ requestId, data, containingFile, stylesheetFile, order, className }) => {
|
|
41
41
|
hostOptions
|
|
42
|
-
.transformStylesheet(data, containingFile, stylesheetFile)
|
|
42
|
+
.transformStylesheet(data, containingFile, stylesheetFile, order, className)
|
|
43
43
|
.then((value) => stylesheetChannel.port1.postMessage({ requestId, value }))
|
|
44
44
|
.catch((error) => stylesheetChannel.port1.postMessage({ requestId, error }));
|
|
45
45
|
});
|
|
@@ -20,6 +20,7 @@ export interface InitRequest {
|
|
|
20
20
|
}
|
|
21
21
|
export declare function initialize(request: InitRequest): Promise<{
|
|
22
22
|
externalStylesheets: ReadonlyMap<string, string> | undefined;
|
|
23
|
+
templateUpdates: ReadonlyMap<string, string> | undefined;
|
|
23
24
|
referencedFiles: readonly string[];
|
|
24
25
|
compilerOptions: {
|
|
25
26
|
allowJs: boolean | undefined;
|
|
@@ -33,11 +33,11 @@ async function initialize(request) {
|
|
|
33
33
|
stylesheetRequests.get(requestId)?.[0](value);
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
-
const { compilerOptions, referencedFiles, externalStylesheets } = await compilation.initialize(request.tsconfig, {
|
|
36
|
+
const { compilerOptions, referencedFiles, externalStylesheets, templateUpdates } = await compilation.initialize(request.tsconfig, {
|
|
37
37
|
fileReplacements: request.fileReplacements,
|
|
38
38
|
sourceFileCache,
|
|
39
39
|
modifiedFiles: sourceFileCache.modifiedFiles,
|
|
40
|
-
transformStylesheet(data, containingFile, stylesheetFile) {
|
|
40
|
+
transformStylesheet(data, containingFile, stylesheetFile, order, className) {
|
|
41
41
|
const requestId = (0, node_crypto_1.randomUUID)();
|
|
42
42
|
const resultPromise = new Promise((resolve, reject) => stylesheetRequests.set(requestId, [resolve, reject]));
|
|
43
43
|
request.stylesheetPort.postMessage({
|
|
@@ -45,6 +45,8 @@ async function initialize(request) {
|
|
|
45
45
|
data,
|
|
46
46
|
containingFile,
|
|
47
47
|
stylesheetFile,
|
|
48
|
+
order,
|
|
49
|
+
className,
|
|
48
50
|
});
|
|
49
51
|
return resultPromise;
|
|
50
52
|
},
|
|
@@ -70,6 +72,7 @@ async function initialize(request) {
|
|
|
70
72
|
});
|
|
71
73
|
return {
|
|
72
74
|
externalStylesheets,
|
|
75
|
+
templateUpdates,
|
|
73
76
|
referencedFiles,
|
|
74
77
|
// TODO: Expand? `allowJs`, `isolatedModules`, `sourceMap`, `inlineSourceMap` are the only fields needed currently.
|
|
75
78
|
compilerOptions: {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Plugin } from 'esbuild';
|
|
9
9
|
import { LoadResultCache } from '../load-result-cache';
|
|
10
|
-
import {
|
|
10
|
+
import { ComponentStylesheetBundler } from './component-stylesheets';
|
|
11
11
|
import { SourceFileCache } from './source-file-cache';
|
|
12
12
|
export interface CompilerPluginOptions {
|
|
13
13
|
sourcemap: boolean | 'external';
|
|
@@ -23,7 +23,6 @@ export interface CompilerPluginOptions {
|
|
|
23
23
|
incremental: boolean;
|
|
24
24
|
externalRuntimeStyles?: boolean;
|
|
25
25
|
instrumentForCoverage?: (request: string) => boolean;
|
|
26
|
+
templateUpdates?: Map<string, string>;
|
|
26
27
|
}
|
|
27
|
-
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions,
|
|
28
|
-
inlineStyleLanguage: string;
|
|
29
|
-
}): Plugin;
|
|
28
|
+
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, stylesheetBundler: ComponentStylesheetBundler): Plugin;
|