@angular/build 19.2.0-next.0 → 19.2.0-next.2
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 +1 -1
- package/package.json +12 -13
- package/src/builders/application/build-action.js +35 -3
- package/src/builders/application/execute-build.js +1 -1
- package/src/builders/application/execute-post-bundle.js +2 -2
- package/src/builders/application/i18n.js +6 -3
- package/src/builders/dev-server/options.d.ts +1 -0
- package/src/builders/dev-server/options.js +2 -1
- package/src/builders/dev-server/schema.d.ts +12 -0
- package/src/builders/dev-server/schema.json +17 -0
- package/src/builders/dev-server/vite-server.js +60 -5
- package/src/tools/angular/compilation/angular-compilation.js +3 -0
- package/src/tools/angular/compilation/hmr-candidates.js +43 -12
- package/src/tools/babel/plugins/add-code-coverage.js +8 -1
- package/src/tools/esbuild/angular/compiler-plugin.js +7 -2
- package/src/tools/esbuild/i18n-inliner.d.ts +9 -0
- package/src/tools/esbuild/i18n-inliner.js +110 -21
- package/src/tools/esbuild/stylesheets/less-language.js +16 -4
- package/src/tools/esbuild/stylesheets/sass-language.js +2 -5
- package/src/tools/esbuild/utils.js +4 -1
- package/src/tools/vite/plugins/angular-memory-plugin.d.ts +1 -1
- package/src/tools/vite/plugins/angular-memory-plugin.js +11 -16
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/server-rendering/manifest.js +7 -2
- package/src/utils/server-rendering/prerender.js +3 -3
- package/src/utils/service-worker.d.ts +1 -1
- package/src/utils/service-worker.js +3 -7
- package/src/utils/version.js +10 -31
package/README.md
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
|
|
3
3
|
The sources for this package are in the [Angular CLI](https://github.com/angular/angular-cli) repository. Please file issues and pull requests against that repository.
|
|
4
4
|
|
|
5
|
-
Usage information and reference details can be found in repository [README](
|
|
5
|
+
Usage information and reference details can be found in repository [README](https://github.com/angular/angular-cli/blob/main/README.md) file.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/build",
|
|
3
|
-
"version": "19.2.0-next.
|
|
3
|
+
"version": "19.2.0-next.2",
|
|
4
4
|
"description": "Official build system for Angular",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Angular CLI",
|
|
@@ -23,17 +23,16 @@
|
|
|
23
23
|
"builders": "builders.json",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ampproject/remapping": "2.3.0",
|
|
26
|
-
"@angular-devkit/
|
|
27
|
-
"@
|
|
28
|
-
"@babel/core": "7.26.0",
|
|
26
|
+
"@angular-devkit/architect": "0.1902.0-next.2",
|
|
27
|
+
"@babel/core": "7.26.8",
|
|
29
28
|
"@babel/helper-annotate-as-pure": "7.25.9",
|
|
30
29
|
"@babel/helper-split-export-declaration": "7.24.7",
|
|
31
30
|
"@babel/plugin-syntax-import-attributes": "7.26.0",
|
|
32
|
-
"@inquirer/confirm": "5.1.
|
|
31
|
+
"@inquirer/confirm": "5.1.5",
|
|
33
32
|
"@vitejs/plugin-basic-ssl": "1.2.0",
|
|
34
33
|
"beasties": "0.2.0",
|
|
35
34
|
"browserslist": "^4.23.0",
|
|
36
|
-
"esbuild": "0.
|
|
35
|
+
"esbuild": "0.25.0",
|
|
37
36
|
"fast-glob": "3.3.3",
|
|
38
37
|
"https-proxy-agent": "7.0.6",
|
|
39
38
|
"istanbul-lib-instrument": "6.0.3",
|
|
@@ -43,14 +42,14 @@
|
|
|
43
42
|
"parse5-html-rewriting-stream": "7.0.0",
|
|
44
43
|
"picomatch": "4.0.2",
|
|
45
44
|
"piscina": "4.8.0",
|
|
46
|
-
"rollup": "4.
|
|
47
|
-
"sass": "1.
|
|
48
|
-
"semver": "7.
|
|
49
|
-
"vite": "6.0
|
|
45
|
+
"rollup": "4.34.6",
|
|
46
|
+
"sass": "1.84.0",
|
|
47
|
+
"semver": "7.7.1",
|
|
48
|
+
"vite": "6.1.0",
|
|
50
49
|
"watchpack": "2.4.2"
|
|
51
50
|
},
|
|
52
51
|
"optionalDependencies": {
|
|
53
|
-
"lmdb": "3.2.
|
|
52
|
+
"lmdb": "3.2.6"
|
|
54
53
|
},
|
|
55
54
|
"peerDependencies": {
|
|
56
55
|
"@angular/compiler": "^19.0.0 || ^19.2.0-next.0",
|
|
@@ -58,11 +57,11 @@
|
|
|
58
57
|
"@angular/localize": "^19.0.0 || ^19.2.0-next.0",
|
|
59
58
|
"@angular/platform-server": "^19.0.0 || ^19.2.0-next.0",
|
|
60
59
|
"@angular/service-worker": "^19.0.0 || ^19.2.0-next.0",
|
|
61
|
-
"@angular/ssr": "^19.2.0-next.
|
|
60
|
+
"@angular/ssr": "^19.2.0-next.2",
|
|
62
61
|
"less": "^4.2.0",
|
|
63
62
|
"ng-packagr": "^19.0.0 || ^19.2.0-next.0",
|
|
64
63
|
"postcss": "^8.4.0",
|
|
65
|
-
"tailwindcss": "^2.0.0 || ^3.0.0",
|
|
64
|
+
"tailwindcss": "^2.0.0 || ^3.0.0 || ^4.0.0",
|
|
66
65
|
"typescript": ">=5.5 <5.8"
|
|
67
66
|
},
|
|
68
67
|
"peerDependenciesMeta": {
|
|
@@ -127,6 +127,9 @@ async function* runEsBuildBuildAction(action, options) {
|
|
|
127
127
|
if (!watcher) {
|
|
128
128
|
return;
|
|
129
129
|
}
|
|
130
|
+
// Used to force a full result on next rebuild if there were initial errors.
|
|
131
|
+
// This ensures at least one full result is emitted.
|
|
132
|
+
let hasInitialErrors = result.errors.length > 0;
|
|
130
133
|
// Wait for changes and rebuild as needed
|
|
131
134
|
const currentWatchFiles = new Set(result.watchFiles);
|
|
132
135
|
try {
|
|
@@ -164,9 +167,11 @@ async function* runEsBuildBuildAction(action, options) {
|
|
|
164
167
|
if (staleWatchFiles?.size) {
|
|
165
168
|
watcher.remove([...staleWatchFiles]);
|
|
166
169
|
}
|
|
167
|
-
for (const outputResult of emitOutputResults(result, outputOptions, changes, incrementalResults ? rebuildState : undefined)) {
|
|
170
|
+
for (const outputResult of emitOutputResults(result, outputOptions, changes, incrementalResults && !hasInitialErrors ? rebuildState : undefined)) {
|
|
168
171
|
yield outputResult;
|
|
169
172
|
}
|
|
173
|
+
// Clear initial build errors flag if no errors are now present
|
|
174
|
+
hasInitialErrors &&= result.errors.length > 0;
|
|
170
175
|
}
|
|
171
176
|
}
|
|
172
177
|
finally {
|
|
@@ -239,6 +244,7 @@ function* emitOutputResults({ outputFiles, assetFiles, errors, warnings, externa
|
|
|
239
244
|
outputOptions,
|
|
240
245
|
},
|
|
241
246
|
};
|
|
247
|
+
let hasCssUpdates = false;
|
|
242
248
|
// Initially assume all previous output files have been removed
|
|
243
249
|
const removedOutputFiles = new Map(previousOutputInfo);
|
|
244
250
|
for (const file of outputFiles) {
|
|
@@ -254,8 +260,11 @@ function* emitOutputResults({ outputFiles, assetFiles, errors, warnings, externa
|
|
|
254
260
|
incrementalResult.modified.push(file.path);
|
|
255
261
|
}
|
|
256
262
|
if (needFile) {
|
|
257
|
-
|
|
258
|
-
|
|
263
|
+
if (file.path.endsWith('.css')) {
|
|
264
|
+
hasCssUpdates = true;
|
|
265
|
+
}
|
|
266
|
+
else if (!/(?:\.m?js|\.map)$/.test(file.path)) {
|
|
267
|
+
// Updates to non-JS files must signal an update with the dev server
|
|
259
268
|
incrementalResult.background = false;
|
|
260
269
|
}
|
|
261
270
|
incrementalResult.files[file.path] = {
|
|
@@ -281,12 +290,21 @@ function* emitOutputResults({ outputFiles, assetFiles, errors, warnings, externa
|
|
|
281
290
|
else {
|
|
282
291
|
continue;
|
|
283
292
|
}
|
|
293
|
+
hasCssUpdates ||= destination.endsWith('.css');
|
|
284
294
|
incrementalResult.files[destination] = {
|
|
285
295
|
type: bundler_context_1.BuildOutputFileType.Browser,
|
|
286
296
|
inputPath: source,
|
|
287
297
|
origin: 'disk',
|
|
288
298
|
};
|
|
289
299
|
}
|
|
300
|
+
// Do not remove stale files yet if there are template updates.
|
|
301
|
+
// Component chunk files may still be referenced in running browser code.
|
|
302
|
+
// Module evaluation time component updates will update any of these files.
|
|
303
|
+
// This typically occurs when a lazy component is changed that has not yet
|
|
304
|
+
// been accessed at runtime.
|
|
305
|
+
if (hasTemplateUpdates && incrementalResult.background) {
|
|
306
|
+
removedOutputFiles.clear();
|
|
307
|
+
}
|
|
290
308
|
// Include the removed output and asset files
|
|
291
309
|
incrementalResult.removed.push(...Array.from(removedOutputFiles, ([file, { type }]) => ({
|
|
292
310
|
path: file,
|
|
@@ -299,6 +317,17 @@ function* emitOutputResults({ outputFiles, assetFiles, errors, warnings, externa
|
|
|
299
317
|
// If there are template updates and the incremental update was background only, a component
|
|
300
318
|
// update is possible.
|
|
301
319
|
if (hasTemplateUpdates && incrementalResult.background) {
|
|
320
|
+
// Template changes may be accompanied by stylesheet changes and these should also be updated hot when possible.
|
|
321
|
+
if (hasCssUpdates) {
|
|
322
|
+
const styleResult = {
|
|
323
|
+
kind: results_1.ResultKind.Incremental,
|
|
324
|
+
added: incrementalResult.added.filter(isCssFilePath),
|
|
325
|
+
removed: incrementalResult.removed.filter(({ path }) => isCssFilePath(path)),
|
|
326
|
+
modified: incrementalResult.modified.filter(isCssFilePath),
|
|
327
|
+
files: Object.fromEntries(Object.entries(incrementalResult.files).filter(([path]) => isCssFilePath(path))),
|
|
328
|
+
};
|
|
329
|
+
yield styleResult;
|
|
330
|
+
}
|
|
302
331
|
const updateResult = {
|
|
303
332
|
kind: results_1.ResultKind.ComponentUpdate,
|
|
304
333
|
updates: Array.from(templateUpdates, ([id, content]) => ({
|
|
@@ -310,3 +339,6 @@ function* emitOutputResults({ outputFiles, assetFiles, errors, warnings, externa
|
|
|
310
339
|
yield updateResult;
|
|
311
340
|
}
|
|
312
341
|
}
|
|
342
|
+
function isCssFilePath(filePath) {
|
|
343
|
+
return /\.css(?:\.map)?$/i.test(filePath);
|
|
344
|
+
}
|
|
@@ -148,7 +148,6 @@ async function executeBuild(options, context, rebuildState) {
|
|
|
148
148
|
}
|
|
149
149
|
const { metafile, initialFiles, outputFiles } = bundlingResult;
|
|
150
150
|
executionResult.outputFiles.push(...outputFiles);
|
|
151
|
-
const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo);
|
|
152
151
|
// Analyze files for bundle budget failures if present
|
|
153
152
|
let budgetFailures;
|
|
154
153
|
if (options.budgets) {
|
|
@@ -218,6 +217,7 @@ async function executeBuild(options, context, rebuildState) {
|
|
|
218
217
|
executionResult.addOutputFile('stats.json', JSON.stringify(metafile, null, 2), bundler_context_1.BuildOutputFileType.Root);
|
|
219
218
|
}
|
|
220
219
|
if (!jsonLogs) {
|
|
220
|
+
const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputInfo);
|
|
221
221
|
executionResult.addLog((0, utils_1.logBuildStats)(metafile, outputFiles, initialFiles, budgetFailures, colors, changedFiles, estimatedTransferSizes, !!ssrOptions, verbose));
|
|
222
222
|
}
|
|
223
223
|
return executionResult;
|
|
@@ -38,7 +38,7 @@ async function executePostBundleSteps(metafile, options, outputFiles, assetFiles
|
|
|
38
38
|
const allErrors = [];
|
|
39
39
|
const allWarnings = [];
|
|
40
40
|
const prerenderedRoutes = {};
|
|
41
|
-
const { baseHref = '/', serviceWorker,
|
|
41
|
+
const { baseHref = '/', serviceWorker, ssrOptions, indexHtmlOptions, optimizationOptions, sourcemapOptions, outputMode, serverEntryPoint, prerenderOptions, appShellOptions, publicPath, workspaceRoot, partialSSRBuild, } = options;
|
|
42
42
|
// Index HTML content without CSS inlining to be used for server rendering (AppShell, SSG and SSR).
|
|
43
43
|
// NOTE: Critical CSS inlining is deliberately omitted here, as it will be handled during server rendering.
|
|
44
44
|
// Additionally, when using prerendering or AppShell, the index HTML file may be regenerated.
|
|
@@ -57,7 +57,7 @@ async function executePostBundleSteps(metafile, options, outputFiles, assetFiles
|
|
|
57
57
|
}
|
|
58
58
|
// Create server manifest
|
|
59
59
|
const initialFilesPaths = new Set(initialFiles.keys());
|
|
60
|
-
if (serverEntryPoint) {
|
|
60
|
+
if (serverEntryPoint && (outputMode || prerenderOptions || appShellOptions || ssrOptions)) {
|
|
61
61
|
const { manifestContent, serverAssetsChunks } = (0, manifest_1.generateAngularServerAppManifest)(additionalHtmlOutputFiles, outputFiles, optimizationOptions.styles.inlineCritical ?? false, undefined, locale, baseHref, initialFilesPaths, metafile, publicPath);
|
|
62
62
|
additionalOutputFiles.push(...serverAssetsChunks, (0, utils_1.createOutputFile)(manifest_1.SERVER_APP_MANIFEST_FILENAME, manifestContent, bundler_context_1.BuildOutputFileType.ServerApplication));
|
|
63
63
|
}
|
|
@@ -26,12 +26,13 @@ const options_1 = require("./options");
|
|
|
26
26
|
* @param initialFiles A map containing initial file information for the executed build.
|
|
27
27
|
*/
|
|
28
28
|
async function inlineI18n(metafile, options, executionResult, initialFiles) {
|
|
29
|
-
const { i18nOptions, optimizationOptions, baseHref } = options;
|
|
29
|
+
const { i18nOptions, optimizationOptions, baseHref, cacheOptions } = options;
|
|
30
30
|
// Create the multi-threaded inliner with common options and the files generated from the build.
|
|
31
31
|
const inliner = new i18n_inliner_1.I18nInliner({
|
|
32
32
|
missingTranslation: i18nOptions.missingTranslationBehavior ?? 'warning',
|
|
33
33
|
outputFiles: executionResult.outputFiles,
|
|
34
34
|
shouldOptimize: optimizationOptions.scripts,
|
|
35
|
+
persistentCachePath: cacheOptions.enabled ? cacheOptions.path : undefined,
|
|
35
36
|
}, environment_options_1.maxWorkers);
|
|
36
37
|
const inlineResult = {
|
|
37
38
|
errors: [],
|
|
@@ -41,6 +42,8 @@ async function inlineI18n(metafile, options, executionResult, initialFiles) {
|
|
|
41
42
|
// For each active locale, use the inliner to process the output files of the build.
|
|
42
43
|
const updatedOutputFiles = [];
|
|
43
44
|
const updatedAssetFiles = [];
|
|
45
|
+
// Root and SSR entry files are not modified.
|
|
46
|
+
const unModifiedOutputFiles = executionResult.outputFiles.filter(({ type }) => type === bundler_context_1.BuildOutputFileType.Root || type === bundler_context_1.BuildOutputFileType.ServerRoot);
|
|
44
47
|
try {
|
|
45
48
|
for (const locale of i18nOptions.inlineLocales) {
|
|
46
49
|
// A locale specific set of files is returned from the inliner.
|
|
@@ -51,7 +54,7 @@ async function inlineI18n(metafile, options, executionResult, initialFiles) {
|
|
|
51
54
|
const { errors, warnings, additionalAssets, additionalOutputFiles, prerenderedRoutes: generatedRoutes, } = await (0, execute_post_bundle_1.executePostBundleSteps)(metafile, {
|
|
52
55
|
...options,
|
|
53
56
|
baseHref: (0, options_1.getLocaleBaseHref)(baseHref, i18nOptions, locale) ?? baseHref,
|
|
54
|
-
}, localeOutputFiles, executionResult.assetFiles, initialFiles, locale);
|
|
57
|
+
}, [...unModifiedOutputFiles, ...localeOutputFiles], executionResult.assetFiles, initialFiles, locale);
|
|
55
58
|
localeOutputFiles.push(...additionalOutputFiles);
|
|
56
59
|
inlineResult.errors.push(...errors);
|
|
57
60
|
inlineResult.warnings.push(...warnings);
|
|
@@ -81,7 +84,7 @@ async function inlineI18n(metafile, options, executionResult, initialFiles) {
|
|
|
81
84
|
// Update the result with all localized files.
|
|
82
85
|
executionResult.outputFiles = [
|
|
83
86
|
// Root and SSR entry files are not modified.
|
|
84
|
-
...
|
|
87
|
+
...unModifiedOutputFiles,
|
|
85
88
|
// Updated files for each locale.
|
|
86
89
|
...updatedOutputFiles,
|
|
87
90
|
];
|
|
@@ -73,7 +73,7 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
// Initial options to keep
|
|
76
|
-
const { host, port, poll, open, verbose, watch, liveReload, hmr, headers, proxyConfig, servePath, ssl, sslCert, sslKey, prebundle, } = options;
|
|
76
|
+
const { host, port, poll, open, verbose, watch, liveReload, hmr, headers, proxyConfig, servePath, ssl, sslCert, sslKey, prebundle, allowedHosts, } = options;
|
|
77
77
|
// Return all the normalized options
|
|
78
78
|
return {
|
|
79
79
|
buildTarget,
|
|
@@ -97,5 +97,6 @@ async function normalizeOptions(context, projectName, options) {
|
|
|
97
97
|
// Prebundling defaults to true but requires caching to function
|
|
98
98
|
prebundle: cacheOptions.enabled && !optimization.scripts && prebundle,
|
|
99
99
|
inspect,
|
|
100
|
+
allowedHosts: allowedHosts ? allowedHosts : [],
|
|
100
101
|
};
|
|
101
102
|
}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* Dev Server target options for Build Facade.
|
|
3
3
|
*/
|
|
4
4
|
export type Schema = {
|
|
5
|
+
/**
|
|
6
|
+
* The hosts that the development server will respond to. This option sets the Vite option
|
|
7
|
+
* of the same name. For further details:
|
|
8
|
+
* https://vite.dev/config/server-options.html#server-allowedhosts
|
|
9
|
+
*/
|
|
10
|
+
allowedHosts?: AllowedHosts;
|
|
5
11
|
/**
|
|
6
12
|
* A build builder target to serve in the format of `project:target[:configuration]`. You
|
|
7
13
|
* can also pass in more than one configuration name as a comma-separated list. Example:
|
|
@@ -79,6 +85,12 @@ export type Schema = {
|
|
|
79
85
|
*/
|
|
80
86
|
watch?: boolean;
|
|
81
87
|
};
|
|
88
|
+
/**
|
|
89
|
+
* The hosts that the development server will respond to. This option sets the Vite option
|
|
90
|
+
* of the same name. For further details:
|
|
91
|
+
* https://vite.dev/config/server-options.html#server-allowedhosts
|
|
92
|
+
*/
|
|
93
|
+
export type AllowedHosts = string[] | boolean;
|
|
82
94
|
/**
|
|
83
95
|
* Activate debugging inspector. This option only has an effect when 'SSR' or 'SSG' are
|
|
84
96
|
* enabled.
|
|
@@ -36,6 +36,23 @@
|
|
|
36
36
|
"type": "string",
|
|
37
37
|
"description": "SSL certificate to use for serving HTTPS."
|
|
38
38
|
},
|
|
39
|
+
"allowedHosts": {
|
|
40
|
+
"description": "The hosts that the development server will respond to. This option sets the Vite option of the same name. For further details: https://vite.dev/config/server-options.html#server-allowedhosts",
|
|
41
|
+
"default": [],
|
|
42
|
+
"oneOf": [
|
|
43
|
+
{
|
|
44
|
+
"type": "array",
|
|
45
|
+
"description": "A list of hosts that the development server will respond to.",
|
|
46
|
+
"items": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"type": "boolean",
|
|
52
|
+
"description": "Indicates that all hosts are allowed. This is not recommended and a security risk."
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
39
56
|
"headers": {
|
|
40
57
|
"type": "object",
|
|
41
58
|
"description": "Custom HTTP headers to be added to all responses.",
|
|
@@ -161,6 +161,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
161
161
|
},
|
|
162
162
|
});
|
|
163
163
|
}
|
|
164
|
+
yield { baseUrl: '', success: false };
|
|
164
165
|
continue;
|
|
165
166
|
}
|
|
166
167
|
// Clear existing error overlay on successful result
|
|
@@ -304,6 +305,46 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
304
305
|
const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), componentStyles, templateUpdates, browserOptions.loader, browserOptions.define, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
|
|
305
306
|
server = await createServer(serverConfiguration);
|
|
306
307
|
await server.listen();
|
|
308
|
+
// Setup builder context logging for browser clients
|
|
309
|
+
server.hot.on('angular:log', (data) => {
|
|
310
|
+
if (typeof data?.text !== 'string') {
|
|
311
|
+
context.logger.warn('Development server client sent invalid internal log event.');
|
|
312
|
+
}
|
|
313
|
+
switch (data.kind) {
|
|
314
|
+
case 'error':
|
|
315
|
+
context.logger.error(`[CLIENT ERROR]: ${data.text}`);
|
|
316
|
+
break;
|
|
317
|
+
case 'warning':
|
|
318
|
+
context.logger.warn(`[CLIENT WARNING]: ${data.text}`);
|
|
319
|
+
break;
|
|
320
|
+
default:
|
|
321
|
+
context.logger.info(`[CLIENT INFO]: ${data.text}`);
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
// Setup component HMR invalidation
|
|
326
|
+
// Invalidation occurs when the runtime cannot update a component
|
|
327
|
+
server.hot.on('angular:invalidate', (data) => {
|
|
328
|
+
if (typeof data?.id !== 'string') {
|
|
329
|
+
context.logger.warn('Development server client sent invalid internal invalidate event.');
|
|
330
|
+
}
|
|
331
|
+
// Clear invalid template update
|
|
332
|
+
templateUpdates.delete(data.id);
|
|
333
|
+
// Some cases are expected unsupported update scenarios but some may be errors.
|
|
334
|
+
// If an error occurred, log the error in addition to the invalidation.
|
|
335
|
+
if (data.error) {
|
|
336
|
+
context.logger.error(`Component update failed${data.message ? `: ${data.message}` : '.'}` +
|
|
337
|
+
'\nPlease consider reporting the error at https://github.com/angular/angular-cli/issues');
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
context.logger.warn(`Component update unsupported${data.message ? `: ${data.message}` : '.'}`);
|
|
341
|
+
}
|
|
342
|
+
server?.ws.send({
|
|
343
|
+
type: 'full-reload',
|
|
344
|
+
path: '*',
|
|
345
|
+
});
|
|
346
|
+
context.logger.info('Page reload sent to client(s).');
|
|
347
|
+
});
|
|
307
348
|
const urls = server.resolvedUrls;
|
|
308
349
|
if (urls && (urls.local.length || urls.network.length)) {
|
|
309
350
|
serverUrl = new URL(urls.local[0] ?? urls.network[0]);
|
|
@@ -420,7 +461,7 @@ function handleUpdate(server, serverOptions, logger, componentStyles, updatedFil
|
|
|
420
461
|
type: 'update',
|
|
421
462
|
updates,
|
|
422
463
|
});
|
|
423
|
-
logger.info('
|
|
464
|
+
logger.info('Stylesheet update sent to client(s).');
|
|
424
465
|
return;
|
|
425
466
|
}
|
|
426
467
|
}
|
|
@@ -489,6 +530,7 @@ function updateResultRecord(outputPath, file, normalizePath, htmlIndexPath, gene
|
|
|
489
530
|
}
|
|
490
531
|
}
|
|
491
532
|
}
|
|
533
|
+
// eslint-disable-next-line max-lines-per-function
|
|
492
534
|
async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, zoneless, componentStyles, templateUpdates, prebundleLoaderExtensions, define, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
|
|
493
535
|
const proxy = await (0, utils_2.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig);
|
|
494
536
|
// dynamically import Vite for ESM compatibility
|
|
@@ -505,6 +547,14 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
505
547
|
ssrFiles = ['./main.server.mjs', './server.mjs'];
|
|
506
548
|
break;
|
|
507
549
|
}
|
|
550
|
+
/**
|
|
551
|
+
* Required when using `externalDependencies` to prevent Vite load errors.
|
|
552
|
+
*
|
|
553
|
+
* @note Can be removed if Vite introduces native support for externals.
|
|
554
|
+
* @note Vite misresolves browser modules in SSR when accessing URLs with multiple segments
|
|
555
|
+
* (e.g., 'foo/bar'), as they are not correctly re-based from the base href.
|
|
556
|
+
*/
|
|
557
|
+
const preTransformRequests = externalMetadata.explicitBrowser.length === 0 && ssrMode === plugins_1.ServerSsrMode.NoSsr;
|
|
508
558
|
const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite');
|
|
509
559
|
const configuration = {
|
|
510
560
|
configFile: false,
|
|
@@ -533,7 +583,11 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
533
583
|
mainFields: ['es2020', 'browser', 'module', 'main'],
|
|
534
584
|
preserveSymlinks,
|
|
535
585
|
},
|
|
586
|
+
dev: {
|
|
587
|
+
preTransformRequests,
|
|
588
|
+
},
|
|
536
589
|
server: {
|
|
590
|
+
preTransformRequests,
|
|
537
591
|
warmup: {
|
|
538
592
|
ssrFiles,
|
|
539
593
|
},
|
|
@@ -541,6 +595,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
541
595
|
strictPort: true,
|
|
542
596
|
host: serverOptions.host,
|
|
543
597
|
open: serverOptions.open,
|
|
598
|
+
allowedHosts: serverOptions.allowedHosts,
|
|
544
599
|
headers: serverOptions.headers,
|
|
545
600
|
// Disable the websocket if live reload is disabled (false/undefined are the only valid values)
|
|
546
601
|
ws: serverOptions.liveReload === false && serverOptions.hmr === false ? false : undefined,
|
|
@@ -553,6 +608,9 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
553
608
|
? (proxy ?? {})
|
|
554
609
|
: proxy,
|
|
555
610
|
cors: {
|
|
611
|
+
// This will add the header `Access-Control-Allow-Origin: http://example.com`,
|
|
612
|
+
// where `http://example.com` is the requesting origin.
|
|
613
|
+
origin: true,
|
|
556
614
|
// Allow preflight requests to be proxied.
|
|
557
615
|
preflightContinue: true,
|
|
558
616
|
},
|
|
@@ -569,9 +627,6 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
569
627
|
...[...assets.values()].map(({ source }) => source),
|
|
570
628
|
],
|
|
571
629
|
},
|
|
572
|
-
// This is needed when `externalDependencies` is used to prevent Vite load errors.
|
|
573
|
-
// NOTE: If Vite adds direct support for externals, this can be removed.
|
|
574
|
-
preTransformRequests: externalMetadata.explicitBrowser.length === 0,
|
|
575
630
|
},
|
|
576
631
|
ssr: {
|
|
577
632
|
// Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
|
|
@@ -613,7 +668,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
613
668
|
outputFiles,
|
|
614
669
|
templateUpdates,
|
|
615
670
|
external: externalMetadata.explicitBrowser,
|
|
616
|
-
|
|
671
|
+
disableViteTransport: !serverOptions.liveReload,
|
|
617
672
|
}),
|
|
618
673
|
],
|
|
619
674
|
// Browser only optimizeDeps. (This does not run for SSR dependencies).
|
|
@@ -80,6 +80,9 @@ class AngularCompilation {
|
|
|
80
80
|
enableResourceInlining: false,
|
|
81
81
|
supportTestBed: false,
|
|
82
82
|
supportJitMode: false,
|
|
83
|
+
// Disable removing of comments as TS is quite aggressive with these and can
|
|
84
|
+
// remove important annotations, such as /* @__PURE__ */ and comments like /* vite-ignore */.
|
|
85
|
+
removeComments: false,
|
|
83
86
|
}));
|
|
84
87
|
}
|
|
85
88
|
async diagnoseFiles(modes = DiagnosticModes.All) {
|
|
@@ -136,7 +136,8 @@ function analyzeFileUpdates(stale, updated, compiler) {
|
|
|
136
136
|
return null;
|
|
137
137
|
}
|
|
138
138
|
// Compare component meta decorator object literals
|
|
139
|
-
|
|
139
|
+
const analysis = analyzeMetaUpdates(staleDecoratorExpression, stale, updatedDecoratorExpression, updated);
|
|
140
|
+
if (analysis === MetaUpdateAnalysis.Unsupported) {
|
|
140
141
|
return null;
|
|
141
142
|
}
|
|
142
143
|
// Compare text of the member nodes to determine if any changes have occurred
|
|
@@ -145,7 +146,9 @@ function analyzeFileUpdates(stale, updated, compiler) {
|
|
|
145
146
|
return null;
|
|
146
147
|
}
|
|
147
148
|
// If all previous class checks passed, this class is supported for HMR updates
|
|
148
|
-
|
|
149
|
+
if (analysis === MetaUpdateAnalysis.Supported) {
|
|
150
|
+
candidates.push(updatedNode);
|
|
151
|
+
}
|
|
149
152
|
continue;
|
|
150
153
|
}
|
|
151
154
|
}
|
|
@@ -161,7 +164,19 @@ function analyzeFileUpdates(stale, updated, compiler) {
|
|
|
161
164
|
/**
|
|
162
165
|
* The set of Angular component metadata fields that are supported by HMR updates.
|
|
163
166
|
*/
|
|
164
|
-
const
|
|
167
|
+
const SUPPORTED_FIELD_NAMES = new Set([
|
|
168
|
+
'template',
|
|
169
|
+
'templateUrl',
|
|
170
|
+
'styles',
|
|
171
|
+
'styleUrl',
|
|
172
|
+
'stylesUrl',
|
|
173
|
+
]);
|
|
174
|
+
var MetaUpdateAnalysis;
|
|
175
|
+
(function (MetaUpdateAnalysis) {
|
|
176
|
+
MetaUpdateAnalysis[MetaUpdateAnalysis["Supported"] = 0] = "Supported";
|
|
177
|
+
MetaUpdateAnalysis[MetaUpdateAnalysis["Unsupported"] = 1] = "Unsupported";
|
|
178
|
+
MetaUpdateAnalysis[MetaUpdateAnalysis["None"] = 2] = "None";
|
|
179
|
+
})(MetaUpdateAnalysis || (MetaUpdateAnalysis = {}));
|
|
165
180
|
/**
|
|
166
181
|
* Analyzes the metadata fields of a decorator call expression for unsupported HMR updates.
|
|
167
182
|
* Only updates to supported fields can be present for HMR to be viable.
|
|
@@ -169,22 +184,25 @@ const SUPPORTED_FIELDS = new Set(['template', 'templateUrl', 'styles', 'styleUrl
|
|
|
169
184
|
* @param staleSource The source file instance containing the stale call instance.
|
|
170
185
|
* @param updatedCall A call expression instance.
|
|
171
186
|
* @param updatedSource The source file instance containing the updated call instance.
|
|
172
|
-
* @returns
|
|
187
|
+
* @returns A MetaUpdateAnalysis enum value.
|
|
173
188
|
*/
|
|
174
|
-
function
|
|
189
|
+
function analyzeMetaUpdates(staleCall, staleSource, updatedCall, updatedSource) {
|
|
175
190
|
const staleObject = staleCall.arguments[0];
|
|
176
191
|
const updatedObject = updatedCall.arguments[0];
|
|
192
|
+
let hasSupportedUpdate = false;
|
|
177
193
|
if (!typescript_1.default.isObjectLiteralExpression(staleObject) || !typescript_1.default.isObjectLiteralExpression(updatedObject)) {
|
|
178
|
-
return
|
|
194
|
+
return MetaUpdateAnalysis.Unsupported;
|
|
179
195
|
}
|
|
196
|
+
const supportedFields = new Map();
|
|
180
197
|
const unsupportedFields = [];
|
|
181
198
|
for (const property of staleObject.properties) {
|
|
182
199
|
if (!typescript_1.default.isPropertyAssignment(property) || typescript_1.default.isComputedPropertyName(property.name)) {
|
|
183
200
|
// Unsupported object literal property
|
|
184
|
-
return
|
|
201
|
+
return MetaUpdateAnalysis.Unsupported;
|
|
185
202
|
}
|
|
186
203
|
const name = property.name.text;
|
|
187
|
-
if (
|
|
204
|
+
if (SUPPORTED_FIELD_NAMES.has(name)) {
|
|
205
|
+
supportedFields.set(name, property.initializer);
|
|
188
206
|
continue;
|
|
189
207
|
}
|
|
190
208
|
unsupportedFields.push(property.initializer);
|
|
@@ -193,18 +211,31 @@ function hasUnsupportedMetaUpdates(staleCall, staleSource, updatedCall, updatedS
|
|
|
193
211
|
for (const property of updatedObject.properties) {
|
|
194
212
|
if (!typescript_1.default.isPropertyAssignment(property) || typescript_1.default.isComputedPropertyName(property.name)) {
|
|
195
213
|
// Unsupported object literal property
|
|
196
|
-
return
|
|
214
|
+
return MetaUpdateAnalysis.Unsupported;
|
|
197
215
|
}
|
|
198
216
|
const name = property.name.text;
|
|
199
|
-
if (
|
|
217
|
+
if (SUPPORTED_FIELD_NAMES.has(name)) {
|
|
218
|
+
const staleInitializer = supportedFields.get(name);
|
|
219
|
+
// If the supported field was added or has its content changed, there has been a supported update
|
|
220
|
+
if (!staleInitializer ||
|
|
221
|
+
!equalRangeText(property.initializer, updatedSource, staleInitializer, staleSource)) {
|
|
222
|
+
hasSupportedUpdate = true;
|
|
223
|
+
}
|
|
224
|
+
// Remove the field entry to allow tracking removed fields
|
|
225
|
+
supportedFields.delete(name);
|
|
200
226
|
continue;
|
|
201
227
|
}
|
|
202
228
|
// Compare in order
|
|
203
229
|
if (!equalRangeText(property.initializer, updatedSource, unsupportedFields[i++], staleSource)) {
|
|
204
|
-
return
|
|
230
|
+
return MetaUpdateAnalysis.Unsupported;
|
|
205
231
|
}
|
|
206
232
|
}
|
|
207
|
-
|
|
233
|
+
if (i !== unsupportedFields.length) {
|
|
234
|
+
return MetaUpdateAnalysis.Unsupported;
|
|
235
|
+
}
|
|
236
|
+
// Any remaining supported field indicates a field removal. This is also considered a supported update.
|
|
237
|
+
hasSupportedUpdate ||= supportedFields.size > 0;
|
|
238
|
+
return hasSupportedUpdate ? MetaUpdateAnalysis.Supported : MetaUpdateAnalysis.None;
|
|
208
239
|
}
|
|
209
240
|
/**
|
|
210
241
|
* Compares the text from a provided range in a source file to the text of a range in a second source file.
|
|
@@ -14,6 +14,7 @@ exports.default = default_1;
|
|
|
14
14
|
const core_1 = require("@babel/core");
|
|
15
15
|
const istanbul_lib_instrument_1 = require("istanbul-lib-instrument");
|
|
16
16
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
17
|
+
const node_url_1 = require("node:url");
|
|
17
18
|
/**
|
|
18
19
|
* A babel plugin factory function for adding istanbul instrumentation.
|
|
19
20
|
*
|
|
@@ -25,9 +26,15 @@ function default_1() {
|
|
|
25
26
|
visitor: {
|
|
26
27
|
Program: {
|
|
27
28
|
enter(path, state) {
|
|
29
|
+
const inputSourceMap = // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
state.file.inputMap?.toObject();
|
|
31
|
+
// istanbul does not support URL as sources.
|
|
32
|
+
if (inputSourceMap?.sources) {
|
|
33
|
+
inputSourceMap.sources = inputSourceMap.sources.map((s) => s.startsWith('file://') ? (0, node_url_1.fileURLToPath)(s) : s);
|
|
34
|
+
}
|
|
28
35
|
const visitor = (0, istanbul_lib_instrument_1.programVisitor)(core_1.types, state.filename, {
|
|
29
36
|
// Babel returns a Converter object from the `convert-source-map` package
|
|
30
|
-
inputSourceMap
|
|
37
|
+
inputSourceMap,
|
|
31
38
|
});
|
|
32
39
|
visitors.set(path, visitor);
|
|
33
40
|
visitor.enter(path);
|
|
@@ -47,6 +47,7 @@ exports.createCompilerPlugin = createCompilerPlugin;
|
|
|
47
47
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
48
48
|
const node_crypto_1 = require("node:crypto");
|
|
49
49
|
const path = __importStar(require("node:path"));
|
|
50
|
+
const node_url_1 = require("node:url");
|
|
50
51
|
const environment_options_1 = require("../../../utils/environment-options");
|
|
51
52
|
const compilation_1 = require("../../angular/compilation");
|
|
52
53
|
const javascript_transformer_1 = require("../javascript-transformer");
|
|
@@ -233,7 +234,7 @@ function createCompilerPlugin(pluginOptions, compilationOrFactory, stylesheetBun
|
|
|
233
234
|
let referencedFiles;
|
|
234
235
|
let externalStylesheets;
|
|
235
236
|
try {
|
|
236
|
-
const initializationResult = await compilation.initialize(pluginOptions.tsconfig, hostOptions, createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks, build.initialOptions.conditions));
|
|
237
|
+
const initializationResult = await compilation.initialize(pluginOptions.tsconfig, hostOptions, createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks, build.initialOptions.conditions, build.initialOptions.absWorkingDir));
|
|
237
238
|
shouldTsIgnoreJs = !initializationResult.compilerOptions.allowJs;
|
|
238
239
|
// Isolated modules option ensures safe non-TypeScript transpilation.
|
|
239
240
|
// Typescript printing support for sourcemaps is not yet integrated.
|
|
@@ -474,7 +475,7 @@ async function bundleExternalStylesheet(stylesheetBundler, stylesheetFile, exter
|
|
|
474
475
|
});
|
|
475
476
|
}
|
|
476
477
|
}
|
|
477
|
-
function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks, customConditions) {
|
|
478
|
+
function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserveSymlinks, customConditions, absWorkingDir) {
|
|
478
479
|
return (compilerOptions) => {
|
|
479
480
|
// target of 9 is ES2022 (using the number avoids an expensive import of typescript just for an enum)
|
|
480
481
|
if (compilerOptions.target === undefined || compilerOptions.target < 9 /** ES2022 */) {
|
|
@@ -542,6 +543,10 @@ function createCompilerOptionsTransformer(setupWarnings, pluginOptions, preserve
|
|
|
542
543
|
return {
|
|
543
544
|
...compilerOptions,
|
|
544
545
|
noEmitOnError: false,
|
|
546
|
+
// Using the path as a URL is necessary here; otherwise, esbuild will not generate source maps correctly.
|
|
547
|
+
// https://github.com/evanw/esbuild/issues/4070
|
|
548
|
+
// https://github.com/evanw/esbuild/issues/4075
|
|
549
|
+
outDir: absWorkingDir ? (0, node_url_1.pathToFileURL)(absWorkingDir + '/').href : undefined,
|
|
545
550
|
inlineSources: !!pluginOptions.sourcemap,
|
|
546
551
|
inlineSourceMap: !!pluginOptions.sourcemap,
|
|
547
552
|
sourceMap: undefined,
|
|
@@ -13,6 +13,7 @@ export interface I18nInlinerOptions {
|
|
|
13
13
|
missingTranslation: 'error' | 'warning' | 'ignore';
|
|
14
14
|
outputFiles: BuildOutputFile[];
|
|
15
15
|
shouldOptimize?: boolean;
|
|
16
|
+
persistentCachePath?: string;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* A class that performs i18n translation inlining of JavaScript code.
|
|
@@ -22,6 +23,7 @@ export interface I18nInlinerOptions {
|
|
|
22
23
|
*/
|
|
23
24
|
export declare class I18nInliner {
|
|
24
25
|
#private;
|
|
26
|
+
private readonly options;
|
|
25
27
|
constructor(options: I18nInlinerOptions, maxThreads?: number);
|
|
26
28
|
/**
|
|
27
29
|
* Performs inlining of translations for the provided locale and translations. The files that
|
|
@@ -41,4 +43,11 @@ export declare class I18nInliner {
|
|
|
41
43
|
* @returns A void promise that resolves when closing is complete.
|
|
42
44
|
*/
|
|
43
45
|
close(): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Initializes the cache for storing translated bundles.
|
|
48
|
+
* If the cache is already initialized, it does nothing.
|
|
49
|
+
*
|
|
50
|
+
* @returns A promise that resolves once the cache initialization process is complete.
|
|
51
|
+
*/
|
|
52
|
+
private initCache;
|
|
44
53
|
}
|
|
@@ -6,12 +6,47 @@
|
|
|
6
6
|
* Use of this source code is governed by an MIT-style license that can be
|
|
7
7
|
* found in the LICENSE file at https://angular.dev/license
|
|
8
8
|
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
9
42
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
43
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
44
|
};
|
|
12
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
46
|
exports.I18nInliner = void 0;
|
|
14
47
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
48
|
+
const node_crypto_1 = require("node:crypto");
|
|
49
|
+
const node_path_1 = require("node:path");
|
|
15
50
|
const worker_pool_1 = require("../../utils/worker-pool");
|
|
16
51
|
const bundler_context_1 = require("./bundler-context");
|
|
17
52
|
const utils_1 = require("./utils");
|
|
@@ -27,37 +62,37 @@ const LOCALIZE_KEYWORD = '$localize';
|
|
|
27
62
|
* localize function (`$localize`).
|
|
28
63
|
*/
|
|
29
64
|
class I18nInliner {
|
|
65
|
+
options;
|
|
66
|
+
#cacheInitFailed = false;
|
|
30
67
|
#workerPool;
|
|
68
|
+
#cache;
|
|
31
69
|
#localizeFiles;
|
|
32
70
|
#unmodifiedFiles;
|
|
33
|
-
#fileToType = new Map();
|
|
34
71
|
constructor(options, maxThreads) {
|
|
72
|
+
this.options = options;
|
|
35
73
|
this.#unmodifiedFiles = [];
|
|
74
|
+
const { outputFiles, shouldOptimize, missingTranslation } = options;
|
|
36
75
|
const files = new Map();
|
|
37
76
|
const pendingMaps = [];
|
|
38
|
-
for (const file of
|
|
77
|
+
for (const file of outputFiles) {
|
|
39
78
|
if (file.type === bundler_context_1.BuildOutputFileType.Root || file.type === bundler_context_1.BuildOutputFileType.ServerRoot) {
|
|
40
79
|
// Skip also the server entry-point.
|
|
41
80
|
// Skip stats and similar files.
|
|
42
81
|
continue;
|
|
43
82
|
}
|
|
44
|
-
|
|
45
|
-
if (
|
|
83
|
+
const fileExtension = (0, node_path_1.extname)(file.path);
|
|
84
|
+
if (fileExtension === '.js' || fileExtension === '.mjs') {
|
|
46
85
|
// Check if localizations are present
|
|
47
86
|
const contentBuffer = Buffer.isBuffer(file.contents)
|
|
48
87
|
? file.contents
|
|
49
88
|
: Buffer.from(file.contents.buffer, file.contents.byteOffset, file.contents.byteLength);
|
|
50
89
|
const hasLocalize = contentBuffer.includes(LOCALIZE_KEYWORD);
|
|
51
90
|
if (hasLocalize) {
|
|
52
|
-
|
|
53
|
-
// without copying until the data is actually used within a Worker. This is useful here
|
|
54
|
-
// since each file may not actually be processed in each Worker and the Blob avoids
|
|
55
|
-
// unneeded repeat copying of potentially large JavaScript files.
|
|
56
|
-
files.set(file.path, new Blob([file.contents]));
|
|
91
|
+
files.set(file.path, file);
|
|
57
92
|
continue;
|
|
58
93
|
}
|
|
59
94
|
}
|
|
60
|
-
else if (
|
|
95
|
+
else if (fileExtension === '.map') {
|
|
61
96
|
// The related JS file may not have been checked yet. To ensure that map files are not
|
|
62
97
|
// missed, store any pending map files and check them after all output files.
|
|
63
98
|
pendingMaps.push(file);
|
|
@@ -68,7 +103,7 @@ class I18nInliner {
|
|
|
68
103
|
// Check if any pending map files should be processed by checking if the parent JS file is present
|
|
69
104
|
for (const file of pendingMaps) {
|
|
70
105
|
if (files.has(file.path.slice(0, -4))) {
|
|
71
|
-
files.set(file.path,
|
|
106
|
+
files.set(file.path, file);
|
|
72
107
|
}
|
|
73
108
|
else {
|
|
74
109
|
this.#unmodifiedFiles.push(file);
|
|
@@ -80,9 +115,13 @@ class I18nInliner {
|
|
|
80
115
|
maxThreads,
|
|
81
116
|
// Extract options to ensure only the named options are serialized and sent to the worker
|
|
82
117
|
workerData: {
|
|
83
|
-
missingTranslation
|
|
84
|
-
shouldOptimize
|
|
85
|
-
|
|
118
|
+
missingTranslation,
|
|
119
|
+
shouldOptimize,
|
|
120
|
+
// A Blob is an immutable data structure that allows sharing the data between workers
|
|
121
|
+
// without copying until the data is actually used within a Worker. This is useful here
|
|
122
|
+
// since each file may not actually be processed in each Worker and the Blob avoids
|
|
123
|
+
// unneeded repeat copying of potentially large JavaScript files.
|
|
124
|
+
files: new Map(Array.from(files, ([name, file]) => [name, new Blob([file.contents])])),
|
|
86
125
|
},
|
|
87
126
|
});
|
|
88
127
|
}
|
|
@@ -95,18 +134,41 @@ class I18nInliner {
|
|
|
95
134
|
* @returns A promise that resolves to an array of OutputFiles representing a translated result.
|
|
96
135
|
*/
|
|
97
136
|
async inlineForLocale(locale, translation) {
|
|
137
|
+
await this.initCache();
|
|
138
|
+
const { shouldOptimize, missingTranslation } = this.options;
|
|
98
139
|
// Request inlining for each file that contains localize calls
|
|
99
140
|
const requests = [];
|
|
100
|
-
|
|
141
|
+
let fileCacheKeyBase;
|
|
142
|
+
for (const [filename, file] of this.#localizeFiles) {
|
|
143
|
+
let cacheKey;
|
|
101
144
|
if (filename.endsWith('.map')) {
|
|
102
145
|
continue;
|
|
103
146
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
locale,
|
|
107
|
-
|
|
147
|
+
let cacheResultPromise = Promise.resolve(null);
|
|
148
|
+
if (this.#cache) {
|
|
149
|
+
fileCacheKeyBase ??= Buffer.from(JSON.stringify({ locale, translation, missingTranslation, shouldOptimize }), 'utf-8');
|
|
150
|
+
// NOTE: If additional options are added, this may need to be updated.
|
|
151
|
+
// TODO: Consider xxhash or similar instead of SHA256
|
|
152
|
+
cacheKey = (0, node_crypto_1.createHash)('sha256')
|
|
153
|
+
.update(file.hash)
|
|
154
|
+
.update(filename)
|
|
155
|
+
.update(fileCacheKeyBase)
|
|
156
|
+
.digest('hex');
|
|
157
|
+
// Failure to get the value should not fail the transform
|
|
158
|
+
cacheResultPromise = this.#cache.get(cacheKey).catch(() => null);
|
|
159
|
+
}
|
|
160
|
+
const fileResult = cacheResultPromise.then(async (cachedResult) => {
|
|
161
|
+
if (cachedResult) {
|
|
162
|
+
return cachedResult;
|
|
163
|
+
}
|
|
164
|
+
const result = await this.#workerPool.run({ filename, locale, translation });
|
|
165
|
+
if (this.#cache && cacheKey) {
|
|
166
|
+
// Failure to set the value should not fail the transform
|
|
167
|
+
await this.#cache.set(cacheKey, result).catch(() => { });
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
108
170
|
});
|
|
109
|
-
requests.push(
|
|
171
|
+
requests.push(fileResult);
|
|
110
172
|
}
|
|
111
173
|
// Wait for all file requests to complete
|
|
112
174
|
const rawResults = await Promise.all(requests);
|
|
@@ -115,7 +177,7 @@ class I18nInliner {
|
|
|
115
177
|
const warnings = [];
|
|
116
178
|
const outputFiles = [
|
|
117
179
|
...rawResults.flatMap(({ file, code, map, messages }) => {
|
|
118
|
-
const type = this.#
|
|
180
|
+
const type = this.#localizeFiles.get(file)?.type;
|
|
119
181
|
(0, node_assert_1.default)(type !== undefined, 'localized file should always have a type' + file);
|
|
120
182
|
const resultFiles = [(0, utils_1.createOutputFile)(file, code, type)];
|
|
121
183
|
if (map) {
|
|
@@ -146,5 +208,32 @@ class I18nInliner {
|
|
|
146
208
|
close() {
|
|
147
209
|
return this.#workerPool.destroy();
|
|
148
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Initializes the cache for storing translated bundles.
|
|
213
|
+
* If the cache is already initialized, it does nothing.
|
|
214
|
+
*
|
|
215
|
+
* @returns A promise that resolves once the cache initialization process is complete.
|
|
216
|
+
*/
|
|
217
|
+
async initCache() {
|
|
218
|
+
if (this.#cache || this.#cacheInitFailed) {
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const { persistentCachePath } = this.options;
|
|
222
|
+
// Webcontainers currently do not support this persistent cache store.
|
|
223
|
+
if (!persistentCachePath || process.versions.webcontainer) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// Initialize a persistent cache for i18n transformations.
|
|
227
|
+
try {
|
|
228
|
+
const { LmbdCacheStore } = await Promise.resolve().then(() => __importStar(require('./lmdb-cache-store')));
|
|
229
|
+
this.#cache = new LmbdCacheStore((0, node_path_1.join)(persistentCachePath, 'angular-i18n.db'));
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
this.#cacheInitFailed = true;
|
|
233
|
+
// eslint-disable-next-line no-console
|
|
234
|
+
console.warn('Unable to initialize JavaScript cache storage.\n' +
|
|
235
|
+
'This will not affect the build output content but may result in slower builds.');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
149
238
|
}
|
|
150
239
|
exports.I18nInliner = I18nInliner;
|
|
@@ -42,6 +42,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.LessStylesheetLanguage = void 0;
|
|
44
44
|
const promises_1 = require("node:fs/promises");
|
|
45
|
+
const node_path_1 = require("node:path");
|
|
46
|
+
const node_url_1 = require("node:url");
|
|
45
47
|
/**
|
|
46
48
|
* The lazy-loaded instance of the less stylesheet preprocessor.
|
|
47
49
|
* It is only imported and initialized if a less stylesheet is used.
|
|
@@ -115,7 +117,7 @@ async function compileString(data, filename, options, resolver, unsafeInlineJava
|
|
|
115
117
|
},
|
|
116
118
|
};
|
|
117
119
|
try {
|
|
118
|
-
const
|
|
120
|
+
const { imports, map, css } = await less.render(data, {
|
|
119
121
|
filename,
|
|
120
122
|
paths: options.includePaths,
|
|
121
123
|
plugins: [resolverPlugin],
|
|
@@ -123,15 +125,15 @@ async function compileString(data, filename, options, resolver, unsafeInlineJava
|
|
|
123
125
|
javascriptEnabled: unsafeInlineJavaScript,
|
|
124
126
|
sourceMap: options.sourcemap
|
|
125
127
|
? {
|
|
126
|
-
sourceMapFileInline:
|
|
128
|
+
sourceMapFileInline: false,
|
|
127
129
|
outputSourceFiles: true,
|
|
128
130
|
}
|
|
129
131
|
: undefined,
|
|
130
132
|
});
|
|
131
133
|
return {
|
|
132
|
-
contents:
|
|
134
|
+
contents: options.sourcemap ? `${css}\n${sourceMapToUrlComment(map)}` : css,
|
|
133
135
|
loader: 'css',
|
|
134
|
-
watchFiles: [filename, ...
|
|
136
|
+
watchFiles: [filename, ...imports],
|
|
135
137
|
};
|
|
136
138
|
}
|
|
137
139
|
catch (error) {
|
|
@@ -182,3 +184,13 @@ function convertExceptionLocation(exception) {
|
|
|
182
184
|
lineText: exception.extract && exception.extract[Math.trunc(exception.extract.length / 2)],
|
|
183
185
|
};
|
|
184
186
|
}
|
|
187
|
+
function sourceMapToUrlComment(sourceMap) {
|
|
188
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
189
|
+
const map = JSON.parse(sourceMap);
|
|
190
|
+
// Using file URLs instead of paths ensures that esbuild correctly resolves the source map.
|
|
191
|
+
// https://github.com/evanw/esbuild/issues/4070
|
|
192
|
+
// https://github.com/evanw/esbuild/issues/4075
|
|
193
|
+
map.sources = map.sources.map((source) => source && (0, node_path_1.isAbsolute)(source) ? (0, node_url_1.pathToFileURL)(source).href : source);
|
|
194
|
+
const urlSourceMap = Buffer.from(JSON.stringify(map), 'utf-8').toString('base64');
|
|
195
|
+
return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${urlSourceMap} */`;
|
|
196
|
+
}
|
|
@@ -175,7 +175,7 @@ async function compileString(data, filePath, syntax, options, resolveUrl) {
|
|
|
175
175
|
});
|
|
176
176
|
return {
|
|
177
177
|
loader: 'css',
|
|
178
|
-
contents: sourceMap ? `${css}\n${sourceMapToUrlComment(sourceMap
|
|
178
|
+
contents: sourceMap ? `${css}\n${sourceMapToUrlComment(sourceMap)}` : css,
|
|
179
179
|
watchFiles: loadedUrls.map((url) => (0, node_url_1.fileURLToPath)(url)),
|
|
180
180
|
warnings,
|
|
181
181
|
};
|
|
@@ -201,10 +201,7 @@ async function compileString(data, filePath, syntax, options, resolveUrl) {
|
|
|
201
201
|
throw error;
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
|
-
function sourceMapToUrlComment(sourceMap
|
|
205
|
-
// Remove `file` protocol from all sourcemap sources and adjust to be relative to the input file.
|
|
206
|
-
// This allows esbuild to correctly process the paths.
|
|
207
|
-
sourceMap.sources = sourceMap.sources.map((source) => (0, node_path_1.relative)(root, (0, node_url_1.fileURLToPath)(source)));
|
|
204
|
+
function sourceMapToUrlComment(sourceMap) {
|
|
208
205
|
const urlSourceMap = Buffer.from(JSON.stringify(sourceMap), 'utf-8').toString('base64');
|
|
209
206
|
return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${urlSourceMap} */`;
|
|
210
207
|
}
|
|
@@ -35,6 +35,9 @@ const manifest_1 = require("../../utils/server-rendering/manifest");
|
|
|
35
35
|
const stats_table_1 = require("../../utils/stats-table");
|
|
36
36
|
const bundler_context_1 = require("./bundler-context");
|
|
37
37
|
function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, changedFiles, estimatedTransferSizes, ssrOutputEnabled, verbose) {
|
|
38
|
+
// Remove the i18n subpath in case the build is using i18n.
|
|
39
|
+
// en-US/main.js -> main.js
|
|
40
|
+
const normalizedChangedFiles = new Set([...(changedFiles ?? [])].map((f) => (0, node_path_1.basename)(f)));
|
|
38
41
|
const browserStats = [];
|
|
39
42
|
const serverStats = [];
|
|
40
43
|
let unchangedCount = 0;
|
|
@@ -45,7 +48,7 @@ function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, c
|
|
|
45
48
|
continue;
|
|
46
49
|
}
|
|
47
50
|
// Show only changed files if a changed list is provided
|
|
48
|
-
if (
|
|
51
|
+
if (normalizedChangedFiles.size && !normalizedChangedFiles.has(file)) {
|
|
49
52
|
++unchangedCount;
|
|
50
53
|
continue;
|
|
51
54
|
}
|
|
@@ -12,7 +12,7 @@ interface AngularMemoryPluginOptions {
|
|
|
12
12
|
outputFiles: AngularMemoryOutputFiles;
|
|
13
13
|
templateUpdates?: ReadonlyMap<string, string>;
|
|
14
14
|
external?: string[];
|
|
15
|
-
|
|
15
|
+
disableViteTransport?: boolean;
|
|
16
16
|
}
|
|
17
17
|
export declare function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): Promise<Plugin>;
|
|
18
18
|
export {};
|
|
@@ -47,24 +47,13 @@ async function createAngularMemoryPlugin(options) {
|
|
|
47
47
|
// `/@id/${source}` but is currently closer to a raw external than a resolved file path.
|
|
48
48
|
return source;
|
|
49
49
|
}
|
|
50
|
-
if (importer) {
|
|
50
|
+
if (importer && source[0] === '.') {
|
|
51
51
|
const normalizedImporter = normalizePath(importer);
|
|
52
|
-
if (
|
|
52
|
+
if (normalizedImporter.startsWith(virtualProjectRoot)) {
|
|
53
53
|
// Remove query if present
|
|
54
54
|
const [importerFile] = normalizedImporter.split('?', 1);
|
|
55
55
|
source = '/' + (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source);
|
|
56
56
|
}
|
|
57
|
-
else if (!ssr &&
|
|
58
|
-
source[0] === '/' &&
|
|
59
|
-
importer.endsWith('index.html') &&
|
|
60
|
-
normalizedImporter.startsWith(virtualProjectRoot)) {
|
|
61
|
-
// This is only needed when using SSR and `angularSsrMiddleware` (old style) to correctly resolve
|
|
62
|
-
// .js files when using lazy-loading.
|
|
63
|
-
// Remove query if present
|
|
64
|
-
const [importerFile] = normalizedImporter.split('?', 1);
|
|
65
|
-
source =
|
|
66
|
-
'/' + (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), (0, node_path_1.basename)(source));
|
|
67
|
-
}
|
|
68
57
|
}
|
|
69
58
|
const [file] = source.split('?', 1);
|
|
70
59
|
if (outputFiles.has(normalizePath(file))) {
|
|
@@ -84,7 +73,7 @@ async function createAngularMemoryPlugin(options) {
|
|
|
84
73
|
const codeContents = outputFiles.get(relativeFile)?.contents;
|
|
85
74
|
if (codeContents === undefined) {
|
|
86
75
|
if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
|
|
87
|
-
return
|
|
76
|
+
return loadViteClientCode(file, options.disableViteTransport);
|
|
88
77
|
}
|
|
89
78
|
return undefined;
|
|
90
79
|
}
|
|
@@ -107,9 +96,9 @@ async function createAngularMemoryPlugin(options) {
|
|
|
107
96
|
* @param file The absolute path to the Vite client code.
|
|
108
97
|
* @returns
|
|
109
98
|
*/
|
|
110
|
-
async function loadViteClientCode(file) {
|
|
99
|
+
async function loadViteClientCode(file, disableViteTransport = false) {
|
|
111
100
|
const originalContents = await (0, promises_1.readFile)(file, 'utf-8');
|
|
112
|
-
|
|
101
|
+
let updatedContents = originalContents.replace(`"You can also disable this overlay by setting ",
|
|
113
102
|
h("code", { part: "config-option-name" }, "server.hmr.overlay"),
|
|
114
103
|
" to ",
|
|
115
104
|
h("code", { part: "config-option-value" }, "false"),
|
|
@@ -117,5 +106,11 @@ async function loadViteClientCode(file) {
|
|
|
117
106
|
h("code", { part: "config-file-name" }, hmrConfigName),
|
|
118
107
|
"."`, '');
|
|
119
108
|
(0, node_assert_1.default)(originalContents !== updatedContents, 'Failed to update Vite client error overlay text.');
|
|
109
|
+
if (disableViteTransport) {
|
|
110
|
+
const previousUpdatedContents = updatedContents;
|
|
111
|
+
updatedContents = updatedContents.replace('transport.connect(handleMessage)', '');
|
|
112
|
+
(0, node_assert_1.default)(previousUpdatedContents !== updatedContents, 'Failed to update Vite client WebSocket disable.');
|
|
113
|
+
updatedContents = updatedContents.replace('console.debug("[vite] connecting...")', '');
|
|
114
|
+
}
|
|
120
115
|
return updatedContents;
|
|
121
116
|
}
|
|
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.normalizeCacheOptions = normalizeCacheOptions;
|
|
11
11
|
const node_path_1 = require("node:path");
|
|
12
12
|
/** Version placeholder is replaced during the build process with actual package version */
|
|
13
|
-
const VERSION = '19.2.0-next.
|
|
13
|
+
const VERSION = '19.2.0-next.2';
|
|
14
14
|
function hasCacheMetadata(value) {
|
|
15
15
|
return (!!value &&
|
|
16
16
|
typeof value === 'object' &&
|
|
@@ -11,6 +11,7 @@ exports.SERVER_APP_ENGINE_MANIFEST_FILENAME = exports.SERVER_APP_MANIFEST_FILENA
|
|
|
11
11
|
exports.generateAngularServerAppEngineManifest = generateAngularServerAppEngineManifest;
|
|
12
12
|
exports.generateAngularServerAppManifest = generateAngularServerAppManifest;
|
|
13
13
|
const node_path_1 = require("node:path");
|
|
14
|
+
const node_vm_1 = require("node:vm");
|
|
14
15
|
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
|
|
15
16
|
const utils_1 = require("../../tools/esbuild/utils");
|
|
16
17
|
const environment_options_1 = require("../environment-options");
|
|
@@ -115,9 +116,13 @@ function generateAngularServerAppManifest(additionalHtmlOutputFiles, outputFiles
|
|
|
115
116
|
const extension = (0, node_path_1.extname)(file.path);
|
|
116
117
|
if (extension === '.html' || (inlineCriticalCss && extension === '.css')) {
|
|
117
118
|
const jsChunkFilePath = `assets-chunks/${file.path.replace(/[./]/g, '_')}.mjs`;
|
|
118
|
-
|
|
119
|
+
const escapedContent = escapeUnsafeChars(file.text);
|
|
120
|
+
serverAssetsChunks.push((0, utils_1.createOutputFile)(jsChunkFilePath, `export default \`${escapedContent}\`;`, bundler_context_1.BuildOutputFileType.ServerApplication));
|
|
121
|
+
// This is needed because JavaScript engines script parser convert `\r\n` to `\n` in template literals,
|
|
122
|
+
// which can result in an incorrect byte length.
|
|
123
|
+
const size = (0, node_vm_1.runInThisContext)(`new TextEncoder().encode(\`${escapedContent}\`).byteLength`);
|
|
119
124
|
serverAssets[file.path] =
|
|
120
|
-
`{size: ${
|
|
125
|
+
`{size: ${size}, hash: '${file.hash}', text: () => import('./${jsChunkFilePath}').then(m => m.default)}`;
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
// When routes have been extracted, mappings are no longer needed, as preloads will be included in the metadata.
|
|
@@ -128,11 +128,11 @@ async function renderPages(baseHref, sourcemap, serializableRouteTreeNode, maxTh
|
|
|
128
128
|
try {
|
|
129
129
|
const renderingPromises = [];
|
|
130
130
|
const appShellRouteWithLeadingSlash = appShellRoute && addLeadingSlash(appShellRoute);
|
|
131
|
-
const
|
|
131
|
+
const baseHrefPathnameWithLeadingSlash = new URL(baseHref, 'http://localhost').pathname;
|
|
132
132
|
for (const { route, redirectTo } of serializableRouteTreeNode) {
|
|
133
133
|
// Remove the base href from the file output path.
|
|
134
|
-
const routeWithoutBaseHref = addTrailingSlash(route).startsWith(
|
|
135
|
-
? addLeadingSlash(route.slice(
|
|
134
|
+
const routeWithoutBaseHref = addTrailingSlash(route).startsWith(baseHrefPathnameWithLeadingSlash)
|
|
135
|
+
? addLeadingSlash(route.slice(baseHrefPathnameWithLeadingSlash.length))
|
|
136
136
|
: route;
|
|
137
137
|
const outPath = node_path_1.posix.join(removeLeadingSlash(routeWithoutBaseHref), 'index.html');
|
|
138
138
|
if (typeof redirectTo === 'string') {
|
|
@@ -9,7 +9,7 @@ import type { Config, Filesystem } from '@angular/service-worker/config';
|
|
|
9
9
|
import { promises as fsPromises } from 'node:fs';
|
|
10
10
|
import { BuildOutputFile } from '../tools/esbuild/bundler-context';
|
|
11
11
|
import { BuildOutputAsset } from '../tools/esbuild/bundler-execution-result';
|
|
12
|
-
export declare function augmentAppWithServiceWorker(appRoot: string, workspaceRoot: string, outputPath: string, baseHref: string, ngswConfigPath?: string,
|
|
12
|
+
export declare function augmentAppWithServiceWorker(appRoot: string, workspaceRoot: string, outputPath: string, baseHref: string, ngswConfigPath?: string, inputFileSystem?: typeof fsPromises, outputFileSystem?: typeof fsPromises): Promise<void>;
|
|
13
13
|
export declare function augmentAppWithServiceWorkerEsbuild(workspaceRoot: string, configPath: string, baseHref: string, indexHtml: string | undefined, outputFiles: BuildOutputFile[], assetFiles: BuildOutputAsset[]): Promise<{
|
|
14
14
|
manifest: string;
|
|
15
15
|
assetFiles: BuildOutputAsset[];
|
|
@@ -133,7 +133,7 @@ class ResultFilesystem {
|
|
|
133
133
|
throw new Error('Serviceworker manifest generator should not attempted to write.');
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
|
-
async function augmentAppWithServiceWorker(appRoot, workspaceRoot, outputPath, baseHref, ngswConfigPath,
|
|
136
|
+
async function augmentAppWithServiceWorker(appRoot, workspaceRoot, outputPath, baseHref, ngswConfigPath, inputFileSystem = node_fs_1.promises, outputFileSystem = node_fs_1.promises) {
|
|
137
137
|
// Determine the configuration file path
|
|
138
138
|
const configPath = ngswConfigPath
|
|
139
139
|
? path.join(workspaceRoot, ngswConfigPath)
|
|
@@ -141,7 +141,7 @@ async function augmentAppWithServiceWorker(appRoot, workspaceRoot, outputPath, b
|
|
|
141
141
|
// Read the configuration file
|
|
142
142
|
let config;
|
|
143
143
|
try {
|
|
144
|
-
const configurationData = await
|
|
144
|
+
const configurationData = await inputFileSystem.readFile(configPath, 'utf-8');
|
|
145
145
|
config = JSON.parse(configurationData);
|
|
146
146
|
}
|
|
147
147
|
catch (error) {
|
|
@@ -158,11 +158,7 @@ async function augmentAppWithServiceWorker(appRoot, workspaceRoot, outputPath, b
|
|
|
158
158
|
const result = await augmentAppWithServiceWorkerCore(config, new CliFilesystem(outputFileSystem, outputPath), baseHref);
|
|
159
159
|
const copy = async (src, dest) => {
|
|
160
160
|
const resolvedDest = path.join(outputPath, dest);
|
|
161
|
-
return
|
|
162
|
-
? // Native FS (Builder).
|
|
163
|
-
inputputFileSystem.copyFile(src, resolvedDest, node_fs_1.constants.COPYFILE_FICLONE)
|
|
164
|
-
: // memfs (Webpack): Read the file from the input FS (disk) and write it to the output FS (memory).
|
|
165
|
-
outputFileSystem.writeFile(resolvedDest, await inputputFileSystem.readFile(src));
|
|
161
|
+
return outputFileSystem.writeFile(resolvedDest, await inputFileSystem.readFile(src));
|
|
166
162
|
};
|
|
167
163
|
await outputFileSystem.writeFile(path.join(outputPath, 'ngsw.json'), result.manifest);
|
|
168
164
|
for (const { source, destination } of result.assetFiles) {
|
package/src/utils/version.js
CHANGED
|
@@ -12,52 +12,31 @@ exports.assertCompatibleAngularVersion = assertCompatibleAngularVersion;
|
|
|
12
12
|
const node_module_1 = require("node:module");
|
|
13
13
|
const semver_1 = require("semver");
|
|
14
14
|
function assertCompatibleAngularVersion(projectRoot) {
|
|
15
|
-
let angularCliPkgJson;
|
|
16
15
|
let angularPkgJson;
|
|
17
16
|
// Create a custom require function for ESM compliance.
|
|
18
17
|
// NOTE: The trailing slash is significant.
|
|
19
18
|
const projectRequire = (0, node_module_1.createRequire)(projectRoot + '/');
|
|
20
19
|
try {
|
|
21
|
-
|
|
22
|
-
angularPkgJson = projectRequire(angularPackagePath);
|
|
20
|
+
angularPkgJson = projectRequire('@angular/core/package.json');
|
|
23
21
|
}
|
|
24
22
|
catch {
|
|
25
|
-
console.error('
|
|
23
|
+
console.error('Error: It appears that "@angular/core" is missing as a dependency. Please ensure it is included in your project.');
|
|
26
24
|
process.exit(2);
|
|
27
25
|
}
|
|
28
|
-
if (!
|
|
29
|
-
console.error('
|
|
30
|
-
'This likely
|
|
26
|
+
if (!angularPkgJson?.['version']) {
|
|
27
|
+
console.error('Error: Unable to determine the versions of "@angular/core".\n' +
|
|
28
|
+
'This likely indicates a corrupted local installation. Please try reinstalling your packages.');
|
|
31
29
|
process.exit(2);
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!(angularCliPkgJson && angularCliPkgJson['version'])) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
// Not using @angular-devkit/build-angular with @angular/cli is ok too.
|
|
42
|
-
// In this case we don't provide as many version checks.
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (angularCliPkgJson['version'] === '0.0.0' || angularPkgJson['version'] === '0.0.0') {
|
|
46
|
-
// Internal CLI testing version or integration testing in the angular/angular
|
|
47
|
-
// repository with the generated development @angular/core npm package which is versioned "0.0.0".
|
|
31
|
+
const supportedAngularSemver = '^19.0.0 || ^19.2.0-next.0';
|
|
32
|
+
if (angularPkgJson['version'] === '0.0.0' || supportedAngularSemver.startsWith('0.0.0')) {
|
|
33
|
+
// Internal CLI and FW testing version.
|
|
48
34
|
return;
|
|
49
35
|
}
|
|
50
|
-
let supportedAngularSemver;
|
|
51
|
-
try {
|
|
52
|
-
supportedAngularSemver = projectRequire('@angular/build/package.json')['peerDependencies']['@angular/compiler-cli'];
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
supportedAngularSemver = projectRequire('@angular-devkit/build-angular/package.json')['peerDependencies']['@angular/compiler-cli'];
|
|
56
|
-
}
|
|
57
36
|
const angularVersion = new semver_1.SemVer(angularPkgJson['version']);
|
|
58
37
|
if (!(0, semver_1.satisfies)(angularVersion, supportedAngularSemver, { includePrerelease: true })) {
|
|
59
|
-
console.error(`
|
|
60
|
-
`but Angular version ${angularVersion}
|
|
38
|
+
console.error(`Error: The current version of "@angular/build" supports Angular versions ${supportedAngularSemver},\n` +
|
|
39
|
+
`but detected Angular version ${angularVersion} instead.\n` +
|
|
61
40
|
'Please visit the link below to find instructions on how to update Angular.\nhttps://update.angular.dev/');
|
|
62
41
|
process.exit(3);
|
|
63
42
|
}
|