@angular/build 19.2.0-next.1 → 19.2.0-rc.0
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/builders.json +5 -0
- package/package.json +18 -13
- package/src/builders/application/build-action.js +6 -1
- package/src/builders/application/execute-build.js +1 -5
- package/src/builders/application/execute-post-bundle.js +2 -2
- package/src/builders/application/i18n.js +6 -3
- package/src/builders/application/options.d.ts +5 -1
- package/src/builders/application/options.js +9 -1
- package/src/builders/dev-server/schema.d.ts +4 -4
- package/src/builders/dev-server/schema.json +2 -2
- package/src/builders/dev-server/vite-server.js +45 -4
- package/src/builders/karma/application_builder.d.ts +15 -0
- package/src/builders/karma/application_builder.js +490 -0
- package/src/builders/karma/find-tests.d.ts +15 -0
- package/src/builders/karma/find-tests.js +144 -0
- package/src/builders/karma/index.d.ts +23 -0
- package/src/builders/karma/index.js +135 -0
- package/src/builders/karma/polyfills/init_sourcemaps.js +10 -0
- package/src/builders/karma/polyfills/init_test_bed.js +19 -0
- package/src/builders/karma/polyfills/jasmine_global.js +18 -0
- package/src/builders/karma/polyfills/jasmine_global_cleanup.js +14 -0
- package/src/builders/karma/schema.d.ts +248 -0
- package/src/builders/karma/schema.js +15 -0
- package/src/builders/karma/schema.json +347 -0
- package/src/private.d.ts +2 -0
- package/src/private.js +6 -1
- 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 +8 -3
- package/src/tools/esbuild/i18n-inliner.d.ts +9 -0
- package/src/tools/esbuild/i18n-inliner.js +110 -21
- package/src/tools/esbuild/index-html-generator.js +1 -8
- 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/middlewares/html-fallback-middleware.js +2 -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/error.js +2 -2
- package/src/utils/index-file/auto-csp.js +1 -1
- package/src/utils/index-file/index-html-generator.js +3 -2
- package/src/utils/index-file/inline-critical-css.d.ts +1 -0
- package/src/utils/index-file/inline-critical-css.js +11 -7
- package/src/utils/load-translations.js +3 -3
- package/src/utils/normalize-asset-patterns.js +3 -3
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.js +1 -2
- package/src/utils/server-rendering/manifest.js +7 -2
- package/src/utils/server-rendering/prerender.js +3 -3
- package/src/utils/server-rendering/render-worker.js +2 -2
- package/src/utils/server-rendering/routes-extractor-worker.js +2 -2
- package/src/utils/service-worker.js +2 -2
- package/src/utils/version.js +2 -2
|
@@ -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;
|
|
@@ -59,13 +59,6 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
|
|
|
59
59
|
}
|
|
60
60
|
throw new Error(`Output file does not exist: ${relativefilePath}`);
|
|
61
61
|
};
|
|
62
|
-
// Read the Auto CSP options.
|
|
63
|
-
const autoCsp = buildOptions.security?.autoCsp;
|
|
64
|
-
const autoCspOptions = autoCsp === true
|
|
65
|
-
? { unsafeEval: false }
|
|
66
|
-
: autoCsp
|
|
67
|
-
? { unsafeEval: !!autoCsp.unsafeEval }
|
|
68
|
-
: undefined;
|
|
69
62
|
// Create an index HTML generator that reads from the in-memory output files
|
|
70
63
|
const indexHtmlGenerator = new index_html_generator_1.IndexHtmlGenerator({
|
|
71
64
|
indexPath: indexHtmlOptions.input,
|
|
@@ -78,7 +71,7 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
|
|
|
78
71
|
generateDedicatedSSRContent: !!(buildOptions.ssrOptions ||
|
|
79
72
|
buildOptions.prerenderOptions ||
|
|
80
73
|
buildOptions.appShellOptions),
|
|
81
|
-
autoCsp:
|
|
74
|
+
autoCsp: buildOptions.security.autoCsp,
|
|
82
75
|
});
|
|
83
76
|
indexHtmlGenerator.readAsset = readAsset;
|
|
84
77
|
return indexHtmlGenerator.process({
|
|
@@ -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
|
}
|
|
@@ -20,7 +20,8 @@ function angularHtmlFallbackMiddleware(req, _res, next) {
|
|
|
20
20
|
}
|
|
21
21
|
if (req.url) {
|
|
22
22
|
const mimeType = (0, utils_1.lookupMimeTypeFromRequest)(req.url);
|
|
23
|
-
if (mimeType === 'text/html' || mimeType === 'application/xhtml+xml')
|
|
23
|
+
if ((mimeType === 'text/html' || mimeType === 'application/xhtml+xml') &&
|
|
24
|
+
!/^\/index\.(?:csr\.)?html/.test(req.url)) {
|
|
24
25
|
// eslint-disable-next-line no-console
|
|
25
26
|
console.warn(`Request for HTML file "${req.url}" was received but no asset found. Asset may be missing from build.`);
|
|
26
27
|
}
|
|
@@ -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
|
}
|
package/src/utils/error.js
CHANGED
|
@@ -11,10 +11,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.assertIsError = assertIsError;
|
|
14
|
-
const
|
|
14
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
15
15
|
function assertIsError(value) {
|
|
16
16
|
const isError = value instanceof Error ||
|
|
17
17
|
// The following is needing to identify errors coming from RxJs.
|
|
18
18
|
(typeof value === 'object' && value && 'name' in value && 'message' in value);
|
|
19
|
-
(0,
|
|
19
|
+
(0, node_assert_1.default)(isError, 'catch clause variable is not an Error instance');
|
|
20
20
|
}
|
|
@@ -112,7 +112,7 @@ async function autoCsp(html, unsafeEval = false) {
|
|
|
112
112
|
rewriter.emitRaw(`<script>${loaderScript}</script>`);
|
|
113
113
|
scriptContent = [];
|
|
114
114
|
}
|
|
115
|
-
rewriter.on('startTag', (tag
|
|
115
|
+
rewriter.on('startTag', (tag) => {
|
|
116
116
|
if (tag.tagName === 'script') {
|
|
117
117
|
openedScriptTag = tag;
|
|
118
118
|
const src = getScriptAttributeValue(tag, 'src');
|
|
@@ -32,7 +32,7 @@ class IndexHtmlGenerator {
|
|
|
32
32
|
this.plugins = [augmentIndexHtmlPlugin(this), ...extraCommonPlugins, postTransformPlugin(this)];
|
|
33
33
|
// CSR plugins
|
|
34
34
|
if (options?.optimization?.styles?.inlineCritical) {
|
|
35
|
-
this.csrPlugins.push(inlineCriticalCssPlugin(this));
|
|
35
|
+
this.csrPlugins.push(inlineCriticalCssPlugin(this, !!options.autoCsp));
|
|
36
36
|
}
|
|
37
37
|
this.csrPlugins.push(addNoncePlugin());
|
|
38
38
|
// SSR plugins
|
|
@@ -127,11 +127,12 @@ function inlineFontsPlugin({ options }) {
|
|
|
127
127
|
});
|
|
128
128
|
return async (html) => inlineFontsProcessor.process(html);
|
|
129
129
|
}
|
|
130
|
-
function inlineCriticalCssPlugin(generator) {
|
|
130
|
+
function inlineCriticalCssPlugin(generator, autoCsp) {
|
|
131
131
|
const inlineCriticalCssProcessor = new inline_critical_css_1.InlineCriticalCssProcessor({
|
|
132
132
|
minify: generator.options.optimization?.styles.minify,
|
|
133
133
|
deployUrl: generator.options.deployUrl,
|
|
134
134
|
readAsset: (filePath) => generator.readAsset(filePath),
|
|
135
|
+
autoCsp,
|
|
135
136
|
});
|
|
136
137
|
return async (html, options) => inlineCriticalCssProcessor.process(html, { outputPath: options.outputPath });
|
|
137
138
|
}
|
|
@@ -12,6 +12,7 @@ export interface InlineCriticalCssProcessorOptions {
|
|
|
12
12
|
minify?: boolean;
|
|
13
13
|
deployUrl?: string;
|
|
14
14
|
readAsset?: (path: string) => Promise<string>;
|
|
15
|
+
autoCsp?: boolean;
|
|
15
16
|
}
|
|
16
17
|
export declare class InlineCriticalCssProcessor {
|
|
17
18
|
protected readonly options: InlineCriticalCssProcessorOptions;
|
|
@@ -113,7 +113,7 @@ class BeastiesExtended extends BeastiesBase {
|
|
|
113
113
|
}
|
|
114
114
|
const returnValue = await super.embedLinkedStylesheet(link, document);
|
|
115
115
|
const cspNonce = this.findCspNonce(document);
|
|
116
|
-
if (cspNonce) {
|
|
116
|
+
if (cspNonce || this.optionsExtended.autoCsp) {
|
|
117
117
|
const beastiesMedia = link.getAttribute('onload')?.match(MEDIA_SET_HANDLER_PATTERN);
|
|
118
118
|
if (beastiesMedia) {
|
|
119
119
|
// If there's a Beasties-generated `onload` handler and the file has an Angular CSP nonce,
|
|
@@ -128,11 +128,13 @@ class BeastiesExtended extends BeastiesBase {
|
|
|
128
128
|
// a way of doing that at the moment so we fall back to doing it any time a `link` tag is
|
|
129
129
|
// inserted. We mitigate it by only iterating the direct children of the `<head>` which
|
|
130
130
|
// should be pretty shallow.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
child.
|
|
134
|
-
|
|
135
|
-
|
|
131
|
+
if (cspNonce) {
|
|
132
|
+
document.head.children.forEach((child) => {
|
|
133
|
+
if (child.tagName === 'style' && !child.hasAttribute('nonce')) {
|
|
134
|
+
child.setAttribute('nonce', cspNonce);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
136
138
|
}
|
|
137
139
|
return returnValue;
|
|
138
140
|
}
|
|
@@ -159,8 +161,10 @@ class BeastiesExtended extends BeastiesBase {
|
|
|
159
161
|
return;
|
|
160
162
|
}
|
|
161
163
|
const script = document.createElement('script');
|
|
162
|
-
script.setAttribute('nonce', nonce);
|
|
163
164
|
script.textContent = LINK_LOAD_SCRIPT_CONTENT;
|
|
165
|
+
if (nonce) {
|
|
166
|
+
script.setAttribute('nonce', nonce);
|
|
167
|
+
}
|
|
164
168
|
// Prepend the script to the head since it needs to
|
|
165
169
|
// run as early as possible, before the `link` tags.
|
|
166
170
|
document.head.insertBefore(script, link);
|
|
@@ -41,8 +41,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
41
41
|
})();
|
|
42
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
43
|
exports.createTranslationLoader = createTranslationLoader;
|
|
44
|
-
const
|
|
45
|
-
const fs = __importStar(require("fs"));
|
|
44
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
46
|
const load_esm_1 = require("./load-esm");
|
|
47
47
|
async function createTranslationLoader() {
|
|
48
48
|
const { parsers, diagnostics } = await importParsers();
|
|
@@ -55,7 +55,7 @@ async function createTranslationLoader() {
|
|
|
55
55
|
// Types don't overlap here so we need to use any.
|
|
56
56
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
57
|
const { locale, translations } = parser.parse(path, content, analysis.hint);
|
|
58
|
-
const integrity = 'sha256-' + (0,
|
|
58
|
+
const integrity = 'sha256-' + (0, node_crypto_1.createHash)('sha256').update(content).digest('base64');
|
|
59
59
|
return { format, locale, translations, diagnostics, integrity };
|
|
60
60
|
}
|
|
61
61
|
else {
|
|
@@ -45,9 +45,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.MissingAssetSourceRootException = void 0;
|
|
47
47
|
exports.normalizeAssetPatterns = normalizeAssetPatterns;
|
|
48
|
-
const fs_1 = require("fs");
|
|
49
48
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
50
|
-
const
|
|
49
|
+
const node_fs_1 = require("node:fs");
|
|
50
|
+
const path = __importStar(require("node:path"));
|
|
51
51
|
class MissingAssetSourceRootException extends Error {
|
|
52
52
|
constructor(path) {
|
|
53
53
|
super(`The ${path} asset path must start with the project source root.`);
|
|
@@ -73,7 +73,7 @@ function normalizeAssetPatterns(assetPatterns, workspaceRoot, projectRoot, proje
|
|
|
73
73
|
let glob, input;
|
|
74
74
|
let isDirectory = false;
|
|
75
75
|
try {
|
|
76
|
-
isDirectory = (0,
|
|
76
|
+
isDirectory = (0, node_fs_1.statSync)(resolvedAssetPath).isDirectory();
|
|
77
77
|
}
|
|
78
78
|
catch {
|
|
79
79
|
isDirectory = true;
|
|
@@ -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-
|
|
13
|
+
const VERSION = '19.2.0-rc.0';
|
|
14
14
|
function hasCacheMetadata(value) {
|
|
15
15
|
return (!!value &&
|
|
16
16
|
typeof value === 'object' &&
|
|
@@ -17,7 +17,6 @@ const node_assert_1 = __importDefault(require("node:assert"));
|
|
|
17
17
|
const node_crypto_1 = require("node:crypto");
|
|
18
18
|
const node_path_1 = require("node:path");
|
|
19
19
|
const node_url_1 = require("node:url");
|
|
20
|
-
const url_1 = require("url");
|
|
21
20
|
const javascript_transformer_1 = require("../../../tools/esbuild/javascript-transformer");
|
|
22
21
|
/**
|
|
23
22
|
* @note For some unknown reason, setting `globalThis.ngServerMode = true` does not work when using ESM loader hooks.
|
|
@@ -110,7 +109,7 @@ async function load(url, context, nextLoad) {
|
|
|
110
109
|
// Only module files potentially require transformation. Angular libraries that would
|
|
111
110
|
// need linking are ESM only.
|
|
112
111
|
if (format === 'module' && isFileProtocol(url)) {
|
|
113
|
-
const filePath = (0,
|
|
112
|
+
const filePath = (0, node_url_1.fileURLToPath)(url);
|
|
114
113
|
let source = await javascriptTransformer.transformFile(filePath);
|
|
115
114
|
if (filePath.includes('@angular/')) {
|
|
116
115
|
// Prepend 'var ngServerMode=true;' to the source.
|
|
@@ -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') {
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
* found in the LICENSE file at https://angular.dev/license
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
const
|
|
10
|
+
const node_worker_threads_1 = require("node:worker_threads");
|
|
11
11
|
const fetch_patch_1 = require("./fetch-patch");
|
|
12
12
|
const launch_server_1 = require("./launch-server");
|
|
13
13
|
const load_esm_from_memory_1 = require("./load-esm-from-memory");
|
|
14
14
|
/**
|
|
15
15
|
* This is passed as workerData when setting up the worker via the `piscina` package.
|
|
16
16
|
*/
|
|
17
|
-
const { outputMode, hasSsrEntry } =
|
|
17
|
+
const { outputMode, hasSsrEntry } = node_worker_threads_1.workerData;
|
|
18
18
|
let serverURL = launch_server_1.DEFAULT_URL;
|
|
19
19
|
/**
|
|
20
20
|
* Renders each route in routes and writes them to <outputPath>/<route>/index.html.
|