@angular/build 18.1.0-rc.0 → 18.1.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/package.json +11 -9
- package/src/builders/application/chunk-optimizer.d.ts +9 -0
- package/src/builders/application/chunk-optimizer.js +169 -0
- package/src/builders/application/execute-build.js +9 -3
- package/src/builders/application/execute-post-bundle.js +4 -4
- package/src/builders/dev-server/vite-server.js +0 -1
- package/src/tools/angular/compilation/aot-compilation.js +3 -1
- package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
- package/src/tools/angular/compilation/parallel-worker.js +2 -1
- package/src/tools/esbuild/angular/compiler-plugin.js +3 -1
- package/src/tools/esbuild/application-code-bundle.js +7 -2
- package/src/tools/esbuild/budget-stats.d.ts +2 -2
- package/src/tools/esbuild/budget-stats.js +19 -14
- package/src/tools/esbuild/bundler-context.d.ts +1 -0
- package/src/tools/esbuild/bundler-execution-result.d.ts +1 -1
- package/src/tools/esbuild/bundler-execution-result.js +1 -1
- package/src/tools/esbuild/i18n-inliner.js +2 -2
- package/src/tools/esbuild/javascript-transformer-worker.js +3 -1
- package/src/tools/esbuild/utils.d.ts +4 -3
- package/src/tools/esbuild/utils.js +97 -56
- package/src/tools/esbuild/wasm-plugin.d.ts +28 -0
- package/src/tools/esbuild/wasm-plugin.js +210 -0
- package/src/tools/esbuild/wasm.d.ts +25 -0
- package/src/tools/vite/angular-memory-plugin.d.ts +2 -5
- package/src/tools/vite/angular-memory-plugin.js +7 -161
- package/src/tools/vite/middlewares/assets-middleware.d.ts +10 -0
- package/src/tools/vite/middlewares/assets-middleware.js +94 -0
- package/src/tools/vite/middlewares/html-fallback-middleware.d.ts +10 -0
- package/src/tools/vite/middlewares/html-fallback-middleware.js +24 -0
- package/src/tools/vite/middlewares/index-html-middleware.d.ts +10 -0
- package/src/tools/vite/middlewares/index-html-middleware.js +43 -0
- package/src/tools/vite/middlewares/index.d.ts +11 -0
- package/src/tools/vite/middlewares/index.js +18 -0
- package/src/tools/vite/middlewares/ssr-middleware.d.ts +12 -0
- package/src/tools/vite/middlewares/ssr-middleware.js +57 -0
- package/src/tools/vite/utils.d.ts +16 -0
- package/src/tools/vite/utils.js +40 -0
- package/src/utils/environment-options.d.ts +1 -0
- package/src/utils/environment-options.js +4 -2
- package/src/utils/normalize-cache.js +1 -1
|
@@ -11,20 +11,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
};
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.logBuildStats = logBuildStats;
|
|
14
|
+
exports.getChunkNameFromMetafile = getChunkNameFromMetafile;
|
|
14
15
|
exports.calculateEstimatedTransferSizes = calculateEstimatedTransferSizes;
|
|
15
16
|
exports.withSpinner = withSpinner;
|
|
16
17
|
exports.withNoProgress = withNoProgress;
|
|
17
18
|
exports.getFeatureSupport = getFeatureSupport;
|
|
18
19
|
exports.writeResultFiles = writeResultFiles;
|
|
19
20
|
exports.emitFilesToDisk = emitFilesToDisk;
|
|
20
|
-
exports.
|
|
21
|
-
exports.createOutputFileFromData = createOutputFileFromData;
|
|
21
|
+
exports.createOutputFile = createOutputFile;
|
|
22
22
|
exports.convertOutputFile = convertOutputFile;
|
|
23
23
|
exports.transformSupportedBrowsersToTargets = transformSupportedBrowsersToTargets;
|
|
24
24
|
exports.getSupportedNodeTargets = getSupportedNodeTargets;
|
|
25
25
|
exports.createJsonBuildManifest = createJsonBuildManifest;
|
|
26
26
|
exports.logMessages = logMessages;
|
|
27
27
|
exports.isZonelessApp = isZonelessApp;
|
|
28
|
+
exports.getEntryPointName = getEntryPointName;
|
|
28
29
|
const esbuild_1 = require("esbuild");
|
|
29
30
|
const node_crypto_1 = require("node:crypto");
|
|
30
31
|
const node_fs_1 = require("node:fs");
|
|
@@ -36,40 +37,29 @@ const semver_1 = require("semver");
|
|
|
36
37
|
const spinner_1 = require("../../utils/spinner");
|
|
37
38
|
const stats_table_1 = require("../../utils/stats-table");
|
|
38
39
|
const bundler_context_1 = require("./bundler-context");
|
|
39
|
-
function logBuildStats(metafile, initial, budgetFailures, colors, changedFiles, estimatedTransferSizes, ssrOutputEnabled, verbose) {
|
|
40
|
+
function logBuildStats(metafile, outputFiles, initial, budgetFailures, colors, changedFiles, estimatedTransferSizes, ssrOutputEnabled, verbose) {
|
|
40
41
|
const browserStats = [];
|
|
41
42
|
const serverStats = [];
|
|
42
43
|
let unchangedCount = 0;
|
|
43
|
-
for (const
|
|
44
|
+
for (const { path: file, size, type } of outputFiles) {
|
|
44
45
|
// Only display JavaScript and CSS files
|
|
45
46
|
if (!/\.(?:css|m?js)$/.test(file)) {
|
|
46
47
|
continue;
|
|
47
48
|
}
|
|
48
|
-
// Skip internal component resources
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
-
if (output['ng-component']) {
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
49
|
// Show only changed files if a changed list is provided
|
|
54
50
|
if (changedFiles && !changedFiles.has(file)) {
|
|
55
51
|
++unchangedCount;
|
|
56
52
|
continue;
|
|
57
53
|
}
|
|
58
|
-
|
|
59
|
-
const isPlatformServer = output['ng-platform-server'];
|
|
54
|
+
const isPlatformServer = type === bundler_context_1.BuildOutputFileType.Server;
|
|
60
55
|
if (isPlatformServer && !ssrOutputEnabled) {
|
|
61
56
|
// Only log server build stats when SSR is enabled.
|
|
62
57
|
continue;
|
|
63
58
|
}
|
|
64
|
-
|
|
65
|
-
if (name === undefined && output.entryPoint) {
|
|
66
|
-
name = (0, node_path_1.basename)(output.entryPoint)
|
|
67
|
-
.replace(/\.[cm]?[jt]s$/, '')
|
|
68
|
-
.replace(/[\\/.]/g, '-');
|
|
69
|
-
}
|
|
59
|
+
const name = initial.get(file)?.name ?? getChunkNameFromMetafile(metafile, file);
|
|
70
60
|
const stat = {
|
|
71
61
|
initial: initial.has(file),
|
|
72
|
-
stats: [file, name ?? '-',
|
|
62
|
+
stats: [file, name ?? '-', size, estimatedTransferSizes?.get(file) ?? '-'],
|
|
73
63
|
};
|
|
74
64
|
if (isPlatformServer) {
|
|
75
65
|
serverStats.push(stat);
|
|
@@ -90,6 +80,11 @@ function logBuildStats(metafile, initial, budgetFailures, colors, changedFiles,
|
|
|
90
80
|
}
|
|
91
81
|
return '';
|
|
92
82
|
}
|
|
83
|
+
function getChunkNameFromMetafile(metafile, file) {
|
|
84
|
+
if (metafile.outputs[file]?.entryPoint) {
|
|
85
|
+
return getEntryPointName(metafile.outputs[file].entryPoint);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
93
88
|
async function calculateEstimatedTransferSizes(outputFiles) {
|
|
94
89
|
const sizes = new Map();
|
|
95
90
|
if (outputFiles.length <= 0) {
|
|
@@ -158,8 +153,6 @@ function getFeatureSupport(target, nativeAsyncAwait) {
|
|
|
158
153
|
// will be used instead which provides a workaround for the performance issue.
|
|
159
154
|
// For more details: https://bugs.chromium.org/p/v8/issues/detail?id=11536
|
|
160
155
|
'object-rest-spread': false,
|
|
161
|
-
// Using top-level-await is not guaranteed to be safe with some code optimizations.
|
|
162
|
-
'top-level-await': false,
|
|
163
156
|
};
|
|
164
157
|
// Detect Safari browser versions that have a class field behavior bug
|
|
165
158
|
// See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033
|
|
@@ -245,49 +238,91 @@ async function emitFilesToDisk(files, writeFileCallback) {
|
|
|
245
238
|
await Promise.all(actions);
|
|
246
239
|
}
|
|
247
240
|
}
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
241
|
+
function createOutputFile(path, data, type) {
|
|
242
|
+
if (typeof data === 'string') {
|
|
243
|
+
let cachedContents = null;
|
|
244
|
+
let cachedText = data;
|
|
245
|
+
let cachedHash = null;
|
|
246
|
+
return {
|
|
247
|
+
path,
|
|
248
|
+
type,
|
|
249
|
+
get contents() {
|
|
250
|
+
cachedContents ??= new TextEncoder().encode(data);
|
|
251
|
+
return cachedContents;
|
|
252
|
+
},
|
|
253
|
+
set contents(value) {
|
|
254
|
+
cachedContents = value;
|
|
255
|
+
cachedText = null;
|
|
256
|
+
},
|
|
257
|
+
get text() {
|
|
258
|
+
cachedText ??= new TextDecoder('utf-8').decode(this.contents);
|
|
259
|
+
return cachedText;
|
|
260
|
+
},
|
|
261
|
+
get size() {
|
|
262
|
+
return this.contents.byteLength;
|
|
263
|
+
},
|
|
264
|
+
get hash() {
|
|
265
|
+
cachedHash ??= (0, node_crypto_1.createHash)('sha256')
|
|
266
|
+
.update(cachedText ?? this.contents)
|
|
267
|
+
.digest('hex');
|
|
268
|
+
return cachedHash;
|
|
269
|
+
},
|
|
270
|
+
clone() {
|
|
271
|
+
return createOutputFile(this.path, cachedText ?? this.contents, this.type);
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
let cachedContents = data;
|
|
277
|
+
let cachedText = null;
|
|
278
|
+
let cachedHash = null;
|
|
279
|
+
return {
|
|
280
|
+
get contents() {
|
|
281
|
+
return cachedContents;
|
|
282
|
+
},
|
|
283
|
+
set contents(value) {
|
|
284
|
+
cachedContents = value;
|
|
285
|
+
cachedText = null;
|
|
286
|
+
},
|
|
287
|
+
path,
|
|
288
|
+
type,
|
|
289
|
+
get size() {
|
|
290
|
+
return this.contents.byteLength;
|
|
291
|
+
},
|
|
292
|
+
get text() {
|
|
293
|
+
cachedText ??= new TextDecoder('utf-8').decode(this.contents);
|
|
294
|
+
return cachedText;
|
|
295
|
+
},
|
|
296
|
+
get hash() {
|
|
297
|
+
cachedHash ??= (0, node_crypto_1.createHash)('sha256').update(this.contents).digest('hex');
|
|
298
|
+
return cachedHash;
|
|
299
|
+
},
|
|
300
|
+
clone() {
|
|
301
|
+
return createOutputFile(this.path, this.contents, this.type);
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
}
|
|
263
305
|
}
|
|
264
|
-
function
|
|
306
|
+
function convertOutputFile(file, type) {
|
|
307
|
+
let { contents: cachedContents } = file;
|
|
308
|
+
let cachedText = null;
|
|
265
309
|
return {
|
|
266
|
-
path,
|
|
267
|
-
type,
|
|
268
|
-
get text() {
|
|
269
|
-
return Buffer.from(data.buffer, data.byteOffset, data.byteLength).toString('utf-8');
|
|
270
|
-
},
|
|
271
|
-
get hash() {
|
|
272
|
-
return (0, node_crypto_1.createHash)('sha256').update(this.text).digest('hex');
|
|
273
|
-
},
|
|
274
310
|
get contents() {
|
|
275
|
-
return
|
|
311
|
+
return cachedContents;
|
|
276
312
|
},
|
|
277
|
-
|
|
278
|
-
|
|
313
|
+
set contents(value) {
|
|
314
|
+
cachedContents = value;
|
|
315
|
+
cachedText = null;
|
|
279
316
|
},
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
function convertOutputFile(file, type) {
|
|
283
|
-
const { path, contents, hash } = file;
|
|
284
|
-
return {
|
|
285
|
-
contents,
|
|
286
|
-
hash,
|
|
287
|
-
path,
|
|
317
|
+
hash: file.hash,
|
|
318
|
+
path: file.path,
|
|
288
319
|
type,
|
|
320
|
+
get size() {
|
|
321
|
+
return this.contents.byteLength;
|
|
322
|
+
},
|
|
289
323
|
get text() {
|
|
290
|
-
|
|
324
|
+
cachedText ??= new TextDecoder('utf-8').decode(this.contents);
|
|
325
|
+
return cachedText;
|
|
291
326
|
},
|
|
292
327
|
clone() {
|
|
293
328
|
return convertOutputFile(this, this.type);
|
|
@@ -389,3 +424,9 @@ function isZonelessApp(polyfills) {
|
|
|
389
424
|
// TODO: Instead, we should rely on the presence of zone.js in the polyfills build metadata.
|
|
390
425
|
return !polyfills?.some((p) => p === 'zone.js' || /\.[mc]?[jt]s$/.test(p));
|
|
391
426
|
}
|
|
427
|
+
function getEntryPointName(entryPoint) {
|
|
428
|
+
return (0, node_path_1.basename)(entryPoint)
|
|
429
|
+
.replace(/(.*:)/, '') // global:bundle.css -> bundle.css
|
|
430
|
+
.replace(/\.[cm]?[jt]s$/, '')
|
|
431
|
+
.replace(/[\\/.]/g, '-');
|
|
432
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
import type { Plugin } from 'esbuild';
|
|
9
|
+
import { LoadResultCache } from './load-result-cache';
|
|
10
|
+
/**
|
|
11
|
+
* Options for the Angular WASM esbuild plugin
|
|
12
|
+
* @see createWasmPlugin
|
|
13
|
+
*/
|
|
14
|
+
export interface WasmPluginOptions {
|
|
15
|
+
/** Allow generation of async (proposal compliant) WASM imports. This requires zoneless to enable async/await. */
|
|
16
|
+
allowAsync?: boolean;
|
|
17
|
+
/** Load results cache. */
|
|
18
|
+
cache?: LoadResultCache;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates an esbuild plugin to use WASM files with import statements and expressions.
|
|
22
|
+
* The default behavior follows the WebAssembly/ES mode integration proposal found at
|
|
23
|
+
* https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration.
|
|
24
|
+
* This behavior requires top-level await support which is only available in zoneless
|
|
25
|
+
* Angular applications.
|
|
26
|
+
* @returns An esbuild plugin.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createWasmPlugin(options: WasmPluginOptions): Plugin;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright Google LLC All Rights Reserved.
|
|
5
|
+
*
|
|
6
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
7
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
8
|
+
*/
|
|
9
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.createWasmPlugin = createWasmPlugin;
|
|
14
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
15
|
+
const node_crypto_1 = require("node:crypto");
|
|
16
|
+
const promises_1 = require("node:fs/promises");
|
|
17
|
+
const node_path_1 = require("node:path");
|
|
18
|
+
const error_1 = require("../../utils/error");
|
|
19
|
+
const load_result_cache_1 = require("./load-result-cache");
|
|
20
|
+
const WASM_INIT_NAMESPACE = 'angular:wasm:init';
|
|
21
|
+
const WASM_CONTENTS_NAMESPACE = 'angular:wasm:contents';
|
|
22
|
+
const WASM_RESOLVE_SYMBOL = Symbol('WASM_RESOLVE_SYMBOL');
|
|
23
|
+
// See: https://github.com/tc39/proposal-regexp-unicode-property-escapes/blob/fe6d07fad74cd0192d154966baa1e95e7cda78a1/README.md#other-examples
|
|
24
|
+
const ecmaIdentifierNameRegExp = /^(?:[$_\p{ID_Start}])(?:[$_\u200C\u200D\p{ID_Continue}])*$/u;
|
|
25
|
+
/**
|
|
26
|
+
* Creates an esbuild plugin to use WASM files with import statements and expressions.
|
|
27
|
+
* The default behavior follows the WebAssembly/ES mode integration proposal found at
|
|
28
|
+
* https://github.com/WebAssembly/esm-integration/tree/main/proposals/esm-integration.
|
|
29
|
+
* This behavior requires top-level await support which is only available in zoneless
|
|
30
|
+
* Angular applications.
|
|
31
|
+
* @returns An esbuild plugin.
|
|
32
|
+
*/
|
|
33
|
+
function createWasmPlugin(options) {
|
|
34
|
+
const { allowAsync = false, cache } = options;
|
|
35
|
+
return {
|
|
36
|
+
name: 'angular-wasm',
|
|
37
|
+
setup(build) {
|
|
38
|
+
build.onResolve({ filter: /.wasm$/ }, async (args) => {
|
|
39
|
+
// Skip if already resolving the WASM file to avoid infinite resolution
|
|
40
|
+
if (args.pluginData?.[WASM_RESOLVE_SYMBOL]) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// Skip if not an import statement or expression
|
|
44
|
+
if (args.kind !== 'import-statement' && args.kind !== 'dynamic-import') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// When in the initialization namespace, the content has already been resolved
|
|
48
|
+
// and only needs to be loaded for use with the initialization code.
|
|
49
|
+
if (args.namespace === WASM_INIT_NAMESPACE) {
|
|
50
|
+
return {
|
|
51
|
+
namespace: WASM_CONTENTS_NAMESPACE,
|
|
52
|
+
path: (0, node_path_1.join)(args.resolveDir, args.path),
|
|
53
|
+
pluginData: args.pluginData,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Skip if a custom loader is defined
|
|
57
|
+
if (build.initialOptions.loader?.['.wasm'] || args.with['loader']) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Attempt full resolution of the WASM file
|
|
61
|
+
const resolveOptions = {
|
|
62
|
+
...args,
|
|
63
|
+
pluginData: { [WASM_RESOLVE_SYMBOL]: true },
|
|
64
|
+
};
|
|
65
|
+
// The "path" property will cause an error if used in the resolve call
|
|
66
|
+
delete resolveOptions.path;
|
|
67
|
+
const result = await build.resolve(args.path, resolveOptions);
|
|
68
|
+
// Skip if there are errors, is external, or another plugin resolves to a custom namespace
|
|
69
|
+
if (result.errors.length > 0 || result.external || result.namespace !== 'file') {
|
|
70
|
+
// Reuse already resolved result
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
...result,
|
|
75
|
+
namespace: WASM_INIT_NAMESPACE,
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
build.onLoad({ filter: /.wasm$/, namespace: WASM_INIT_NAMESPACE }, (0, load_result_cache_1.createCachedLoad)(cache, async (args) => {
|
|
79
|
+
// Ensure async mode is supported
|
|
80
|
+
if (!allowAsync) {
|
|
81
|
+
return {
|
|
82
|
+
errors: [
|
|
83
|
+
{
|
|
84
|
+
text: 'WASM/ES module integration imports are not supported with Zone.js applications',
|
|
85
|
+
notes: [
|
|
86
|
+
{
|
|
87
|
+
text: 'Information about zoneless Angular applications can be found here: https://angular.dev/guide/experimental/zoneless',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
const wasmContents = await (0, promises_1.readFile)(args.path);
|
|
95
|
+
// Inline WASM code less than 10kB
|
|
96
|
+
const inlineWasm = wasmContents.byteLength < 10_000;
|
|
97
|
+
// Add import of WASM contents
|
|
98
|
+
let initContents = `import ${inlineWasm ? 'wasmData' : 'wasmPath'} from ${JSON.stringify((0, node_path_1.basename)(args.path))}`;
|
|
99
|
+
initContents += inlineWasm ? ' with { loader: "binary" };' : ';\n\n';
|
|
100
|
+
// Read from the file system when on Node.js (SSR) and not inline
|
|
101
|
+
if (!inlineWasm && build.initialOptions.platform === 'node') {
|
|
102
|
+
initContents += 'import { readFile } from "node:fs/promises";\n';
|
|
103
|
+
initContents +=
|
|
104
|
+
'const wasmData = await readFile(new URL(wasmPath, import.meta.url));\n';
|
|
105
|
+
}
|
|
106
|
+
// Create initialization function
|
|
107
|
+
initContents += generateInitHelper(!inlineWasm && build.initialOptions.platform !== 'node', wasmContents);
|
|
108
|
+
// Analyze WASM for imports and exports
|
|
109
|
+
let importModuleNames, exportNames;
|
|
110
|
+
try {
|
|
111
|
+
const wasm = await WebAssembly.compile(wasmContents);
|
|
112
|
+
importModuleNames = new Set(WebAssembly.Module.imports(wasm).map((value) => value.module));
|
|
113
|
+
exportNames = WebAssembly.Module.exports(wasm).map((value) => value.name);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
(0, error_1.assertIsError)(error);
|
|
117
|
+
return {
|
|
118
|
+
errors: [{ text: 'Unable to analyze WASM file', notes: [{ text: error.message }] }],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// Ensure export names are valid JavaScript identifiers
|
|
122
|
+
const invalidExportNames = exportNames.filter((name) => !ecmaIdentifierNameRegExp.test(name));
|
|
123
|
+
if (invalidExportNames.length > 0) {
|
|
124
|
+
return {
|
|
125
|
+
errors: invalidExportNames.map((name) => ({
|
|
126
|
+
text: 'WASM export names must be valid JavaScript identifiers',
|
|
127
|
+
notes: [
|
|
128
|
+
{
|
|
129
|
+
text: `The export "${name}" is not valid. The WASM file should be updated to remove this error.`,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
})),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
// Add import statements and setup import object
|
|
136
|
+
initContents += 'const importObject = Object.create(null);\n';
|
|
137
|
+
let importIndex = 0;
|
|
138
|
+
for (const moduleName of importModuleNames) {
|
|
139
|
+
// Add a namespace import for each module name
|
|
140
|
+
initContents += `import * as wasm_import_${++importIndex} from ${JSON.stringify(moduleName)};\n`;
|
|
141
|
+
// Add the namespace object to the import object
|
|
142
|
+
initContents += `importObject[${JSON.stringify(moduleName)}] = wasm_import_${importIndex};\n`;
|
|
143
|
+
}
|
|
144
|
+
// Instantiate the module
|
|
145
|
+
initContents += 'const instance = await init(importObject);\n';
|
|
146
|
+
// Add exports
|
|
147
|
+
const exportNameList = exportNames.join(', ');
|
|
148
|
+
initContents += `const { ${exportNameList} } = instance.exports;\n`;
|
|
149
|
+
initContents += `export { ${exportNameList} }\n`;
|
|
150
|
+
return {
|
|
151
|
+
contents: initContents,
|
|
152
|
+
loader: 'js',
|
|
153
|
+
resolveDir: (0, node_path_1.dirname)(args.path),
|
|
154
|
+
pluginData: { wasmContents },
|
|
155
|
+
watchFiles: [args.path],
|
|
156
|
+
};
|
|
157
|
+
}));
|
|
158
|
+
build.onLoad({ filter: /.wasm$/, namespace: WASM_CONTENTS_NAMESPACE }, async (args) => {
|
|
159
|
+
const contents = args.pluginData.wasmContents ?? (await (0, promises_1.readFile)(args.path));
|
|
160
|
+
let loader = 'file';
|
|
161
|
+
if (args.with.loader) {
|
|
162
|
+
(0, node_assert_1.default)(args.with.loader === 'binary' || args.with.loader === 'file', 'WASM loader type should only be binary or file.');
|
|
163
|
+
loader = args.with.loader;
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
contents,
|
|
167
|
+
loader,
|
|
168
|
+
watchFiles: [args.path],
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Generates the string content of the WASM initialization helper function.
|
|
176
|
+
* This function supports both file fetching and inline byte data depending on
|
|
177
|
+
* the preferred option for the WASM file. When fetching, an integrity hash is
|
|
178
|
+
* also generated and used with the fetch action.
|
|
179
|
+
*
|
|
180
|
+
* @param streaming Uses fetch and WebAssembly.instantiateStreaming.
|
|
181
|
+
* @param wasmContents The binary contents to generate an integrity hash.
|
|
182
|
+
* @returns A string containing the initialization function.
|
|
183
|
+
*/
|
|
184
|
+
function generateInitHelper(streaming, wasmContents) {
|
|
185
|
+
let resultContents;
|
|
186
|
+
if (streaming) {
|
|
187
|
+
const fetchOptions = {
|
|
188
|
+
integrity: 'sha256-' + (0, node_crypto_1.createHash)('sha-256').update(wasmContents).digest('base64'),
|
|
189
|
+
};
|
|
190
|
+
const fetchContents = `fetch(new URL(wasmPath, import.meta.url), ${JSON.stringify(fetchOptions)})`;
|
|
191
|
+
resultContents = `await WebAssembly.instantiateStreaming(${fetchContents}, imports)`;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
resultContents = 'await WebAssembly.instantiate(wasmData, imports)';
|
|
195
|
+
}
|
|
196
|
+
const contents = `
|
|
197
|
+
let mod;
|
|
198
|
+
async function init(imports) {
|
|
199
|
+
if (mod) {
|
|
200
|
+
return await WebAssembly.instantiate(mod, imports);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = ${resultContents};
|
|
204
|
+
mod = result.module;
|
|
205
|
+
|
|
206
|
+
return result.instance;
|
|
207
|
+
}
|
|
208
|
+
`;
|
|
209
|
+
return contents;
|
|
210
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright Google LLC All Rights Reserved.
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by an MIT-style license that can be
|
|
6
|
+
* found in the LICENSE file at https://angular.dev/license
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** @fileoverview
|
|
10
|
+
* TypeScript does not provide a separate lib for WASM types and the Node.js
|
|
11
|
+
* types (`@types/node`) does not contain them either. This type definition
|
|
12
|
+
* file provides type information for the subset of functionality required
|
|
13
|
+
* by the Angular build process. Ideally this can be removed when the WASM
|
|
14
|
+
* type situation has improved.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
declare namespace WebAssembly {
|
|
18
|
+
class Module {
|
|
19
|
+
constructor(data: Uint8Array);
|
|
20
|
+
|
|
21
|
+
static imports(mod: Module): { module: string; name: string }[];
|
|
22
|
+
static exports(mode: Module): { name: string }[];
|
|
23
|
+
}
|
|
24
|
+
function compile(data: Uint8Array): Promise<Module>;
|
|
25
|
+
}
|
|
@@ -6,18 +6,15 @@
|
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
8
|
import type { Connect, Plugin } from 'vite';
|
|
9
|
+
import { AngularMemoryOutputFiles } from './utils';
|
|
9
10
|
export interface AngularMemoryPluginOptions {
|
|
10
11
|
workspaceRoot: string;
|
|
11
12
|
virtualProjectRoot: string;
|
|
12
|
-
outputFiles:
|
|
13
|
-
contents: Uint8Array;
|
|
14
|
-
servable: boolean;
|
|
15
|
-
}>;
|
|
13
|
+
outputFiles: AngularMemoryOutputFiles;
|
|
16
14
|
assets: Map<string, string>;
|
|
17
15
|
ssr: boolean;
|
|
18
16
|
external?: string[];
|
|
19
17
|
extensionMiddleware?: Connect.NextHandleFunction[];
|
|
20
|
-
extraHeaders?: Record<string, string>;
|
|
21
18
|
indexHtmlTransformer?: (content: string) => Promise<string>;
|
|
22
19
|
normalizePath: (path: string) => string;
|
|
23
20
|
}
|