@angular/build 19.0.0-next.11 → 19.0.0-next.13
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 +8 -8
- package/src/builders/application/execute-build.js +18 -6
- package/src/builders/application/options.d.ts +1 -0
- package/src/builders/application/options.js +2 -1
- package/src/builders/application/schema.d.ts +29 -0
- package/src/builders/application/schema.json +27 -0
- package/src/builders/application/setup-bundling.d.ts +3 -1
- package/src/builders/application/setup-bundling.js +33 -6
- package/src/tools/angular/compilation/angular-compilation.d.ts +1 -0
- package/src/tools/angular/compilation/aot-compilation.d.ts +1 -0
- package/src/tools/angular/compilation/aot-compilation.js +39 -0
- package/src/tools/angular/compilation/parallel-worker.d.ts +1 -0
- package/src/tools/angular/compilation/parallel-worker.js +2 -1
- package/src/tools/esbuild/angular/compiler-plugin.d.ts +2 -4
- package/src/tools/esbuild/angular/compiler-plugin.js +1 -5
- package/src/tools/esbuild/application-code-bundle.d.ts +5 -4
- package/src/tools/esbuild/application-code-bundle.js +15 -15
- package/src/tools/esbuild/bundler-execution-result.d.ts +5 -2
- package/src/tools/esbuild/bundler-execution-result.js +5 -1
- package/src/tools/esbuild/compiler-plugin-options.d.ts +1 -4
- package/src/tools/esbuild/compiler-plugin-options.js +14 -40
- package/src/tools/esbuild/index-html-generator.js +8 -0
- package/src/tools/esbuild/javascript-transformer.js +2 -0
- package/src/tools/esbuild/utils.js +1 -30
- package/src/tools/vite/middlewares/assets-middleware.js +1 -1
- package/src/utils/index-file/auto-csp.d.ts +23 -0
- package/src/utils/index-file/auto-csp.js +283 -0
- package/src/utils/index-file/html-rewriting-stream.d.ts +5 -1
- package/src/utils/index-file/index-html-generator.d.ts +4 -0
- package/src/utils/index-file/index-html-generator.js +11 -0
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/server-rendering/manifest.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/build",
|
|
3
|
-
"version": "19.0.0-next.
|
|
3
|
+
"version": "19.0.0-next.13",
|
|
4
4
|
"description": "Official build system for Angular",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Angular CLI",
|
|
@@ -23,11 +23,11 @@
|
|
|
23
23
|
"builders": "builders.json",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ampproject/remapping": "2.3.0",
|
|
26
|
-
"@angular-devkit/architect": "0.1900.0-next.
|
|
27
|
-
"@babel/core": "7.25.
|
|
28
|
-
"@babel/helper-annotate-as-pure": "7.25.
|
|
26
|
+
"@angular-devkit/architect": "0.1900.0-next.13",
|
|
27
|
+
"@babel/core": "7.25.9",
|
|
28
|
+
"@babel/helper-annotate-as-pure": "7.25.9",
|
|
29
29
|
"@babel/helper-split-export-declaration": "7.24.7",
|
|
30
|
-
"@babel/plugin-syntax-import-attributes": "7.25.
|
|
30
|
+
"@babel/plugin-syntax-import-attributes": "7.25.9",
|
|
31
31
|
"@inquirer/confirm": "5.0.0",
|
|
32
32
|
"@vitejs/plugin-basic-ssl": "1.1.0",
|
|
33
33
|
"browserslist": "^4.23.0",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"picomatch": "4.0.2",
|
|
44
44
|
"piscina": "4.7.0",
|
|
45
45
|
"rollup": "4.24.0",
|
|
46
|
-
"sass": "1.
|
|
46
|
+
"sass": "1.80.3",
|
|
47
47
|
"semver": "7.6.3",
|
|
48
|
-
"vite": "5.4.
|
|
48
|
+
"vite": "5.4.10",
|
|
49
49
|
"watchpack": "2.4.2"
|
|
50
50
|
},
|
|
51
51
|
"optionalDependencies": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"@angular/localize": "^19.0.0-next.9",
|
|
58
58
|
"@angular/platform-server": "^19.0.0-next.9",
|
|
59
59
|
"@angular/service-worker": "^19.0.0-next.9",
|
|
60
|
-
"@angular/ssr": "^19.0.0-next.
|
|
60
|
+
"@angular/ssr": "^19.0.0-next.13",
|
|
61
61
|
"less": "^4.2.0",
|
|
62
62
|
"postcss": "^8.4.0",
|
|
63
63
|
"tailwindcss": "^2.0.0 || ^3.0.0",
|
|
@@ -41,17 +41,25 @@ async function executeBuild(options, context, rebuildState) {
|
|
|
41
41
|
await (0, i18n_1.loadActiveTranslations)(context, i18nOptions);
|
|
42
42
|
}
|
|
43
43
|
// Reuse rebuild state or create new bundle contexts for code and global stylesheets
|
|
44
|
-
let bundlerContexts
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
bundlerContexts =
|
|
44
|
+
let bundlerContexts;
|
|
45
|
+
let componentStyleBundler;
|
|
46
|
+
let codeBundleCache;
|
|
47
|
+
if (rebuildState) {
|
|
48
|
+
bundlerContexts = rebuildState.rebuildContexts;
|
|
49
|
+
componentStyleBundler = rebuildState.componentStyleBundler;
|
|
50
|
+
codeBundleCache = rebuildState.codeBundleCache;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const target = (0, utils_1.transformSupportedBrowsersToTargets)(browsers);
|
|
54
|
+
codeBundleCache = new source_file_cache_1.SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined);
|
|
55
|
+
componentStyleBundler = (0, setup_bundling_1.createComponentStyleBundler)(options, target);
|
|
56
|
+
bundlerContexts = (0, setup_bundling_1.setupBundlerContexts)(options, target, codeBundleCache, componentStyleBundler);
|
|
49
57
|
}
|
|
50
58
|
let bundlingResult = await bundler_context_1.BundlerContext.bundleAll(bundlerContexts, rebuildState?.fileChanges.all);
|
|
51
59
|
if (options.optimizationOptions.scripts && environment_options_1.shouldOptimizeChunks) {
|
|
52
60
|
bundlingResult = await (0, profiling_1.profileAsync)('OPTIMIZE_CHUNKS', () => (0, chunk_optimizer_1.optimizeChunks)(bundlingResult, options.sourcemapOptions.scripts ? !options.sourcemapOptions.hidden || 'hidden' : false));
|
|
53
61
|
}
|
|
54
|
-
const executionResult = new bundler_execution_result_1.ExecutionResult(bundlerContexts, codeBundleCache);
|
|
62
|
+
const executionResult = new bundler_execution_result_1.ExecutionResult(bundlerContexts, componentStyleBundler, codeBundleCache);
|
|
55
63
|
executionResult.addWarnings(bundlingResult.warnings);
|
|
56
64
|
// Return if the bundling has errors
|
|
57
65
|
if (bundlingResult.errors) {
|
|
@@ -115,6 +123,10 @@ async function executeBuild(options, context, rebuildState) {
|
|
|
115
123
|
if (serverEntryPoint) {
|
|
116
124
|
executionResult.addOutputFile(manifest_1.SERVER_APP_ENGINE_MANIFEST_FILENAME, (0, manifest_1.generateAngularServerAppEngineManifest)(i18nOptions, baseHref, undefined), bundler_context_1.BuildOutputFileType.ServerRoot);
|
|
117
125
|
}
|
|
126
|
+
// Override auto-CSP settings if we are serving through Vite middleware.
|
|
127
|
+
if (context.builder.builderName === 'dev-server' && options.security) {
|
|
128
|
+
options.security.autoCsp = false;
|
|
129
|
+
}
|
|
118
130
|
// Perform i18n translation inlining if enabled
|
|
119
131
|
if (i18nOptions.shouldInline) {
|
|
120
132
|
const result = await (0, i18n_1.inlineI18n)(options, executionResult, initialFiles);
|
|
@@ -183,6 +183,7 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s
|
|
|
183
183
|
partialSSRBuild: boolean;
|
|
184
184
|
externalRuntimeStyles: boolean | undefined;
|
|
185
185
|
instrumentForCoverage: ((filename: string) => boolean) | undefined;
|
|
186
|
+
security: import("./schema").Security | undefined;
|
|
186
187
|
}>;
|
|
187
188
|
export declare function getLocaleBaseHref(baseHref: string | undefined, i18n: NormalizedApplicationBuildOptions['i18nOptions'], locale: string): string | undefined;
|
|
188
189
|
export {};
|
|
@@ -238,7 +238,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
|
|
|
238
238
|
}
|
|
239
239
|
}
|
|
240
240
|
// Initial options to keep
|
|
241
|
-
const { allowedCommonJsDependencies, aot, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, outputMode, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, namedChunks, budgets, deployUrl, clearScreen, define, partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, } = options;
|
|
241
|
+
const { allowedCommonJsDependencies, aot, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, outputMode, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, namedChunks, budgets, deployUrl, clearScreen, define, partialSSRBuild = false, externalRuntimeStyles, instrumentForCoverage, security, } = options;
|
|
242
242
|
// Return all the normalized options
|
|
243
243
|
return {
|
|
244
244
|
advancedOptimizations: !!aot && optimizationOptions.scripts,
|
|
@@ -297,6 +297,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
|
|
|
297
297
|
partialSSRBuild: environment_options_1.usePartialSsrBuild || partialSSRBuild,
|
|
298
298
|
externalRuntimeStyles,
|
|
299
299
|
instrumentForCoverage,
|
|
300
|
+
security,
|
|
300
301
|
};
|
|
301
302
|
}
|
|
302
303
|
async function getTailwindConfig(searchDirectories, workspaceRoot, context) {
|
|
@@ -153,6 +153,10 @@ export interface Schema {
|
|
|
153
153
|
* Global scripts to be included in the build.
|
|
154
154
|
*/
|
|
155
155
|
scripts?: ScriptElement[];
|
|
156
|
+
/**
|
|
157
|
+
* Security features to protect against XSS and other common attacks
|
|
158
|
+
*/
|
|
159
|
+
security?: Security;
|
|
156
160
|
/**
|
|
157
161
|
* The full path for the server entry point to the application, relative to the current
|
|
158
162
|
* workspace.
|
|
@@ -464,6 +468,31 @@ export interface ScriptClass {
|
|
|
464
468
|
*/
|
|
465
469
|
input: string;
|
|
466
470
|
}
|
|
471
|
+
/**
|
|
472
|
+
* Security features to protect against XSS and other common attacks
|
|
473
|
+
*/
|
|
474
|
+
export interface Security {
|
|
475
|
+
/**
|
|
476
|
+
* Enables automatic generation of a hash-based Strict Content Security Policy
|
|
477
|
+
* (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will
|
|
478
|
+
* default to true once we are out of experimental/preview phases.
|
|
479
|
+
*/
|
|
480
|
+
autoCsp?: AutoCspUnion;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Enables automatic generation of a hash-based Strict Content Security Policy
|
|
484
|
+
* (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will
|
|
485
|
+
* default to true once we are out of experimental/preview phases.
|
|
486
|
+
*/
|
|
487
|
+
export type AutoCspUnion = boolean | AutoCspClass;
|
|
488
|
+
export interface AutoCspClass {
|
|
489
|
+
/**
|
|
490
|
+
* Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in
|
|
491
|
+
* the auto-CSP. Please only enable this if you are absolutely sure that you need to, as
|
|
492
|
+
* allowing calls to eval will weaken the XSS defenses provided by the auto-CSP.
|
|
493
|
+
*/
|
|
494
|
+
unsafeEval?: boolean;
|
|
495
|
+
}
|
|
467
496
|
/**
|
|
468
497
|
* Generates a service worker configuration.
|
|
469
498
|
*/
|
|
@@ -37,6 +37,33 @@
|
|
|
37
37
|
"type": "string",
|
|
38
38
|
"description": "Customize the base path for the URLs of resources in 'index.html' and component stylesheets. This option is only necessary for specific deployment scenarios, such as with Angular Elements or when utilizing different CDN locations."
|
|
39
39
|
},
|
|
40
|
+
"security": {
|
|
41
|
+
"description": "Security features to protect against XSS and other common attacks",
|
|
42
|
+
"type": "object",
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"properties": {
|
|
45
|
+
"autoCsp": {
|
|
46
|
+
"description": "Enables automatic generation of a hash-based Strict Content Security Policy (https://web.dev/articles/strict-csp#choose-hash) based on scripts in index.html. Will default to true once we are out of experimental/preview phases.",
|
|
47
|
+
"default": false,
|
|
48
|
+
"oneOf": [
|
|
49
|
+
{
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"unsafeEval": {
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"description": "Include the `unsafe-eval` directive (https://web.dev/articles/strict-csp#remove-eval) in the auto-CSP. Please only enable this if you are absolutely sure that you need to, as allowing calls to eval will weaken the XSS defenses provided by the auto-CSP.",
|
|
55
|
+
"default": false
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"additionalProperties": false
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "boolean"
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
40
67
|
"scripts": {
|
|
41
68
|
"description": "Global scripts to be included in the build.",
|
|
42
69
|
"type": "array",
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
|
+
import { ComponentStylesheetBundler } from '../../tools/esbuild/angular/component-stylesheets';
|
|
8
9
|
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
|
|
9
10
|
import { BundlerContext } from '../../tools/esbuild/bundler-context';
|
|
10
11
|
import { NormalizedApplicationBuildOptions } from './options';
|
|
@@ -16,4 +17,5 @@ import { NormalizedApplicationBuildOptions } from './options';
|
|
|
16
17
|
* @param codeBundleCache An instance of the TypeScript source file cache.
|
|
17
18
|
* @returns An array of BundlerContext objects.
|
|
18
19
|
*/
|
|
19
|
-
export declare function setupBundlerContexts(options: NormalizedApplicationBuildOptions,
|
|
20
|
+
export declare function setupBundlerContexts(options: NormalizedApplicationBuildOptions, target: string[], codeBundleCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler): BundlerContext[];
|
|
21
|
+
export declare function createComponentStyleBundler(options: NormalizedApplicationBuildOptions, target: string[]): ComponentStylesheetBundler;
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.setupBundlerContexts = setupBundlerContexts;
|
|
11
|
+
exports.createComponentStyleBundler = createComponentStyleBundler;
|
|
12
|
+
const component_stylesheets_1 = require("../../tools/esbuild/angular/component-stylesheets");
|
|
11
13
|
const application_code_bundle_1 = require("../../tools/esbuild/application-code-bundle");
|
|
12
14
|
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
|
|
13
15
|
const global_scripts_1 = require("../../tools/esbuild/global-scripts");
|
|
@@ -21,14 +23,13 @@ const utils_1 = require("../../tools/esbuild/utils");
|
|
|
21
23
|
* @param codeBundleCache An instance of the TypeScript source file cache.
|
|
22
24
|
* @returns An array of BundlerContext objects.
|
|
23
25
|
*/
|
|
24
|
-
function setupBundlerContexts(options,
|
|
26
|
+
function setupBundlerContexts(options, target, codeBundleCache, stylesheetBundler) {
|
|
25
27
|
const { outputMode, serverEntryPoint, appShellOptions, prerenderOptions, ssrOptions, workspaceRoot, watch = false, } = options;
|
|
26
|
-
const target = (0, utils_1.transformSupportedBrowsersToTargets)(browsers);
|
|
27
28
|
const bundlerContexts = [];
|
|
28
29
|
// Browser application code
|
|
29
|
-
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createBrowserCodeBundleOptions)(options, target, codeBundleCache)));
|
|
30
|
+
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createBrowserCodeBundleOptions)(options, target, codeBundleCache, stylesheetBundler)));
|
|
30
31
|
// Browser polyfills code
|
|
31
|
-
const browserPolyfillBundleOptions = (0, application_code_bundle_1.createBrowserPolyfillBundleOptions)(options, target, codeBundleCache);
|
|
32
|
+
const browserPolyfillBundleOptions = (0, application_code_bundle_1.createBrowserPolyfillBundleOptions)(options, target, codeBundleCache, stylesheetBundler);
|
|
32
33
|
if (browserPolyfillBundleOptions) {
|
|
33
34
|
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, browserPolyfillBundleOptions));
|
|
34
35
|
}
|
|
@@ -53,10 +54,10 @@ function setupBundlerContexts(options, browsers, codeBundleCache) {
|
|
|
53
54
|
// Skip server build when none of the features are enabled.
|
|
54
55
|
if (serverEntryPoint && (outputMode || prerenderOptions || appShellOptions || ssrOptions)) {
|
|
55
56
|
const nodeTargets = [...target, ...(0, utils_1.getSupportedNodeTargets)()];
|
|
56
|
-
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createServerMainCodeBundleOptions)(options, nodeTargets, codeBundleCache)));
|
|
57
|
+
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createServerMainCodeBundleOptions)(options, nodeTargets, codeBundleCache, stylesheetBundler)));
|
|
57
58
|
if (outputMode && ssrOptions?.entry) {
|
|
58
59
|
// New behavior introduced: 'server.ts' is now bundled separately from 'main.server.ts'.
|
|
59
|
-
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createSsrEntryCodeBundleOptions)(options, nodeTargets, codeBundleCache)));
|
|
60
|
+
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, watch, (0, application_code_bundle_1.createSsrEntryCodeBundleOptions)(options, nodeTargets, codeBundleCache, stylesheetBundler)));
|
|
60
61
|
}
|
|
61
62
|
// Server polyfills code
|
|
62
63
|
const serverPolyfillBundleOptions = (0, application_code_bundle_1.createServerPolyfillBundleOptions)(options, nodeTargets, codeBundleCache);
|
|
@@ -66,3 +67,29 @@ function setupBundlerContexts(options, browsers, codeBundleCache) {
|
|
|
66
67
|
}
|
|
67
68
|
return bundlerContexts;
|
|
68
69
|
}
|
|
70
|
+
function createComponentStyleBundler(options, target) {
|
|
71
|
+
const { workspaceRoot, optimizationOptions, sourcemapOptions, outputNames, externalDependencies, preserveSymlinks, stylePreprocessorOptions, inlineStyleLanguage, cacheOptions, tailwindConfiguration, postcssConfiguration, publicPath, } = options;
|
|
72
|
+
const incremental = !!options.watch;
|
|
73
|
+
return new component_stylesheets_1.ComponentStylesheetBundler({
|
|
74
|
+
workspaceRoot,
|
|
75
|
+
inlineFonts: !!optimizationOptions.fonts.inline,
|
|
76
|
+
optimization: !!optimizationOptions.styles.minify,
|
|
77
|
+
sourcemap:
|
|
78
|
+
// Hidden component stylesheet sourcemaps are inaccessible which is effectively
|
|
79
|
+
// the same as being disabled. Disabling has the advantage of avoiding the overhead
|
|
80
|
+
// of sourcemap processing.
|
|
81
|
+
sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
|
|
82
|
+
outputNames,
|
|
83
|
+
includePaths: stylePreprocessorOptions?.includePaths,
|
|
84
|
+
// string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
sass: stylePreprocessorOptions?.sass,
|
|
87
|
+
externalDependencies,
|
|
88
|
+
target,
|
|
89
|
+
preserveSymlinks,
|
|
90
|
+
tailwindConfiguration,
|
|
91
|
+
postcssConfiguration,
|
|
92
|
+
cacheOptions,
|
|
93
|
+
publicPath,
|
|
94
|
+
}, inlineStyleLanguage, incremental);
|
|
95
|
+
}
|
|
@@ -31,6 +31,7 @@ export declare abstract class AngularCompilation {
|
|
|
31
31
|
compilerOptions: ng.CompilerOptions;
|
|
32
32
|
referencedFiles: readonly string[];
|
|
33
33
|
externalStylesheets?: ReadonlyMap<string, string>;
|
|
34
|
+
templateUpdates?: ReadonlyMap<string, string>;
|
|
34
35
|
}>;
|
|
35
36
|
abstract emitAffectedFiles(): Iterable<EmitFileResult> | Promise<Iterable<EmitFileResult>>;
|
|
36
37
|
protected abstract collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic> | Promise<Iterable<ts.Diagnostic>>;
|
|
@@ -16,6 +16,7 @@ export declare class AotCompilation extends AngularCompilation {
|
|
|
16
16
|
compilerOptions: ng.CompilerOptions;
|
|
17
17
|
referencedFiles: readonly string[];
|
|
18
18
|
externalStylesheets?: ReadonlyMap<string, string>;
|
|
19
|
+
templateUpdates?: ReadonlyMap<string, string>;
|
|
19
20
|
}>;
|
|
20
21
|
collectDiagnostics(modes: DiagnosticModes): Iterable<ts.Diagnostic>;
|
|
21
22
|
emitAffectedFiles(): Iterable<EmitFileResult>;
|
|
@@ -12,6 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
12
12
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
13
|
exports.AotCompilation = void 0;
|
|
14
14
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
15
16
|
const typescript_1 = __importDefault(require("typescript"));
|
|
16
17
|
const profiling_1 = require("../../esbuild/profiling");
|
|
17
18
|
const angular_host_1 = require("../angular-host");
|
|
@@ -65,6 +66,33 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
|
|
|
65
66
|
}
|
|
66
67
|
const typeScriptProgram = typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram(angularTypeScriptProgram, host, oldProgram, configurationDiagnostics);
|
|
67
68
|
await (0, profiling_1.profileAsync)('NG_ANALYZE_PROGRAM', () => angularCompiler.analyzeAsync());
|
|
69
|
+
let templateUpdates;
|
|
70
|
+
if (compilerOptions['_enableHmr'] &&
|
|
71
|
+
hostOptions.modifiedFiles &&
|
|
72
|
+
hasOnlyTemplates(hostOptions.modifiedFiles)) {
|
|
73
|
+
const componentNodes = [...hostOptions.modifiedFiles].flatMap((file) => [
|
|
74
|
+
...angularCompiler.getComponentsWithTemplateFile(file),
|
|
75
|
+
]);
|
|
76
|
+
for (const node of componentNodes) {
|
|
77
|
+
if (!typescript_1.default.isClassDeclaration(node)) {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const componentFilename = node.getSourceFile().fileName;
|
|
81
|
+
let relativePath = (0, node_path_1.relative)(host.getCurrentDirectory(), componentFilename);
|
|
82
|
+
if (relativePath.startsWith('..')) {
|
|
83
|
+
relativePath = componentFilename;
|
|
84
|
+
}
|
|
85
|
+
const updateId = encodeURIComponent(`${host.getCanonicalFileName(relativePath)}@${node.name?.text}`);
|
|
86
|
+
const updateText = angularCompiler.emitHmrUpdateModule(node);
|
|
87
|
+
if (updateText === null) {
|
|
88
|
+
// Build is needed if a template cannot be updated
|
|
89
|
+
templateUpdates = undefined;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
templateUpdates ??= new Map();
|
|
93
|
+
templateUpdates.set(updateId, updateText);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
68
96
|
const affectedFiles = (0, profiling_1.profileSync)('NG_FIND_AFFECTED', () => findAffectedFiles(typeScriptProgram, angularCompiler, usingBuildInfo));
|
|
69
97
|
// Get all files referenced in the TypeScript/Angular program including component resources
|
|
70
98
|
const referencedFiles = typeScriptProgram
|
|
@@ -90,6 +118,7 @@ class AotCompilation extends angular_compilation_1.AngularCompilation {
|
|
|
90
118
|
compilerOptions,
|
|
91
119
|
referencedFiles,
|
|
92
120
|
externalStylesheets: hostOptions.externalStylesheets,
|
|
121
|
+
templateUpdates,
|
|
93
122
|
};
|
|
94
123
|
}
|
|
95
124
|
*collectDiagnostics(modes) {
|
|
@@ -267,3 +296,13 @@ function findAffectedFiles(builder, { ignoreForDiagnostics }, includeTTC) {
|
|
|
267
296
|
}
|
|
268
297
|
return affectedFiles;
|
|
269
298
|
}
|
|
299
|
+
function hasOnlyTemplates(modifiedFiles) {
|
|
300
|
+
for (const file of modifiedFiles) {
|
|
301
|
+
const lowerFile = file.toLowerCase();
|
|
302
|
+
if (lowerFile.endsWith('.html') || lowerFile.endsWith('.svg')) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
return true;
|
|
308
|
+
}
|
|
@@ -20,6 +20,7 @@ export interface InitRequest {
|
|
|
20
20
|
}
|
|
21
21
|
export declare function initialize(request: InitRequest): Promise<{
|
|
22
22
|
externalStylesheets: ReadonlyMap<string, string> | undefined;
|
|
23
|
+
templateUpdates: ReadonlyMap<string, string> | undefined;
|
|
23
24
|
referencedFiles: readonly string[];
|
|
24
25
|
compilerOptions: {
|
|
25
26
|
allowJs: boolean | undefined;
|
|
@@ -33,7 +33,7 @@ async function initialize(request) {
|
|
|
33
33
|
stylesheetRequests.get(requestId)?.[0](value);
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
|
-
const { compilerOptions, referencedFiles, externalStylesheets } = await compilation.initialize(request.tsconfig, {
|
|
36
|
+
const { compilerOptions, referencedFiles, externalStylesheets, templateUpdates } = await compilation.initialize(request.tsconfig, {
|
|
37
37
|
fileReplacements: request.fileReplacements,
|
|
38
38
|
sourceFileCache,
|
|
39
39
|
modifiedFiles: sourceFileCache.modifiedFiles,
|
|
@@ -72,6 +72,7 @@ async function initialize(request) {
|
|
|
72
72
|
});
|
|
73
73
|
return {
|
|
74
74
|
externalStylesheets,
|
|
75
|
+
templateUpdates,
|
|
75
76
|
referencedFiles,
|
|
76
77
|
// TODO: Expand? `allowJs`, `isolatedModules`, `sourceMap`, `inlineSourceMap` are the only fields needed currently.
|
|
77
78
|
compilerOptions: {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Plugin } from 'esbuild';
|
|
9
9
|
import { LoadResultCache } from '../load-result-cache';
|
|
10
|
-
import {
|
|
10
|
+
import { ComponentStylesheetBundler } from './component-stylesheets';
|
|
11
11
|
import { SourceFileCache } from './source-file-cache';
|
|
12
12
|
export interface CompilerPluginOptions {
|
|
13
13
|
sourcemap: boolean | 'external';
|
|
@@ -24,6 +24,4 @@ export interface CompilerPluginOptions {
|
|
|
24
24
|
externalRuntimeStyles?: boolean;
|
|
25
25
|
instrumentForCoverage?: (request: string) => boolean;
|
|
26
26
|
}
|
|
27
|
-
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions,
|
|
28
|
-
inlineStyleLanguage: string;
|
|
29
|
-
}): Plugin;
|
|
27
|
+
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, stylesheetBundler: ComponentStylesheetBundler): Plugin;
|
|
@@ -43,11 +43,10 @@ const javascript_transformer_1 = require("../javascript-transformer");
|
|
|
43
43
|
const load_result_cache_1 = require("../load-result-cache");
|
|
44
44
|
const profiling_1 = require("../profiling");
|
|
45
45
|
const compilation_state_1 = require("./compilation-state");
|
|
46
|
-
const component_stylesheets_1 = require("./component-stylesheets");
|
|
47
46
|
const file_reference_tracker_1 = require("./file-reference-tracker");
|
|
48
47
|
const jit_plugin_callbacks_1 = require("./jit-plugin-callbacks");
|
|
49
48
|
// eslint-disable-next-line max-lines-per-function
|
|
50
|
-
function createCompilerPlugin(pluginOptions,
|
|
49
|
+
function createCompilerPlugin(pluginOptions, stylesheetBundler) {
|
|
51
50
|
return {
|
|
52
51
|
name: 'angular-compiler',
|
|
53
52
|
// eslint-disable-next-line max-lines-per-function
|
|
@@ -101,8 +100,6 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|
|
101
100
|
let shouldTsIgnoreJs = true;
|
|
102
101
|
// Determines if transpilation should be handle by TypeScript or esbuild
|
|
103
102
|
let useTypeScriptTranspilation = true;
|
|
104
|
-
// Track incremental component stylesheet builds
|
|
105
|
-
const stylesheetBundler = new component_stylesheets_1.ComponentStylesheetBundler(styleOptions, styleOptions.inlineStyleLanguage, pluginOptions.incremental);
|
|
106
103
|
let sharedTSCompilationState;
|
|
107
104
|
// To fully invalidate files, track resource referenced files and their referencing source
|
|
108
105
|
const referencedFileTracker = new file_reference_tracker_1.FileReferenceTracker();
|
|
@@ -398,7 +395,6 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|
|
398
395
|
});
|
|
399
396
|
build.onDispose(() => {
|
|
400
397
|
sharedTSCompilationState?.dispose();
|
|
401
|
-
void stylesheetBundler.dispose();
|
|
402
398
|
void compilation.close?.();
|
|
403
399
|
void cacheStore?.close();
|
|
404
400
|
});
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { BuildOptions } from 'esbuild';
|
|
9
9
|
import type { NormalizedApplicationBuildOptions } from '../../builders/application/options';
|
|
10
|
+
import { ComponentStylesheetBundler } from './angular/component-stylesheets';
|
|
10
11
|
import { SourceFileCache } from './angular/source-file-cache';
|
|
11
12
|
import { BundlerOptionsFactory } from './bundler-context';
|
|
12
|
-
export declare function createBrowserCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache
|
|
13
|
-
export declare function createBrowserPolyfillBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache
|
|
13
|
+
export declare function createBrowserCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler): BuildOptions;
|
|
14
|
+
export declare function createBrowserPolyfillBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler): BuildOptions | BundlerOptionsFactory | undefined;
|
|
14
15
|
export declare function createServerPolyfillBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache?: SourceFileCache): BundlerOptionsFactory | undefined;
|
|
15
|
-
export declare function createServerMainCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache): BuildOptions;
|
|
16
|
-
export declare function createSsrEntryCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache): BuildOptions;
|
|
16
|
+
export declare function createServerMainCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler): BuildOptions;
|
|
17
|
+
export declare function createSsrEntryCodeBundleOptions(options: NormalizedApplicationBuildOptions, target: string[], sourceFileCache: SourceFileCache, stylesheetBundler: ComponentStylesheetBundler): BuildOptions;
|
|
@@ -32,9 +32,9 @@ const sourcemap_ignorelist_plugin_1 = require("./sourcemap-ignorelist-plugin");
|
|
|
32
32
|
const utils_1 = require("./utils");
|
|
33
33
|
const virtual_module_plugin_1 = require("./virtual-module-plugin");
|
|
34
34
|
const wasm_plugin_1 = require("./wasm-plugin");
|
|
35
|
-
function createBrowserCodeBundleOptions(options, target, sourceFileCache) {
|
|
35
|
+
function createBrowserCodeBundleOptions(options, target, sourceFileCache, stylesheetBundler) {
|
|
36
36
|
const { entryPoints, outputNames, polyfills } = options;
|
|
37
|
-
const
|
|
37
|
+
const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache);
|
|
38
38
|
const zoneless = (0, utils_1.isZonelessApp)(polyfills);
|
|
39
39
|
const buildOptions = {
|
|
40
40
|
...getEsBuildCommonOptions(options),
|
|
@@ -55,8 +55,8 @@ function createBrowserCodeBundleOptions(options, target, sourceFileCache) {
|
|
|
55
55
|
(0, compiler_plugin_1.createCompilerPlugin)(
|
|
56
56
|
// JS/TS options
|
|
57
57
|
pluginOptions,
|
|
58
|
-
// Component stylesheet
|
|
59
|
-
|
|
58
|
+
// Component stylesheet bundler
|
|
59
|
+
stylesheetBundler),
|
|
60
60
|
],
|
|
61
61
|
};
|
|
62
62
|
if (options.plugins) {
|
|
@@ -77,7 +77,7 @@ function createBrowserCodeBundleOptions(options, target, sourceFileCache) {
|
|
|
77
77
|
}
|
|
78
78
|
return buildOptions;
|
|
79
79
|
}
|
|
80
|
-
function createBrowserPolyfillBundleOptions(options, target, sourceFileCache) {
|
|
80
|
+
function createBrowserPolyfillBundleOptions(options, target, sourceFileCache, stylesheetBundler) {
|
|
81
81
|
const namespace = 'angular:polyfills';
|
|
82
82
|
const polyfillBundleOptions = getEsBuildCommonPolyfillsOptions(options, namespace, true, sourceFileCache);
|
|
83
83
|
if (!polyfillBundleOptions) {
|
|
@@ -102,12 +102,12 @@ function createBrowserPolyfillBundleOptions(options, target, sourceFileCache) {
|
|
|
102
102
|
// Only add the Angular TypeScript compiler if TypeScript files are provided in the polyfills
|
|
103
103
|
if (hasTypeScriptEntries) {
|
|
104
104
|
buildOptions.plugins ??= [];
|
|
105
|
-
const
|
|
105
|
+
const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache);
|
|
106
106
|
buildOptions.plugins.push((0, compiler_plugin_1.createCompilerPlugin)(
|
|
107
107
|
// JS/TS options
|
|
108
108
|
{ ...pluginOptions, noopTypeScriptCompilation: true },
|
|
109
109
|
// Component stylesheet options are unused for polyfills but required by the plugin
|
|
110
|
-
|
|
110
|
+
stylesheetBundler));
|
|
111
111
|
}
|
|
112
112
|
// Use an options factory to allow fully incremental bundling when no TypeScript files are present.
|
|
113
113
|
// The TypeScript compilation is not currently integrated into the bundler invalidation so
|
|
@@ -163,10 +163,10 @@ function createServerPolyfillBundleOptions(options, target, sourceFileCache) {
|
|
|
163
163
|
buildOptions.plugins.push((0, server_bundle_metadata_plugin_1.createServerBundleMetadata)());
|
|
164
164
|
return () => buildOptions;
|
|
165
165
|
}
|
|
166
|
-
function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
|
|
166
|
+
function createServerMainCodeBundleOptions(options, target, sourceFileCache, stylesheetBundler) {
|
|
167
167
|
const { serverEntryPoint: mainServerEntryPoint, workspaceRoot, outputMode, externalPackages, ssrOptions, polyfills, } = options;
|
|
168
168
|
(0, node_assert_1.default)(mainServerEntryPoint, 'createServerCodeBundleOptions should not be called without a defined serverEntryPoint.');
|
|
169
|
-
const
|
|
169
|
+
const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache);
|
|
170
170
|
const mainServerNamespace = 'angular:main-server';
|
|
171
171
|
const mainServerInjectPolyfillsNamespace = 'angular:main-server-inject-polyfills';
|
|
172
172
|
const mainServerInjectManifestNamespace = 'angular:main-server-inject-manifest';
|
|
@@ -193,8 +193,8 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
|
|
|
193
193
|
(0, compiler_plugin_1.createCompilerPlugin)(
|
|
194
194
|
// JS/TS options
|
|
195
195
|
{ ...pluginOptions, noopTypeScriptCompilation: true },
|
|
196
|
-
// Component stylesheet
|
|
197
|
-
|
|
196
|
+
// Component stylesheet bundler
|
|
197
|
+
stylesheetBundler),
|
|
198
198
|
],
|
|
199
199
|
};
|
|
200
200
|
buildOptions.plugins ??= [];
|
|
@@ -265,11 +265,11 @@ function createServerMainCodeBundleOptions(options, target, sourceFileCache) {
|
|
|
265
265
|
}
|
|
266
266
|
return buildOptions;
|
|
267
267
|
}
|
|
268
|
-
function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
|
|
268
|
+
function createSsrEntryCodeBundleOptions(options, target, sourceFileCache, stylesheetBundler) {
|
|
269
269
|
const { workspaceRoot, ssrOptions, externalPackages } = options;
|
|
270
270
|
const serverEntryPoint = ssrOptions?.entry;
|
|
271
271
|
(0, node_assert_1.default)(serverEntryPoint, 'createSsrEntryCodeBundleOptions should not be called without a defined serverEntryPoint.');
|
|
272
|
-
const
|
|
272
|
+
const pluginOptions = (0, compiler_plugin_options_1.createCompilerPluginOptions)(options, sourceFileCache);
|
|
273
273
|
const ssrEntryNamespace = 'angular:ssr-entry';
|
|
274
274
|
const ssrInjectManifestNamespace = 'angular:ssr-entry-inject-manifest';
|
|
275
275
|
const ssrInjectRequireNamespace = 'angular:ssr-entry-inject-require';
|
|
@@ -291,8 +291,8 @@ function createSsrEntryCodeBundleOptions(options, target, sourceFileCache) {
|
|
|
291
291
|
(0, compiler_plugin_1.createCompilerPlugin)(
|
|
292
292
|
// JS/TS options
|
|
293
293
|
{ ...pluginOptions, noopTypeScriptCompilation: true },
|
|
294
|
-
// Component stylesheet
|
|
295
|
-
|
|
294
|
+
// Component stylesheet bundler
|
|
295
|
+
stylesheetBundler),
|
|
296
296
|
],
|
|
297
297
|
inject,
|
|
298
298
|
};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { Message, PartialMessage } from 'esbuild';
|
|
9
9
|
import type { ChangedFiles } from '../../tools/esbuild/watcher';
|
|
10
|
+
import type { ComponentStylesheetBundler } from './angular/component-stylesheets';
|
|
10
11
|
import type { SourceFileCache } from './angular/source-file-cache';
|
|
11
12
|
import type { BuildOutputFile, BuildOutputFileType, BundlerContext } from './bundler-context';
|
|
12
13
|
export interface BuildOutputAsset {
|
|
@@ -15,6 +16,7 @@ export interface BuildOutputAsset {
|
|
|
15
16
|
}
|
|
16
17
|
export interface RebuildState {
|
|
17
18
|
rebuildContexts: BundlerContext[];
|
|
19
|
+
componentStyleBundler: ComponentStylesheetBundler;
|
|
18
20
|
codeBundleCache?: SourceFileCache;
|
|
19
21
|
fileChanges: ChangedFiles;
|
|
20
22
|
previousOutputHashes: Map<string, string>;
|
|
@@ -32,6 +34,7 @@ export type PrerenderedRoutesRecord = Record<string, {
|
|
|
32
34
|
*/
|
|
33
35
|
export declare class ExecutionResult {
|
|
34
36
|
private rebuildContexts;
|
|
37
|
+
private componentStyleBundler;
|
|
35
38
|
private codeBundleCache?;
|
|
36
39
|
outputFiles: BuildOutputFile[];
|
|
37
40
|
assetFiles: BuildOutputAsset[];
|
|
@@ -43,7 +46,7 @@ export declare class ExecutionResult {
|
|
|
43
46
|
extraWatchFiles: string[];
|
|
44
47
|
htmlIndexPath?: string;
|
|
45
48
|
htmlBaseHref?: string;
|
|
46
|
-
constructor(rebuildContexts: BundlerContext[], codeBundleCache?: SourceFileCache | undefined);
|
|
49
|
+
constructor(rebuildContexts: BundlerContext[], componentStyleBundler: ComponentStylesheetBundler, codeBundleCache?: SourceFileCache | undefined);
|
|
47
50
|
addOutputFile(path: string, content: string | Uint8Array, type: BuildOutputFileType): void;
|
|
48
51
|
addAssets(assets: BuildOutputAsset[]): void;
|
|
49
52
|
addLog(value: string): void;
|
|
@@ -67,7 +70,7 @@ export declare class ExecutionResult {
|
|
|
67
70
|
success: boolean;
|
|
68
71
|
outputFiles: BuildOutputFile[];
|
|
69
72
|
assetFiles: BuildOutputAsset[];
|
|
70
|
-
errors: (
|
|
73
|
+
errors: (PartialMessage | Message)[];
|
|
71
74
|
externalMetadata: ExternalResultMetadata | undefined;
|
|
72
75
|
};
|
|
73
76
|
get watchFiles(): string[];
|
|
@@ -15,6 +15,7 @@ const utils_1 = require("./utils");
|
|
|
15
15
|
*/
|
|
16
16
|
class ExecutionResult {
|
|
17
17
|
rebuildContexts;
|
|
18
|
+
componentStyleBundler;
|
|
18
19
|
codeBundleCache;
|
|
19
20
|
outputFiles = [];
|
|
20
21
|
assetFiles = [];
|
|
@@ -26,8 +27,9 @@ class ExecutionResult {
|
|
|
26
27
|
extraWatchFiles = [];
|
|
27
28
|
htmlIndexPath;
|
|
28
29
|
htmlBaseHref;
|
|
29
|
-
constructor(rebuildContexts, codeBundleCache) {
|
|
30
|
+
constructor(rebuildContexts, componentStyleBundler, codeBundleCache) {
|
|
30
31
|
this.rebuildContexts = rebuildContexts;
|
|
32
|
+
this.componentStyleBundler = componentStyleBundler;
|
|
31
33
|
this.codeBundleCache = codeBundleCache;
|
|
32
34
|
}
|
|
33
35
|
addOutputFile(path, content, type) {
|
|
@@ -117,6 +119,7 @@ class ExecutionResult {
|
|
|
117
119
|
return {
|
|
118
120
|
rebuildContexts: this.rebuildContexts,
|
|
119
121
|
codeBundleCache: this.codeBundleCache,
|
|
122
|
+
componentStyleBundler: this.componentStyleBundler,
|
|
120
123
|
fileChanges,
|
|
121
124
|
previousOutputHashes: new Map(this.outputFiles.map((file) => [file.path, file.hash])),
|
|
122
125
|
};
|
|
@@ -133,6 +136,7 @@ class ExecutionResult {
|
|
|
133
136
|
}
|
|
134
137
|
async dispose() {
|
|
135
138
|
await Promise.allSettled(this.rebuildContexts.map((context) => context.dispose()));
|
|
139
|
+
await this.componentStyleBundler.dispose();
|
|
136
140
|
}
|
|
137
141
|
}
|
|
138
142
|
exports.ExecutionResult = ExecutionResult;
|
|
@@ -9,8 +9,5 @@ import { NormalizedApplicationBuildOptions } from '../../builders/application/op
|
|
|
9
9
|
import type { createCompilerPlugin } from './angular/compiler-plugin';
|
|
10
10
|
import type { SourceFileCache } from './angular/source-file-cache';
|
|
11
11
|
type CreateCompilerPluginParameters = Parameters<typeof createCompilerPlugin>;
|
|
12
|
-
export declare function createCompilerPluginOptions(options: NormalizedApplicationBuildOptions,
|
|
13
|
-
pluginOptions: CreateCompilerPluginParameters[0];
|
|
14
|
-
styleOptions: CreateCompilerPluginParameters[1];
|
|
15
|
-
};
|
|
12
|
+
export declare function createCompilerPluginOptions(options: NormalizedApplicationBuildOptions, sourceFileCache?: SourceFileCache): CreateCompilerPluginParameters[0];
|
|
16
13
|
export {};
|
|
@@ -8,46 +8,20 @@
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.createCompilerPluginOptions = createCompilerPluginOptions;
|
|
11
|
-
function createCompilerPluginOptions(options,
|
|
12
|
-
const {
|
|
11
|
+
function createCompilerPluginOptions(options, sourceFileCache) {
|
|
12
|
+
const { sourcemapOptions, tsconfig, fileReplacements, advancedOptimizations, jit, externalRuntimeStyles, instrumentForCoverage, } = options;
|
|
13
|
+
const incremental = !!options.watch;
|
|
13
14
|
return {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
externalRuntimeStyles,
|
|
26
|
-
instrumentForCoverage,
|
|
27
|
-
},
|
|
28
|
-
// Component stylesheet options
|
|
29
|
-
styleOptions: {
|
|
30
|
-
workspaceRoot,
|
|
31
|
-
inlineFonts: !!optimizationOptions.fonts.inline,
|
|
32
|
-
optimization: !!optimizationOptions.styles.minify,
|
|
33
|
-
sourcemap:
|
|
34
|
-
// Hidden component stylesheet sourcemaps are inaccessible which is effectively
|
|
35
|
-
// the same as being disabled. Disabling has the advantage of avoiding the overhead
|
|
36
|
-
// of sourcemap processing.
|
|
37
|
-
sourcemapOptions.styles && !sourcemapOptions.hidden ? 'linked' : false,
|
|
38
|
-
outputNames,
|
|
39
|
-
includePaths: stylePreprocessorOptions?.includePaths,
|
|
40
|
-
// string[] | undefined' is not assignable to type '(Version | DeprecationOrId)[] | undefined'.
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
42
|
-
sass: stylePreprocessorOptions?.sass,
|
|
43
|
-
externalDependencies,
|
|
44
|
-
target,
|
|
45
|
-
inlineStyleLanguage,
|
|
46
|
-
preserveSymlinks,
|
|
47
|
-
tailwindConfiguration,
|
|
48
|
-
postcssConfiguration,
|
|
49
|
-
cacheOptions,
|
|
50
|
-
publicPath,
|
|
51
|
-
},
|
|
15
|
+
sourcemap: !!sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true),
|
|
16
|
+
thirdPartySourcemaps: sourcemapOptions.vendor,
|
|
17
|
+
tsconfig,
|
|
18
|
+
jit,
|
|
19
|
+
advancedOptimizations,
|
|
20
|
+
fileReplacements,
|
|
21
|
+
sourceFileCache,
|
|
22
|
+
loadResultCache: sourceFileCache?.loadResultCache,
|
|
23
|
+
incremental,
|
|
24
|
+
externalRuntimeStyles,
|
|
25
|
+
instrumentForCoverage,
|
|
52
26
|
};
|
|
53
27
|
}
|
|
@@ -59,6 +59,13 @@ 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;
|
|
62
69
|
// Create an index HTML generator that reads from the in-memory output files
|
|
63
70
|
const indexHtmlGenerator = new index_html_generator_1.IndexHtmlGenerator({
|
|
64
71
|
indexPath: indexHtmlOptions.input,
|
|
@@ -71,6 +78,7 @@ async function generateIndexHtml(initialFiles, outputFiles, buildOptions, lang)
|
|
|
71
78
|
generateDedicatedSSRContent: !!(buildOptions.ssrOptions ||
|
|
72
79
|
buildOptions.prerenderOptions ||
|
|
73
80
|
buildOptions.appShellOptions),
|
|
81
|
+
autoCsp: autoCspOptions,
|
|
74
82
|
});
|
|
75
83
|
indexHtmlGenerator.readAsset = readAsset;
|
|
76
84
|
return indexHtmlGenerator.process({
|
|
@@ -41,6 +41,8 @@ class JavaScriptTransformer {
|
|
|
41
41
|
this.#workerPool ??= new worker_pool_1.WorkerPool({
|
|
42
42
|
filename: require.resolve('./javascript-transformer-worker'),
|
|
43
43
|
maxThreads: this.maxThreads,
|
|
44
|
+
// Prevent passing `--import` (loader-hooks) from parent to child worker.
|
|
45
|
+
execArgv: [],
|
|
44
46
|
});
|
|
45
47
|
return this.#workerPool;
|
|
46
48
|
}
|
|
@@ -155,7 +155,7 @@ async function withNoProgress(text, action) {
|
|
|
155
155
|
* @returns An object that can be used with the esbuild build `supported` option.
|
|
156
156
|
*/
|
|
157
157
|
function getFeatureSupport(target, nativeAsyncAwait) {
|
|
158
|
-
|
|
158
|
+
return {
|
|
159
159
|
// Native async/await is not supported with Zone.js. Disabling support here will cause
|
|
160
160
|
// esbuild to downlevel async/await, async generators, and for await...of to a Zone.js supported form.
|
|
161
161
|
'async-await': nativeAsyncAwait,
|
|
@@ -165,35 +165,6 @@ function getFeatureSupport(target, nativeAsyncAwait) {
|
|
|
165
165
|
// For more details: https://bugs.chromium.org/p/v8/issues/detail?id=11536
|
|
166
166
|
'object-rest-spread': false,
|
|
167
167
|
};
|
|
168
|
-
// Detect Safari browser versions that have a class field behavior bug
|
|
169
|
-
// See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033
|
|
170
|
-
// See: https://github.com/WebKit/WebKit/commit/e8788a34b3d5f5b4edd7ff6450b80936bff396f2
|
|
171
|
-
let safariClassFieldScopeBug = false;
|
|
172
|
-
for (const browser of target) {
|
|
173
|
-
let majorVersion;
|
|
174
|
-
if (browser.startsWith('ios')) {
|
|
175
|
-
majorVersion = Number(browser.slice(3, 5));
|
|
176
|
-
}
|
|
177
|
-
else if (browser.startsWith('safari')) {
|
|
178
|
-
majorVersion = Number(browser.slice(6, 8));
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
// Technically, 14.0 is not broken but rather does not have support. However, the behavior
|
|
184
|
-
// is identical since it would be set to false by esbuild if present as a target.
|
|
185
|
-
if (majorVersion === 14 || majorVersion === 15) {
|
|
186
|
-
safariClassFieldScopeBug = true;
|
|
187
|
-
break;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// If class field support cannot be used set to false; otherwise leave undefined to allow
|
|
191
|
-
// esbuild to use `target` to determine support.
|
|
192
|
-
if (safariClassFieldScopeBug) {
|
|
193
|
-
supported['class-field'] = false;
|
|
194
|
-
supported['class-static-field'] = false;
|
|
195
|
-
}
|
|
196
|
-
return supported;
|
|
197
168
|
}
|
|
198
169
|
const MAX_CONCURRENT_WRITES = 64;
|
|
199
170
|
async function emitFilesToDisk(files, writeFileCallback) {
|
|
@@ -75,7 +75,7 @@ function createAngularAssetsMiddleware(server, assets, outputFiles, usedComponen
|
|
|
75
75
|
// Shim the stylesheet if a component ID is provided
|
|
76
76
|
if (componentId.length > 0) {
|
|
77
77
|
// Validate component ID
|
|
78
|
-
if (/^[_.\-\p{Letter}\d]+-c\d
|
|
78
|
+
if (/^[_.\-\p{Letter}\d]+-c\d+$/u.test(componentId)) {
|
|
79
79
|
(0, load_esm_1.loadEsmModule)('@angular/compiler')
|
|
80
80
|
.then((compilerModule) => {
|
|
81
81
|
const encapsulatedData = compilerModule.encapsulateStyle(new TextDecoder().decode(data), componentId);
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
* Calculates a CSP compatible hash of an inline script.
|
|
10
|
+
* @param scriptText Text between opening and closing script tag. Has to
|
|
11
|
+
* include whitespaces and newlines!
|
|
12
|
+
* @returns The hash of the text formatted appropriately for CSP.
|
|
13
|
+
*/
|
|
14
|
+
export declare function hashTextContent(scriptText: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Finds all `<script>` tags and creates a dynamic script loading block for consecutive `<script>` with `src` attributes.
|
|
17
|
+
* Hashes all scripts, both inline and generated dynamic script loading blocks.
|
|
18
|
+
* Inserts a `<meta>` tag at the end of the `<head>` of the document with the generated hash-based CSP.
|
|
19
|
+
*
|
|
20
|
+
* @param html Markup that should be processed.
|
|
21
|
+
* @returns The transformed HTML that contains the `<meta>` tag CSP and dynamic loader scripts.
|
|
22
|
+
*/
|
|
23
|
+
export declare function autoCsp(html: string, unsafeEval?: boolean): Promise<string>;
|
|
@@ -0,0 +1,283 @@
|
|
|
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 __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 (mod) {
|
|
26
|
+
if (mod && mod.__esModule) return mod;
|
|
27
|
+
var result = {};
|
|
28
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
29
|
+
__setModuleDefault(result, mod);
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.hashTextContent = hashTextContent;
|
|
34
|
+
exports.autoCsp = autoCsp;
|
|
35
|
+
const crypto = __importStar(require("node:crypto"));
|
|
36
|
+
const html_rewriting_stream_1 = require("./html-rewriting-stream");
|
|
37
|
+
/**
|
|
38
|
+
* The hash function to use for hash directives to use in the CSP.
|
|
39
|
+
*/
|
|
40
|
+
const HASH_FUNCTION = 'sha256';
|
|
41
|
+
/**
|
|
42
|
+
* Get the specified attribute or return undefined if the tag doesn't have that attribute.
|
|
43
|
+
*
|
|
44
|
+
* @param tag StartTag of the <script>
|
|
45
|
+
* @returns
|
|
46
|
+
*/
|
|
47
|
+
function getScriptAttributeValue(tag, attrName) {
|
|
48
|
+
return tag.attrs.find((attr) => attr.name === attrName)?.value;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Checks whether a particular string is a MIME type associated with JavaScript, according to
|
|
52
|
+
* https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types#textjavascript
|
|
53
|
+
*
|
|
54
|
+
* @param mimeType a string that may be a MIME type
|
|
55
|
+
* @returns whether the string is a MIME type that is associated with JavaScript
|
|
56
|
+
*/
|
|
57
|
+
function isJavascriptMimeType(mimeType) {
|
|
58
|
+
return mimeType.split(';')[0] === 'text/javascript';
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Which of the type attributes on the script tag we should try passing along
|
|
62
|
+
* based on https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type
|
|
63
|
+
* @param scriptType the `type` attribute on the `<script>` tag under question
|
|
64
|
+
* @returns whether to add the script tag to the dynamically loaded script tag
|
|
65
|
+
*/
|
|
66
|
+
function shouldDynamicallyLoadScriptTagBasedOnType(scriptType) {
|
|
67
|
+
return (scriptType === undefined ||
|
|
68
|
+
scriptType === '' ||
|
|
69
|
+
scriptType === 'module' ||
|
|
70
|
+
isJavascriptMimeType(scriptType));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Calculates a CSP compatible hash of an inline script.
|
|
74
|
+
* @param scriptText Text between opening and closing script tag. Has to
|
|
75
|
+
* include whitespaces and newlines!
|
|
76
|
+
* @returns The hash of the text formatted appropriately for CSP.
|
|
77
|
+
*/
|
|
78
|
+
function hashTextContent(scriptText) {
|
|
79
|
+
const hash = crypto.createHash(HASH_FUNCTION).update(scriptText, 'utf-8').digest('base64');
|
|
80
|
+
return `'${HASH_FUNCTION}-${hash}'`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Finds all `<script>` tags and creates a dynamic script loading block for consecutive `<script>` with `src` attributes.
|
|
84
|
+
* Hashes all scripts, both inline and generated dynamic script loading blocks.
|
|
85
|
+
* Inserts a `<meta>` tag at the end of the `<head>` of the document with the generated hash-based CSP.
|
|
86
|
+
*
|
|
87
|
+
* @param html Markup that should be processed.
|
|
88
|
+
* @returns The transformed HTML that contains the `<meta>` tag CSP and dynamic loader scripts.
|
|
89
|
+
*/
|
|
90
|
+
async function autoCsp(html, unsafeEval = false) {
|
|
91
|
+
const { rewriter, transformedContent } = await (0, html_rewriting_stream_1.htmlRewritingStream)(html);
|
|
92
|
+
let openedScriptTag = undefined;
|
|
93
|
+
let scriptContent = [];
|
|
94
|
+
const hashes = [];
|
|
95
|
+
/**
|
|
96
|
+
* Generates the dynamic loading script and puts it in the rewriter and adds the hash of the dynamic
|
|
97
|
+
* loader script to the collection of hashes to add to the <meta> tag CSP.
|
|
98
|
+
*/
|
|
99
|
+
function emitLoaderScript() {
|
|
100
|
+
const loaderScript = createLoaderScript(scriptContent);
|
|
101
|
+
hashes.push(hashTextContent(loaderScript));
|
|
102
|
+
rewriter.emitRaw(`<script>${loaderScript}</script>`);
|
|
103
|
+
scriptContent = [];
|
|
104
|
+
}
|
|
105
|
+
rewriter.on('startTag', (tag, html) => {
|
|
106
|
+
if (tag.tagName === 'script') {
|
|
107
|
+
openedScriptTag = tag;
|
|
108
|
+
const src = getScriptAttributeValue(tag, 'src');
|
|
109
|
+
if (src) {
|
|
110
|
+
// If there are any interesting attributes, note them down.
|
|
111
|
+
const scriptType = getScriptAttributeValue(tag, 'type');
|
|
112
|
+
if (shouldDynamicallyLoadScriptTagBasedOnType(scriptType)) {
|
|
113
|
+
scriptContent.push({
|
|
114
|
+
src: src,
|
|
115
|
+
type: scriptType,
|
|
116
|
+
async: getScriptAttributeValue(tag, 'async') !== undefined,
|
|
117
|
+
defer: getScriptAttributeValue(tag, 'defer') !== undefined,
|
|
118
|
+
});
|
|
119
|
+
return; // Skip writing my script tag until we've read it all.
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// We are encountering the first start tag that's not <script src="..."> after a string of
|
|
124
|
+
// consecutive <script src="...">. <script> tags without a src attribute will also end a chain
|
|
125
|
+
// of src attributes that can be loaded in a single loader script, so those will end up here.
|
|
126
|
+
//
|
|
127
|
+
// The first place when we can determine this to be the case is
|
|
128
|
+
// during the first opening tag that's not <script src="...">, where we need to insert the
|
|
129
|
+
// dynamic loader script before continuing on with writing the rest of the tags.
|
|
130
|
+
// (One edge case is where there are no more opening tags after the last <script src="..."> is
|
|
131
|
+
// closed, but this case is handled below with the final </body> tag.)
|
|
132
|
+
if (scriptContent.length > 0) {
|
|
133
|
+
emitLoaderScript();
|
|
134
|
+
}
|
|
135
|
+
rewriter.emitStartTag(tag);
|
|
136
|
+
});
|
|
137
|
+
rewriter.on('text', (tag, html) => {
|
|
138
|
+
if (openedScriptTag && !getScriptAttributeValue(openedScriptTag, 'src')) {
|
|
139
|
+
hashes.push(hashTextContent(html));
|
|
140
|
+
}
|
|
141
|
+
rewriter.emitText(tag);
|
|
142
|
+
});
|
|
143
|
+
rewriter.on('endTag', (tag, html) => {
|
|
144
|
+
if (openedScriptTag && tag.tagName === 'script') {
|
|
145
|
+
const src = getScriptAttributeValue(openedScriptTag, 'src');
|
|
146
|
+
const scriptType = getScriptAttributeValue(openedScriptTag, 'type');
|
|
147
|
+
openedScriptTag = undefined;
|
|
148
|
+
// Return early to avoid writing the closing </script> tag if it's a part of the
|
|
149
|
+
// dynamic loader script.
|
|
150
|
+
if (src && shouldDynamicallyLoadScriptTagBasedOnType(scriptType)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (tag.tagName === 'body' || tag.tagName === 'html') {
|
|
155
|
+
// Write the loader script if a string of <script>s were the last opening tag of the document.
|
|
156
|
+
if (scriptContent.length > 0) {
|
|
157
|
+
emitLoaderScript();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
rewriter.emitEndTag(tag);
|
|
161
|
+
});
|
|
162
|
+
const rewritten = await transformedContent();
|
|
163
|
+
// Second pass to add the header
|
|
164
|
+
const secondPass = await (0, html_rewriting_stream_1.htmlRewritingStream)(rewritten);
|
|
165
|
+
secondPass.rewriter.on('startTag', (tag, _) => {
|
|
166
|
+
secondPass.rewriter.emitStartTag(tag);
|
|
167
|
+
if (tag.tagName === 'head') {
|
|
168
|
+
// See what hashes we came up with!
|
|
169
|
+
secondPass.rewriter.emitRaw(`<meta http-equiv="Content-Security-Policy" content="${getStrictCsp(hashes, {
|
|
170
|
+
enableBrowserFallbacks: true,
|
|
171
|
+
enableTrustedTypes: false,
|
|
172
|
+
enableUnsafeEval: unsafeEval,
|
|
173
|
+
})}">`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
return secondPass.transformedContent();
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Returns a strict Content Security Policy for mitigating XSS.
|
|
180
|
+
* For more details read csp.withgoogle.com.
|
|
181
|
+
* If you modify this CSP, make sure it has not become trivially bypassable by
|
|
182
|
+
* checking the policy using csp-evaluator.withgoogle.com.
|
|
183
|
+
*
|
|
184
|
+
* @param hashes A list of sha-256 hashes of trusted inline scripts.
|
|
185
|
+
* @param enableTrustedTypes If Trusted Types should be enabled for scripts.
|
|
186
|
+
* @param enableBrowserFallbacks If fallbacks for older browsers should be
|
|
187
|
+
* added. This is will not weaken the policy as modern browsers will ignore
|
|
188
|
+
* the fallbacks.
|
|
189
|
+
* @param enableUnsafeEval If you cannot remove all uses of eval(), you can
|
|
190
|
+
* still set a strict CSP, but you will have to use the 'unsafe-eval'
|
|
191
|
+
* keyword which will make your policy slightly less secure.
|
|
192
|
+
*/
|
|
193
|
+
function getStrictCsp(hashes,
|
|
194
|
+
// default CSP options
|
|
195
|
+
cspOptions = {
|
|
196
|
+
enableBrowserFallbacks: true,
|
|
197
|
+
enableTrustedTypes: false,
|
|
198
|
+
enableUnsafeEval: false,
|
|
199
|
+
}) {
|
|
200
|
+
hashes = hashes || [];
|
|
201
|
+
const strictCspTemplate = {
|
|
202
|
+
// 'strict-dynamic' allows hashed scripts to create new scripts.
|
|
203
|
+
'script-src': [`'strict-dynamic'`, ...hashes],
|
|
204
|
+
// Restricts `object-src` to disable dangerous plugins like Flash.
|
|
205
|
+
'object-src': [`'none'`],
|
|
206
|
+
// Restricts `base-uri` to block the injection of `<base>` tags. This
|
|
207
|
+
// prevents attackers from changing the locations of scripts loaded from
|
|
208
|
+
// relative URLs.
|
|
209
|
+
'base-uri': [`'self'`],
|
|
210
|
+
};
|
|
211
|
+
// Adds fallbacks for browsers not compatible to CSP3 and CSP2.
|
|
212
|
+
// These fallbacks are ignored by modern browsers in presence of hashes,
|
|
213
|
+
// and 'strict-dynamic'.
|
|
214
|
+
if (cspOptions.enableBrowserFallbacks) {
|
|
215
|
+
// Fallback for Safari. All modern browsers supporting strict-dynamic will
|
|
216
|
+
// ignore the 'https:' fallback.
|
|
217
|
+
strictCspTemplate['script-src'].push('https:');
|
|
218
|
+
// 'unsafe-inline' is only ignored in presence of a hash or nonce.
|
|
219
|
+
if (hashes.length > 0) {
|
|
220
|
+
strictCspTemplate['script-src'].push(`'unsafe-inline'`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// If enabled, dangerous DOM sinks will only accept typed objects instead of
|
|
224
|
+
// strings.
|
|
225
|
+
if (cspOptions.enableTrustedTypes) {
|
|
226
|
+
strictCspTemplate['require-trusted-types-for'] = ['script'];
|
|
227
|
+
}
|
|
228
|
+
// If enabled, `eval()`-calls will be allowed, making the policy slightly
|
|
229
|
+
// less secure.
|
|
230
|
+
if (cspOptions.enableUnsafeEval) {
|
|
231
|
+
strictCspTemplate['script-src'].push(`'unsafe-eval'`);
|
|
232
|
+
}
|
|
233
|
+
return Object.entries(strictCspTemplate)
|
|
234
|
+
.map(([directive, values]) => {
|
|
235
|
+
return `${directive} ${values.join(' ')};`;
|
|
236
|
+
})
|
|
237
|
+
.join('');
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Returns JS code for dynamically loading sourced (external) scripts.
|
|
241
|
+
* @param srcList A list of paths for scripts that should be loaded.
|
|
242
|
+
*/
|
|
243
|
+
function createLoaderScript(srcList, enableTrustedTypes = false) {
|
|
244
|
+
if (!srcList.length) {
|
|
245
|
+
throw new Error('Cannot create a loader script with no scripts to load.');
|
|
246
|
+
}
|
|
247
|
+
const srcListFormatted = srcList
|
|
248
|
+
.map((s) => {
|
|
249
|
+
// URI encoding means value can't escape string, JS, or HTML context.
|
|
250
|
+
const srcAttr = encodeURI(s.src).replaceAll("'", "\\'");
|
|
251
|
+
// Can only be 'module' or a JS MIME type or an empty string.
|
|
252
|
+
const typeAttr = s.type ? "'" + s.type + "'" : undefined;
|
|
253
|
+
const asyncAttr = s.async ? 'true' : 'false';
|
|
254
|
+
const deferAttr = s.defer ? 'true' : 'false';
|
|
255
|
+
return `['${srcAttr}', ${typeAttr}, ${asyncAttr}, ${deferAttr}]`;
|
|
256
|
+
})
|
|
257
|
+
.join();
|
|
258
|
+
return enableTrustedTypes
|
|
259
|
+
? `
|
|
260
|
+
var scripts = [${srcListFormatted}];
|
|
261
|
+
var policy = self.trustedTypes && self.trustedTypes.createPolicy ?
|
|
262
|
+
self.trustedTypes.createPolicy('angular#auto-csp', {createScriptURL: function(u) {
|
|
263
|
+
return scripts.includes(u) ? u : null;
|
|
264
|
+
}}) : { createScriptURL: function(u) { return u; } };
|
|
265
|
+
scripts.forEach(function(scriptUrl) {
|
|
266
|
+
var s = document.createElement('script');
|
|
267
|
+
s.src = policy.createScriptURL(scriptUrl[0]);
|
|
268
|
+
s.type = scriptUrl[1];
|
|
269
|
+
s.async = !!scriptUrl[2];
|
|
270
|
+
s.defer = !!scriptUrl[3];
|
|
271
|
+
document.body.appendChild(s);
|
|
272
|
+
});\n`
|
|
273
|
+
: `
|
|
274
|
+
var scripts = [${srcListFormatted}];
|
|
275
|
+
scripts.forEach(function(scriptUrl) {
|
|
276
|
+
var s = document.createElement('script');
|
|
277
|
+
s.src = scriptUrl[0];
|
|
278
|
+
s.type = scriptUrl[1];
|
|
279
|
+
s.async = !!scriptUrl[2];
|
|
280
|
+
s.defer = !!scriptUrl[3];
|
|
281
|
+
document.body.appendChild(s);
|
|
282
|
+
});\n`;
|
|
283
|
+
}
|
|
@@ -5,7 +5,11 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.dev/license
|
|
7
7
|
*/
|
|
8
|
+
import type { RewritingStream } from 'parse5-html-rewriting-stream';
|
|
9
|
+
export type StartTag = Parameters<RewritingStream['emitStartTag']>[0];
|
|
10
|
+
export type EndTag = Parameters<RewritingStream['emitEndTag']>[0];
|
|
11
|
+
export type { RewritingStream };
|
|
8
12
|
export declare function htmlRewritingStream(content: string): Promise<{
|
|
9
|
-
rewriter:
|
|
13
|
+
rewriter: RewritingStream;
|
|
10
14
|
transformedContent: () => Promise<string>;
|
|
11
15
|
}>;
|
|
@@ -20,6 +20,9 @@ export interface IndexHtmlGeneratorProcessOptions {
|
|
|
20
20
|
as?: string;
|
|
21
21
|
}[];
|
|
22
22
|
}
|
|
23
|
+
export interface AutoCspOptions {
|
|
24
|
+
unsafeEval: boolean;
|
|
25
|
+
}
|
|
23
26
|
export interface IndexHtmlGeneratorOptions {
|
|
24
27
|
indexPath: string;
|
|
25
28
|
deployUrl?: string;
|
|
@@ -31,6 +34,7 @@ export interface IndexHtmlGeneratorOptions {
|
|
|
31
34
|
cache?: NormalizedCachedOptions;
|
|
32
35
|
imageDomains?: string[];
|
|
33
36
|
generateDedicatedSSRContent?: boolean;
|
|
37
|
+
autoCsp?: AutoCspOptions;
|
|
34
38
|
}
|
|
35
39
|
export type IndexHtmlTransform = (content: string) => Promise<string>;
|
|
36
40
|
export interface IndexHtmlPluginTransformResult {
|
|
@@ -12,6 +12,7 @@ const promises_1 = require("node:fs/promises");
|
|
|
12
12
|
const node_path_1 = require("node:path");
|
|
13
13
|
const add_event_dispatch_contract_1 = require("./add-event-dispatch-contract");
|
|
14
14
|
const augment_index_html_1 = require("./augment-index-html");
|
|
15
|
+
const auto_csp_1 = require("./auto-csp");
|
|
15
16
|
const inline_critical_css_1 = require("./inline-critical-css");
|
|
16
17
|
const inline_fonts_1 = require("./inline-fonts");
|
|
17
18
|
const ngcm_attribute_1 = require("./ngcm-attribute");
|
|
@@ -39,6 +40,13 @@ class IndexHtmlGenerator {
|
|
|
39
40
|
this.csrPlugins.push(addNgcmAttributePlugin());
|
|
40
41
|
this.ssrPlugins.push(addEventDispatchContractPlugin(), addNoncePlugin());
|
|
41
42
|
}
|
|
43
|
+
// Auto-CSP (as the last step)
|
|
44
|
+
if (options.autoCsp) {
|
|
45
|
+
if (options.generateDedicatedSSRContent) {
|
|
46
|
+
throw new Error('Cannot set both SSR and auto-CSP at the same time.');
|
|
47
|
+
}
|
|
48
|
+
this.csrPlugins.push(autoCspPlugin(options.autoCsp.unsafeEval));
|
|
49
|
+
}
|
|
42
50
|
}
|
|
43
51
|
async process(options) {
|
|
44
52
|
let content = await this.readIndex(this.options.indexPath);
|
|
@@ -130,6 +138,9 @@ function inlineCriticalCssPlugin(generator) {
|
|
|
130
138
|
function addNoncePlugin() {
|
|
131
139
|
return (html) => (0, nonce_1.addNonce)(html);
|
|
132
140
|
}
|
|
141
|
+
function autoCspPlugin(unsafeEval) {
|
|
142
|
+
return (html) => (0, auto_csp_1.autoCsp)(html, unsafeEval);
|
|
143
|
+
}
|
|
133
144
|
function postTransformPlugin({ options }) {
|
|
134
145
|
return async (html) => (options.postTransform ? options.postTransform(html) : html);
|
|
135
146
|
}
|
|
@@ -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.0.0-next.
|
|
13
|
+
const VERSION = '19.0.0-next.13';
|
|
14
14
|
function hasCacheMetadata(value) {
|
|
15
15
|
return (!!value &&
|
|
16
16
|
typeof value === 'object' &&
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { NormalizedApplicationBuildOptions } from '../../builders/application/options';
|
|
9
9
|
import type { BuildOutputFile } from '../../tools/esbuild/bundler-context';
|
|
10
|
-
import { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
|
|
10
|
+
import type { PrerenderedRoutesRecord } from '../../tools/esbuild/bundler-execution-result';
|
|
11
11
|
export declare const SERVER_APP_MANIFEST_FILENAME = "angular-app-manifest.mjs";
|
|
12
12
|
export declare const SERVER_APP_ENGINE_MANIFEST_FILENAME = "angular-app-engine-manifest.mjs";
|
|
13
13
|
/**
|