Updated the files.

This commit is contained in:
Batuhan Berk Başoğlu 2024-02-08 19:38:41 -05:00
parent 1553e6b971
commit 753967d4f5
23418 changed files with 3784666 additions and 0 deletions

21
my-app/node_modules/@angular-devkit/build-angular/LICENSE generated vendored Executable file
View file

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2017 Google, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

24
my-app/node_modules/@angular-devkit/build-angular/README.md generated vendored Executable file
View file

@ -0,0 +1,24 @@
# @angular-devkit/build-angular
This package contains [Architect builders](/packages/angular_devkit/architect/README.md) used to build and test Angular applications and libraries.
## Builders
| Name | Description |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| application | Build an Angular application targeting a browser and server environment using [esbuild](https://esbuild.github.io). |
| app-shell | Build an Angular [App shell](https://angular.io/guide/app-shell). |
| browser | Build an Angular application targeting a browser environment using [Webpack](https://webpack.js.org). |
| browser-esbuild | Build an Angular application targeting a browser environment using [esbuild](https://esbuild.github.io). |
| dev-server | A development server that provides live reloading. |
| extract-i18n | Extract i18n messages from an Angular application. |
| karma | Execute unit tests using [Karma](https://github.com/karma-runner/karma) test runner. |
| ng-packagr | Build and package an Angular library in [Angular Package Format (APF)](https://angular.io/guide/angular-package-format) format using [ng-packagr](https://github.com/ng-packagr/ng-packagr). |
| prerender | [Prerender](https://angular.io/guide/prerendering) pages of your application. Prerendering is the process where a dynamic page is processed at build time generating static HTML. |
| server | Build an Angular application targeting a [Node.js](https://nodejs.org) environment. |
| ssr-dev-server | A development server which offers live reload during development, but uses server-side rendering. |
| protractor | **Deprecated** - Run end-to-end tests using [Protractor](https://www.protractortest.org/) framework. |
## Disclaimer
While the builders when executed via the Angular CLI and their associated options are considered stable, the programmatic APIs are not considered officially supported and are not subject to the breaking change guarantees of SemVer.

View file

@ -0,0 +1,75 @@
{
"$schema": "../architect/src/builders-schema.json",
"builders": {
"application": {
"implementation": "./src/builders/application",
"schema": "./src/builders/application/schema.json",
"description": "Build an application."
},
"app-shell": {
"implementation": "./src/builders/app-shell",
"schema": "./src/builders/app-shell/schema.json",
"description": "Build a server application and a browser application, then render the index.html and use it for the browser output."
},
"browser": {
"implementation": "./src/builders/browser",
"schema": "./src/builders/browser/schema.json",
"description": "Build a browser application."
},
"browser-esbuild": {
"implementation": "./src/builders/browser-esbuild",
"schema": "./src/builders/browser-esbuild/schema.json",
"description": "Build a browser application."
},
"dev-server": {
"implementation": "./src/builders/dev-server",
"schema": "./src/builders/dev-server/schema.json",
"description": "Serve a browser application."
},
"extract-i18n": {
"implementation": "./src/builders/extract-i18n",
"schema": "./src/builders/extract-i18n/schema.json",
"description": "Extract i18n strings from a browser application."
},
"jest": {
"implementation": "./src/builders/jest",
"schema": "./src/builders/jest/schema.json",
"description": "Run unit tests using Jest."
},
"karma": {
"implementation": "./src/builders/karma",
"schema": "./src/builders/karma/schema.json",
"description": "Run Karma unit tests."
},
"web-test-runner": {
"implementation": "./src/builders/web-test-runner",
"schema": "./src/builders/web-test-runner/schema.json",
"description": "Run unit tests with Web Test Runner."
},
"protractor": {
"implementation": "./src/builders/protractor",
"schema": "./src/builders/protractor/schema.json",
"description": "Run protractor over a dev server."
},
"server": {
"implementation": "./src/builders/server",
"schema": "./src/builders/server/schema.json",
"description": "Build a server Angular application."
},
"ng-packagr": {
"implementation": "./src/builders/ng-packagr",
"schema": "./src/builders/ng-packagr/schema.json",
"description": "Build a library with ng-packagr."
},
"ssr-dev-server": {
"implementation": "./src/builders/ssr-dev-server",
"schema": "./src/builders/ssr-dev-server/schema.json",
"description": "Serve a universal application."
},
"prerender": {
"implementation": "./src/builders/prerender",
"schema": "./src/builders/prerender/schema.json",
"description": "Perform build-time prerendering of chosen routes."
}
}
}

View file

@ -0,0 +1,149 @@
{
"name": "@angular-devkit/build-angular",
"version": "17.1.3",
"description": "Angular Webpack Build Facade",
"main": "src/index.js",
"typings": "src/index.d.ts",
"builders": "builders.json",
"dependencies": {
"@ampproject/remapping": "2.2.1",
"@angular-devkit/architect": "0.1701.3",
"@angular-devkit/build-webpack": "0.1701.3",
"@angular-devkit/core": "17.1.3",
"@babel/core": "7.23.7",
"@babel/generator": "7.23.6",
"@babel/helper-annotate-as-pure": "7.22.5",
"@babel/helper-split-export-declaration": "7.22.6",
"@babel/plugin-transform-async-generator-functions": "7.23.7",
"@babel/plugin-transform-async-to-generator": "7.23.3",
"@babel/plugin-transform-runtime": "7.23.7",
"@babel/preset-env": "7.23.7",
"@babel/runtime": "7.23.7",
"@discoveryjs/json-ext": "0.5.7",
"@ngtools/webpack": "17.1.3",
"@vitejs/plugin-basic-ssl": "1.0.2",
"ansi-colors": "4.1.3",
"autoprefixer": "10.4.16",
"babel-loader": "9.1.3",
"babel-plugin-istanbul": "6.1.1",
"browserslist": "^4.21.5",
"copy-webpack-plugin": "11.0.0",
"critters": "0.0.20",
"css-loader": "6.8.1",
"esbuild-wasm": "0.19.11",
"fast-glob": "3.3.2",
"https-proxy-agent": "7.0.2",
"http-proxy-middleware": "2.0.6",
"inquirer": "9.2.12",
"jsonc-parser": "3.2.0",
"karma-source-map-support": "1.4.0",
"less": "4.2.0",
"less-loader": "11.1.0",
"license-webpack-plugin": "4.0.2",
"loader-utils": "3.2.1",
"magic-string": "0.30.5",
"mini-css-extract-plugin": "2.7.6",
"mrmime": "2.0.0",
"open": "8.4.2",
"ora": "5.4.1",
"parse5-html-rewriting-stream": "7.0.0",
"picomatch": "3.0.1",
"piscina": "4.2.1",
"postcss": "8.4.33",
"postcss-loader": "7.3.4",
"resolve-url-loader": "5.0.0",
"rxjs": "7.8.1",
"sass": "1.69.7",
"sass-loader": "13.3.3",
"semver": "7.5.4",
"source-map-loader": "5.0.0",
"source-map-support": "0.5.21",
"terser": "5.26.0",
"text-table": "0.2.0",
"tree-kill": "1.2.2",
"tslib": "2.6.2",
"undici": "6.2.1",
"vite": "5.0.12",
"watchpack": "2.4.0",
"webpack": "5.89.0",
"webpack-dev-middleware": "6.1.1",
"webpack-dev-server": "4.15.1",
"webpack-merge": "5.10.0",
"webpack-subresource-integrity": "5.1.0"
},
"optionalDependencies": {
"esbuild": "0.19.11"
},
"peerDependencies": {
"@angular/compiler-cli": "^17.0.0",
"@angular/localize": "^17.0.0",
"@angular/platform-server": "^17.0.0",
"@angular/service-worker": "^17.0.0",
"@web/test-runner": "^0.18.0",
"browser-sync": "^3.0.2",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.5.0",
"karma": "^6.3.0",
"ng-packagr": "^17.0.0",
"protractor": "^7.0.0",
"tailwindcss": "^2.0.0 || ^3.0.0",
"typescript": ">=5.2 <5.4"
},
"peerDependenciesMeta": {
"@angular/localize": {
"optional": true
},
"@angular/platform-server": {
"optional": true
},
"@angular/service-worker": {
"optional": true
},
"@web/test-runner": {
"optional": true
},
"browser-sync": {
"optional": true
},
"jest": {
"optional": true
},
"jest-environment-jsdom": {
"optional": true
},
"karma": {
"optional": true
},
"ng-packagr": {
"optional": true
},
"protractor": {
"optional": true
},
"tailwindcss": {
"optional": true
}
},
"keywords": [
"Angular CLI",
"Angular DevKit",
"angular",
"devkit",
"sdk"
],
"repository": {
"type": "git",
"url": "https://github.com/angular/angular-cli.git"
},
"engines": {
"node": "^18.13.0 || >=20.9.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"author": "Angular Authors",
"license": "MIT",
"bugs": {
"url": "https://github.com/angular/angular-cli/issues"
},
"homepage": "https://github.com/angular/angular-cli"
}

View file

@ -0,0 +1,7 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

View file

@ -0,0 +1,9 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
module.exports = require('../src/tools/webpack/plugins/karma/karma');

View file

@ -0,0 +1,17 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/* eslint-disable import/no-extraneous-dependencies */
// Workaround for https://github.com/bazelbuild/rules_nodejs/issues/1033
// Alternative approach instead of https://github.com/angular/angular/pull/33226
declare module '@babel/core' {
export * from '@types/babel__core';
}
declare module '@babel/generator' {
export { default } from '@types/babel__generator';
}

View file

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { JsonObject } from '@angular-devkit/core';
import { Schema as BuildWebpackAppShellSchema } from './schema';
declare const _default: import("../../../../architect/src/internal").Builder<BuildWebpackAppShellSchema & JsonObject>;
export default _default;

View file

@ -0,0 +1,179 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const architect_1 = require("@angular-devkit/architect");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const piscina_1 = __importDefault(require("piscina"));
const utils_1 = require("../../utils");
const error_1 = require("../../utils/error");
const service_worker_1 = require("../../utils/service-worker");
const spinner_1 = require("../../utils/spinner");
async function _renderUniversal(options, context, browserResult, serverResult, spinner) {
// Get browser target options.
const browserTarget = (0, architect_1.targetFromTargetString)(options.browserTarget);
const rawBrowserOptions = (await context.getTargetOptions(browserTarget));
const browserBuilderName = await context.getBuilderNameForTarget(browserTarget);
const browserOptions = await context.validateOptions(rawBrowserOptions, browserBuilderName);
// Locate zone.js to load in the render worker
const root = context.workspaceRoot;
const zonePackage = require.resolve('zone.js', { paths: [root] });
const projectName = context.target && context.target.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = path.join(root, projectMetadata.root ?? '');
const { styles } = (0, utils_1.normalizeOptimization)(browserOptions.optimization);
let inlineCriticalCssProcessor;
if (styles.inlineCritical) {
const { InlineCriticalCssProcessor } = await Promise.resolve().then(() => __importStar(require('../../utils/index-file/inline-critical-css')));
inlineCriticalCssProcessor = new InlineCriticalCssProcessor({
minify: styles.minify,
deployUrl: browserOptions.deployUrl,
});
}
const renderWorker = new piscina_1.default({
filename: require.resolve('./render-worker'),
maxThreads: 1,
workerData: { zonePackage },
});
try {
for (const { path: outputPath, baseHref } of browserResult.outputs) {
const localeDirectory = path.relative(browserResult.baseOutputPath, outputPath);
const browserIndexOutputPath = path.join(outputPath, 'index.html');
const indexHtml = await fs.promises.readFile(browserIndexOutputPath, 'utf8');
const serverBundlePath = await _getServerModuleBundlePath(options, context, serverResult, localeDirectory);
let html = await renderWorker.run({
serverBundlePath,
document: indexHtml,
url: options.route,
});
// Overwrite the client index file.
const outputIndexPath = options.outputIndexPath
? path.join(root, options.outputIndexPath)
: browserIndexOutputPath;
if (inlineCriticalCssProcessor) {
const { content, warnings, errors } = await inlineCriticalCssProcessor.process(html, {
outputPath,
});
html = content;
if (warnings.length || errors.length) {
spinner.stop();
warnings.forEach((m) => context.logger.warn(m));
errors.forEach((m) => context.logger.error(m));
spinner.start();
}
}
await fs.promises.writeFile(outputIndexPath, html);
if (browserOptions.serviceWorker) {
await (0, service_worker_1.augmentAppWithServiceWorker)(projectRoot, root, outputPath, baseHref ?? '/', browserOptions.ngswConfigPath);
}
}
}
finally {
await renderWorker.destroy();
}
return browserResult;
}
async function _getServerModuleBundlePath(options, context, serverResult, browserLocaleDirectory) {
if (options.appModuleBundle) {
return path.join(context.workspaceRoot, options.appModuleBundle);
}
const { baseOutputPath = '' } = serverResult;
const outputPath = path.join(baseOutputPath, browserLocaleDirectory);
if (!fs.existsSync(outputPath)) {
throw new Error(`Could not find server output directory: ${outputPath}.`);
}
const re = /^main\.(?:[a-zA-Z0-9]{16}\.)?js$/;
const maybeMain = fs.readdirSync(outputPath).find((x) => re.test(x));
if (!maybeMain) {
throw new Error('Could not find the main bundle.');
}
return path.join(outputPath, maybeMain);
}
async function _appShellBuilder(options, context) {
const browserTarget = (0, architect_1.targetFromTargetString)(options.browserTarget);
const serverTarget = (0, architect_1.targetFromTargetString)(options.serverTarget);
// Never run the browser target in watch mode.
// If service worker is needed, it will be added in _renderUniversal();
const browserOptions = (await context.getTargetOptions(browserTarget));
const optimization = (0, utils_1.normalizeOptimization)(browserOptions.optimization);
optimization.styles.inlineCritical = false;
// Webpack based builders do not have the `removeSpecialComments` option.
delete optimization.styles.removeSpecialComments;
const browserTargetRun = await context.scheduleTarget(browserTarget, {
watch: false,
serviceWorker: false,
optimization: optimization,
});
if (browserTargetRun.info.builderName === '@angular-devkit/build-angular:application') {
return {
success: false,
error: '"@angular-devkit/build-angular:application" has built-in app-shell generation capabilities. ' +
'The "appShell" option should be used instead.',
};
}
const serverTargetRun = await context.scheduleTarget(serverTarget, {
watch: false,
});
let spinner;
try {
const [browserResult, serverResult] = await Promise.all([
browserTargetRun.result,
serverTargetRun.result,
]);
if (browserResult.success === false || browserResult.baseOutputPath === undefined) {
return browserResult;
}
else if (serverResult.success === false) {
return serverResult;
}
spinner = new spinner_1.Spinner();
spinner.start('Generating application shell...');
const result = await _renderUniversal(options, context, browserResult, serverResult, spinner);
spinner.succeed('Application shell generation complete.');
return result;
}
catch (err) {
spinner?.fail('Application shell generation failed.');
(0, error_1.assertIsError)(err);
return { success: false, error: err.message };
}
finally {
await Promise.all([browserTargetRun.stop(), serverTargetRun.stop()]);
}
}
exports.default = (0, architect_1.createBuilder)(_appShellBuilder);

View file

@ -0,0 +1,36 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* A request to render a Server bundle generate by the universal server builder.
*/
interface RenderRequest {
/**
* The path to the server bundle that should be loaded and rendered.
*/
serverBundlePath: string;
/**
* The existing HTML document as a string that will be augmented with the rendered application.
*/
document: string;
/**
* An optional URL path that represents the Angular route that should be rendered.
*/
url: string;
}
/**
* Renders an application based on a provided server bundle path, initial document, and optional URL route.
* @param param0 A request to render a server bundle.
* @returns A promise that resolves to the render HTML document for the application.
*/
declare function render({ serverBundlePath, document, url }: RenderRequest): Promise<string>;
/**
* The default export will be the promise returned by the initialize function.
* This is awaited by piscina prior to using the Worker.
*/
declare const _default: Promise<typeof render>;
export default _default;

View file

@ -0,0 +1,102 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = __importDefault(require("node:assert"));
const node_worker_threads_1 = require("node:worker_threads");
/**
* The fully resolved path to the zone.js package that will be loaded during worker initialization.
* This is passed as workerData when setting up the worker via the `piscina` package.
*/
const { zonePackage } = node_worker_threads_1.workerData;
/**
* Renders an application based on a provided server bundle path, initial document, and optional URL route.
* @param param0 A request to render a server bundle.
* @returns A promise that resolves to the render HTML document for the application.
*/
async function render({ serverBundlePath, document, url }) {
const { ɵSERVER_CONTEXT, AppServerModule, renderModule, renderApplication, default: bootstrapAppFn, } = (await Promise.resolve(`${serverBundlePath}`).then(s => __importStar(require(s))));
(0, node_assert_1.default)(ɵSERVER_CONTEXT, `ɵSERVER_CONTEXT was not exported from: ${serverBundlePath}.`);
const platformProviders = [
{
provide: ɵSERVER_CONTEXT,
useValue: 'app-shell',
},
];
let renderAppPromise;
// Render platform server module
if (isBootstrapFn(bootstrapAppFn)) {
(0, node_assert_1.default)(renderApplication, `renderApplication was not exported from: ${serverBundlePath}.`);
renderAppPromise = renderApplication(bootstrapAppFn, {
document,
url,
platformProviders,
});
}
else {
(0, node_assert_1.default)(renderModule, `renderModule was not exported from: ${serverBundlePath}.`);
const moduleClass = bootstrapAppFn || AppServerModule;
(0, node_assert_1.default)(moduleClass, `Neither an AppServerModule nor a bootstrapping function was exported from: ${serverBundlePath}.`);
renderAppPromise = renderModule(moduleClass, {
document,
url,
extraProviders: platformProviders,
});
}
// The below should really handled by the framework!!!.
let timer;
const renderingTimeout = new Promise((_, reject) => (timer = setTimeout(() => reject(new Error(`Page ${new URL(url, 'resolve://').pathname} did not render in 30 seconds.`)), 30000)));
return Promise.race([renderAppPromise, renderingTimeout]).finally(() => clearTimeout(timer));
}
function isBootstrapFn(value) {
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
return typeof value === 'function' && !('ɵmod' in value);
}
/**
* Initializes the worker when it is first created by loading the Zone.js package
* into the worker instance.
*
* @returns A promise resolving to the render function of the worker.
*/
async function initialize() {
// Setup Zone.js
await Promise.resolve(`${zonePackage}`).then(s => __importStar(require(s)));
// Return the render function for use
return render;
}
/**
* The default export will be the promise returned by the initialize function.
* This is awaited by piscina prior to using the Worker.
*/
exports.default = initialize();

View file

@ -0,0 +1,36 @@
/**
* App Shell target options for Build Facade.
*/
export interface Schema {
/**
* Script that exports the Server AppModule to render. This should be the main JavaScript
* outputted by the server target. By default we will resolve the outputPath of the
* serverTarget and find a bundle named 'main' in it (whether or not there's a hash tag).
*/
appModuleBundle?: string;
/**
* A browser builder target use for rendering the application shell in the format of
* `project:target[:configuration]`. You can also pass in more than one configuration name
* as a comma-separated list. Example: `project:target:production,staging`.
*/
browserTarget: string;
/**
* The input path for the index.html file. By default uses the output index.html of the
* browser target.
*/
inputIndexPath?: string;
/**
* The output path of the index.html file. By default will overwrite the input file.
*/
outputIndexPath?: string;
/**
* The route to render.
*/
route?: string;
/**
* A server builder target use for rendering the application shell in the format of
* `project:target[:configuration]`. You can also pass in more than one configuration name
* as a comma-separated list. Example: `project:target:production,staging`.
*/
serverTarget: string;
}

View file

@ -0,0 +1,4 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });

View file

@ -0,0 +1,37 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "App Shell Target",
"description": "App Shell target options for Build Facade.",
"type": "object",
"properties": {
"browserTarget": {
"type": "string",
"description": "A browser builder target use for rendering the application shell in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"serverTarget": {
"type": "string",
"description": "A server builder target use for rendering the application shell in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"appModuleBundle": {
"type": "string",
"description": "Script that exports the Server AppModule to render. This should be the main JavaScript outputted by the server target. By default we will resolve the outputPath of the serverTarget and find a bundle named 'main' in it (whether or not there's a hash tag)."
},
"route": {
"type": "string",
"description": "The route to render.",
"default": "/"
},
"inputIndexPath": {
"type": "string",
"description": "The input path for the index.html file. By default uses the output index.html of the browser target."
},
"outputIndexPath": {
"type": "string",
"description": "The output path of the index.html file. By default will overwrite the input file."
}
},
"additionalProperties": false,
"required": ["browserTarget", "serverTarget"]
}

View file

@ -0,0 +1,29 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderOutput } from '@angular-devkit/architect';
import type { logging } from '@angular-devkit/core';
import { BuildOutputFile } from '../../tools/esbuild/bundler-context';
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedCachedOptions } from '../../utils/normalize-cache';
import { NormalizedOutputOptions } from './options';
export declare function runEsBuildBuildAction(action: (rebuildState?: RebuildState) => ExecutionResult | Promise<ExecutionResult>, options: {
workspaceRoot: string;
projectRoot: string;
outputOptions: NormalizedOutputOptions;
logger: logging.LoggerApi;
cacheOptions: NormalizedCachedOptions;
writeToFileSystem: boolean;
writeToFileSystemFilter: ((file: BuildOutputFile) => boolean) | undefined;
watch?: boolean;
verbose?: boolean;
progress?: boolean;
deleteOutputPath?: boolean;
poll?: number;
signal?: AbortSignal;
preserveSymlinks?: boolean;
}): AsyncIterable<(ExecutionResult['outputWithFiles'] | ExecutionResult['output']) & BuilderOutput>;

View file

@ -0,0 +1,185 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runEsBuildBuildAction = void 0;
const node_fs_1 = require("node:fs");
const node_path_1 = __importDefault(require("node:path"));
const sass_language_1 = require("../../tools/esbuild/stylesheets/sass-language");
const utils_1 = require("../../tools/esbuild/utils");
const delete_output_dir_1 = require("../../utils/delete-output-dir");
const environment_options_1 = require("../../utils/environment-options");
async function* runEsBuildBuildAction(action, options) {
const { writeToFileSystemFilter, writeToFileSystem, watch, poll, logger, deleteOutputPath, cacheOptions, outputOptions, verbose, projectRoot, workspaceRoot, progress, preserveSymlinks, } = options;
if (deleteOutputPath && writeToFileSystem) {
await (0, delete_output_dir_1.deleteOutputDir)(workspaceRoot, outputOptions.base, [
outputOptions.browser,
outputOptions.server,
]);
}
const withProgress = progress ? utils_1.withSpinner : utils_1.withNoProgress;
// Initial build
let result;
try {
// Perform the build action
result = await withProgress('Building...', () => action());
// Log all diagnostic (error/warning) messages from the build
await (0, utils_1.logMessages)(logger, result);
}
finally {
// Ensure Sass workers are shutdown if not watching
if (!watch) {
(0, sass_language_1.shutdownSassWorkerPool)();
}
}
// Setup watcher if watch mode enabled
let watcher;
if (watch) {
if (progress) {
logger.info('Watch mode enabled. Watching for file changes...');
}
const ignored = [
// Ignore the output and cache paths to avoid infinite rebuild cycles
outputOptions.base,
cacheOptions.basePath,
`${workspaceRoot.replace(/\\/g, '/')}/**/.*/**`,
];
if (!preserveSymlinks) {
// Ignore all node modules directories to avoid excessive file watchers.
// Package changes are handled below by watching manifest and lock files.
// NOTE: this is not enable when preserveSymlinks is true as this would break `npm link` usages.
ignored.push('**/node_modules/**');
}
// Setup a watcher
const { createWatcher } = await Promise.resolve().then(() => __importStar(require('../../tools/esbuild/watcher')));
watcher = createWatcher({
polling: typeof poll === 'number',
interval: poll,
followSymlinks: preserveSymlinks,
ignored,
});
// Setup abort support
options.signal?.addEventListener('abort', () => void watcher?.close());
// Watch the entire project root if 'NG_BUILD_WATCH_ROOT' environment variable is set
if (environment_options_1.shouldWatchRoot) {
watcher.add(projectRoot);
}
// Watch workspace for package manager changes
const packageWatchFiles = [
// manifest can affect module resolution
'package.json',
// npm lock file
'package-lock.json',
// pnpm lock file
'pnpm-lock.yaml',
// yarn lock file including Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
'yarn.lock',
'.pnp.cjs',
'.pnp.data.json',
];
watcher.add(packageWatchFiles
.map((file) => node_path_1.default.join(workspaceRoot, file))
.filter((file) => (0, node_fs_1.existsSync)(file)));
// Watch locations provided by the initial build result
watcher.add(result.watchFiles);
}
// Output the first build results after setting up the watcher to ensure that any code executed
// higher in the iterator call stack will trigger the watcher. This is particularly relevant for
// unit tests which execute the builder and modify the file system programmatically.
if (writeToFileSystem) {
// Write output files
await (0, utils_1.writeResultFiles)(result.outputFiles, result.assetFiles, outputOptions);
yield result.output;
}
else {
// Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
yield result.outputWithFiles;
}
// Finish if watch mode is not enabled
if (!watcher) {
return;
}
// Wait for changes and rebuild as needed
const currentWatchFiles = new Set(result.watchFiles);
try {
for await (const changes of watcher) {
if (options.signal?.aborted) {
break;
}
if (verbose) {
logger.info(changes.toDebugString());
}
result = await withProgress('Changes detected. Rebuilding...', () => action(result.createRebuildState(changes)));
// Log all diagnostic (error/warning) messages from the rebuild
await (0, utils_1.logMessages)(logger, result);
// Update watched locations provided by the new build result.
// Keep watching all previous files if there are any errors; otherwise consider all
// files stale until confirmed present in the new result's watch files.
const staleWatchFiles = result.errors.length > 0 ? undefined : new Set(currentWatchFiles);
for (const watchFile of result.watchFiles) {
if (!currentWatchFiles.has(watchFile)) {
// Add new watch location
watcher.add(watchFile);
currentWatchFiles.add(watchFile);
}
// Present so remove from stale locations
staleWatchFiles?.delete(watchFile);
}
// Remove any stale locations if the build was successful
if (staleWatchFiles?.size) {
watcher.remove([...staleWatchFiles]);
}
if (writeToFileSystem) {
// Write output files
const filesToWrite = writeToFileSystemFilter
? result.outputFiles.filter(writeToFileSystemFilter)
: result.outputFiles;
await (0, utils_1.writeResultFiles)(filesToWrite, result.assetFiles, outputOptions);
yield result.output;
}
else {
// Requires casting due to unneeded `JsonObject` requirement. Remove once fixed.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
yield result.outputWithFiles;
}
}
}
finally {
// Stop the watcher and cleanup incremental rebuild state
await Promise.allSettled([watcher.close(), result.dispose()]);
(0, sass_language_1.shutdownSassWorkerPool)();
}
}
exports.runEsBuildBuildAction = runEsBuildBuildAction;

View file

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedApplicationBuildOptions } from './options';
export declare function executeBuild(options: NormalizedApplicationBuildOptions, context: BuilderContext, rebuildState?: RebuildState): Promise<ExecutionResult>;

View file

@ -0,0 +1,134 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeBuild = void 0;
const source_file_cache_1 = require("../../tools/esbuild/angular/source-file-cache");
const budget_stats_1 = require("../../tools/esbuild/budget-stats");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const bundler_execution_result_1 = require("../../tools/esbuild/bundler-execution-result");
const commonjs_checker_1 = require("../../tools/esbuild/commonjs-checker");
const license_extractor_1 = require("../../tools/esbuild/license-extractor");
const utils_1 = require("../../tools/esbuild/utils");
const bundle_calculator_1 = require("../../utils/bundle-calculator");
const color_1 = require("../../utils/color");
const copy_assets_1 = require("../../utils/copy-assets");
const supported_browsers_1 = require("../../utils/supported-browsers");
const execute_post_bundle_1 = require("./execute-post-bundle");
const i18n_1 = require("./i18n");
const setup_bundling_1 = require("./setup-bundling");
async function executeBuild(options, context, rebuildState) {
const { projectRoot, workspaceRoot, i18nOptions, optimizationOptions, assets, cacheOptions, prerenderOptions, } = options;
// TODO: Consider integrating into watch mode. Would require full rebuild on target changes.
const browsers = (0, supported_browsers_1.getSupportedBrowsers)(projectRoot, context.logger);
// Load active translations if inlining
// TODO: Integrate into watch mode and only load changed translations
if (i18nOptions.shouldInline) {
await (0, i18n_1.loadActiveTranslations)(context, i18nOptions);
}
// Reuse rebuild state or create new bundle contexts for code and global stylesheets
let bundlerContexts = rebuildState?.rebuildContexts;
const codeBundleCache = rebuildState?.codeBundleCache ??
new source_file_cache_1.SourceFileCache(cacheOptions.enabled ? cacheOptions.path : undefined);
if (bundlerContexts === undefined) {
bundlerContexts = (0, setup_bundling_1.setupBundlerContexts)(options, browsers, codeBundleCache);
}
const bundlingResult = await bundler_context_1.BundlerContext.bundleAll(bundlerContexts, rebuildState?.fileChanges.all);
const executionResult = new bundler_execution_result_1.ExecutionResult(bundlerContexts, codeBundleCache);
executionResult.addWarnings(bundlingResult.warnings);
// Return if the bundling has errors
if (bundlingResult.errors) {
executionResult.addErrors(bundlingResult.errors);
return executionResult;
}
// Analyze external imports if external options are enabled
if (options.externalPackages || bundlingResult.externalConfiguration) {
const { externalConfiguration, externalImports: { browser, server }, } = bundlingResult;
const implicitBrowser = browser ? [...browser] : [];
const implicitServer = server ? [...server] : [];
// TODO: Implement wildcard externalConfiguration filtering
executionResult.setExternalMetadata(externalConfiguration
? implicitBrowser.filter((value) => !externalConfiguration.includes(value))
: implicitBrowser, externalConfiguration
? implicitServer.filter((value) => !externalConfiguration.includes(value))
: implicitServer, externalConfiguration);
}
const { metafile, initialFiles, outputFiles } = bundlingResult;
executionResult.outputFiles.push(...outputFiles);
const changedFiles = rebuildState && executionResult.findChangedFiles(rebuildState.previousOutputHashes);
// Analyze files for bundle budget failures if present
let budgetFailures;
if (options.budgets) {
const compatStats = (0, budget_stats_1.generateBudgetStats)(metafile, initialFiles);
budgetFailures = [...(0, bundle_calculator_1.checkBudgets)(options.budgets, compatStats, true)];
for (const { message, severity } of budgetFailures) {
if (severity === 'error') {
executionResult.addError(message);
}
else {
executionResult.addWarning(message);
}
}
}
// Calculate estimated transfer size if scripts are optimized
let estimatedTransferSizes;
if (optimizationOptions.scripts || optimizationOptions.styles.minify) {
estimatedTransferSizes = await (0, utils_1.calculateEstimatedTransferSizes)(executionResult.outputFiles);
}
// Check metafile for CommonJS module usage if optimizing scripts
if (optimizationOptions.scripts) {
const messages = (0, commonjs_checker_1.checkCommonJSModules)(metafile, options.allowedCommonJsDependencies);
executionResult.addWarnings(messages);
}
// Copy assets
if (assets) {
// The webpack copy assets helper is used with no base paths defined. This prevents the helper
// from directly writing to disk. This should eventually be replaced with a more optimized helper.
executionResult.addAssets(await (0, copy_assets_1.copyAssets)(assets, [], workspaceRoot));
}
// Extract and write licenses for used packages
if (options.extractLicenses) {
executionResult.addOutputFile('3rdpartylicenses.txt', await (0, license_extractor_1.extractLicenses)(metafile, workspaceRoot), bundler_context_1.BuildOutputFileType.Root);
}
// Perform i18n translation inlining if enabled
let prerenderedRoutes;
if (i18nOptions.shouldInline) {
const result = await (0, i18n_1.inlineI18n)(options, executionResult, initialFiles);
executionResult.addErrors(result.errors);
executionResult.addWarnings(result.warnings);
prerenderedRoutes = result.prerenderedRoutes;
}
else {
const result = await (0, execute_post_bundle_1.executePostBundleSteps)(options, executionResult.outputFiles, executionResult.assetFiles, initialFiles,
// Set lang attribute to the defined source locale if present
i18nOptions.hasDefinedSourceLocale ? i18nOptions.sourceLocale : undefined);
executionResult.addErrors(result.errors);
executionResult.addWarnings(result.warnings);
prerenderedRoutes = result.prerenderedRoutes;
executionResult.outputFiles.push(...result.additionalOutputFiles);
executionResult.assetFiles.push(...result.additionalAssets);
}
if (prerenderOptions) {
executionResult.addOutputFile('prerendered-routes.json', JSON.stringify({ routes: prerenderedRoutes.sort((a, b) => a.localeCompare(b)) }, null, 2), bundler_context_1.BuildOutputFileType.Root);
let prerenderMsg = `Prerendered ${prerenderedRoutes.length} static route`;
if (prerenderedRoutes.length > 1) {
prerenderMsg += 's.';
}
else {
prerenderMsg += '.';
}
context.logger.info(color_1.colors.magenta(prerenderMsg) + '\n');
}
(0, utils_1.logBuildStats)(context.logger, metafile, initialFiles, budgetFailures, changedFiles, estimatedTransferSizes);
// Write metafile if stats option is enabled
if (options.stats) {
executionResult.addOutputFile('stats.json', JSON.stringify(metafile, null, 2), bundler_context_1.BuildOutputFileType.Root);
}
return executionResult;
}
exports.executeBuild = executeBuild;

View file

@ -0,0 +1,25 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuildOutputFile, InitialFileRecord } from '../../tools/esbuild/bundler-context';
import { BuildOutputAsset } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedApplicationBuildOptions } from './options';
/**
* Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation.
* @param options The normalized application builder options used to create the build.
* @param outputFiles The output files of an executed build.
* @param assetFiles The assets of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
* @param locale A language locale to insert in the index.html.
*/
export declare function executePostBundleSteps(options: NormalizedApplicationBuildOptions, outputFiles: BuildOutputFile[], assetFiles: BuildOutputAsset[], initialFiles: Map<string, InitialFileRecord>, locale: string | undefined): Promise<{
errors: string[];
warnings: string[];
additionalOutputFiles: BuildOutputFile[];
additionalAssets: BuildOutputAsset[];
prerenderedRoutes: string[];
}>;

View file

@ -0,0 +1,96 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executePostBundleSteps = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const index_html_generator_1 = require("../../tools/esbuild/index-html-generator");
const utils_1 = require("../../tools/esbuild/utils");
const environment_options_1 = require("../../utils/environment-options");
const prerender_1 = require("../../utils/server-rendering/prerender");
const service_worker_1 = require("../../utils/service-worker");
/**
* Run additional builds steps including SSG, AppShell, Index HTML file and Service worker generation.
* @param options The normalized application builder options used to create the build.
* @param outputFiles The output files of an executed build.
* @param assetFiles The assets of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
* @param locale A language locale to insert in the index.html.
*/
async function executePostBundleSteps(options, outputFiles, assetFiles, initialFiles, locale) {
const additionalAssets = [];
const additionalOutputFiles = [];
const allErrors = [];
const allWarnings = [];
const prerenderedRoutes = [];
const { serviceWorker, indexHtmlOptions, optimizationOptions, sourcemapOptions, ssrOptions, prerenderOptions, appShellOptions, workspaceRoot, verbose, } = options;
/**
* Index HTML content without CSS inlining to be used for server rendering (AppShell, SSG and SSR).
*
* NOTE: we don't perform critical CSS inlining as this will be done during server rendering.
*/
let indexContentOutputNoCssInlining;
// When using prerender/app-shell the index HTML file can be regenerated.
// Thus, we use a Map so that we do not generate 2 files with the same filename.
const additionalHtmlOutputFiles = new Map();
// Generate index HTML file
// If localization is enabled, index generation is handled in the inlining process.
if (indexHtmlOptions) {
const { content, contentWithoutCriticalCssInlined, errors, warnings } = await (0, index_html_generator_1.generateIndexHtml)(initialFiles, outputFiles, {
...options,
optimizationOptions,
}, locale);
indexContentOutputNoCssInlining = contentWithoutCriticalCssInlined;
allErrors.push(...errors);
allWarnings.push(...warnings);
additionalHtmlOutputFiles.set(indexHtmlOptions.output, (0, utils_1.createOutputFileFromText)(indexHtmlOptions.output, content, bundler_context_1.BuildOutputFileType.Browser));
if (ssrOptions) {
const serverIndexHtmlFilename = 'index.server.html';
additionalHtmlOutputFiles.set(serverIndexHtmlFilename, (0, utils_1.createOutputFileFromText)(serverIndexHtmlFilename, contentWithoutCriticalCssInlined, bundler_context_1.BuildOutputFileType.Server));
}
}
// Pre-render (SSG) and App-shell
// If localization is enabled, prerendering is handled in the inlining process.
if (prerenderOptions || appShellOptions) {
(0, node_assert_1.default)(indexContentOutputNoCssInlining, 'The "index" option is required when using the "ssg" or "appShell" options.');
const { output, warnings, errors, prerenderedRoutes: generatedRoutes, } = await (0, prerender_1.prerenderPages)(workspaceRoot, appShellOptions, prerenderOptions, outputFiles, assetFiles, indexContentOutputNoCssInlining, sourcemapOptions.scripts, optimizationOptions.styles.inlineCritical, environment_options_1.maxWorkers, verbose);
allErrors.push(...errors);
allWarnings.push(...warnings);
prerenderedRoutes.push(...Array.from(generatedRoutes));
for (const [path, content] of Object.entries(output)) {
additionalHtmlOutputFiles.set(path, (0, utils_1.createOutputFileFromText)(path, content, bundler_context_1.BuildOutputFileType.Browser));
}
}
additionalOutputFiles.push(...additionalHtmlOutputFiles.values());
// Augment the application with service worker support
// If localization is enabled, service worker is handled in the inlining process.
if (serviceWorker) {
try {
const serviceWorkerResult = await (0, service_worker_1.augmentAppWithServiceWorkerEsbuild)(workspaceRoot, serviceWorker, options.baseHref || '/',
// Ensure additional files recently added are used
[...outputFiles, ...additionalOutputFiles], assetFiles);
additionalOutputFiles.push((0, utils_1.createOutputFileFromText)('ngsw.json', serviceWorkerResult.manifest, bundler_context_1.BuildOutputFileType.Browser));
additionalAssets.push(...serviceWorkerResult.assetFiles);
}
catch (error) {
allErrors.push(error instanceof Error ? error.message : `${error}`);
}
}
return {
errors: allErrors,
warnings: allWarnings,
additionalAssets,
prerenderedRoutes,
additionalOutputFiles,
};
}
exports.executePostBundleSteps = executePostBundleSteps;

View file

@ -0,0 +1,29 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { InitialFileRecord } from '../../tools/esbuild/bundler-context';
import { ExecutionResult } from '../../tools/esbuild/bundler-execution-result';
import { NormalizedApplicationBuildOptions } from './options';
/**
* Inlines all active locales as specified by the application build options into all
* application JavaScript files created during the build.
* @param options The normalized application builder options used to create the build.
* @param executionResult The result of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
*/
export declare function inlineI18n(options: NormalizedApplicationBuildOptions, executionResult: ExecutionResult, initialFiles: Map<string, InitialFileRecord>): Promise<{
errors: string[];
warnings: string[];
prerenderedRoutes: string[];
}>;
/**
* Loads all active translations using the translation loaders from the `@angular/localize` package.
* @param context The architect builder context for the current build.
* @param i18n The normalized i18n options to use.
*/
export declare function loadActiveTranslations(context: BuilderContext, i18n: NormalizedApplicationBuildOptions['i18nOptions']): Promise<void>;

View file

@ -0,0 +1,128 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadActiveTranslations = exports.inlineI18n = void 0;
const node_path_1 = require("node:path");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const i18n_inliner_1 = require("../../tools/esbuild/i18n-inliner");
const environment_options_1 = require("../../utils/environment-options");
const i18n_options_1 = require("../../utils/i18n-options");
const load_translations_1 = require("../../utils/load-translations");
const url_1 = require("../../utils/url");
const execute_post_bundle_1 = require("./execute-post-bundle");
/**
* Inlines all active locales as specified by the application build options into all
* application JavaScript files created during the build.
* @param options The normalized application builder options used to create the build.
* @param executionResult The result of an executed build.
* @param initialFiles A map containing initial file information for the executed build.
*/
async function inlineI18n(options, executionResult, initialFiles) {
// Create the multi-threaded inliner with common options and the files generated from the build.
const inliner = new i18n_inliner_1.I18nInliner({
missingTranslation: options.i18nOptions.missingTranslationBehavior ?? 'warning',
outputFiles: executionResult.outputFiles,
shouldOptimize: options.optimizationOptions.scripts,
}, environment_options_1.maxWorkers);
const inlineResult = {
errors: [],
warnings: [],
prerenderedRoutes: [],
};
// For each active locale, use the inliner to process the output files of the build.
const updatedOutputFiles = [];
const updatedAssetFiles = [];
try {
for (const locale of options.i18nOptions.inlineLocales) {
// A locale specific set of files is returned from the inliner.
const localeInlineResult = await inliner.inlineForLocale(locale, options.i18nOptions.locales[locale].translation);
const localeOutputFiles = localeInlineResult.outputFiles;
inlineResult.errors.push(...localeInlineResult.errors);
inlineResult.warnings.push(...localeInlineResult.warnings);
const baseHref = getLocaleBaseHref(options.baseHref, options.i18nOptions, locale) ?? options.baseHref;
const { errors, warnings, additionalAssets, additionalOutputFiles, prerenderedRoutes: generatedRoutes, } = await (0, execute_post_bundle_1.executePostBundleSteps)({
...options,
baseHref,
}, localeOutputFiles, executionResult.assetFiles, initialFiles, locale);
localeOutputFiles.push(...additionalOutputFiles);
inlineResult.errors.push(...errors);
inlineResult.warnings.push(...warnings);
// Update directory with locale base
if (options.i18nOptions.flatOutput !== true) {
localeOutputFiles.forEach((file) => {
file.path = (0, node_path_1.join)(locale, file.path);
});
for (const assetFile of [...executionResult.assetFiles, ...additionalAssets]) {
updatedAssetFiles.push({
source: assetFile.source,
destination: (0, node_path_1.join)(locale, assetFile.destination),
});
}
inlineResult.prerenderedRoutes.push(...generatedRoutes.map((route) => node_path_1.posix.join('/', locale, route)));
}
else {
inlineResult.prerenderedRoutes.push(...generatedRoutes);
executionResult.assetFiles.push(...additionalAssets);
}
updatedOutputFiles.push(...localeOutputFiles);
}
}
finally {
await inliner.close();
}
// Update the result with all localized files.
executionResult.outputFiles = [
// Root files are not modified.
...executionResult.outputFiles.filter(({ type }) => type === bundler_context_1.BuildOutputFileType.Root),
// Updated files for each locale.
...updatedOutputFiles,
];
// Assets are only changed if not using the flat output option
if (options.i18nOptions.flatOutput !== true) {
executionResult.assetFiles = updatedAssetFiles;
}
return inlineResult;
}
exports.inlineI18n = inlineI18n;
function getLocaleBaseHref(baseHref, i18n, locale) {
if (i18n.flatOutput) {
return undefined;
}
if (i18n.locales[locale] && i18n.locales[locale].baseHref !== '') {
return (0, url_1.urlJoin)(baseHref || '', i18n.locales[locale].baseHref ?? `/${locale}/`);
}
return undefined;
}
/**
* Loads all active translations using the translation loaders from the `@angular/localize` package.
* @param context The architect builder context for the current build.
* @param i18n The normalized i18n options to use.
*/
async function loadActiveTranslations(context, i18n) {
// Load locale data and translations (if present)
let loader;
for (const [locale, desc] of Object.entries(i18n.locales)) {
if (!i18n.inlineLocales.has(locale) && locale !== i18n.sourceLocale) {
continue;
}
if (!desc.files.length) {
continue;
}
loader ??= await (0, load_translations_1.createTranslationLoader)();
(0, i18n_options_1.loadTranslations)(locale, desc, context.workspaceRoot, loader, {
warn(message) {
context.logger.warn(message);
},
error(message) {
throw new Error(message);
},
}, undefined, i18n.duplicateTranslationBehavior);
}
}
exports.loadActiveTranslations = loadActiveTranslations;

View file

@ -0,0 +1,57 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import { BuildOutputFile } from '../../tools/esbuild/bundler-context';
import { ApplicationBuilderExtensions, ApplicationBuilderInternalOptions } from './options';
import { Schema as ApplicationBuilderOptions } from './schema';
export { ApplicationBuilderOptions };
export declare function buildApplicationInternal(options: ApplicationBuilderInternalOptions, context: BuilderContext & {
signal?: AbortSignal;
}, infrastructureSettings?: {
write?: boolean;
}, extensions?: ApplicationBuilderExtensions): AsyncIterable<ApplicationBuilderOutput>;
export interface ApplicationBuilderOutput extends BuilderOutput {
outputFiles?: BuildOutputFile[];
assetFiles?: {
source: string;
destination: string;
}[];
}
/**
* Builds an application using the `application` builder with the provided
* options.
*
* Usage of the `plugins` parameter is NOT supported and may cause unexpected
* build output or build failures.
*
* @experimental Direct usage of this function is considered experimental.
*
* @param options The options defined by the builder's schema to use.
* @param context An Architect builder context instance.
* @param plugins An array of plugins to apply to the main code bundling.
* @returns The build output results of the build.
*/
export declare function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, plugins?: Plugin[]): AsyncIterable<ApplicationBuilderOutput>;
/**
* Builds an application using the `application` builder with the provided
* options.
*
* Usage of the `extensions` parameter is NOT supported and may cause unexpected
* build output or build failures.
*
* @experimental Direct usage of this function is considered experimental.
*
* @param options The options defined by the builder's schema to use.
* @param context An Architect builder context instance.
* @param extensions An object contain extension points for the build.
* @returns The build output results of the build.
*/
export declare function buildApplication(options: ApplicationBuilderOptions, context: BuilderContext, extensions?: ApplicationBuilderExtensions): AsyncIterable<ApplicationBuilderOutput>;
declare const _default: import("../../../../architect/src/internal").Builder<ApplicationBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;

View file

@ -0,0 +1,101 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildApplication = exports.buildApplicationInternal = void 0;
const architect_1 = require("@angular-devkit/architect");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const purge_cache_1 = require("../../utils/purge-cache");
const version_1 = require("../../utils/version");
const build_action_1 = require("./build-action");
const execute_build_1 = require("./execute-build");
const options_1 = require("./options");
async function* buildApplicationInternal(options,
// TODO: Integrate abort signal support into builder system
context, infrastructureSettings, extensions) {
const { workspaceRoot, logger, target } = context;
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(workspaceRoot);
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
// Determine project name from builder context target
const projectName = target?.project;
if (!projectName) {
yield { success: false, error: `The 'application' builder requires a target to be specified.` };
return;
}
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options, extensions);
const writeToFileSystem = infrastructureSettings?.write ?? true;
const writeServerBundles = writeToFileSystem && !!(normalizedOptions.ssrOptions && normalizedOptions.serverEntryPoint);
if (writeServerBundles) {
const { browser, server } = normalizedOptions.outputOptions;
if (browser === '') {
yield {
success: false,
error: `'outputPath.browser' cannot be configured to an empty string when SSR is enabled.`,
};
return;
}
if (browser === server) {
yield {
success: false,
error: `'outputPath.browser' and 'outputPath.server' cannot be configured to the same value.`,
};
return;
}
}
// Setup an abort controller with a builder teardown if no signal is present
let signal = context.signal;
if (!signal) {
const controller = new AbortController();
signal = controller.signal;
context.addTeardown(() => controller.abort('builder-teardown'));
}
yield* (0, build_action_1.runEsBuildBuildAction)(async (rebuildState) => {
const startTime = process.hrtime.bigint();
const result = await (0, execute_build_1.executeBuild)(normalizedOptions, context, rebuildState);
const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
const status = result.errors.length > 0 ? 'failed' : 'complete';
logger.info(`Application bundle generation ${status}. [${buildTime.toFixed(3)} seconds]`);
return result;
}, {
watch: normalizedOptions.watch,
preserveSymlinks: normalizedOptions.preserveSymlinks,
poll: normalizedOptions.poll,
deleteOutputPath: normalizedOptions.deleteOutputPath,
cacheOptions: normalizedOptions.cacheOptions,
outputOptions: normalizedOptions.outputOptions,
verbose: normalizedOptions.verbose,
projectRoot: normalizedOptions.projectRoot,
workspaceRoot: normalizedOptions.workspaceRoot,
progress: normalizedOptions.progress,
writeToFileSystem,
// For app-shell and SSG server files are not required by users.
// Omit these when SSR is not enabled.
writeToFileSystemFilter: writeServerBundles
? undefined
: (file) => file.type !== bundler_context_1.BuildOutputFileType.Server,
logger,
signal,
});
}
exports.buildApplicationInternal = buildApplicationInternal;
function buildApplication(options, context, pluginsOrExtensions) {
let extensions;
if (pluginsOrExtensions && Array.isArray(pluginsOrExtensions)) {
extensions = {
codePlugins: pluginsOrExtensions,
};
}
else {
extensions = pluginsOrExtensions;
}
return buildApplicationInternal(options, context, undefined, extensions);
}
exports.buildApplication = buildApplication;
exports.default = (0, architect_1.createBuilder)(buildApplication);

View file

@ -0,0 +1,140 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import { I18nOptions } from '../../utils/i18n-options';
import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
import { Schema as ApplicationBuilderOptions, I18NTranslation, OutputPathClass } from './schema';
export type NormalizedOutputOptions = Required<OutputPathClass>;
export type NormalizedApplicationBuildOptions = Awaited<ReturnType<typeof normalizeOptions>>;
export interface ApplicationBuilderExtensions {
codePlugins?: Plugin[];
indexHtmlTransformer?: IndexHtmlTransform;
}
/** Internal options hidden from builder schema but available when invoked programmatically. */
interface InternalOptions {
/**
* Entry points to use for the compilation. Incompatible with `browser`, which must not be provided. May be relative or absolute paths.
* If given a relative path, it is resolved relative to the current workspace and will generate an output at the same relative location
* in the output directory. If given an absolute path, the output will be generated in the root of the output directory with the same base
* name.
*/
entryPoints?: Set<string>;
/** File extension to use for the generated output files. */
outExtension?: 'js' | 'mjs';
/**
* Indicates whether all node packages should be marked as external.
* Currently used by the dev-server to support prebundling.
*/
externalPackages?: boolean;
/**
* Forces the output from the localize post-processing to not create nested directories per locale output.
* This is only used by the development server which currently only supports a single locale per build.
*/
forceI18nFlatOutput?: boolean;
/**
* Allows for usage of the deprecated `deployUrl` option with the compatibility builder `browser-esbuild`.
*/
deployUrl?: string;
}
/** Full set of options for `application` builder. */
export type ApplicationBuilderInternalOptions = Omit<ApplicationBuilderOptions & InternalOptions, 'browser'> & {
browser?: string;
};
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @param plugins An optional array of programmatically supplied build plugins.
* @returns An object containing normalized options required to perform the build.
*/
export declare function normalizeOptions(context: BuilderContext, projectName: string, options: ApplicationBuilderInternalOptions, extensions?: ApplicationBuilderExtensions): Promise<{
advancedOptimizations: boolean;
allowedCommonJsDependencies: string[] | undefined;
baseHref: string | undefined;
cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
crossOrigin: import("./schema").CrossOrigin | undefined;
deleteOutputPath: boolean | undefined;
externalDependencies: string[] | undefined;
extractLicenses: boolean | undefined;
inlineStyleLanguage: string;
jit: boolean;
stats: boolean;
polyfills: string[] | undefined;
poll: number | undefined;
progress: boolean;
externalPackages: boolean | undefined;
preserveSymlinks: boolean;
stylePreprocessorOptions: import("./schema").StylePreprocessorOptions | undefined;
subresourceIntegrity: boolean | undefined;
serverEntryPoint: string | undefined;
prerenderOptions: {
discoverRoutes: boolean;
routesFile: string | undefined;
} | undefined;
appShellOptions: {
route: string;
} | undefined;
ssrOptions: {
entry?: undefined;
} | {
entry: string | undefined;
} | undefined;
verbose: boolean | undefined;
watch: boolean | undefined;
workspaceRoot: string;
entryPoints: Record<string, string>;
optimizationOptions: import("../../utils").NormalizedOptimizationOptions;
outputOptions: Required<OutputPathClass>;
outExtension: "js" | "mjs" | undefined;
sourcemapOptions: import("../..").SourceMapObject;
tsconfig: string;
projectRoot: string;
assets: import("../..").AssetPatternObject[] | undefined;
outputNames: {
bundles: string;
media: string;
};
fileReplacements: Record<string, string> | undefined;
globalStyles: {
name: string;
files: string[];
initial: boolean;
}[];
globalScripts: {
name: string;
files: string[];
initial: boolean;
}[];
serviceWorker: string | undefined;
indexHtmlOptions: {
input: string;
output: string;
insertionOrder: import("../../utils/package-chunk-sort").EntryPointsType[];
transformer: IndexHtmlTransform | undefined;
preloadInitial: boolean;
} | undefined;
tailwindConfiguration: {
file: string;
package: string;
} | undefined;
i18nOptions: I18nOptions & {
duplicateTranslationBehavior?: I18NTranslation | undefined;
missingTranslationBehavior?: I18NTranslation | undefined;
};
namedChunks: boolean | undefined;
budgets: import("./schema").Budget[] | undefined;
publicPath: string | undefined;
plugins: Plugin[] | undefined;
loaderExtensions: Record<string, "binary" | "file" | "text"> | undefined;
}>;
export {};

View file

@ -0,0 +1,313 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = void 0;
const node_fs_1 = require("node:fs");
const promises_1 = require("node:fs/promises");
const node_module_1 = require("node:module");
const node_path_1 = __importDefault(require("node:path"));
const helpers_1 = require("../../tools/webpack/utils/helpers");
const utils_1 = require("../../utils");
const i18n_options_1 = require("../../utils/i18n-options");
const normalize_cache_1 = require("../../utils/normalize-cache");
const package_chunk_sort_1 = require("../../utils/package-chunk-sort");
const tailwind_1 = require("../../utils/tailwind");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
const schema_1 = require("./schema");
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @param plugins An optional array of programmatically supplied build plugins.
* @returns An object containing normalized options required to perform the build.
*/
// eslint-disable-next-line max-lines-per-function
async function normalizeOptions(context, projectName, options, extensions) {
// If not explicitly set, default to the Node.js process argument
const preserveSymlinks = options.preserveSymlinks ?? process.execArgv.includes('--preserve-symlinks');
// Setup base paths based on workspace root and project information
const workspaceRoot = preserveSymlinks
? context.workspaceRoot
: // NOTE: promises.realpath should not be used here since it uses realpath.native which
// can cause case conversion and other undesirable behavior on Windows systems.
// ref: https://github.com/nodejs/node/issues/7726
(0, node_fs_1.realpathSync)(context.workspaceRoot);
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = normalizeDirectoryPath(node_path_1.default.join(workspaceRoot, projectMetadata.root ?? ''));
const projectSourceRoot = normalizeDirectoryPath(node_path_1.default.join(workspaceRoot, projectMetadata.sourceRoot ?? 'src'));
// Gather persistent caching option and provide a project specific cache location
const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
cacheOptions.path = node_path_1.default.join(cacheOptions.path, projectName);
const i18nOptions = (0, i18n_options_1.createI18nOptions)(projectMetadata, options.localize);
i18nOptions.duplicateTranslationBehavior = options.i18nDuplicateTranslation;
i18nOptions.missingTranslationBehavior = options.i18nMissingTranslation;
if (options.forceI18nFlatOutput) {
i18nOptions.flatOutput = true;
}
const entryPoints = normalizeEntryPoints(workspaceRoot, options.browser, options.entryPoints);
const tsconfig = node_path_1.default.join(workspaceRoot, options.tsConfig);
const optimizationOptions = (0, utils_1.normalizeOptimization)(options.optimization);
const sourcemapOptions = (0, utils_1.normalizeSourceMaps)(options.sourceMap ?? false);
const assets = options.assets?.length
? (0, utils_1.normalizeAssetPatterns)(options.assets, workspaceRoot, projectRoot, projectSourceRoot)
: undefined;
const outputPath = options.outputPath;
const outputOptions = {
browser: 'browser',
server: 'server',
media: 'media',
...(typeof outputPath === 'string' ? undefined : outputPath),
base: normalizeDirectoryPath(node_path_1.default.resolve(workspaceRoot, typeof outputPath === 'string' ? outputPath : outputPath.base)),
};
const outputNames = {
bundles: options.outputHashing === schema_1.OutputHashing.All || options.outputHashing === schema_1.OutputHashing.Bundles
? '[name]-[hash]'
: '[name]',
media: outputOptions.media +
(options.outputHashing === schema_1.OutputHashing.All || options.outputHashing === schema_1.OutputHashing.Media
? '/[name]-[hash]'
: '/[name]'),
};
let fileReplacements;
if (options.fileReplacements) {
for (const replacement of options.fileReplacements) {
const fileReplaceWith = node_path_1.default.join(workspaceRoot, replacement.with);
try {
await (0, promises_1.access)(fileReplaceWith, promises_1.constants.F_OK);
}
catch {
throw new Error(`The ${fileReplaceWith} path in file replacements does not exist.`);
}
fileReplacements ??= {};
fileReplacements[node_path_1.default.join(workspaceRoot, replacement.replace)] = fileReplaceWith;
}
}
let loaderExtensions;
if (options.loader) {
for (const [extension, value] of Object.entries(options.loader)) {
if (extension[0] !== '.' || /\.[cm]?[jt]sx?$/.test(extension)) {
continue;
}
if (value !== 'text' && value !== 'binary' && value !== 'file' && value !== 'empty') {
continue;
}
loaderExtensions ??= {};
loaderExtensions[extension] = value;
}
}
const globalStyles = [];
if (options.styles?.length) {
const { entryPoints: stylesheetEntrypoints, noInjectNames } = (0, helpers_1.normalizeGlobalStyles)(options.styles || []);
for (const [name, files] of Object.entries(stylesheetEntrypoints)) {
globalStyles.push({ name, files, initial: !noInjectNames.includes(name) });
}
}
const globalScripts = [];
if (options.scripts?.length) {
for (const { bundleName, paths, inject } of (0, helpers_1.globalScriptsByBundleName)(options.scripts)) {
globalScripts.push({ name: bundleName, files: paths, initial: inject });
}
}
let indexHtmlOptions;
// index can never have a value of `true` but in the schema it's of type `boolean`.
if (typeof options.index !== 'boolean') {
indexHtmlOptions = {
input: node_path_1.default.join(workspaceRoot, (0, webpack_browser_config_1.getIndexInputFile)(options.index)),
// The output file will be created within the configured output path
output: (0, webpack_browser_config_1.getIndexOutputFile)(options.index),
// TODO: Use existing information from above to create the insertion order
insertionOrder: (0, package_chunk_sort_1.generateEntryPoints)({
scripts: options.scripts ?? [],
styles: options.styles ?? [],
}),
transformer: extensions?.indexHtmlTransformer,
// Preload initial defaults to true
preloadInitial: typeof options.index !== 'object' || (options.index.preloadInitial ?? true),
};
}
let serverEntryPoint;
if (options.server) {
serverEntryPoint = node_path_1.default.join(workspaceRoot, options.server);
}
else if (options.server === '') {
throw new Error('`server` option cannot be an empty string.');
}
let prerenderOptions;
if (options.prerender) {
const { discoverRoutes = true, routesFile = undefined } = options.prerender === true ? {} : options.prerender;
prerenderOptions = {
discoverRoutes,
routesFile: routesFile && node_path_1.default.join(workspaceRoot, routesFile),
};
}
let ssrOptions;
if (options.ssr === true) {
ssrOptions = {};
}
else if (typeof options.ssr === 'object') {
const { entry } = options.ssr;
ssrOptions = {
entry: entry && node_path_1.default.join(workspaceRoot, entry),
};
}
let appShellOptions;
if (options.appShell) {
appShellOptions = {
route: 'shell',
};
}
// Initial options to keep
const { allowedCommonJsDependencies, aot, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, deleteOutputPath, namedChunks, budgets, deployUrl, } = options;
// Return all the normalized options
return {
advancedOptimizations: !!aot && optimizationOptions.scripts,
allowedCommonJsDependencies,
baseHref,
cacheOptions,
crossOrigin,
deleteOutputPath,
externalDependencies,
extractLicenses,
inlineStyleLanguage,
jit: !aot,
stats: !!statsJson,
polyfills: polyfills === undefined || Array.isArray(polyfills) ? polyfills : [polyfills],
poll,
progress,
externalPackages,
preserveSymlinks,
stylePreprocessorOptions,
subresourceIntegrity,
serverEntryPoint,
prerenderOptions,
appShellOptions,
ssrOptions,
verbose,
watch,
workspaceRoot,
entryPoints,
optimizationOptions,
outputOptions,
outExtension,
sourcemapOptions,
tsconfig,
projectRoot,
assets,
outputNames,
fileReplacements,
globalStyles,
globalScripts,
serviceWorker: typeof serviceWorker === 'string' ? node_path_1.default.join(workspaceRoot, serviceWorker) : undefined,
indexHtmlOptions,
tailwindConfiguration: await getTailwindConfig(workspaceRoot, projectRoot, context),
i18nOptions,
namedChunks,
budgets: budgets?.length ? budgets : undefined,
publicPath: deployUrl ? deployUrl : undefined,
plugins: extensions?.codePlugins?.length ? extensions?.codePlugins : undefined,
loaderExtensions,
};
}
exports.normalizeOptions = normalizeOptions;
async function getTailwindConfig(workspaceRoot, projectRoot, context) {
const tailwindConfigurationPath = await (0, tailwind_1.findTailwindConfigurationFile)(workspaceRoot, projectRoot);
if (!tailwindConfigurationPath) {
return undefined;
}
// Create a node resolver at the project root as a directory
const resolver = (0, node_module_1.createRequire)(projectRoot + '/');
try {
return {
file: tailwindConfigurationPath,
package: resolver.resolve('tailwindcss'),
};
}
catch {
const relativeTailwindConfigPath = node_path_1.default.relative(workspaceRoot, tailwindConfigurationPath);
context.logger.warn(`Tailwind CSS configuration file found (${relativeTailwindConfigPath})` +
` but the 'tailwindcss' package is not installed.` +
` To enable Tailwind CSS, please install the 'tailwindcss' package.`);
}
return undefined;
}
/**
* Normalize entry point options. To maintain compatibility with the legacy browser builder, we need a single `browser`
* option which defines a single entry point. However, we also want to support multiple entry points as an internal option.
* The two options are mutually exclusive and if `browser` is provided it will be used as the sole entry point.
* If `entryPoints` are provided, they will be used as the set of entry points.
*
* @param workspaceRoot Path to the root of the Angular workspace.
* @param browser The `browser` option pointing at the application entry point. While required per the schema file, it may be omitted by
* programmatic usages of `browser-esbuild`.
* @param entryPoints Set of entry points to use if provided.
* @returns An object mapping entry point names to their file paths.
*/
function normalizeEntryPoints(workspaceRoot, browser, entryPoints = new Set()) {
if (browser === '') {
throw new Error('`browser` option cannot be an empty string.');
}
// `browser` and `entryPoints` are mutually exclusive.
if (browser && entryPoints.size > 0) {
throw new Error('Only one of `browser` or `entryPoints` may be provided.');
}
if (!browser && entryPoints.size === 0) {
// Schema should normally reject this case, but programmatic usages of the builder might make this mistake.
throw new Error('Either `browser` or at least one `entryPoints` value must be provided.');
}
// Schema types force `browser` to always be provided, but it may be omitted when the builder is invoked programmatically.
if (browser) {
// Use `browser` alone.
return { 'main': node_path_1.default.join(workspaceRoot, browser) };
}
else {
// Use `entryPoints` alone.
const entryPointPaths = {};
for (const entryPoint of entryPoints) {
const parsedEntryPoint = node_path_1.default.parse(entryPoint);
// Use the input file path without an extension as the "name" of the entry point dictating its output location.
// Relative entry points are generated at the same relative path in the output directory.
// Absolute entry points are always generated with the same file name in the root of the output directory. This includes absolute
// paths pointing at files actually within the workspace root.
const entryPointName = node_path_1.default.isAbsolute(entryPoint)
? parsedEntryPoint.name
: node_path_1.default.join(parsedEntryPoint.dir, parsedEntryPoint.name);
// Get the full file path to a relative entry point input. Leave bare specifiers alone so they are resolved as modules.
const isRelativePath = entryPoint.startsWith('.');
const entryPointPath = isRelativePath ? node_path_1.default.join(workspaceRoot, entryPoint) : entryPoint;
// Check for conflicts with previous entry points.
const existingEntryPointPath = entryPointPaths[entryPointName];
if (existingEntryPointPath) {
throw new Error(`\`${existingEntryPointPath}\` and \`${entryPointPath}\` both output to the same location \`${entryPointName}\`.` +
' Rename or move one of the files to fix the conflict.');
}
entryPointPaths[entryPointName] = entryPointPath;
}
return entryPointPaths;
}
}
/**
* Normalize a directory path string.
* Currently only removes a trailing slash if present.
* @param path A path string.
* @returns A normalized path string.
*/
function normalizeDirectoryPath(path) {
const last = path[path.length - 1];
if (last === '/' || last === '\\') {
return path.slice(0, -1);
}
return path;
}

View file

@ -0,0 +1,493 @@
/**
* Application builder target options
*/
export interface Schema {
/**
* A list of CommonJS or AMD packages that are allowed to be used without a build time
* warning. Use `'*'` to allow all.
*/
allowedCommonJsDependencies?: string[];
/**
* Build using Ahead of Time compilation.
*/
aot?: boolean;
/**
* Generates an application shell during build time.
*/
appShell?: boolean;
/**
* List of static application assets.
*/
assets?: AssetPattern[];
/**
* Base url for the application being built.
*/
baseHref?: string;
/**
* The full path for the browser entry point to the application, relative to the current
* workspace.
*/
browser: string;
/**
* Budget thresholds to ensure parts of your application stay within boundaries which you
* set.
*/
budgets?: Budget[];
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
crossOrigin?: CrossOrigin;
/**
* Delete the output path before building.
*/
deleteOutputPath?: boolean;
/**
* Exclude the listed external dependencies from being bundled into the bundle. Instead, the
* created bundle relies on these dependencies to be available during runtime.
*/
externalDependencies?: string[];
/**
* Extract all licenses in a separate file.
*/
extractLicenses?: boolean;
/**
* Replace compilation source files with other compilation source files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* How to handle duplicate translations for i18n.
*/
i18nDuplicateTranslation?: I18NTranslation;
/**
* How to handle missing translations for i18n.
*/
i18nMissingTranslation?: I18NTranslation;
/**
* Configures the generation of the application's HTML index.
*/
index: IndexUnion;
/**
* The stylesheet language to use for the application's inline component styles.
*/
inlineStyleLanguage?: InlineStyleLanguage;
/**
* Defines the type of loader to use with a specified file extension when used with a
* JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content
* as a Uint8Array; `file` emits the file and provides the runtime location of the file;
* `empty` considers the content to be empty and not include it in bundles.
*/
loader?: {
[key: string]: any;
};
/**
* Translate the bundles in one or more locales.
*/
localize?: Localize;
/**
* Use file name for lazy loaded chunks.
*/
namedChunks?: boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
optimization?: OptimizationUnion;
/**
* Define the output filename cache-busting hashing mode.
*/
outputHashing?: OutputHashing;
/**
* Specify the output path relative to workspace root.
*/
outputPath: OutputPathUnion;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* A list of polyfills to include in the build. Can be a full path for a file, relative to
* the current workspace or module specifier. Example: 'zone.js'.
*/
polyfills?: string[];
/**
* Prerender (SSG) pages of your application during build time.
*/
prerender?: PrerenderUnion;
/**
* Do not use the real path when resolving modules. If unset then will default to `true` if
* NodeJS option --preserve-symlinks is set.
*/
preserveSymlinks?: boolean;
/**
* Log progress to the console while building.
*/
progress?: boolean;
/**
* Global scripts to be included in the build.
*/
scripts?: ScriptElement[];
/**
* The full path for the server entry point to the application, relative to the current
* workspace.
*/
server?: string;
/**
* Generates a service worker configuration.
*/
serviceWorker?: ServiceWorker;
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
sourceMap?: SourceMapUnion;
/**
* Server side render (SSR) pages of your application during runtime.
*/
ssr?: SsrUnion;
/**
* Generates a 'stats.json' file which can be analyzed with
* https://esbuild.github.io/analyze/.
*/
statsJson?: boolean;
/**
* Options to pass to style preprocessors.
*/
stylePreprocessorOptions?: StylePreprocessorOptions;
/**
* Global styles to be included in the build.
*/
styles?: StyleElement[];
/**
* Enables the use of subresource integrity validation.
*/
subresourceIntegrity?: boolean;
/**
* The full path for the TypeScript configuration file, relative to the current workspace.
*/
tsConfig: string;
/**
* Adds more details to output logging.
*/
verbose?: boolean;
/**
* Run build when files change.
*/
watch?: boolean;
/**
* TypeScript configuration for Web Worker modules.
*/
webWorkerTsConfig?: string;
}
export type AssetPattern = AssetPatternClass | string;
export interface AssetPatternClass {
/**
* Allow glob patterns to follow symlink directories. This allows subdirectories of the
* symlink to be searched.
*/
followSymlinks?: boolean;
/**
* The pattern to match.
*/
glob: string;
/**
* An array of globs to ignore.
*/
ignore?: string[];
/**
* The input directory path in which to apply 'glob'. Defaults to the project root.
*/
input: string;
/**
* Absolute path within the output.
*/
output: string;
}
export interface Budget {
/**
* The baseline size for comparison.
*/
baseline?: string;
/**
* The threshold for error relative to the baseline (min & max).
*/
error?: string;
/**
* The maximum threshold for error relative to the baseline.
*/
maximumError?: string;
/**
* The maximum threshold for warning relative to the baseline.
*/
maximumWarning?: string;
/**
* The minimum threshold for error relative to the baseline.
*/
minimumError?: string;
/**
* The minimum threshold for warning relative to the baseline.
*/
minimumWarning?: string;
/**
* The name of the bundle.
*/
name?: string;
/**
* The type of budget.
*/
type: Type;
/**
* The threshold for warning relative to the baseline (min & max).
*/
warning?: string;
}
/**
* The type of budget.
*/
export declare enum Type {
All = "all",
AllScript = "allScript",
Any = "any",
AnyComponentStyle = "anyComponentStyle",
AnyScript = "anyScript",
Bundle = "bundle",
Initial = "initial"
}
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
export declare enum CrossOrigin {
Anonymous = "anonymous",
None = "none",
UseCredentials = "use-credentials"
}
export interface FileReplacement {
replace: string;
with: string;
}
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
export declare enum I18NTranslation {
Error = "error",
Ignore = "ignore",
Warning = "warning"
}
/**
* Configures the generation of the application's HTML index.
*/
export type IndexUnion = boolean | IndexObject | string;
export interface IndexObject {
/**
* The path of a file to use for the application's generated HTML index.
*/
input: string;
/**
* The output path of the application's generated HTML index file. The full provided path
* will be used and will be considered relative to the application's configured output path.
*/
output?: string;
/**
* Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial
* application files and resources.
*/
preloadInitial?: boolean;
[property: string]: any;
}
/**
* The stylesheet language to use for the application's inline component styles.
*/
export declare enum InlineStyleLanguage {
Css = "css",
Less = "less",
Sass = "sass",
Scss = "scss"
}
/**
* Translate the bundles in one or more locales.
*/
export type Localize = string[] | boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
export type OptimizationUnion = boolean | OptimizationClass;
export interface OptimizationClass {
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
fonts?: FontsUnion;
/**
* Enables optimization of the scripts output.
*/
scripts?: boolean;
/**
* Enables optimization of the styles output.
*/
styles?: StylesUnion;
}
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
export type FontsUnion = boolean | FontsClass;
export interface FontsClass {
/**
* Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS
* definitions in the application's HTML index file. This option requires internet access.
* `HTTPS_PROXY` environment variable can be used to specify a proxy server.
*/
inline?: boolean;
}
/**
* Enables optimization of the styles output.
*/
export type StylesUnion = boolean | StylesClass;
export interface StylesClass {
/**
* Extract and inline critical CSS definitions to improve first paint time.
*/
inlineCritical?: boolean;
/**
* Minify CSS definitions by removing extraneous whitespace and comments, merging
* identifiers and minimizing values.
*/
minify?: boolean;
/**
* Remove comments in global CSS that contains '@license' or '@preserve' or that starts with
* '//!' or '/*!'.
*/
removeSpecialComments?: boolean;
}
/**
* Define the output filename cache-busting hashing mode.
*/
export declare enum OutputHashing {
All = "all",
Bundles = "bundles",
Media = "media",
None = "none"
}
/**
* Specify the output path relative to workspace root.
*/
export type OutputPathUnion = OutputPathClass | string;
export interface OutputPathClass {
/**
* Specify the output path relative to workspace root.
*/
base: string;
/**
* The output directory name of your browser build within the output path base. Defaults to
* 'browser'.
*/
browser?: string;
/**
* The output directory name of your media files within the output browser directory.
* Defaults to 'media'.
*/
media?: string;
/**
* The output directory name of your server build within the output path base. Defaults to
* 'server'.
*/
server?: string;
}
/**
* Prerender (SSG) pages of your application during build time.
*/
export type PrerenderUnion = boolean | PrerenderClass;
export interface PrerenderClass {
/**
* Whether the builder should process the Angular Router configuration to find all
* unparameterized routes and prerender them.
*/
discoverRoutes?: boolean;
/**
* The path to a file that contains a list of all routes to prerender, separated by
* newlines. This option is useful if you want to prerender routes with parameterized URLs.
*/
routesFile?: string;
}
export type ScriptElement = ScriptClass | string;
export interface ScriptClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}
/**
* Generates a service worker configuration.
*/
export type ServiceWorker = boolean | string;
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
export type SourceMapUnion = boolean | SourceMapClass;
export interface SourceMapClass {
/**
* Output source maps used for error reporting tools.
*/
hidden?: boolean;
/**
* Output source maps for all scripts.
*/
scripts?: boolean;
/**
* Output source maps for all styles.
*/
styles?: boolean;
/**
* Resolve vendor packages source maps.
*/
vendor?: boolean;
}
/**
* Server side render (SSR) pages of your application during runtime.
*/
export type SsrUnion = boolean | SsrClass;
export interface SsrClass {
/**
* The server entry-point that when executed will spawn the web server.
*/
entry?: string;
}
/**
* Options to pass to style preprocessors.
*/
export interface StylePreprocessorOptions {
/**
* Paths to include. Paths will be resolved to workspace root.
*/
includePaths?: string[];
}
export type StyleElement = StyleClass | string;
export interface StyleClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}

View file

@ -0,0 +1,58 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutputHashing = exports.InlineStyleLanguage = exports.I18NTranslation = exports.CrossOrigin = exports.Type = void 0;
/**
* The type of budget.
*/
var Type;
(function (Type) {
Type["All"] = "all";
Type["AllScript"] = "allScript";
Type["Any"] = "any";
Type["AnyComponentStyle"] = "anyComponentStyle";
Type["AnyScript"] = "anyScript";
Type["Bundle"] = "bundle";
Type["Initial"] = "initial";
})(Type || (exports.Type = Type = {}));
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
var CrossOrigin;
(function (CrossOrigin) {
CrossOrigin["Anonymous"] = "anonymous";
CrossOrigin["None"] = "none";
CrossOrigin["UseCredentials"] = "use-credentials";
})(CrossOrigin || (exports.CrossOrigin = CrossOrigin = {}));
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
var I18NTranslation;
(function (I18NTranslation) {
I18NTranslation["Error"] = "error";
I18NTranslation["Ignore"] = "ignore";
I18NTranslation["Warning"] = "warning";
})(I18NTranslation || (exports.I18NTranslation = I18NTranslation = {}));
/**
* The stylesheet language to use for the application's inline component styles.
*/
var InlineStyleLanguage;
(function (InlineStyleLanguage) {
InlineStyleLanguage["Css"] = "css";
InlineStyleLanguage["Less"] = "less";
InlineStyleLanguage["Sass"] = "sass";
InlineStyleLanguage["Scss"] = "scss";
})(InlineStyleLanguage || (exports.InlineStyleLanguage = InlineStyleLanguage = {}));
/**
* Define the output filename cache-busting hashing mode.
*/
var OutputHashing;
(function (OutputHashing) {
OutputHashing["All"] = "all";
OutputHashing["Bundles"] = "bundles";
OutputHashing["Media"] = "media";
OutputHashing["None"] = "none";
})(OutputHashing || (exports.OutputHashing = OutputHashing = {}));

View file

@ -0,0 +1,618 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Application schema for Build Facade.",
"description": "Application builder target options",
"type": "object",
"properties": {
"assets": {
"type": "array",
"description": "List of static application assets.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"browser": {
"type": "string",
"description": "The full path for the browser entry point to the application, relative to the current workspace."
},
"server": {
"type": "string",
"description": "The full path for the server entry point to the application, relative to the current workspace."
},
"polyfills": {
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"type": "array",
"items": {
"type": "string",
"uniqueItems": true
},
"default": []
},
"tsConfig": {
"type": "string",
"description": "The full path for the TypeScript configuration file, relative to the current workspace."
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The JavaScript/TypeScript file or package containing the file to include."
}
]
}
},
"styles": {
"description": "Global styles to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": ["css", "less", "sass", "scss"]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors.",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to workspace root.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false
},
"externalDependencies": {
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"optimization": {
"description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.",
"default": true,
"x-user-analytics": "ep.ng_optimization",
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Enables optimization of the scripts output.",
"default": true
},
"styles": {
"description": "Enables optimization of the styles output.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"minify": {
"type": "boolean",
"description": "Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers and minimizing values.",
"default": true
},
"inlineCritical": {
"type": "boolean",
"description": "Extract and inline critical CSS definitions to improve first paint time.",
"default": true
},
"removeSpecialComments": {
"type": "boolean",
"description": "Remove comments in global CSS that contains '@license' or '@preserve' or that starts with '//!' or '/*!'.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"fonts": {
"description": "Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"inline": {
"type": "boolean",
"description": "Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS definitions in the application's HTML index file. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"loader": {
"description": "Defines the type of loader to use with a specified file extension when used with a JavaScript `import`. `text` inlines the content as a string; `binary` inlines the content as a Uint8Array; `file` emits the file and provides the runtime location of the file; `empty` considers the content to be empty and not include it in bundles.",
"type": "object",
"patternProperties": {
"^\\.\\S+$": { "enum": ["text", "binary", "file", "empty"] }
}
},
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
"items": {
"$ref": "#/definitions/fileReplacement"
},
"default": []
},
"outputPath": {
"description": "Specify the output path relative to workspace root.",
"oneOf": [
{
"type": "object",
"properties": {
"base": {
"type": "string",
"description": "Specify the output path relative to workspace root."
},
"browser": {
"type": "string",
"pattern": "^[-\\w\\.]*$",
"default": "browser",
"description": "The output directory name of your browser build within the output path base. Defaults to 'browser'."
},
"server": {
"type": "string",
"pattern": "^[-\\w\\.]*$",
"default": "server",
"description": "The output directory name of your server build within the output path base. Defaults to 'server'."
},
"media": {
"type": "string",
"pattern": "^[-\\w\\.]+$",
"default": "media",
"description": "The output directory name of your media files within the output browser directory. Defaults to 'media'."
}
},
"required": ["base"],
"additionalProperties": false
},
{
"type": "string"
}
]
},
"aot": {
"type": "boolean",
"description": "Build using Ahead of Time compilation.",
"x-user-analytics": "ep.ng_aot",
"default": true
},
"sourceMap": {
"description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.",
"default": false,
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Output source maps for all scripts.",
"default": true
},
"styles": {
"type": "boolean",
"description": "Output source maps for all styles.",
"default": true
},
"hidden": {
"type": "boolean",
"description": "Output source maps used for error reporting tools.",
"default": false
},
"vendor": {
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"baseHref": {
"type": "string",
"description": "Base url for the application being built."
},
"verbose": {
"type": "boolean",
"description": "Adds more details to output logging.",
"default": false
},
"progress": {
"type": "boolean",
"description": "Log progress to the console while building.",
"default": true
},
"i18nMissingTranslation": {
"type": "string",
"description": "How to handle missing translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"i18nDuplicateTranslation": {
"type": "string",
"description": "How to handle duplicate translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"localize": {
"description": "Translate the bundles in one or more locales.",
"oneOf": [
{
"type": "boolean",
"description": "Translate all locales."
},
{
"type": "array",
"description": "List of locales ID's to translate.",
"minItems": 1,
"items": {
"type": "string",
"pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$"
}
}
]
},
"watch": {
"type": "boolean",
"description": "Run build when files change.",
"default": false
},
"outputHashing": {
"type": "string",
"description": "Define the output filename cache-busting hashing mode.",
"default": "none",
"enum": ["none", "all", "media", "bundles"]
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"deleteOutputPath": {
"type": "boolean",
"description": "Delete the output path before building.",
"default": true
},
"preserveSymlinks": {
"type": "boolean",
"description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set."
},
"extractLicenses": {
"type": "boolean",
"description": "Extract all licenses in a separate file.",
"default": true
},
"namedChunks": {
"type": "boolean",
"description": "Use file name for lazy loaded chunks.",
"default": false
},
"subresourceIntegrity": {
"type": "boolean",
"description": "Enables the use of subresource integrity validation.",
"default": false
},
"serviceWorker": {
"description": "Generates a service worker configuration.",
"default": false,
"oneOf": [
{
"type": "string",
"description": "Path to ngsw-config.json."
},
{
"const": false,
"type": "boolean",
"description": "Does not generate a service worker configuration."
}
]
},
"index": {
"description": "Configures the generation of the application's HTML index.",
"oneOf": [
{
"type": "string",
"description": "The path of a file to use for the application's HTML index. The filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path."
},
{
"type": "object",
"description": "",
"properties": {
"input": {
"type": "string",
"minLength": 1,
"description": "The path of a file to use for the application's generated HTML index."
},
"output": {
"type": "string",
"minLength": 1,
"default": "index.html",
"description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path."
},
"preloadInitial": {
"type": "boolean",
"default": true,
"description": "Generates 'preload', 'modulepreload', and 'preconnect' link elements for initial application files and resources."
}
},
"required": ["input"]
},
{
"const": false,
"type": "boolean",
"description": "Does not generate an `index.html` file."
}
]
},
"statsJson": {
"type": "boolean",
"description": "Generates a 'stats.json' file which can be analyzed with https://esbuild.github.io/analyze/.",
"default": false
},
"budgets": {
"description": "Budget thresholds to ensure parts of your application stay within boundaries which you set.",
"type": "array",
"items": {
"$ref": "#/definitions/budget"
},
"default": []
},
"webWorkerTsConfig": {
"type": "string",
"description": "TypeScript configuration for Web Worker modules."
},
"crossOrigin": {
"type": "string",
"description": "Define the crossorigin attribute setting of elements that provide CORS support.",
"default": "none",
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"prerender": {
"description": "Prerender (SSG) pages of your application during build time.",
"default": false,
"oneOf": [
{
"type": "boolean",
"description": "Enable prerending of pages of your application during build time."
},
{
"type": "object",
"properties": {
"routesFile": {
"type": "string",
"description": "The path to a file that contains a list of all routes to prerender, separated by newlines. This option is useful if you want to prerender routes with parameterized URLs."
},
"discoverRoutes": {
"type": "boolean",
"description": "Whether the builder should process the Angular Router configuration to find all unparameterized routes and prerender them.",
"default": true
}
},
"additionalProperties": false
}
]
},
"ssr": {
"description": "Server side render (SSR) pages of your application during runtime.",
"default": false,
"oneOf": [
{
"type": "boolean",
"description": "Enable the server bundles to be written to disk."
},
{
"type": "object",
"properties": {
"entry": {
"type": "string",
"description": "The server entry-point that when executed will spawn the web server."
}
},
"additionalProperties": false
}
]
},
"appShell": {
"type": "boolean",
"description": "Generates an application shell during build time.",
"default": false
}
},
"additionalProperties": false,
"required": ["outputPath", "index", "browser", "tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"followSymlinks": {
"type": "boolean",
"default": false,
"description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched."
},
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
},
"ignore": {
"description": "An array of globs to ignore.",
"type": "array",
"items": {
"type": "string"
}
},
"output": {
"type": "string",
"description": "Absolute path within the output."
}
},
"additionalProperties": false,
"required": ["glob", "input", "output"]
},
{
"type": "string"
}
]
},
"fileReplacement": {
"type": "object",
"properties": {
"replace": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
},
"with": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
},
"budget": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "The type of budget.",
"enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"]
},
"name": {
"type": "string",
"description": "The name of the bundle."
},
"baseline": {
"type": "string",
"description": "The baseline size for comparison."
},
"maximumWarning": {
"type": "string",
"description": "The maximum threshold for warning relative to the baseline."
},
"maximumError": {
"type": "string",
"description": "The maximum threshold for error relative to the baseline."
},
"minimumWarning": {
"type": "string",
"description": "The minimum threshold for warning relative to the baseline."
},
"minimumError": {
"type": "string",
"description": "The minimum threshold for error relative to the baseline."
},
"warning": {
"type": "string",
"description": "The threshold for warning relative to the baseline (min & max)."
},
"error": {
"type": "string",
"description": "The threshold for error relative to the baseline (min & max)."
}
},
"additionalProperties": false,
"required": ["type"]
}
}
}

View file

@ -0,0 +1,19 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { SourceFileCache } from '../../tools/esbuild/angular/source-file-cache';
import { BundlerContext } from '../../tools/esbuild/bundler-context';
import { NormalizedApplicationBuildOptions } from './options';
/**
* Generates one or more BundlerContext instances based on the builder provided
* configuration.
* @param options The normalized application builder options to use.
* @param browsers An string array of browserslist browsers to support.
* @param codeBundleCache An instance of the TypeScript source file cache.
* @returns An array of BundlerContext objects.
*/
export declare function setupBundlerContexts(options: NormalizedApplicationBuildOptions, browsers: string[], codeBundleCache: SourceFileCache): BundlerContext[];

View file

@ -0,0 +1,71 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupBundlerContexts = void 0;
const application_code_bundle_1 = require("../../tools/esbuild/application-code-bundle");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const global_scripts_1 = require("../../tools/esbuild/global-scripts");
const global_styles_1 = require("../../tools/esbuild/global-styles");
const utils_1 = require("../../tools/esbuild/utils");
/**
* Generates one or more BundlerContext instances based on the builder provided
* configuration.
* @param options The normalized application builder options to use.
* @param browsers An string array of browserslist browsers to support.
* @param codeBundleCache An instance of the TypeScript source file cache.
* @returns An array of BundlerContext objects.
*/
function setupBundlerContexts(options, browsers, codeBundleCache) {
const { appShellOptions, prerenderOptions, serverEntryPoint, ssrOptions, workspaceRoot } = options;
const target = (0, utils_1.transformSupportedBrowsersToTargets)(browsers);
const bundlerContexts = [];
// Browser application code
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, (0, application_code_bundle_1.createBrowserCodeBundleOptions)(options, target, codeBundleCache)));
// Browser polyfills code
const browserPolyfillBundleOptions = (0, application_code_bundle_1.createBrowserPolyfillBundleOptions)(options, target, codeBundleCache);
if (browserPolyfillBundleOptions) {
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, browserPolyfillBundleOptions));
}
// Global Stylesheets
if (options.globalStyles.length > 0) {
for (const initial of [true, false]) {
const bundleOptions = (0, global_styles_1.createGlobalStylesBundleOptions)(options, target, initial);
if (bundleOptions) {
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, bundleOptions, () => initial));
}
}
}
// Global Scripts
if (options.globalScripts.length > 0) {
for (const initial of [true, false]) {
const bundleOptions = (0, global_scripts_1.createGlobalScriptsBundleOptions)(options, target, initial);
if (bundleOptions) {
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, bundleOptions, () => initial));
}
}
}
// Skip server build when none of the features are enabled.
if (serverEntryPoint && (prerenderOptions || appShellOptions || ssrOptions)) {
const nodeTargets = [...target, ...(0, utils_1.getSupportedNodeTargets)()];
// Server application code
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, (0, application_code_bundle_1.createServerCodeBundleOptions)({
...options,
// Disable external deps for server bundles.
// This is because it breaks Vite 'optimizeDeps' for SSR.
externalPackages: false,
}, nodeTargets, codeBundleCache), () => false));
// Server polyfills code
const serverPolyfillBundleOptions = (0, application_code_bundle_1.createServerPolyfillBundleOptions)(options, nodeTargets, codeBundleCache);
if (serverPolyfillBundleOptions) {
bundlerContexts.push(new bundler_context_1.BundlerContext(workspaceRoot, !!options.watch, serverPolyfillBundleOptions, () => false));
}
}
return bundlerContexts;
}
exports.setupBundlerContexts = setupBundlerContexts;

View file

@ -0,0 +1,10 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { Schema as BrowserBuilderOptions } from './schema';
export declare function logBuilderStatusWarnings(options: BrowserBuilderOptions, { logger }: BuilderContext): void;

View file

@ -0,0 +1,40 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.logBuilderStatusWarnings = void 0;
const UNSUPPORTED_OPTIONS = [
// * Always enabled with esbuild
// 'commonChunk',
// * Unused by builder and will be removed in a future release
'vendorChunk',
'resourcesOutputPath',
// * Currently unsupported by esbuild
'webWorkerTsConfig',
];
function logBuilderStatusWarnings(options, { logger }) {
// Validate supported options
for (const unsupportedOption of UNSUPPORTED_OPTIONS) {
const value = options[unsupportedOption];
if (value === undefined || value === false) {
continue;
}
if (Array.isArray(value) && value.length === 0) {
continue;
}
if (typeof value === 'object' && Object.keys(value).length === 0) {
continue;
}
if (unsupportedOption === 'vendorChunk' || unsupportedOption === 'resourcesOutputPath') {
logger.warn(`The '${unsupportedOption}' option is not used by this builder and will be ignored.`);
continue;
}
logger.warn(`The '${unsupportedOption}' option is not yet supported by this builder.`);
}
}
exports.logBuilderStatusWarnings = logBuilderStatusWarnings;

View file

@ -0,0 +1,29 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import { BuildOutputFile } from '../../tools/esbuild/bundler-context';
import { Schema as BrowserBuilderOptions } from './schema';
/**
* Main execution function for the esbuild-based application builder.
* The options are compatible with the Webpack-based builder.
* @param userOptions The browser builder options to use when setting up the application build
* @param context The Architect builder context object
* @returns An async iterable with the builder result output
*/
export declare function buildEsbuildBrowser(userOptions: BrowserBuilderOptions, context: BuilderContext, infrastructureSettings?: {
write?: boolean;
}, plugins?: Plugin[]): AsyncIterable<BuilderOutput & {
outputFiles?: BuildOutputFile[];
assetFiles?: {
source: string;
destination: string;
}[];
}>;
declare const _default: import("../../../../architect/src/internal").Builder<BrowserBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;

View file

@ -0,0 +1,98 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildEsbuildBrowser = void 0;
const architect_1 = require("@angular-devkit/architect");
const node_fs_1 = require("node:fs");
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
const utils_1 = require("../../tools/esbuild/utils");
const utils_2 = require("../../utils");
const application_1 = require("../application");
const builder_status_warnings_1 = require("./builder-status-warnings");
/**
* Main execution function for the esbuild-based application builder.
* The options are compatible with the Webpack-based builder.
* @param userOptions The browser builder options to use when setting up the application build
* @param context The Architect builder context object
* @returns An async iterable with the builder result output
*/
async function* buildEsbuildBrowser(userOptions, context, infrastructureSettings, plugins) {
// Inform user of status of builder and options
(0, builder_status_warnings_1.logBuilderStatusWarnings)(userOptions, context);
const normalizedOptions = normalizeOptions(userOptions);
const { deleteOutputPath, outputPath } = normalizedOptions;
const fullOutputPath = node_path_1.default.join(context.workspaceRoot, outputPath.base);
if (deleteOutputPath && infrastructureSettings?.write !== false) {
await (0, utils_2.deleteOutputDir)(context.workspaceRoot, outputPath.base);
}
for await (const result of (0, application_1.buildApplicationInternal)(normalizedOptions, context, {
write: false,
}, plugins && { codePlugins: plugins })) {
if (infrastructureSettings?.write !== false && result.outputFiles) {
// Write output files
await writeResultFiles(result.outputFiles, result.assetFiles, fullOutputPath);
}
// The builder system (architect) currently attempts to treat all results as JSON and
// attempts to validate the object with a JSON schema validator. This can lead to slow
// build completion (even after the actual build is fully complete) or crashes if the
// size and/or quantity of output files is large. Architect only requires a `success`
// property so that is all that will be passed here if the infrastructure settings have
// not been explicitly set to avoid writes. Writing is only disabled when used directly
// by the dev server which bypasses the architect behavior.
const builderResult = infrastructureSettings?.write === false ? result : { success: result.success };
yield builderResult;
}
}
exports.buildEsbuildBrowser = buildEsbuildBrowser;
function normalizeOptions(options) {
const { main: browser, outputPath, ngswConfigPath, serviceWorker, polyfills, ...otherOptions } = options;
return {
browser,
serviceWorker: serviceWorker ? ngswConfigPath : false,
polyfills: typeof polyfills === 'string' ? [polyfills] : polyfills,
outputPath: {
base: outputPath,
browser: '',
},
...otherOptions,
};
}
// We write the file directly from this builder to maintain webpack output compatibility
// and not output browser files into '/browser'.
async function writeResultFiles(outputFiles, assetFiles, outputPath) {
const directoryExists = new Set();
const ensureDirectoryExists = async (basePath) => {
if (basePath && !directoryExists.has(basePath)) {
await promises_1.default.mkdir(node_path_1.default.join(outputPath, basePath), { recursive: true });
directoryExists.add(basePath);
}
};
// Writes the output file to disk and ensures the containing directories are present
await (0, utils_1.emitFilesToDisk)(outputFiles, async (file) => {
// Ensure output subdirectories exist
const basePath = node_path_1.default.dirname(file.path);
await ensureDirectoryExists(basePath);
// Write file contents
await promises_1.default.writeFile(node_path_1.default.join(outputPath, file.path), file.contents);
});
if (assetFiles?.length) {
await (0, utils_1.emitFilesToDisk)(assetFiles, async ({ source, destination }) => {
const basePath = node_path_1.default.dirname(destination);
// Ensure output subdirectories exist
await ensureDirectoryExists(basePath);
// Copy file contents
await promises_1.default.copyFile(source, node_path_1.default.join(outputPath, destination), node_fs_1.constants.COPYFILE_FICLONE);
});
}
}
exports.default = (0, architect_1.createBuilder)(buildEsbuildBrowser);

View file

@ -0,0 +1,431 @@
/**
* Browser target options
*/
export interface Schema {
/**
* A list of CommonJS or AMD packages that are allowed to be used without a build time
* warning. Use `'*'` to allow all.
*/
allowedCommonJsDependencies?: string[];
/**
* Build using Ahead of Time compilation.
*/
aot?: boolean;
/**
* List of static application assets.
*/
assets?: AssetPattern[];
/**
* Base url for the application being built.
*/
baseHref?: string;
/**
* Budget thresholds to ensure parts of your application stay within boundaries which you
* set.
*/
budgets?: Budget[];
/**
* Enables advanced build optimizations when using the 'aot' option.
*/
buildOptimizer?: boolean;
/**
* Generate a seperate bundle containing code used across multiple bundles.
*/
commonChunk?: boolean;
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
crossOrigin?: CrossOrigin;
/**
* Delete the output path before building.
*/
deleteOutputPath?: boolean;
/**
* URL where files will be deployed.
* @deprecated Use "baseHref" option, "APP_BASE_HREF" DI token or a combination of both
* instead. For more information, see https://angular.io/guide/deployment#the-deploy-url.
*/
deployUrl?: string;
/**
* Exclude the listed external dependencies from being bundled into the bundle. Instead, the
* created bundle relies on these dependencies to be available during runtime.
*/
externalDependencies?: string[];
/**
* Extract all licenses in a separate file.
*/
extractLicenses?: boolean;
/**
* Replace compilation source files with other compilation source files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* How to handle duplicate translations for i18n.
*/
i18nDuplicateTranslation?: I18NTranslation;
/**
* How to handle missing translations for i18n.
*/
i18nMissingTranslation?: I18NTranslation;
/**
* Configures the generation of the application's HTML index.
*/
index: IndexUnion;
/**
* The stylesheet language to use for the application's inline component styles.
*/
inlineStyleLanguage?: InlineStyleLanguage;
/**
* Translate the bundles in one or more locales.
*/
localize?: Localize;
/**
* The full path for the main entry point to the app, relative to the current workspace.
*/
main: string;
/**
* Use file name for lazy loaded chunks.
*/
namedChunks?: boolean;
/**
* Path to ngsw-config.json.
*/
ngswConfigPath?: string;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
optimization?: OptimizationUnion;
/**
* Define the output filename cache-busting hashing mode.
*/
outputHashing?: OutputHashing;
/**
* The full path for the new output directory, relative to the current workspace.
*/
outputPath: string;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* Polyfills to be included in the build.
*/
polyfills?: Polyfills;
/**
* Do not use the real path when resolving modules. If unset then will default to `true` if
* NodeJS option --preserve-symlinks is set.
*/
preserveSymlinks?: boolean;
/**
* Log progress to the console while building.
*/
progress?: boolean;
/**
* The path where style resources will be placed, relative to outputPath.
*/
resourcesOutputPath?: string;
/**
* Global scripts to be included in the build.
*/
scripts?: ScriptElement[];
/**
* Generates a service worker config for production builds.
*/
serviceWorker?: boolean;
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
sourceMap?: SourceMapUnion;
/**
* Generates a 'stats.json' file which can be analyzed using tools such as
* 'webpack-bundle-analyzer'.
*/
statsJson?: boolean;
/**
* Options to pass to style preprocessors.
*/
stylePreprocessorOptions?: StylePreprocessorOptions;
/**
* Global styles to be included in the build.
*/
styles?: StyleElement[];
/**
* Enables the use of subresource integrity validation.
*/
subresourceIntegrity?: boolean;
/**
* The full path for the TypeScript configuration file, relative to the current workspace.
*/
tsConfig: string;
/**
* Generate a seperate bundle containing only vendor libraries. This option should only be
* used for development to reduce the incremental compilation time.
*/
vendorChunk?: boolean;
/**
* Adds more details to output logging.
*/
verbose?: boolean;
/**
* Run build when files change.
*/
watch?: boolean;
/**
* TypeScript configuration for Web Worker modules.
*/
webWorkerTsConfig?: string;
}
export type AssetPattern = AssetPatternClass | string;
export interface AssetPatternClass {
/**
* Allow glob patterns to follow symlink directories. This allows subdirectories of the
* symlink to be searched.
*/
followSymlinks?: boolean;
/**
* The pattern to match.
*/
glob: string;
/**
* An array of globs to ignore.
*/
ignore?: string[];
/**
* The input directory path in which to apply 'glob'. Defaults to the project root.
*/
input: string;
/**
* Absolute path within the output.
*/
output: string;
}
export interface Budget {
/**
* The baseline size for comparison.
*/
baseline?: string;
/**
* The threshold for error relative to the baseline (min & max).
*/
error?: string;
/**
* The maximum threshold for error relative to the baseline.
*/
maximumError?: string;
/**
* The maximum threshold for warning relative to the baseline.
*/
maximumWarning?: string;
/**
* The minimum threshold for error relative to the baseline.
*/
minimumError?: string;
/**
* The minimum threshold for warning relative to the baseline.
*/
minimumWarning?: string;
/**
* The name of the bundle.
*/
name?: string;
/**
* The type of budget.
*/
type: Type;
/**
* The threshold for warning relative to the baseline (min & max).
*/
warning?: string;
}
/**
* The type of budget.
*/
export declare enum Type {
All = "all",
AllScript = "allScript",
Any = "any",
AnyComponentStyle = "anyComponentStyle",
AnyScript = "anyScript",
Bundle = "bundle",
Initial = "initial"
}
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
export declare enum CrossOrigin {
Anonymous = "anonymous",
None = "none",
UseCredentials = "use-credentials"
}
export interface FileReplacement {
replace: string;
with: string;
}
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
export declare enum I18NTranslation {
Error = "error",
Ignore = "ignore",
Warning = "warning"
}
/**
* Configures the generation of the application's HTML index.
*/
export type IndexUnion = boolean | IndexObject | string;
export interface IndexObject {
/**
* The path of a file to use for the application's generated HTML index.
*/
input: string;
/**
* The output path of the application's generated HTML index file. The full provided path
* will be used and will be considered relative to the application's configured output path.
*/
output?: string;
[property: string]: any;
}
/**
* The stylesheet language to use for the application's inline component styles.
*/
export declare enum InlineStyleLanguage {
Css = "css",
Less = "less",
Sass = "sass",
Scss = "scss"
}
/**
* Translate the bundles in one or more locales.
*/
export type Localize = string[] | boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
export type OptimizationUnion = boolean | OptimizationClass;
export interface OptimizationClass {
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
fonts?: FontsUnion;
/**
* Enables optimization of the scripts output.
*/
scripts?: boolean;
/**
* Enables optimization of the styles output.
*/
styles?: StylesUnion;
}
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
export type FontsUnion = boolean | FontsClass;
export interface FontsClass {
/**
* Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS
* definitions in the application's HTML index file. This option requires internet access.
* `HTTPS_PROXY` environment variable can be used to specify a proxy server.
*/
inline?: boolean;
}
/**
* Enables optimization of the styles output.
*/
export type StylesUnion = boolean | StylesClass;
export interface StylesClass {
/**
* Extract and inline critical CSS definitions to improve first paint time.
*/
inlineCritical?: boolean;
/**
* Minify CSS definitions by removing extraneous whitespace and comments, merging
* identifiers and minimizing values.
*/
minify?: boolean;
}
/**
* Define the output filename cache-busting hashing mode.
*/
export declare enum OutputHashing {
All = "all",
Bundles = "bundles",
Media = "media",
None = "none"
}
/**
* Polyfills to be included in the build.
*/
export type Polyfills = string[] | string;
export type ScriptElement = ScriptClass | string;
export interface ScriptClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
export type SourceMapUnion = boolean | SourceMapClass;
export interface SourceMapClass {
/**
* Output source maps used for error reporting tools.
*/
hidden?: boolean;
/**
* Output source maps for all scripts.
*/
scripts?: boolean;
/**
* Output source maps for all styles.
*/
styles?: boolean;
/**
* Resolve vendor packages source maps.
*/
vendor?: boolean;
}
/**
* Options to pass to style preprocessors.
*/
export interface StylePreprocessorOptions {
/**
* Paths to include. Paths will be resolved to workspace root.
*/
includePaths?: string[];
}
export type StyleElement = StyleClass | string;
export interface StyleClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}

View file

@ -0,0 +1,58 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutputHashing = exports.InlineStyleLanguage = exports.I18NTranslation = exports.CrossOrigin = exports.Type = void 0;
/**
* The type of budget.
*/
var Type;
(function (Type) {
Type["All"] = "all";
Type["AllScript"] = "allScript";
Type["Any"] = "any";
Type["AnyComponentStyle"] = "anyComponentStyle";
Type["AnyScript"] = "anyScript";
Type["Bundle"] = "bundle";
Type["Initial"] = "initial";
})(Type || (exports.Type = Type = {}));
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
var CrossOrigin;
(function (CrossOrigin) {
CrossOrigin["Anonymous"] = "anonymous";
CrossOrigin["None"] = "none";
CrossOrigin["UseCredentials"] = "use-credentials";
})(CrossOrigin || (exports.CrossOrigin = CrossOrigin = {}));
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
var I18NTranslation;
(function (I18NTranslation) {
I18NTranslation["Error"] = "error";
I18NTranslation["Ignore"] = "ignore";
I18NTranslation["Warning"] = "warning";
})(I18NTranslation || (exports.I18NTranslation = I18NTranslation = {}));
/**
* The stylesheet language to use for the application's inline component styles.
*/
var InlineStyleLanguage;
(function (InlineStyleLanguage) {
InlineStyleLanguage["Css"] = "css";
InlineStyleLanguage["Less"] = "less";
InlineStyleLanguage["Sass"] = "sass";
InlineStyleLanguage["Scss"] = "scss";
})(InlineStyleLanguage || (exports.InlineStyleLanguage = InlineStyleLanguage = {}));
/**
* Define the output filename cache-busting hashing mode.
*/
var OutputHashing;
(function (OutputHashing) {
OutputHashing["All"] = "all";
OutputHashing["Bundles"] = "bundles";
OutputHashing["Media"] = "media";
OutputHashing["None"] = "none";
})(OutputHashing || (exports.OutputHashing = OutputHashing = {}));

View file

@ -0,0 +1,541 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Esbuild browser schema for Build Facade.",
"description": "Browser target options",
"type": "object",
"properties": {
"assets": {
"type": "array",
"description": "List of static application assets.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"main": {
"type": "string",
"description": "The full path for the main entry point to the app, relative to the current workspace."
},
"polyfills": {
"description": "Polyfills to be included in the build.",
"oneOf": [
{
"type": "array",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"items": {
"type": "string",
"uniqueItems": true
},
"default": []
},
{
"type": "string",
"description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'."
}
]
},
"tsConfig": {
"type": "string",
"description": "The full path for the TypeScript configuration file, relative to the current workspace."
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The JavaScript/TypeScript file or package containing the file to include."
}
]
}
},
"styles": {
"description": "Global styles to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": ["css", "less", "sass", "scss"]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors.",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to workspace root.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false
},
"externalDependencies": {
"description": "Exclude the listed external dependencies from being bundled into the bundle. Instead, the created bundle relies on these dependencies to be available during runtime.",
"type": "array",
"items": {
"type": "string"
},
"default": []
},
"optimization": {
"description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.",
"default": true,
"x-user-analytics": "ep.ng_optimization",
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Enables optimization of the scripts output.",
"default": true
},
"styles": {
"description": "Enables optimization of the styles output.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"minify": {
"type": "boolean",
"description": "Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers and minimizing values.",
"default": true
},
"inlineCritical": {
"type": "boolean",
"description": "Extract and inline critical CSS definitions to improve first paint time.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"fonts": {
"description": "Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"inline": {
"type": "boolean",
"description": "Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS definitions in the application's HTML index file. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
"items": {
"$ref": "#/definitions/fileReplacement"
},
"default": []
},
"outputPath": {
"type": "string",
"description": "The full path for the new output directory, relative to the current workspace."
},
"resourcesOutputPath": {
"type": "string",
"description": "The path where style resources will be placed, relative to outputPath."
},
"aot": {
"type": "boolean",
"description": "Build using Ahead of Time compilation.",
"x-user-analytics": "ep.ng_aot",
"default": true
},
"sourceMap": {
"description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.",
"default": false,
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Output source maps for all scripts.",
"default": true
},
"styles": {
"type": "boolean",
"description": "Output source maps for all styles.",
"default": true
},
"hidden": {
"type": "boolean",
"description": "Output source maps used for error reporting tools.",
"default": false
},
"vendor": {
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false
},
"commonChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing code used across multiple bundles.",
"default": true
},
"baseHref": {
"type": "string",
"description": "Base url for the application being built."
},
"deployUrl": {
"type": "string",
"description": "URL where files will be deployed.",
"x-deprecated": "Use \"baseHref\" option, \"APP_BASE_HREF\" DI token or a combination of both instead. For more information, see https://angular.io/guide/deployment#the-deploy-url."
},
"verbose": {
"type": "boolean",
"description": "Adds more details to output logging.",
"default": false
},
"progress": {
"type": "boolean",
"description": "Log progress to the console while building.",
"default": true
},
"i18nMissingTranslation": {
"type": "string",
"description": "How to handle missing translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"i18nDuplicateTranslation": {
"type": "string",
"description": "How to handle duplicate translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"localize": {
"description": "Translate the bundles in one or more locales.",
"oneOf": [
{
"type": "boolean",
"description": "Translate all locales."
},
{
"type": "array",
"description": "List of locales ID's to translate.",
"minItems": 1,
"items": {
"type": "string",
"pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$"
}
}
]
},
"watch": {
"type": "boolean",
"description": "Run build when files change.",
"default": false
},
"outputHashing": {
"type": "string",
"description": "Define the output filename cache-busting hashing mode.",
"default": "none",
"enum": ["none", "all", "media", "bundles"]
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"deleteOutputPath": {
"type": "boolean",
"description": "Delete the output path before building.",
"default": true
},
"preserveSymlinks": {
"type": "boolean",
"description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set."
},
"extractLicenses": {
"type": "boolean",
"description": "Extract all licenses in a separate file.",
"default": true
},
"buildOptimizer": {
"type": "boolean",
"description": "Enables advanced build optimizations when using the 'aot' option.",
"default": true
},
"namedChunks": {
"type": "boolean",
"description": "Use file name for lazy loaded chunks.",
"default": false
},
"subresourceIntegrity": {
"type": "boolean",
"description": "Enables the use of subresource integrity validation.",
"default": false
},
"serviceWorker": {
"type": "boolean",
"description": "Generates a service worker config for production builds.",
"default": false
},
"ngswConfigPath": {
"type": "string",
"description": "Path to ngsw-config.json."
},
"index": {
"description": "Configures the generation of the application's HTML index.",
"oneOf": [
{
"type": "string",
"description": "The path of a file to use for the application's HTML index. The filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path."
},
{
"type": "object",
"description": "",
"properties": {
"input": {
"type": "string",
"minLength": 1,
"description": "The path of a file to use for the application's generated HTML index."
},
"output": {
"type": "string",
"minLength": 1,
"default": "index.html",
"description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path."
}
},
"required": ["input"]
},
{
"const": false,
"type": "boolean",
"description": "Does not generate an `index.html` file."
}
]
},
"statsJson": {
"type": "boolean",
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
"default": false
},
"budgets": {
"description": "Budget thresholds to ensure parts of your application stay within boundaries which you set.",
"type": "array",
"items": {
"$ref": "#/definitions/budget"
},
"default": []
},
"webWorkerTsConfig": {
"type": "string",
"description": "TypeScript configuration for Web Worker modules."
},
"crossOrigin": {
"type": "string",
"description": "Define the crossorigin attribute setting of elements that provide CORS support.",
"default": "none",
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false,
"required": ["outputPath", "index", "main", "tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"followSymlinks": {
"type": "boolean",
"default": false,
"description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched."
},
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
},
"ignore": {
"description": "An array of globs to ignore.",
"type": "array",
"items": {
"type": "string"
}
},
"output": {
"type": "string",
"description": "Absolute path within the output."
}
},
"additionalProperties": false,
"required": ["glob", "input", "output"]
},
{
"type": "string"
}
]
},
"fileReplacement": {
"type": "object",
"properties": {
"replace": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
},
"with": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
},
"budget": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "The type of budget.",
"enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"]
},
"name": {
"type": "string",
"description": "The name of the bundle."
},
"baseline": {
"type": "string",
"description": "The baseline size for comparison."
},
"maximumWarning": {
"type": "string",
"description": "The maximum threshold for warning relative to the baseline."
},
"maximumError": {
"type": "string",
"description": "The maximum threshold for error relative to the baseline."
},
"minimumWarning": {
"type": "string",
"description": "The minimum threshold for warning relative to the baseline."
},
"minimumError": {
"type": "string",
"description": "The minimum threshold for error relative to the baseline."
},
"warning": {
"type": "string",
"description": "The threshold for warning relative to the baseline (min & max)."
},
"error": {
"type": "string",
"description": "The threshold for error relative to the baseline (min & max)."
}
},
"additionalProperties": false,
"required": ["type"]
}
}
}

View file

@ -0,0 +1,42 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
import { Observable } from 'rxjs';
import webpack from 'webpack';
import { BuildEventStats } from '../../tools/webpack/utils/stats';
import { ExecutionTransformer } from '../../transforms';
import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
import { Schema as BrowserBuilderSchema } from './schema';
/**
* @experimental Direct usage of this type is considered experimental.
*/
export type BrowserBuilderOutput = BuilderOutput & {
stats: BuildEventStats;
baseOutputPath: string;
outputs: {
locale?: string;
path: string;
baseHref?: string;
}[];
};
/**
* Maximum time in milliseconds for single build/rebuild
* This accounts for CI variability.
*/
export declare const BUILD_TIMEOUT = 30000;
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function buildWebpackBrowser(options: BrowserBuilderSchema, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
logging?: WebpackLoggingCallback;
indexHtml?: IndexHtmlTransform;
}): Observable<BrowserBuilderOutput>;
declare const _default: import("../../../../architect/src/internal").Builder<BrowserBuilderSchema & import("../../../../core/src").JsonObject>;
export default _default;

View file

@ -0,0 +1,330 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildWebpackBrowser = exports.BUILD_TIMEOUT = void 0;
const architect_1 = require("@angular-devkit/architect");
const build_webpack_1 = require("@angular-devkit/build-webpack");
const webpack_1 = require("@ngtools/webpack");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const rxjs_1 = require("rxjs");
const configs_1 = require("../../tools/webpack/configs");
const async_chunks_1 = require("../../tools/webpack/utils/async-chunks");
const helpers_1 = require("../../tools/webpack/utils/helpers");
const stats_1 = require("../../tools/webpack/utils/stats");
const utils_1 = require("../../utils");
const bundle_calculator_1 = require("../../utils/bundle-calculator");
const color_1 = require("../../utils/color");
const copy_assets_1 = require("../../utils/copy-assets");
const error_1 = require("../../utils/error");
const i18n_inlining_1 = require("../../utils/i18n-inlining");
const index_html_generator_1 = require("../../utils/index-file/index-html-generator");
const normalize_cache_1 = require("../../utils/normalize-cache");
const output_paths_1 = require("../../utils/output-paths");
const package_chunk_sort_1 = require("../../utils/package-chunk-sort");
const purge_cache_1 = require("../../utils/purge-cache");
const service_worker_1 = require("../../utils/service-worker");
const spinner_1 = require("../../utils/spinner");
const version_1 = require("../../utils/version");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
/**
* Maximum time in milliseconds for single build/rebuild
* This accounts for CI variability.
*/
exports.BUILD_TIMEOUT = 30000;
async function initialize(options, context, webpackConfigurationTransform) {
const originalOutputPath = options.outputPath;
// Assets are processed directly by the builder except when watching
const adjustedOptions = options.watch ? options : { ...options, assets: [] };
const { config, projectRoot, projectSourceRoot, i18n } = await (0, webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext)(adjustedOptions, context, (wco) => [
(0, configs_1.getCommonConfig)(wco),
(0, configs_1.getStylesConfig)(wco),
]);
let transformedConfig;
if (webpackConfigurationTransform) {
transformedConfig = await webpackConfigurationTransform(config);
}
if (options.deleteOutputPath) {
await (0, utils_1.deleteOutputDir)(context.workspaceRoot, originalOutputPath);
}
return { config: transformedConfig || config, projectRoot, projectSourceRoot, i18n };
}
/**
* @experimental Direct usage of this function is considered experimental.
*/
// eslint-disable-next-line max-lines-per-function
function buildWebpackBrowser(options, context, transforms = {}) {
const projectName = context.target?.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
const baseOutputPath = path.resolve(context.workspaceRoot, options.outputPath);
let outputPaths;
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(context.workspaceRoot);
return (0, rxjs_1.from)(context.getProjectMetadata(projectName)).pipe((0, rxjs_1.switchMap)(async (projectMetadata) => {
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
// Initialize builder
const initialization = await initialize(options, context, transforms.webpackConfiguration);
// Add index file to watched files.
if (options.watch) {
const indexInputFile = path.join(context.workspaceRoot, (0, webpack_browser_config_1.getIndexInputFile)(options.index));
initialization.config.plugins ??= [];
initialization.config.plugins.push({
apply: (compiler) => {
compiler.hooks.thisCompilation.tap('build-angular', (compilation) => {
compilation.fileDependencies.add(indexInputFile);
});
},
});
}
return {
...initialization,
cacheOptions: (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, context.workspaceRoot),
};
}), (0, rxjs_1.switchMap)(
// eslint-disable-next-line max-lines-per-function
({ config, projectRoot, projectSourceRoot, i18n, cacheOptions }) => {
const normalizedOptimization = (0, utils_1.normalizeOptimization)(options.optimization);
return (0, build_webpack_1.runWebpack)(config, context, {
webpackFactory: require('webpack'),
logging: transforms.logging ||
((stats, config) => {
if (options.verbose) {
context.logger.info(stats.toString(config.stats));
}
}),
}).pipe((0, rxjs_1.concatMap)(
// eslint-disable-next-line max-lines-per-function
async (buildEvent) => {
const spinner = new spinner_1.Spinner();
spinner.enabled = options.progress !== false;
const { success, emittedFiles = [], outputPath: webpackOutputPath } = buildEvent;
const webpackRawStats = buildEvent.webpackStats;
if (!webpackRawStats) {
throw new Error('Webpack stats build result is required.');
}
// Fix incorrectly set `initial` value on chunks.
const extraEntryPoints = [
...(0, helpers_1.normalizeExtraEntryPoints)(options.styles || [], 'styles'),
...(0, helpers_1.normalizeExtraEntryPoints)(options.scripts || [], 'scripts'),
];
const webpackStats = {
...webpackRawStats,
chunks: (0, async_chunks_1.markAsyncChunksNonInitial)(webpackRawStats, extraEntryPoints),
};
if (!success) {
// If using bundle downleveling then there is only one build
// If it fails show any diagnostic messages and bail
if ((0, stats_1.statsHasWarnings)(webpackStats)) {
context.logger.warn((0, stats_1.statsWarningsToString)(webpackStats, { colors: true }));
}
if ((0, stats_1.statsHasErrors)(webpackStats)) {
context.logger.error((0, stats_1.statsErrorsToString)(webpackStats, { colors: true }));
}
return {
webpackStats: webpackRawStats,
output: { success: false },
};
}
else {
outputPaths = (0, output_paths_1.ensureOutputPaths)(baseOutputPath, i18n);
const scriptsEntryPointName = (0, helpers_1.normalizeExtraEntryPoints)(options.scripts || [], 'scripts').map((x) => x.bundleName);
if (i18n.shouldInline) {
const success = await (0, i18n_inlining_1.i18nInlineEmittedFiles)(context, emittedFiles, i18n, baseOutputPath, Array.from(outputPaths.values()), scriptsEntryPointName, webpackOutputPath, options.i18nMissingTranslation);
if (!success) {
return {
webpackStats: webpackRawStats,
output: { success: false },
};
}
}
// Check for budget errors and display them to the user.
const budgets = options.budgets;
let budgetFailures;
if (budgets?.length) {
budgetFailures = [...(0, bundle_calculator_1.checkBudgets)(budgets, webpackStats)];
for (const { severity, message } of budgetFailures) {
switch (severity) {
case bundle_calculator_1.ThresholdSeverity.Warning:
webpackStats.warnings?.push({ message });
break;
case bundle_calculator_1.ThresholdSeverity.Error:
webpackStats.errors?.push({ message });
break;
default:
assertNever(severity);
}
}
}
const buildSuccess = success && !(0, stats_1.statsHasErrors)(webpackStats);
if (buildSuccess) {
// Copy assets
if (!options.watch && options.assets?.length) {
spinner.start('Copying assets...');
try {
await (0, copy_assets_1.copyAssets)((0, utils_1.normalizeAssetPatterns)(options.assets, context.workspaceRoot, projectRoot, projectSourceRoot), Array.from(outputPaths.values()), context.workspaceRoot);
spinner.succeed('Copying assets complete.');
}
catch (err) {
spinner.fail(color_1.colors.redBright('Copying of assets failed.'));
(0, error_1.assertIsError)(err);
return {
output: {
success: false,
error: 'Unable to copy assets: ' + err.message,
},
webpackStats: webpackRawStats,
};
}
}
if (options.index) {
spinner.start('Generating index html...');
const entrypoints = (0, package_chunk_sort_1.generateEntryPoints)({
scripts: options.scripts ?? [],
styles: options.styles ?? [],
});
const indexHtmlGenerator = new index_html_generator_1.IndexHtmlGenerator({
cache: cacheOptions,
indexPath: path.join(context.workspaceRoot, (0, webpack_browser_config_1.getIndexInputFile)(options.index)),
entrypoints,
deployUrl: options.deployUrl,
sri: options.subresourceIntegrity,
optimization: normalizedOptimization,
crossOrigin: options.crossOrigin,
postTransform: transforms.indexHtml,
imageDomains: Array.from(webpack_1.imageDomains),
});
let hasErrors = false;
for (const [locale, outputPath] of outputPaths.entries()) {
try {
const { content, warnings, errors } = await indexHtmlGenerator.process({
baseHref: getLocaleBaseHref(i18n, locale) ?? options.baseHref,
// i18nLocale is used when Ivy is disabled
lang: locale || undefined,
outputPath,
files: mapEmittedFilesToFileInfo(emittedFiles),
});
if (warnings.length || errors.length) {
spinner.stop();
warnings.forEach((m) => context.logger.warn(m));
errors.forEach((m) => {
context.logger.error(m);
hasErrors = true;
});
spinner.start();
}
const indexOutput = path.join(outputPath, (0, webpack_browser_config_1.getIndexOutputFile)(options.index));
await fs.promises.mkdir(path.dirname(indexOutput), { recursive: true });
await fs.promises.writeFile(indexOutput, content);
}
catch (error) {
spinner.fail('Index html generation failed.');
(0, error_1.assertIsError)(error);
return {
webpackStats: webpackRawStats,
output: { success: false, error: error.message },
};
}
}
if (hasErrors) {
spinner.fail('Index html generation failed.');
return {
webpackStats: webpackRawStats,
output: { success: false },
};
}
else {
spinner.succeed('Index html generation complete.');
}
}
if (options.serviceWorker) {
spinner.start('Generating service worker...');
for (const [locale, outputPath] of outputPaths.entries()) {
try {
await (0, service_worker_1.augmentAppWithServiceWorker)(projectRoot, context.workspaceRoot, outputPath, getLocaleBaseHref(i18n, locale) ?? options.baseHref ?? '/', options.ngswConfigPath);
}
catch (error) {
spinner.fail('Service worker generation failed.');
(0, error_1.assertIsError)(error);
return {
webpackStats: webpackRawStats,
output: { success: false, error: error.message },
};
}
}
spinner.succeed('Service worker generation complete.');
}
}
(0, stats_1.webpackStatsLogger)(context.logger, webpackStats, config, budgetFailures);
return {
webpackStats: webpackRawStats,
output: { success: buildSuccess },
};
}
}), (0, rxjs_1.map)(({ output: event, webpackStats }) => ({
...event,
stats: (0, stats_1.generateBuildEventStats)(webpackStats, options),
baseOutputPath,
outputs: (outputPaths &&
[...outputPaths.entries()].map(([locale, path]) => ({
locale,
path,
baseHref: getLocaleBaseHref(i18n, locale) ?? options.baseHref,
}))) || {
path: baseOutputPath,
baseHref: options.baseHref,
},
})));
}));
function getLocaleBaseHref(i18n, locale) {
if (i18n.locales[locale] && i18n.locales[locale]?.baseHref !== '') {
return (0, utils_1.urlJoin)(options.baseHref || '', i18n.locales[locale].baseHref ?? `/${locale}/`);
}
return undefined;
}
}
exports.buildWebpackBrowser = buildWebpackBrowser;
function assertNever(input) {
throw new Error(`Unexpected call to assertNever() with input: ${JSON.stringify(input, null /* replacer */, 4 /* tabSize */)}`);
}
function mapEmittedFilesToFileInfo(files = []) {
const filteredFiles = [];
for (const { file, name, extension, initial } of files) {
if (name && initial) {
filteredFiles.push({ file, extension, name });
}
}
return filteredFiles;
}
exports.default = (0, architect_1.createBuilder)(buildWebpackBrowser);

View file

@ -0,0 +1,428 @@
/**
* Browser target options
*/
export interface Schema {
/**
* A list of CommonJS or AMD packages that are allowed to be used without a build time
* warning. Use `'*'` to allow all.
*/
allowedCommonJsDependencies?: string[];
/**
* Build using Ahead of Time compilation.
*/
aot?: boolean;
/**
* List of static application assets.
*/
assets?: AssetPattern[];
/**
* Base url for the application being built.
*/
baseHref?: string;
/**
* Budget thresholds to ensure parts of your application stay within boundaries which you
* set.
*/
budgets?: Budget[];
/**
* Enables advanced build optimizations when using the 'aot' option.
*/
buildOptimizer?: boolean;
/**
* Generate a seperate bundle containing code used across multiple bundles.
*/
commonChunk?: boolean;
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
crossOrigin?: CrossOrigin;
/**
* Delete the output path before building.
*/
deleteOutputPath?: boolean;
/**
* URL where files will be deployed.
* @deprecated Use "baseHref" option, "APP_BASE_HREF" DI token or a combination of both
* instead. For more information, see https://angular.io/guide/deployment#the-deploy-url.
*/
deployUrl?: string;
/**
* Extract all licenses in a separate file.
*/
extractLicenses?: boolean;
/**
* Replace compilation source files with other compilation source files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* How to handle duplicate translations for i18n.
*/
i18nDuplicateTranslation?: I18NTranslation;
/**
* How to handle missing translations for i18n.
*/
i18nMissingTranslation?: I18NTranslation;
/**
* Configures the generation of the application's HTML index.
*/
index: IndexUnion;
/**
* The stylesheet language to use for the application's inline component styles.
*/
inlineStyleLanguage?: InlineStyleLanguage;
/**
* Translate the bundles in one or more locales.
*/
localize?: Localize;
/**
* The full path for the main entry point to the app, relative to the current workspace.
*/
main: string;
/**
* Use file name for lazy loaded chunks.
*/
namedChunks?: boolean;
/**
* Path to ngsw-config.json.
*/
ngswConfigPath?: string;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
optimization?: OptimizationUnion;
/**
* Define the output filename cache-busting hashing mode.
*/
outputHashing?: OutputHashing;
/**
* The full path for the new output directory, relative to the current workspace.
*/
outputPath: string;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* Polyfills to be included in the build.
*/
polyfills?: Polyfills;
/**
* Do not use the real path when resolving modules. If unset then will default to `true` if
* NodeJS option --preserve-symlinks is set.
*/
preserveSymlinks?: boolean;
/**
* Log progress to the console while building.
*/
progress?: boolean;
/**
* The path where style resources will be placed, relative to outputPath.
*/
resourcesOutputPath?: string;
/**
* Global scripts to be included in the build.
*/
scripts?: ScriptElement[];
/**
* Generates a service worker config for production builds.
*/
serviceWorker?: boolean;
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
sourceMap?: SourceMapUnion;
/**
* Generates a 'stats.json' file which can be analyzed using tools such as
* 'webpack-bundle-analyzer'.
*/
statsJson?: boolean;
/**
* Options to pass to style preprocessors.
*/
stylePreprocessorOptions?: StylePreprocessorOptions;
/**
* Global styles to be included in the build.
*/
styles?: StyleElement[];
/**
* Enables the use of subresource integrity validation.
*/
subresourceIntegrity?: boolean;
/**
* The full path for the TypeScript configuration file, relative to the current workspace.
*/
tsConfig: string;
/**
* Generate a seperate bundle containing only vendor libraries. This option should only be
* used for development to reduce the incremental compilation time.
*/
vendorChunk?: boolean;
/**
* Adds more details to output logging.
*/
verbose?: boolean;
/**
* Run build when files change.
*/
watch?: boolean;
/**
* TypeScript configuration for Web Worker modules.
*/
webWorkerTsConfig?: string;
}
export type AssetPattern = AssetPatternClass | string;
export interface AssetPatternClass {
/**
* Allow glob patterns to follow symlink directories. This allows subdirectories of the
* symlink to be searched.
*/
followSymlinks?: boolean;
/**
* The pattern to match.
*/
glob: string;
/**
* An array of globs to ignore.
*/
ignore?: string[];
/**
* The input directory path in which to apply 'glob'. Defaults to the project root.
*/
input: string;
/**
* Absolute path within the output.
*/
output: string;
}
export interface Budget {
/**
* The baseline size for comparison.
*/
baseline?: string;
/**
* The threshold for error relative to the baseline (min & max).
*/
error?: string;
/**
* The maximum threshold for error relative to the baseline.
*/
maximumError?: string;
/**
* The maximum threshold for warning relative to the baseline.
*/
maximumWarning?: string;
/**
* The minimum threshold for error relative to the baseline.
*/
minimumError?: string;
/**
* The minimum threshold for warning relative to the baseline.
*/
minimumWarning?: string;
/**
* The name of the bundle.
*/
name?: string;
/**
* The type of budget.
*/
type: Type;
/**
* The threshold for warning relative to the baseline (min & max).
*/
warning?: string;
}
/**
* The type of budget.
*/
export declare enum Type {
All = "all",
AllScript = "allScript",
Any = "any",
AnyComponentStyle = "anyComponentStyle",
AnyScript = "anyScript",
Bundle = "bundle",
Initial = "initial"
}
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
export declare enum CrossOrigin {
Anonymous = "anonymous",
None = "none",
UseCredentials = "use-credentials"
}
export interface FileReplacement {
replace?: string;
replaceWith?: string;
src?: string;
with?: string;
}
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
export declare enum I18NTranslation {
Error = "error",
Ignore = "ignore",
Warning = "warning"
}
/**
* Configures the generation of the application's HTML index.
*/
export type IndexUnion = IndexObject | string;
export interface IndexObject {
/**
* The path of a file to use for the application's generated HTML index.
*/
input: string;
/**
* The output path of the application's generated HTML index file. The full provided path
* will be used and will be considered relative to the application's configured output path.
*/
output?: string;
[property: string]: any;
}
/**
* The stylesheet language to use for the application's inline component styles.
*/
export declare enum InlineStyleLanguage {
Css = "css",
Less = "less",
Sass = "sass",
Scss = "scss"
}
/**
* Translate the bundles in one or more locales.
*/
export type Localize = string[] | boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For
* more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
export type OptimizationUnion = boolean | OptimizationClass;
export interface OptimizationClass {
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
fonts?: FontsUnion;
/**
* Enables optimization of the scripts output.
*/
scripts?: boolean;
/**
* Enables optimization of the styles output.
*/
styles?: StylesUnion;
}
/**
* Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY`
* environment variable can be used to specify a proxy server.
*/
export type FontsUnion = boolean | FontsClass;
export interface FontsClass {
/**
* Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS
* definitions in the application's HTML index file. This option requires internet access.
* `HTTPS_PROXY` environment variable can be used to specify a proxy server.
*/
inline?: boolean;
}
/**
* Enables optimization of the styles output.
*/
export type StylesUnion = boolean | StylesClass;
export interface StylesClass {
/**
* Extract and inline critical CSS definitions to improve first paint time.
*/
inlineCritical?: boolean;
/**
* Minify CSS definitions by removing extraneous whitespace and comments, merging
* identifiers and minimizing values.
*/
minify?: boolean;
}
/**
* Define the output filename cache-busting hashing mode.
*/
export declare enum OutputHashing {
All = "all",
Bundles = "bundles",
Media = "media",
None = "none"
}
/**
* Polyfills to be included in the build.
*/
export type Polyfills = string[] | string;
export type ScriptElement = ScriptClass | string;
export interface ScriptClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
export type SourceMapUnion = boolean | SourceMapClass;
export interface SourceMapClass {
/**
* Output source maps used for error reporting tools.
*/
hidden?: boolean;
/**
* Output source maps for all scripts.
*/
scripts?: boolean;
/**
* Output source maps for all styles.
*/
styles?: boolean;
/**
* Resolve vendor packages source maps.
*/
vendor?: boolean;
}
/**
* Options to pass to style preprocessors.
*/
export interface StylePreprocessorOptions {
/**
* Paths to include. Paths will be resolved to workspace root.
*/
includePaths?: string[];
}
export type StyleElement = StyleClass | string;
export interface StyleClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}

View file

@ -0,0 +1,58 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.OutputHashing = exports.InlineStyleLanguage = exports.I18NTranslation = exports.CrossOrigin = exports.Type = void 0;
/**
* The type of budget.
*/
var Type;
(function (Type) {
Type["All"] = "all";
Type["AllScript"] = "allScript";
Type["Any"] = "any";
Type["AnyComponentStyle"] = "anyComponentStyle";
Type["AnyScript"] = "anyScript";
Type["Bundle"] = "bundle";
Type["Initial"] = "initial";
})(Type || (exports.Type = Type = {}));
/**
* Define the crossorigin attribute setting of elements that provide CORS support.
*/
var CrossOrigin;
(function (CrossOrigin) {
CrossOrigin["Anonymous"] = "anonymous";
CrossOrigin["None"] = "none";
CrossOrigin["UseCredentials"] = "use-credentials";
})(CrossOrigin || (exports.CrossOrigin = CrossOrigin = {}));
/**
* How to handle duplicate translations for i18n.
*
* How to handle missing translations for i18n.
*/
var I18NTranslation;
(function (I18NTranslation) {
I18NTranslation["Error"] = "error";
I18NTranslation["Ignore"] = "ignore";
I18NTranslation["Warning"] = "warning";
})(I18NTranslation || (exports.I18NTranslation = I18NTranslation = {}));
/**
* The stylesheet language to use for the application's inline component styles.
*/
var InlineStyleLanguage;
(function (InlineStyleLanguage) {
InlineStyleLanguage["Css"] = "css";
InlineStyleLanguage["Less"] = "less";
InlineStyleLanguage["Sass"] = "sass";
InlineStyleLanguage["Scss"] = "scss";
})(InlineStyleLanguage || (exports.InlineStyleLanguage = InlineStyleLanguage = {}));
/**
* Define the output filename cache-busting hashing mode.
*/
var OutputHashing;
(function (OutputHashing) {
OutputHashing["All"] = "all";
OutputHashing["Bundles"] = "bundles";
OutputHashing["Media"] = "media";
OutputHashing["None"] = "none";
})(OutputHashing || (exports.OutputHashing = OutputHashing = {}));

View file

@ -0,0 +1,548 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Webpack browser schema for Build Facade.",
"description": "Browser target options",
"type": "object",
"properties": {
"assets": {
"type": "array",
"description": "List of static application assets.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"main": {
"type": "string",
"description": "The full path for the main entry point to the app, relative to the current workspace."
},
"polyfills": {
"description": "Polyfills to be included in the build.",
"oneOf": [
{
"type": "array",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"items": {
"type": "string",
"uniqueItems": true
},
"default": []
},
{
"type": "string",
"description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'."
}
]
},
"tsConfig": {
"type": "string",
"description": "The full path for the TypeScript configuration file, relative to the current workspace."
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
}
]
}
},
"styles": {
"description": "Global styles to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": ["css", "less", "sass", "scss"]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors.",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to workspace root.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false
},
"optimization": {
"description": "Enables optimization of the build output. Including minification of scripts and styles, tree-shaking, dead-code elimination, inlining of critical CSS and fonts inlining. For more information, see https://angular.io/guide/workspace-config#optimization-configuration.",
"default": true,
"x-user-analytics": "ep.ng_optimization",
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Enables optimization of the scripts output.",
"default": true
},
"styles": {
"description": "Enables optimization of the styles output.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"minify": {
"type": "boolean",
"description": "Minify CSS definitions by removing extraneous whitespace and comments, merging identifiers and minimizing values.",
"default": true
},
"inlineCritical": {
"type": "boolean",
"description": "Extract and inline critical CSS definitions to improve first paint time.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"fonts": {
"description": "Enables optimization for fonts. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"inline": {
"type": "boolean",
"description": "Reduce render blocking requests by inlining external Google Fonts and Adobe Fonts CSS definitions in the application's HTML index file. This option requires internet access. `HTTPS_PROXY` environment variable can be used to specify a proxy server.",
"default": true
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
"items": {
"$ref": "#/definitions/fileReplacement"
},
"default": []
},
"outputPath": {
"type": "string",
"description": "The full path for the new output directory, relative to the current workspace."
},
"resourcesOutputPath": {
"type": "string",
"description": "The path where style resources will be placed, relative to outputPath."
},
"aot": {
"type": "boolean",
"description": "Build using Ahead of Time compilation.",
"x-user-analytics": "ep.ng_aot",
"default": true
},
"sourceMap": {
"description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.",
"default": false,
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Output source maps for all scripts.",
"default": true
},
"styles": {
"type": "boolean",
"description": "Output source maps for all styles.",
"default": true
},
"hidden": {
"type": "boolean",
"description": "Output source maps used for error reporting tools.",
"default": false
},
"vendor": {
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"vendorChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing only vendor libraries. This option should only be used for development to reduce the incremental compilation time.",
"default": false
},
"commonChunk": {
"type": "boolean",
"description": "Generate a seperate bundle containing code used across multiple bundles.",
"default": true
},
"baseHref": {
"type": "string",
"description": "Base url for the application being built."
},
"deployUrl": {
"type": "string",
"description": "URL where files will be deployed.",
"x-deprecated": "Use \"baseHref\" option, \"APP_BASE_HREF\" DI token or a combination of both instead. For more information, see https://angular.io/guide/deployment#the-deploy-url."
},
"verbose": {
"type": "boolean",
"description": "Adds more details to output logging.",
"default": false
},
"progress": {
"type": "boolean",
"description": "Log progress to the console while building.",
"default": true
},
"i18nMissingTranslation": {
"type": "string",
"description": "How to handle missing translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"i18nDuplicateTranslation": {
"type": "string",
"description": "How to handle duplicate translations for i18n.",
"enum": ["warning", "error", "ignore"],
"default": "warning"
},
"localize": {
"description": "Translate the bundles in one or more locales.",
"oneOf": [
{
"type": "boolean",
"description": "Translate all locales."
},
{
"type": "array",
"description": "List of locales ID's to translate.",
"minItems": 1,
"items": {
"type": "string",
"pattern": "^[a-zA-Z]{2,3}(-[a-zA-Z]{4})?(-([a-zA-Z]{2}|[0-9]{3}))?(-[a-zA-Z]{5,8})?(-x(-[a-zA-Z0-9]{1,8})+)?$"
}
}
]
},
"watch": {
"type": "boolean",
"description": "Run build when files change.",
"default": false
},
"outputHashing": {
"type": "string",
"description": "Define the output filename cache-busting hashing mode.",
"default": "none",
"enum": ["none", "all", "media", "bundles"]
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"deleteOutputPath": {
"type": "boolean",
"description": "Delete the output path before building.",
"default": true
},
"preserveSymlinks": {
"type": "boolean",
"description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set."
},
"extractLicenses": {
"type": "boolean",
"description": "Extract all licenses in a separate file.",
"default": true
},
"buildOptimizer": {
"type": "boolean",
"description": "Enables advanced build optimizations when using the 'aot' option.",
"default": true
},
"namedChunks": {
"type": "boolean",
"description": "Use file name for lazy loaded chunks.",
"default": false
},
"subresourceIntegrity": {
"type": "boolean",
"description": "Enables the use of subresource integrity validation.",
"default": false
},
"serviceWorker": {
"type": "boolean",
"description": "Generates a service worker config for production builds.",
"default": false
},
"ngswConfigPath": {
"type": "string",
"description": "Path to ngsw-config.json."
},
"index": {
"description": "Configures the generation of the application's HTML index.",
"oneOf": [
{
"type": "string",
"description": "The path of a file to use for the application's HTML index. The filename of the specified path will be used for the generated file and will be created in the root of the application's configured output path."
},
{
"type": "object",
"description": "",
"properties": {
"input": {
"type": "string",
"minLength": 1,
"description": "The path of a file to use for the application's generated HTML index."
},
"output": {
"type": "string",
"minLength": 1,
"default": "index.html",
"description": "The output path of the application's generated HTML index file. The full provided path will be used and will be considered relative to the application's configured output path."
}
},
"required": ["input"]
}
]
},
"statsJson": {
"type": "boolean",
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
"default": false
},
"budgets": {
"description": "Budget thresholds to ensure parts of your application stay within boundaries which you set.",
"type": "array",
"items": {
"$ref": "#/definitions/budget"
},
"default": []
},
"webWorkerTsConfig": {
"type": "string",
"description": "TypeScript configuration for Web Worker modules."
},
"crossOrigin": {
"type": "string",
"description": "Define the crossorigin attribute setting of elements that provide CORS support.",
"default": "none",
"enum": ["none", "anonymous", "use-credentials"]
},
"allowedCommonJsDependencies": {
"description": "A list of CommonJS or AMD packages that are allowed to be used without a build time warning. Use `'*'` to allow all.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false,
"required": ["outputPath", "index", "main", "tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"followSymlinks": {
"type": "boolean",
"default": false,
"description": "Allow glob patterns to follow symlink directories. This allows subdirectories of the symlink to be searched."
},
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
},
"ignore": {
"description": "An array of globs to ignore.",
"type": "array",
"items": {
"type": "string"
}
},
"output": {
"type": "string",
"description": "Absolute path within the output."
}
},
"additionalProperties": false,
"required": ["glob", "input", "output"]
},
{
"type": "string"
}
]
},
"fileReplacement": {
"oneOf": [
{
"type": "object",
"properties": {
"src": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
},
"replaceWith": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
}
},
"additionalProperties": false,
"required": ["src", "replaceWith"]
},
{
"type": "object",
"properties": {
"replace": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
},
"with": {
"type": "string",
"pattern": "\\.(([cm]?j|t)sx?|json)$"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
}
]
},
"budget": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "The type of budget.",
"enum": ["all", "allScript", "any", "anyScript", "anyComponentStyle", "bundle", "initial"]
},
"name": {
"type": "string",
"description": "The name of the bundle."
},
"baseline": {
"type": "string",
"description": "The baseline size for comparison."
},
"maximumWarning": {
"type": "string",
"description": "The maximum threshold for warning relative to the baseline."
},
"maximumError": {
"type": "string",
"description": "The maximum threshold for error relative to the baseline."
},
"minimumWarning": {
"type": "string",
"description": "The minimum threshold for warning relative to the baseline."
},
"minimumError": {
"type": "string",
"description": "The minimum threshold for error relative to the baseline."
},
"warning": {
"type": "string",
"description": "The threshold for warning relative to the baseline (min & max)."
},
"error": {
"type": "string",
"description": "The threshold for error relative to the baseline (min & max)."
}
},
"additionalProperties": false,
"required": ["type"]
}
}
}

View file

@ -0,0 +1,47 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/// <reference types="node" />
/// <reference types="@types/node/http" />
/// <reference types="@types/node/ts4.8/http" />
import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import type http from 'node:http';
import { Observable } from 'rxjs';
import type { ExecutionTransformer } from '../../transforms';
import type { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
import type { Schema as DevServerBuilderOptions } from './schema';
import type { DevServerBuilderOutput } from './webpack-server';
/**
* A Builder that executes a development server based on the provided browser target option.
*
* Usage of the `transforms` and/or `extensions` parameters is NOT supported and may cause
* unexpected build output or build failures.
*
* @param options Dev Server options.
* @param context The build context.
* @param transforms A map of transforms that can be used to hook into some logic (such as
* transforming webpack configuration before passing it to webpack).
* @param extensions An optional object containing an array of build plugins (esbuild-based)
* and/or HTTP request middleware.
*
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: DevServerBuilderOptions, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<import('webpack').Configuration>;
logging?: import('@angular-devkit/build-webpack').WebpackLoggingCallback;
indexHtml?: IndexHtmlTransform;
}, extensions?: {
buildPlugins?: Plugin[];
middleware?: ((req: http.IncomingMessage, res: http.ServerResponse, next: (err?: unknown) => void) => void)[];
builderSelector?: (info: BuilderSelectorInfo, logger: BuilderContext['logger']) => string;
}): Observable<DevServerBuilderOutput>;
interface BuilderSelectorInfo {
builderName: string;
forceEsbuild: boolean;
}
export {};

View file

@ -0,0 +1,127 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const rxjs_1 = require("rxjs");
const check_port_1 = require("../../utils/check-port");
const purge_cache_1 = require("../../utils/purge-cache");
const options_1 = require("./options");
/**
* A Builder that executes a development server based on the provided browser target option.
*
* Usage of the `transforms` and/or `extensions` parameters is NOT supported and may cause
* unexpected build output or build failures.
*
* @param options Dev Server options.
* @param context The build context.
* @param transforms A map of transforms that can be used to hook into some logic (such as
* transforming webpack configuration before passing it to webpack).
* @param extensions An optional object containing an array of build plugins (esbuild-based)
* and/or HTTP request middleware.
*
* @experimental Direct usage of this function is considered experimental.
*/
function execute(options, context, transforms = {}, extensions) {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
context.logger.error(`The 'dev-server' builder requires a target to be specified.`);
return rxjs_1.EMPTY;
}
return (0, rxjs_1.defer)(() => initialize(options, projectName, context, extensions?.builderSelector)).pipe((0, rxjs_1.switchMap)(({ builderName, normalizedOptions }) => {
// Use vite-based development server for esbuild-based builds
if (isEsbuildBased(builderName)) {
if (transforms?.logging || transforms?.webpackConfiguration) {
throw new Error('The `application` and `browser-esbuild` builders do not support Webpack transforms.');
}
return (0, rxjs_1.defer)(() => Promise.resolve().then(() => __importStar(require('./vite-server')))).pipe((0, rxjs_1.switchMap)(({ serveWithVite }) => serveWithVite(normalizedOptions, builderName, context, transforms, extensions)));
}
if (extensions?.buildPlugins?.length) {
throw new Error('Only the `application` and `browser-esbuild` builders support plugins.');
}
if (extensions?.middleware?.length) {
throw new Error('Only the `application` and `browser-esbuild` builders support middleware.');
}
// Use Webpack for all other browser targets
return (0, rxjs_1.defer)(() => Promise.resolve().then(() => __importStar(require('./webpack-server')))).pipe((0, rxjs_1.switchMap)(({ serveWebpackBrowser }) => serveWebpackBrowser(normalizedOptions, builderName, context, transforms)));
}));
}
exports.execute = execute;
async function initialize(initialOptions, projectName, context, builderSelector = defaultBuilderSelector) {
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, initialOptions);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
if (!normalizedOptions.disableHostCheck &&
!/^127\.\d+\.\d+\.\d+/g.test(normalizedOptions.host) &&
normalizedOptions.host !== 'localhost') {
context.logger.warn(`
Warning: This is a simple server for use in testing or debugging Angular applications
locally. It hasn't been reviewed for security issues.
Binding this server to an open connection can result in compromising your application or
computer. Using a different host than the one passed to the "--host" flag might result in
websocket connection issues. You might need to use "--disable-host-check" if that's the
case.
`);
}
if (normalizedOptions.disableHostCheck) {
context.logger.warn('Warning: Running a server with --disable-host-check is a security risk. ' +
'See https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a for more information.');
}
normalizedOptions.port = await (0, check_port_1.checkPort)(normalizedOptions.port, normalizedOptions.host);
return {
builderName: builderSelector({ builderName, forceEsbuild: !!normalizedOptions.forceEsbuild }, context.logger),
normalizedOptions,
};
}
function isEsbuildBased(builderName) {
if (builderName === '@angular-devkit/build-angular:application' ||
builderName === '@angular-devkit/build-angular:browser-esbuild') {
return true;
}
return false;
}
function defaultBuilderSelector(info, logger) {
if (isEsbuildBased(info.builderName)) {
return info.builderName;
}
if (info.forceEsbuild) {
if (!info.builderName.startsWith('@angular-devkit/build-angular:')) {
logger.warn('Warning: Forcing the use of the esbuild-based build system with third-party builders' +
' may cause unexpected behavior and/or build failures.');
}
// The compatibility builder should be used if esbuild is force enabled.
return '@angular-devkit/build-angular:browser-esbuild';
}
return info.builderName;
}

View file

@ -0,0 +1,14 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { execute } from './builder';
import { Schema as DevServerBuilderOptions } from './schema';
import { DevServerBuilderOutput } from './webpack-server';
export { DevServerBuilderOptions, DevServerBuilderOutput, execute as executeDevServerBuilder };
declare const _default: import("../../../../architect/src/internal").Builder<DevServerBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;
export { execute as executeDevServer };

View file

@ -0,0 +1,15 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeDevServer = exports.executeDevServerBuilder = void 0;
const architect_1 = require("@angular-devkit/architect");
const builder_1 = require("./builder");
Object.defineProperty(exports, "executeDevServerBuilder", { enumerable: true, get: function () { return builder_1.execute; } });
Object.defineProperty(exports, "executeDevServer", { enumerable: true, get: function () { return builder_1.execute; } });
exports.default = (0, architect_1.createBuilder)(builder_1.execute);

View file

@ -0,0 +1,46 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { Schema as DevServerOptions } from './schema';
export type NormalizedDevServerOptions = Awaited<ReturnType<typeof normalizeOptions>>;
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
export declare function normalizeOptions(context: BuilderContext, projectName: string, options: DevServerOptions): Promise<{
buildTarget: import("@angular-devkit/architect").Target;
host: string;
port: number;
poll: number | undefined;
open: boolean | undefined;
verbose: boolean | undefined;
watch: boolean | undefined;
liveReload: boolean | undefined;
hmr: boolean | undefined;
headers: {
[key: string]: string;
} | undefined;
workspaceRoot: string;
projectRoot: string;
cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
allowedHosts: string[] | undefined;
disableHostCheck: boolean | undefined;
proxyConfig: string | undefined;
servePath: string | undefined;
publicHost: string | undefined;
ssl: boolean | undefined;
sslCert: string | undefined;
sslKey: string | undefined;
forceEsbuild: boolean | undefined;
}>;

View file

@ -0,0 +1,62 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = void 0;
const architect_1 = require("@angular-devkit/architect");
const node_path_1 = __importDefault(require("node:path"));
const normalize_cache_1 = require("../../utils/normalize-cache");
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
async function normalizeOptions(context, projectName, options) {
const workspaceRoot = context.workspaceRoot;
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const buildTarget = (0, architect_1.targetFromTargetString)(options.buildTarget ?? options.browserTarget);
// Initial options to keep
const { host, port, poll, open, verbose, watch, allowedHosts, disableHostCheck, liveReload, hmr, headers, proxyConfig, servePath, publicHost, ssl, sslCert, sslKey, forceEsbuild, } = options;
// Return all the normalized options
return {
buildTarget,
host: host ?? 'localhost',
port: port ?? 4200,
poll,
open,
verbose,
watch,
liveReload,
hmr,
headers,
workspaceRoot,
projectRoot,
cacheOptions,
allowedHosts,
disableHostCheck,
proxyConfig,
servePath,
publicHost,
ssl,
sslCert,
sslKey,
forceEsbuild,
};
}
exports.normalizeOptions = normalizeOptions;

View file

@ -0,0 +1,96 @@
/**
* Dev Server target options for Build Facade.
*/
export interface Schema {
/**
* List of hosts that are allowed to access the dev server.
*/
allowedHosts?: string[];
/**
* A browser builder target to serve in the format of `project:target[:configuration]`. You
* can also pass in more than one configuration name as a comma-separated list. Example:
* `project:target:production,staging`.
* @deprecated Use 'buildTarget' instead.
*/
browserTarget?: string;
/**
* A build builder target to serve in the format of `project:target[:configuration]`. You
* can also pass in more than one configuration name as a comma-separated list. Example:
* `project:target:production,staging`.
*/
buildTarget?: string;
/**
* Don't verify connected clients are part of allowed hosts.
*/
disableHostCheck?: boolean;
/**
* Force the development server to use the 'browser-esbuild' builder when building. This is
* a developer preview option for the esbuild-based build system.
*/
forceEsbuild?: boolean;
/**
* Custom HTTP headers to be added to all responses.
*/
headers?: {
[key: string]: string;
};
/**
* Enable hot module replacement.
*/
hmr?: boolean;
/**
* Host to listen on.
*/
host?: string;
/**
* Whether to reload the page on change, using live-reload.
*/
liveReload?: boolean;
/**
* Opens the url in default browser.
*/
open?: boolean;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* Port to listen on.
*/
port?: number;
/**
* Proxy configuration file. For more information, see
* https://angular.io/guide/build#proxying-to-a-backend-server.
*/
proxyConfig?: string;
/**
* The URL that the browser client (or live-reload client, if enabled) should use to connect
* to the development server. Use for a complex dev server setup, such as one with reverse
* proxies.
*/
publicHost?: string;
/**
* The pathname where the application will be served.
*/
servePath?: string;
/**
* Serve using HTTPS.
*/
ssl?: boolean;
/**
* SSL certificate to use for serving HTTPS.
*/
sslCert?: string;
/**
* SSL key to use for serving HTTPS.
*/
sslKey?: string;
/**
* Adds more details to output logging.
*/
verbose?: boolean;
/**
* Rebuild on change.
*/
watch?: boolean;
}

View file

@ -0,0 +1,4 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });

View file

@ -0,0 +1,113 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Dev Server Target",
"description": "Dev Server target options for Build Facade.",
"type": "object",
"properties": {
"browserTarget": {
"type": "string",
"description": "A browser builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
"x-deprecated": "Use 'buildTarget' instead."
},
"buildTarget": {
"type": "string",
"description": "A build builder target to serve in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"port": {
"type": "number",
"description": "Port to listen on.",
"default": 4200
},
"host": {
"type": "string",
"description": "Host to listen on.",
"default": "localhost"
},
"proxyConfig": {
"type": "string",
"description": "Proxy configuration file. For more information, see https://angular.io/guide/build#proxying-to-a-backend-server."
},
"ssl": {
"type": "boolean",
"description": "Serve using HTTPS.",
"default": false
},
"sslKey": {
"type": "string",
"description": "SSL key to use for serving HTTPS."
},
"sslCert": {
"type": "string",
"description": "SSL certificate to use for serving HTTPS."
},
"headers": {
"type": "object",
"description": "Custom HTTP headers to be added to all responses.",
"propertyNames": {
"pattern": "^[-_A-Za-z0-9]+$"
},
"additionalProperties": {
"type": "string"
}
},
"open": {
"type": "boolean",
"description": "Opens the url in default browser.",
"default": false,
"alias": "o"
},
"verbose": {
"type": "boolean",
"description": "Adds more details to output logging."
},
"liveReload": {
"type": "boolean",
"description": "Whether to reload the page on change, using live-reload.",
"default": true
},
"publicHost": {
"type": "string",
"description": "The URL that the browser client (or live-reload client, if enabled) should use to connect to the development server. Use for a complex dev server setup, such as one with reverse proxies."
},
"allowedHosts": {
"type": "array",
"description": "List of hosts that are allowed to access the dev server.",
"default": [],
"items": {
"type": "string"
}
},
"servePath": {
"type": "string",
"description": "The pathname where the application will be served."
},
"disableHostCheck": {
"type": "boolean",
"description": "Don't verify connected clients are part of allowed hosts.",
"default": false
},
"hmr": {
"type": "boolean",
"description": "Enable hot module replacement.",
"default": false
},
"watch": {
"type": "boolean",
"description": "Rebuild on change.",
"default": true
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"forceEsbuild": {
"type": "boolean",
"description": "Force the development server to use the 'browser-esbuild' builder when building. This is a developer preview option for the esbuild-based build system.",
"default": false
}
},
"additionalProperties": false,
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
}

View file

@ -0,0 +1,30 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import type { BuilderContext } from '@angular-devkit/architect';
import type { Plugin } from 'esbuild';
import type { Connect, DepOptimizationConfig, InlineConfig } from 'vite';
import { ExternalResultMetadata } from '../../tools/esbuild/bundler-execution-result';
import { JavaScriptTransformer } from '../../tools/esbuild/javascript-transformer';
import type { NormalizedDevServerOptions } from './options';
import type { DevServerBuilderOutput } from './webpack-server';
interface OutputFileRecord {
contents: Uint8Array;
size: number;
hash?: string;
updated: boolean;
servable: boolean;
}
export declare function serveWithVite(serverOptions: NormalizedDevServerOptions, builderName: string, context: BuilderContext, transformers?: {
indexHtml?: (content: string) => Promise<string>;
}, extensions?: {
middleware?: Connect.NextHandleFunction[];
buildPlugins?: Plugin[];
}): AsyncIterableIterator<DevServerBuilderOutput>;
export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map<string, OutputFileRecord>, assets: Map<string, string>, preserveSymlinks: boolean | undefined, externalMetadata: ExternalResultMetadata, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[], prebundleLoaderExtensions: EsbuildLoaderOption | undefined, extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise<string>, thirdPartySourcemaps?: boolean): Promise<InlineConfig>;
type EsbuildLoaderOption = Exclude<DepOptimizationConfig['esbuildOptions'], undefined>['loader'];
export {};

View file

@ -0,0 +1,488 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setupServer = exports.serveWithVite = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const bundler_context_1 = require("../../tools/esbuild/bundler-context");
const javascript_transformer_1 = require("../../tools/esbuild/javascript-transformer");
const rxjs_esm_resolution_plugin_1 = require("../../tools/esbuild/rxjs-esm-resolution-plugin");
const utils_1 = require("../../tools/esbuild/utils");
const angular_memory_plugin_1 = require("../../tools/vite/angular-memory-plugin");
const i18n_locale_plugin_1 = require("../../tools/vite/i18n-locale-plugin");
const utils_2 = require("../../utils");
const load_esm_1 = require("../../utils/load-esm");
const supported_browsers_1 = require("../../utils/supported-browsers");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
const application_1 = require("../application");
const browser_esbuild_1 = require("../browser-esbuild");
// eslint-disable-next-line max-lines-per-function
async function* serveWithVite(serverOptions, builderName, context, transformers, extensions) {
// Get the browser configuration from the target name.
const rawBrowserOptions = (await context.getTargetOptions(serverOptions.buildTarget));
const browserOptions = (await context.validateOptions({
...rawBrowserOptions,
watch: serverOptions.watch,
poll: serverOptions.poll,
verbose: serverOptions.verbose,
}, builderName));
if (browserOptions.prerender || browserOptions.ssr) {
// Disable prerendering if enabled and force SSR.
// This is so instead of prerendering all the routes for every change, the page is "prerendered" when it is requested.
browserOptions.prerender = false;
// Avoid bundling and processing the ssr entry-point as this is not used by the dev-server.
browserOptions.ssr = true;
// https://nodejs.org/api/process.html#processsetsourcemapsenabledval
process.setSourceMapsEnabled(true);
}
// Set all packages as external to support Vite's prebundle caching
browserOptions.externalPackages = serverOptions.cacheOptions.enabled;
const baseHref = browserOptions.baseHref;
if (serverOptions.servePath === undefined && baseHref !== undefined) {
// Remove trailing slash
serverOptions.servePath =
baseHref !== './' && baseHref[baseHref.length - 1] === '/' ? baseHref.slice(0, -1) : baseHref;
}
// The development server currently only supports a single locale when localizing.
// This matches the behavior of the Webpack-based development server but could be expanded in the future.
if (browserOptions.localize === true ||
(Array.isArray(browserOptions.localize) && browserOptions.localize.length > 1)) {
context.logger.warn('Localization (`localize` option) has been disabled. The development server only supports localizing a single locale per build.');
browserOptions.localize = false;
}
else if (browserOptions.localize) {
// When localization is enabled with a single locale, force a flat path to maintain behavior with the existing Webpack-based dev server.
browserOptions.forceI18nFlatOutput = true;
}
const { vendor: thirdPartySourcemaps } = (0, utils_2.normalizeSourceMaps)(browserOptions.sourceMap ?? false);
// Setup the prebundling transformer that will be shared across Vite prebundling requests
const prebundleTransformer = new javascript_transformer_1.JavaScriptTransformer(
// Always enable JIT linking to support applications built with and without AOT.
// In a development environment the additional scope information does not
// have a negative effect unlike production where final output size is relevant.
{ sourcemap: true, jit: true, thirdPartySourcemaps }, 1);
// Extract output index from options
// TODO: Provide this info from the build results
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const htmlIndexPath = (0, webpack_browser_config_1.getIndexOutputFile)(browserOptions.index);
// dynamically import Vite for ESM compatibility
const { createServer, normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
let server;
let serverUrl;
let hadError = false;
const generatedFiles = new Map();
const assetFiles = new Map();
const externalMetadata = {
implicitBrowser: [],
implicitServer: [],
explicit: [],
};
// Add cleanup logic via a builder teardown.
let deferred;
context.addTeardown(async () => {
await server?.close();
await prebundleTransformer.close();
deferred?.();
});
const build = builderName === '@angular-devkit/build-angular:browser-esbuild'
? browser_esbuild_1.buildEsbuildBrowser.bind(undefined, browserOptions, context, { write: false }, extensions?.buildPlugins)
: application_1.buildApplicationInternal.bind(undefined, browserOptions, context, { write: false }, { codePlugins: extensions?.buildPlugins });
// TODO: Switch this to an architect schedule call when infrastructure settings are supported
for await (const result of build()) {
(0, node_assert_1.default)(result.outputFiles, 'Builder did not provide result files.');
// If build failed, nothing to serve
if (!result.success) {
// If server is active, send an error notification
if (result.errors?.length && server) {
hadError = true;
server.ws.send({
type: 'error',
err: {
message: result.errors[0].text,
stack: '',
loc: result.errors[0].location,
},
});
}
continue;
}
else if (hadError && server) {
hadError = false;
// Send an empty update to clear the error overlay
server.ws.send({
'type': 'update',
updates: [],
});
}
// Analyze result files for changes
analyzeResultFiles(normalizePath, htmlIndexPath, result.outputFiles, generatedFiles);
assetFiles.clear();
if (result.assetFiles) {
for (const asset of result.assetFiles) {
assetFiles.set('/' + normalizePath(asset.destination), normalizePath(asset.source));
}
}
// To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
if (result.externalMetadata) {
const { implicitBrowser, implicitServer, explicit } = result.externalMetadata;
// Empty Arrays to avoid growing unlimited with every re-build.
externalMetadata.explicit.length = 0;
externalMetadata.implicitServer.length = 0;
externalMetadata.implicitBrowser.length = 0;
externalMetadata.explicit.push(...explicit);
externalMetadata.implicitServer.push(...implicitServer);
externalMetadata.implicitBrowser.push(...implicitBrowser);
// The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm.
// See: https://github.com/vitejs/vite/blob/0873bae0cfe0f0718ad2f5743dd34a17e4ab563d/packages/vite/src/node/optimizer/index.ts#L1203-L1239
externalMetadata.explicit.sort();
externalMetadata.implicitServer.sort();
externalMetadata.implicitBrowser.sort();
}
if (server) {
// Update fs allow list to include any new assets from the build option.
server.config.server.fs.allow = [
...new Set([...server.config.server.fs.allow, ...assetFiles.values()]),
];
handleUpdate(normalizePath, generatedFiles, server, serverOptions, context.logger);
}
else {
const projectName = context.target?.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
const { root = '' } = await context.getProjectMetadata(projectName);
const projectRoot = (0, node_path_1.join)(context.workspaceRoot, root);
const browsers = (0, supported_browsers_1.getSupportedBrowsers)(projectRoot, context.logger);
const target = (0, utils_1.transformSupportedBrowsersToTargets)(browsers);
// Setup server and start listening
const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, !!browserOptions.ssr, prebundleTransformer, target, browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
server = await createServer(serverConfiguration);
await server.listen();
if (serverConfiguration.ssr?.optimizeDeps?.disabled === false) {
/**
* Vite will only start dependency optimization of SSR modules when the first request comes in.
* In some cases, this causes a long waiting time. To mitigate this, we call `ssrLoadModule` to
* initiate this process before the first request.
*
* NOTE: This will intentionally fail from the unknown module, but currently there is no other way
* to initiate the SSR dep optimizer.
*/
void server.ssrLoadModule('<deps-caller>').catch(() => { });
}
const urls = server.resolvedUrls;
if (urls && (urls.local.length || urls.network.length)) {
serverUrl = new URL(urls.local[0] ?? urls.network[0]);
}
// log connection information
server.printUrls();
server.bindCLIShortcuts({
print: true,
customShortcuts: [
{
key: 'r',
description: 'force reload browser',
action(server) {
server.ws.send({
type: 'full-reload',
path: '*',
});
},
},
],
});
}
// TODO: adjust output typings to reflect both development servers
yield {
success: true,
port: serverUrl?.port,
baseUrl: serverUrl?.href,
};
}
await new Promise((resolve) => (deferred = resolve));
}
exports.serveWithVite = serveWithVite;
function handleUpdate(normalizePath, generatedFiles, server, serverOptions, logger) {
const updatedFiles = [];
// Invalidate any updated files
for (const [file, record] of generatedFiles) {
if (record.updated) {
updatedFiles.push(file);
const updatedModules = server.moduleGraph.getModulesByFile(normalizePath((0, node_path_1.join)(server.config.root, file)));
updatedModules?.forEach((m) => server?.moduleGraph.invalidateModule(m));
}
}
if (!updatedFiles.length) {
return;
}
if (serverOptions.liveReload || serverOptions.hmr) {
if (updatedFiles.every((f) => f.endsWith('.css'))) {
const timestamp = Date.now();
server.ws.send({
type: 'update',
updates: updatedFiles.map((filePath) => {
return {
type: 'css-update',
timestamp,
path: filePath,
acceptedPath: filePath,
};
}),
});
logger.info('HMR update sent to client(s).');
return;
}
}
// Send reload command to clients
if (serverOptions.liveReload) {
server.ws.send({
type: 'full-reload',
path: '*',
});
logger.info('Page reload sent to client(s).');
}
}
function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generatedFiles) {
const seen = new Set(['/index.html']);
for (const file of resultFiles) {
let filePath;
if (file.path === htmlIndexPath) {
// Convert custom index output path to standard index path for dev-server usage.
// This mimics the Webpack dev-server behavior.
filePath = '/index.html';
}
else {
filePath = '/' + normalizePath(file.path);
}
seen.add(filePath);
// Skip analysis of sourcemaps
if (filePath.endsWith('.map')) {
generatedFiles.set(filePath, {
contents: file.contents,
servable: file.type === bundler_context_1.BuildOutputFileType.Browser || file.type === bundler_context_1.BuildOutputFileType.Media,
size: file.contents.byteLength,
updated: false,
});
continue;
}
const existingRecord = generatedFiles.get(filePath);
if (existingRecord &&
existingRecord.size === file.contents.byteLength &&
existingRecord.hash === file.hash) {
// Same file
existingRecord.updated = false;
continue;
}
// New or updated file
generatedFiles.set(filePath, {
contents: file.contents,
size: file.contents.byteLength,
hash: file.hash,
updated: true,
servable: file.type === bundler_context_1.BuildOutputFileType.Browser || file.type === bundler_context_1.BuildOutputFileType.Media,
});
}
// Clear stale output files
for (const file of generatedFiles.keys()) {
if (!seen.has(file)) {
generatedFiles.delete(file);
}
}
}
async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
const proxy = await (0, utils_2.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig, true);
// dynamically import Vite for ESM compatibility
const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite');
// Path will not exist on disk and only used to provide separate path for Vite requests
const virtualProjectRoot = normalizePath((0, node_path_1.join)(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project));
const serverExplicitExternal = [
...(await Promise.resolve().then(() => __importStar(require('node:module')))).builtinModules,
...externalMetadata.explicit,
];
const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, 'vite');
const configuration = {
configFile: false,
envFile: false,
cacheDir,
root: virtualProjectRoot,
publicDir: false,
esbuild: false,
mode: 'development',
appType: 'mpa',
css: {
devSourcemap: true,
},
// Vite will normalize the `base` option by adding a leading slash.
base: serverOptions.servePath,
resolve: {
mainFields: ['es2020', 'browser', 'module', 'main'],
preserveSymlinks,
},
server: {
port: serverOptions.port,
strictPort: true,
host: serverOptions.host,
open: serverOptions.open,
headers: serverOptions.headers,
proxy,
cors: {
// Allow preflight requests to be proxied.
preflightContinue: true,
},
// File watching is handled by the build directly. `null` disables file watching for Vite.
watch: null,
fs: {
// Ensure cache directory, node modules, and all assets are accessible by the client.
// The first two are required for Vite to function in prebundling mode (the default) and to load
// the Vite client-side code for browser reloading. These would be available by default but when
// the `allow` option is explicitly configured, they must be included manually.
allow: [cacheDir, (0, node_path_1.join)(serverOptions.workspaceRoot, 'node_modules'), ...assets.values()],
},
// This is needed when `externalDependencies` is used to prevent Vite load errors.
// NOTE: If Vite adds direct support for externals, this can be removed.
preTransformRequests: externalMetadata.explicit.length === 0,
},
ssr: {
// Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored.
noExternal: /.*/,
// Exclude any Node.js built in module and provided dependencies (currently build defined externals)
external: serverExplicitExternal,
optimizeDeps: getDepOptimizationConfig({
/**
* *********************************************
* NOTE: Temporary disable 'optimizeDeps' for SSR.
* *********************************************
*
* Currently this causes a number of issues.
* - Deps are re-optimized everytime the server is started.
* - Added deps after a rebuild are not optimized.
* - Breaks RxJs (Unless it is added as external). See: https://github.com/angular/angular-cli/issues/26235
*/
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: true, // !serverOptions.cacheOptions.enabled,
// Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins)
exclude: serverExplicitExternal,
// Include all implict dependencies from the external packages internal option
include: externalMetadata.implicitServer,
ssr: true,
prebundleTransformer,
target,
loader: prebundleLoaderExtensions,
thirdPartySourcemaps,
}),
},
plugins: [
(0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)(),
(0, angular_memory_plugin_1.createAngularMemoryPlugin)({
workspaceRoot: serverOptions.workspaceRoot,
virtualProjectRoot,
outputFiles,
assets,
ssr,
external: externalMetadata.explicit,
indexHtmlTransformer,
extensionMiddleware,
extraHeaders: serverOptions.headers,
normalizePath,
}),
],
// Browser only optimizeDeps. (This does not run for SSR dependencies).
optimizeDeps: getDepOptimizationConfig({
// Only enable with caching since it causes prebundle dependencies to be cached
disabled: !serverOptions.cacheOptions.enabled,
// Exclude any explicitly defined dependencies (currently build defined externals)
exclude: externalMetadata.explicit,
// Include all implict dependencies from the external packages internal option
include: externalMetadata.implicitBrowser,
ssr: false,
prebundleTransformer,
target,
loader: prebundleLoaderExtensions,
thirdPartySourcemaps,
}),
};
if (serverOptions.ssl) {
if (serverOptions.sslCert && serverOptions.sslKey) {
// server configuration is defined above
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
configuration.server.https = {
cert: await (0, promises_1.readFile)(serverOptions.sslCert),
key: await (0, promises_1.readFile)(serverOptions.sslKey),
};
}
else {
const { default: basicSslPlugin } = await Promise.resolve().then(() => __importStar(require('@vitejs/plugin-basic-ssl')));
// eslint-disable-next-line @typescript-eslint/no-floating-promises
configuration.plugins ??= [];
configuration.plugins.push(basicSslPlugin());
}
}
return configuration;
}
exports.setupServer = setupServer;
function getDepOptimizationConfig({ disabled, exclude, include, target, prebundleTransformer, ssr, loader, thirdPartySourcemaps, }) {
const plugins = [
{
name: `angular-vite-optimize-deps${ssr ? '-ssr' : ''}${thirdPartySourcemaps ? '-vendor-sourcemap' : ''}`,
setup(build) {
build.onLoad({ filter: /\.[cm]?js$/ }, async (args) => {
return {
contents: await prebundleTransformer.transformFile(args.path),
loader: 'js',
};
});
},
},
];
if (ssr) {
plugins.unshift((0, rxjs_esm_resolution_plugin_1.createRxjsEsmResolutionPlugin)());
}
return {
// Only enable with caching since it causes prebundle dependencies to be cached
disabled,
// Exclude any explicitly defined dependencies (currently build defined externals)
exclude,
// Include all implict dependencies from the external packages internal option
include,
// Add an esbuild plugin to run the Angular linker on dependencies
esbuildOptions: {
// Set esbuild supported targets.
target,
supported: (0, utils_1.getFeatureSupport)(target),
plugins,
loader,
},
};
}

View file

@ -0,0 +1,35 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { DevServerBuildOutput, WebpackLoggingCallback } from '@angular-devkit/build-webpack';
import { Observable } from 'rxjs';
import webpack from 'webpack';
import { BuildEventStats } from '../../tools/webpack/utils/stats';
import { ExecutionTransformer } from '../../transforms';
import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
import { NormalizedDevServerOptions } from './options';
/**
* @experimental Direct usage of this type is considered experimental.
*/
export type DevServerBuilderOutput = DevServerBuildOutput & {
baseUrl: string;
stats: BuildEventStats;
};
/**
* Reusable implementation of the Angular Webpack development server builder.
* @param options Dev Server options.
* @param builderName The name of the builder used to build the application.
* @param context The build context.
* @param transforms A map of transforms that can be used to hook into some logic (such as
* transforming webpack configuration before passing it to webpack).
*/
export declare function serveWebpackBrowser(options: NormalizedDevServerOptions, builderName: string, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
logging?: WebpackLoggingCallback;
indexHtml?: IndexHtmlTransform;
}): Observable<DevServerBuilderOutput>;

View file

@ -0,0 +1,286 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.serveWebpackBrowser = void 0;
const build_webpack_1 = require("@angular-devkit/build-webpack");
const core_1 = require("@angular-devkit/core");
const path = __importStar(require("path"));
const rxjs_1 = require("rxjs");
const url = __importStar(require("url"));
const configs_1 = require("../../tools/webpack/configs");
const index_html_webpack_plugin_1 = require("../../tools/webpack/plugins/index-html-webpack-plugin");
const service_worker_plugin_1 = require("../../tools/webpack/plugins/service-worker-plugin");
const stats_1 = require("../../tools/webpack/utils/stats");
const utils_1 = require("../../utils");
const color_1 = require("../../utils/color");
const i18n_options_1 = require("../../utils/i18n-options");
const load_translations_1 = require("../../utils/load-translations");
const package_chunk_sort_1 = require("../../utils/package-chunk-sort");
const version_1 = require("../../utils/version");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
const webpack_diagnostics_1 = require("../../utils/webpack-diagnostics");
const schema_1 = require("../browser/schema");
/**
* Reusable implementation of the Angular Webpack development server builder.
* @param options Dev Server options.
* @param builderName The name of the builder used to build the application.
* @param context The build context.
* @param transforms A map of transforms that can be used to hook into some logic (such as
* transforming webpack configuration before passing it to webpack).
*/
// eslint-disable-next-line max-lines-per-function
function serveWebpackBrowser(options, builderName, context, transforms = {}) {
// Check Angular version.
const { logger, workspaceRoot } = context;
(0, version_1.assertCompatibleAngularVersion)(workspaceRoot);
async function setup() {
if (options.hmr) {
logger.warn(core_1.tags.stripIndents `NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.
See https://webpack.js.org/guides/hot-module-replacement for information on working with HMR for Webpack.`);
}
// Get the browser configuration from the target name.
const rawBrowserOptions = (await context.getTargetOptions(options.buildTarget));
if (rawBrowserOptions.outputHashing && rawBrowserOptions.outputHashing !== schema_1.OutputHashing.None) {
// Disable output hashing for dev build as this can cause memory leaks
// See: https://github.com/webpack/webpack-dev-server/issues/377#issuecomment-241258405
rawBrowserOptions.outputHashing = schema_1.OutputHashing.None;
logger.warn(`Warning: 'outputHashing' option is disabled when using the dev-server.`);
}
const browserOptions = (await context.validateOptions({
...rawBrowserOptions,
watch: options.watch,
verbose: options.verbose,
// In dev server we should not have budgets because of extra libs such as socks-js
budgets: undefined,
}, builderName));
const { styles, scripts } = (0, utils_1.normalizeOptimization)(browserOptions.optimization);
if (scripts || styles.minify) {
logger.error(core_1.tags.stripIndents `
****************************************************************************************
This is a simple server for use in testing or debugging Angular applications locally.
It hasn't been reviewed for security issues.
DON'T USE IT FOR PRODUCTION!
****************************************************************************************
`);
}
const { config, i18n } = await (0, webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext)(browserOptions, context, (wco) => [(0, configs_1.getDevServerConfig)(wco), (0, configs_1.getCommonConfig)(wco), (0, configs_1.getStylesConfig)(wco)], options);
if (!config.devServer) {
throw new Error('Webpack Dev Server configuration was not set.');
}
let locale;
if (i18n.shouldInline) {
// Dev-server only supports one locale
locale = [...i18n.inlineLocales][0];
}
else if (i18n.hasDefinedSourceLocale) {
// use source locale if not localizing
locale = i18n.sourceLocale;
}
let webpackConfig = config;
// If a locale is defined, setup localization
if (locale) {
if (i18n.inlineLocales.size > 1) {
throw new Error('The development server only supports localizing a single locale per build.');
}
await setupLocalize(locale, i18n, browserOptions, webpackConfig, options.cacheOptions, context);
}
if (transforms.webpackConfiguration) {
webpackConfig = await transforms.webpackConfiguration(webpackConfig);
}
webpackConfig.plugins ??= [];
if (browserOptions.index) {
const { scripts = [], styles = [], baseHref } = browserOptions;
const entrypoints = (0, package_chunk_sort_1.generateEntryPoints)({
scripts,
styles,
// The below is needed as otherwise HMR for CSS will break.
// styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null.
// https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39
isHMREnabled: !!webpackConfig.devServer?.hot,
});
webpackConfig.plugins.push(new index_html_webpack_plugin_1.IndexHtmlWebpackPlugin({
indexPath: path.resolve(workspaceRoot, (0, webpack_browser_config_1.getIndexInputFile)(browserOptions.index)),
outputPath: (0, webpack_browser_config_1.getIndexOutputFile)(browserOptions.index),
baseHref,
entrypoints,
deployUrl: browserOptions.deployUrl,
sri: browserOptions.subresourceIntegrity,
cache: options.cacheOptions,
postTransform: transforms.indexHtml,
optimization: (0, utils_1.normalizeOptimization)(browserOptions.optimization),
crossOrigin: browserOptions.crossOrigin,
lang: locale,
}));
}
if (browserOptions.serviceWorker) {
webpackConfig.plugins.push(new service_worker_plugin_1.ServiceWorkerPlugin({
baseHref: browserOptions.baseHref,
root: context.workspaceRoot,
projectRoot: options.projectRoot,
ngswConfigPath: browserOptions.ngswConfigPath,
}));
}
return {
browserOptions,
webpackConfig,
};
}
return (0, rxjs_1.from)(setup()).pipe((0, rxjs_1.switchMap)(({ browserOptions, webpackConfig }) => {
return (0, build_webpack_1.runWebpackDevServer)(webpackConfig, context, {
logging: transforms.logging || (0, stats_1.createWebpackLoggingCallback)(browserOptions, logger),
webpackFactory: require('webpack'),
webpackDevServerFactory: require('webpack-dev-server'),
}).pipe((0, rxjs_1.concatMap)(async (buildEvent, index) => {
const webpackRawStats = buildEvent.webpackStats;
if (!webpackRawStats) {
throw new Error('Webpack stats build result is required.');
}
// Resolve serve address.
const publicPath = webpackConfig.devServer?.devMiddleware?.publicPath;
const serverAddress = url.format({
protocol: options.ssl ? 'https' : 'http',
hostname: options.host === '0.0.0.0' ? 'localhost' : options.host,
port: buildEvent.port,
pathname: typeof publicPath === 'string' ? publicPath : undefined,
});
if (index === 0) {
logger.info('\n' +
core_1.tags.oneLine `
**
Angular Live Development Server is listening on ${options.host}:${buildEvent.port},
open your browser on ${serverAddress}
**
` +
'\n');
if (options.open) {
const open = (await Promise.resolve().then(() => __importStar(require('open')))).default;
await open(serverAddress);
}
}
if (buildEvent.success) {
logger.info(`\n${color_1.colors.greenBright(color_1.colors.symbols.check)} Compiled successfully.`);
}
else {
logger.info(`\n${color_1.colors.redBright(color_1.colors.symbols.cross)} Failed to compile.`);
}
return {
...buildEvent,
baseUrl: serverAddress,
stats: (0, stats_1.generateBuildEventStats)(webpackRawStats, browserOptions),
};
}));
}));
}
exports.serveWebpackBrowser = serveWebpackBrowser;
async function setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions, context) {
const localeDescription = i18n.locales[locale];
// Modify main entrypoint to include locale data
if (localeDescription?.dataPath &&
typeof webpackConfig.entry === 'object' &&
!Array.isArray(webpackConfig.entry) &&
webpackConfig.entry['main']) {
if (Array.isArray(webpackConfig.entry['main'])) {
webpackConfig.entry['main'].unshift(localeDescription.dataPath);
}
else {
webpackConfig.entry['main'] = [
localeDescription.dataPath,
webpackConfig.entry['main'],
];
}
}
let missingTranslationBehavior = browserOptions.i18nMissingTranslation || 'ignore';
let translation = localeDescription?.translation || {};
if (locale === i18n.sourceLocale) {
missingTranslationBehavior = 'ignore';
translation = {};
}
const i18nLoaderOptions = {
locale,
missingTranslationBehavior,
translation: i18n.shouldInline ? translation : undefined,
translationFiles: localeDescription?.files.map((file) => path.resolve(context.workspaceRoot, file.path)),
};
const i18nRule = {
test: /\.[cm]?[tj]sx?$/,
enforce: 'post',
use: [
{
loader: require.resolve('../../tools/babel/webpack-loader'),
options: {
cacheDirectory: (cacheOptions.enabled && path.join(cacheOptions.path, 'babel-dev-server-i18n')) ||
false,
cacheIdentifier: JSON.stringify({
locale,
translationIntegrity: localeDescription?.files.map((file) => file.integrity),
}),
i18n: i18nLoaderOptions,
},
},
],
};
// Get the rules and ensure the Webpack configuration is setup properly
const rules = webpackConfig.module?.rules || [];
if (!webpackConfig.module) {
webpackConfig.module = { rules };
}
else if (!webpackConfig.module.rules) {
webpackConfig.module.rules = rules;
}
rules.push(i18nRule);
// Add a plugin to reload translation files on rebuilds
const loader = await (0, load_translations_1.createTranslationLoader)();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
webpackConfig.plugins.push({
apply: (compiler) => {
compiler.hooks.thisCompilation.tap('build-angular', (compilation) => {
if (i18n.shouldInline && i18nLoaderOptions.translation === undefined) {
// Reload translations
(0, i18n_options_1.loadTranslations)(locale, localeDescription, context.workspaceRoot, loader, {
warn(message) {
(0, webpack_diagnostics_1.addWarning)(compilation, message);
},
error(message) {
(0, webpack_diagnostics_1.addError)(compilation, message);
},
}, undefined, browserOptions.i18nDuplicateTranslation);
i18nLoaderOptions.translation = localeDescription.translation ?? {};
}
compilation.hooks.finishModules.tap('build-angular', () => {
// After loaders are finished, clear out the now unneeded translations
i18nLoaderOptions.translation = undefined;
});
});
},
});
}

View file

@ -0,0 +1,17 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize';
import type { MessageExtractor } from '@angular/localize/tools';
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type { NormalizedExtractI18nOptions } from './options';
export declare function extractMessages(options: NormalizedExtractI18nOptions, builderName: string, context: BuilderContext, extractorConstructor: typeof MessageExtractor): Promise<{
builderResult: BuilderOutput;
basePath: string;
messages: LocalizeMessage[];
useLegacyIds: boolean;
}>;

View file

@ -0,0 +1,140 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractMessages = void 0;
const node_assert_1 = __importDefault(require("node:assert"));
const node_path_1 = __importDefault(require("node:path"));
const application_1 = require("../application");
const browser_esbuild_1 = require("../browser-esbuild");
async function extractMessages(options, builderName, context, extractorConstructor) {
const messages = [];
// Setup the build options for the application based on the buildTarget option
const buildOptions = (await context.validateOptions(await context.getTargetOptions(options.buildTarget), builderName));
buildOptions.optimization = false;
buildOptions.sourceMap = { scripts: true, vendor: true, styles: false };
buildOptions.localize = false;
buildOptions.budgets = undefined;
buildOptions.index = false;
buildOptions.serviceWorker = false;
let build;
if (builderName === '@angular-devkit/build-angular:application') {
build = application_1.buildApplicationInternal;
buildOptions.ssr = false;
buildOptions.appShell = false;
buildOptions.prerender = false;
}
else {
build = browser_esbuild_1.buildEsbuildBrowser;
}
// Build the application with the build options
let builderResult;
try {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
for await (const result of build(buildOptions, context, { write: false })) {
builderResult = result;
break;
}
(0, node_assert_1.default)(builderResult !== undefined, 'Application builder did not provide a result.');
}
catch (err) {
builderResult = {
success: false,
error: err.message,
};
}
// Extract messages from each output JavaScript file.
// Output files are only present on a successful build.
if (builderResult.outputFiles) {
// Store the JS and JS map files for lookup during extraction
const files = new Map();
for (const outputFile of builderResult.outputFiles) {
if (outputFile.path.endsWith('.js')) {
files.set(outputFile.path, outputFile.text);
}
else if (outputFile.path.endsWith('.js.map')) {
files.set(outputFile.path, outputFile.text);
}
}
// Setup the localize message extractor based on the in-memory files
const extractor = setupLocalizeExtractor(extractorConstructor, files, context);
// Attempt extraction of all output JS files
for (const filePath of files.keys()) {
if (!filePath.endsWith('.js')) {
continue;
}
const fileMessages = extractor.extractMessages(filePath);
messages.push(...fileMessages);
}
}
return {
builderResult,
basePath: context.workspaceRoot,
messages,
// Legacy i18n identifiers are not supported with the new application builder
useLegacyIds: false,
};
}
exports.extractMessages = extractMessages;
function setupLocalizeExtractor(extractorConstructor, files, context) {
// Setup a virtual file system instance for the extractor
// * MessageExtractor itself uses readFile, relative and resolve
// * Internal SourceFileLoader (sourcemap support) uses dirname, exists, readFile, and resolve
const filesystem = {
readFile(path) {
// Output files are stored as relative to the workspace root
const requestedPath = node_path_1.default.relative(context.workspaceRoot, path);
const content = files.get(requestedPath);
if (content === undefined) {
throw new Error('Unknown file requested: ' + requestedPath);
}
return content;
},
relative(from, to) {
return node_path_1.default.relative(from, to);
},
resolve(...paths) {
return node_path_1.default.resolve(...paths);
},
exists(path) {
// Output files are stored as relative to the workspace root
const requestedPath = node_path_1.default.relative(context.workspaceRoot, path);
return files.has(requestedPath);
},
dirname(path) {
return node_path_1.default.dirname(path);
},
};
const logger = {
// level 2 is warnings
level: 2,
debug(...args) {
// eslint-disable-next-line no-console
console.debug(...args);
},
info(...args) {
context.logger.info(args.join(''));
},
warn(...args) {
context.logger.warn(args.join(''));
},
error(...args) {
context.logger.error(args.join(''));
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const extractor = new extractorConstructor(filesystem, logger, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath: context.workspaceRoot,
useSourceMaps: true,
});
return extractor;
}

View file

@ -0,0 +1,17 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type webpack from 'webpack';
import type { ExecutionTransformer } from '../../transforms';
import { Schema as ExtractI18nBuilderOptions } from './schema';
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: ExtractI18nBuilderOptions, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
}): Promise<BuilderOutput>;

View file

@ -0,0 +1,152 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const load_esm_1 = require("../../utils/load-esm");
const purge_cache_1 = require("../../utils/purge-cache");
const version_1 = require("../../utils/version");
const options_1 = require("./options");
const schema_1 = require("./schema");
/**
* @experimental Direct usage of this function is considered experimental.
*/
async function execute(options, context, transforms) {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
context.logger.error(`The 'extract-i18n' builder requires a target to be specified.`);
return { success: false };
}
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(context.workspaceRoot);
// Load the Angular localize package.
// The package is a peer dependency and might not be present
let localizeToolsModule;
try {
localizeToolsModule =
await (0, load_esm_1.loadEsmModule)('@angular/localize/tools');
}
catch {
return {
success: false,
error: `i18n extraction requires the '@angular/localize' package.` +
` You can add it by using 'ng add @angular/localize'.`,
};
}
// Normalize options
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options);
const builderName = await context.getBuilderNameForTarget(normalizedOptions.buildTarget);
// Extract messages based on configured builder
let extractionResult;
if (builderName === '@angular-devkit/build-angular:application' ||
builderName === '@angular-devkit/build-angular:browser-esbuild') {
const { extractMessages } = await Promise.resolve().then(() => __importStar(require('./application-extraction')));
extractionResult = await extractMessages(normalizedOptions, builderName, context, localizeToolsModule.MessageExtractor);
}
else {
// Purge old build disk cache.
// Other build systems handle stale cache purging directly.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
const { extractMessages } = await Promise.resolve().then(() => __importStar(require('./webpack-extraction')));
extractionResult = await extractMessages(normalizedOptions, builderName, context, transforms);
}
// Return the builder result if it failed
if (!extractionResult.builderResult.success) {
return extractionResult.builderResult;
}
// Perform duplicate message checks
const { checkDuplicateMessages } = localizeToolsModule;
// The filesystem is used to create a relative path for each file
// from the basePath. This relative path is then used in the error message.
const checkFileSystem = {
relative(from, to) {
return node_path_1.default.relative(from, to);
},
};
const diagnostics = checkDuplicateMessages(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
checkFileSystem, extractionResult.messages, 'warning',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
extractionResult.basePath);
if (diagnostics.messages.length > 0) {
context.logger.warn(diagnostics.formatDiagnostics(''));
}
// Serialize all extracted messages
const serializer = await createSerializer(localizeToolsModule, normalizedOptions.format, normalizedOptions.i18nOptions.sourceLocale, extractionResult.basePath, extractionResult.useLegacyIds, diagnostics);
const content = serializer.serialize(extractionResult.messages);
// Ensure directory exists
const outputPath = node_path_1.default.dirname(normalizedOptions.outFile);
if (!node_fs_1.default.existsSync(outputPath)) {
node_fs_1.default.mkdirSync(outputPath, { recursive: true });
}
// Write translation file
node_fs_1.default.writeFileSync(normalizedOptions.outFile, content);
if (normalizedOptions.progress) {
context.logger.info(`Extraction Complete. (Messages: ${extractionResult.messages.length})`);
}
return { success: true, outputPath: normalizedOptions.outFile };
}
exports.execute = execute;
async function createSerializer(localizeToolsModule, format, sourceLocale, basePath, useLegacyIds, diagnostics) {
const { XmbTranslationSerializer, LegacyMessageIdMigrationSerializer, ArbTranslationSerializer, Xliff1TranslationSerializer, Xliff2TranslationSerializer, SimpleJsonTranslationSerializer, } = localizeToolsModule;
switch (format) {
case schema_1.Format.Xmb:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new XmbTranslationSerializer(basePath, useLegacyIds);
case schema_1.Format.Xlf:
case schema_1.Format.Xlif:
case schema_1.Format.Xliff:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Xliff1TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
case schema_1.Format.Xlf2:
case schema_1.Format.Xliff2:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Xliff2TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
case schema_1.Format.Json:
return new SimpleJsonTranslationSerializer(sourceLocale);
case schema_1.Format.LegacyMigrate:
return new LegacyMessageIdMigrationSerializer(diagnostics);
case schema_1.Format.Arb:
const fileSystem = {
relative(from, to) {
return node_path_1.default.relative(from, to);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new ArbTranslationSerializer(sourceLocale, basePath, fileSystem);
}
}

View file

@ -0,0 +1,8 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export default function (): string;

View file

@ -0,0 +1,13 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
function default_1() {
return `export default '';`;
}
exports.default = default_1;

View file

@ -0,0 +1,12 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { execute } from './builder';
import type { Schema as ExtractI18nBuilderOptions } from './schema';
export { ExtractI18nBuilderOptions, execute };
declare const _default: import("../../../../architect/src/internal").Builder<ExtractI18nBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;

View file

@ -0,0 +1,14 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const architect_1 = require("@angular-devkit/architect");
const builder_1 = require("./builder");
Object.defineProperty(exports, "execute", { enumerable: true, get: function () { return builder_1.execute; } });
exports.default = (0, architect_1.createBuilder)(builder_1.execute);

View file

@ -0,0 +1,13 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
type LoaderSourceMap = Parameters<import('webpack').LoaderDefinitionFunction>[1];
interface LocalizeExtractLoaderOptions {
messageHandler: (messages: import('@angular/localize').ɵParsedMessage[]) => void;
}
export default function localizeExtractLoader(this: import('webpack').LoaderContext<LocalizeExtractLoaderOptions>, content: string, map: LoaderSourceMap): void;
export {};

View file

@ -0,0 +1,127 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const nodePath = __importStar(require("path"));
const load_esm_1 = require("../../utils/load-esm");
function localizeExtractLoader(content, map) {
// This loader is not cacheable due to how message extraction works.
// Extracted messages are not part of webpack pipeline and hence they cannot be retrieved from cache.
// TODO: We should investigate in the future on making this deterministic and more cacheable.
this.cacheable(false);
const options = this.getOptions();
const callback = this.async();
extract(this, content, map, options).then(() => {
// Pass through the original content now that messages have been extracted
callback(undefined, content, map);
}, (error) => {
callback(error);
});
}
exports.default = localizeExtractLoader;
async function extract(loaderContext, content, map, options) {
// Try to load the `@angular/localize` message extractor.
// All the localize usages are setup to first try the ESM entry point then fallback to the deep imports.
// This provides interim compatibility while the framework is transitioned to bundled ESM packages.
let MessageExtractor;
try {
// Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround.
// Once TypeScript provides support for keeping the dynamic import this workaround can be
// changed to a direct dynamic import.
const localizeToolsModule = await (0, load_esm_1.loadEsmModule)('@angular/localize/tools');
MessageExtractor = localizeToolsModule.MessageExtractor;
}
catch {
throw new Error(`Unable to load message extractor. Please ensure '@angular/localize' is installed.`);
}
// Setup a Webpack-based logger instance
const logger = {
// level 2 is warnings
level: 2,
debug(...args) {
// eslint-disable-next-line no-console
console.debug(...args);
},
info(...args) {
loaderContext.emitWarning(new Error(args.join('')));
},
warn(...args) {
loaderContext.emitWarning(new Error(args.join('')));
},
error(...args) {
loaderContext.emitError(new Error(args.join('')));
},
};
let filename = loaderContext.resourcePath;
const mapObject = typeof map === 'string' ? JSON.parse(map) : map;
if (mapObject?.file) {
// The extractor's internal sourcemap handling expects the filenames to match
filename = nodePath.join(loaderContext.context, mapObject.file);
}
// Setup a virtual file system instance for the extractor
// * MessageExtractor itself uses readFile, relative and resolve
// * Internal SourceFileLoader (sourcemap support) uses dirname, exists, readFile, and resolve
const filesystem = {
readFile(path) {
if (path === filename) {
return content;
}
else if (path === filename + '.map') {
return typeof map === 'string' ? map : JSON.stringify(map);
}
else {
throw new Error('Unknown file requested: ' + path);
}
},
relative(from, to) {
return nodePath.relative(from, to);
},
resolve(...paths) {
return nodePath.resolve(...paths);
},
exists(path) {
return path === filename || path === filename + '.map';
},
dirname(path) {
return nodePath.dirname(path);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const extractor = new MessageExtractor(filesystem, logger, {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath: loaderContext.rootContext,
useSourceMaps: !!map,
});
const messages = extractor.extractMessages(filename);
if (messages.length > 0) {
options?.messageHandler(messages);
}
}

View file

@ -0,0 +1,29 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext } from '@angular-devkit/architect';
import { Schema as ExtractI18nOptions, Format } from './schema';
export type NormalizedExtractI18nOptions = Awaited<ReturnType<typeof normalizeOptions>>;
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
export declare function normalizeOptions(context: BuilderContext, projectName: string, options: ExtractI18nOptions): Promise<{
workspaceRoot: string;
projectRoot: string;
buildTarget: import("@angular-devkit/architect").Target;
i18nOptions: import("../../utils/i18n-options").I18nOptions;
format: Format.Arb | Format.Json | Format.LegacyMigrate | Format.Xliff | Format.Xliff2 | Format.Xmb;
outFile: string;
progress: boolean;
}>;

View file

@ -0,0 +1,82 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = void 0;
const architect_1 = require("@angular-devkit/architect");
const node_assert_1 = require("node:assert");
const node_path_1 = __importDefault(require("node:path"));
const i18n_options_1 = require("../../utils/i18n-options");
const schema_1 = require("./schema");
/**
* Normalize the user provided options by creating full paths for all path based options
* and converting multi-form options into a single form that can be directly used
* by the build process.
*
* @param context The context for current builder execution.
* @param projectName The name of the project for the current execution.
* @param options An object containing the options to use for the build.
* @returns An object containing normalized options required to perform the build.
*/
async function normalizeOptions(context, projectName, options) {
const workspaceRoot = context.workspaceRoot;
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = node_path_1.default.join(workspaceRoot, projectMetadata.root ?? '');
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const buildTarget = (0, architect_1.targetFromTargetString)(options.buildTarget ?? options.browserTarget);
const i18nOptions = (0, i18n_options_1.createI18nOptions)(projectMetadata);
// Normalize xliff format extensions
let format = options.format;
switch (format) {
case undefined:
// Default format is xliff1
case schema_1.Format.Xlf:
case schema_1.Format.Xlif:
case schema_1.Format.Xliff:
format = schema_1.Format.Xliff;
break;
case schema_1.Format.Xlf2:
case schema_1.Format.Xliff2:
format = schema_1.Format.Xliff2;
break;
}
let outFile = options.outFile || getDefaultOutFile(format);
if (options.outputPath) {
outFile = node_path_1.default.join(options.outputPath, outFile);
}
outFile = node_path_1.default.resolve(context.workspaceRoot, outFile);
return {
workspaceRoot,
projectRoot,
buildTarget,
i18nOptions,
format,
outFile,
progress: options.progress ?? true,
};
}
exports.normalizeOptions = normalizeOptions;
function getDefaultOutFile(format) {
switch (format) {
case schema_1.Format.Xmb:
return 'messages.xmb';
case schema_1.Format.Xliff:
case schema_1.Format.Xliff2:
return 'messages.xlf';
case schema_1.Format.Json:
case schema_1.Format.LegacyMigrate:
return 'messages.json';
case schema_1.Format.Arb:
return 'messages.arb';
default:
(0, node_assert_1.fail)(`Invalid Format enum value: ${format}`);
}
}

View file

@ -0,0 +1,48 @@
/**
* Extract i18n target options for Build Facade.
*/
export interface Schema {
/**
* A browser builder target to extract i18n messages in the format of
* `project:target[:configuration]`. You can also pass in more than one configuration name
* as a comma-separated list. Example: `project:target:production,staging`.
* @deprecated Use 'buildTarget' instead.
*/
browserTarget?: string;
/**
* A builder target to extract i18n messages in the format of
* `project:target[:configuration]`. You can also pass in more than one configuration name
* as a comma-separated list. Example: `project:target:production,staging`.
*/
buildTarget?: string;
/**
* Output format for the generated file.
*/
format?: Format;
/**
* Name of the file to output.
*/
outFile?: string;
/**
* Path where output will be placed.
*/
outputPath?: string;
/**
* Log progress to the console.
*/
progress?: boolean;
}
/**
* Output format for the generated file.
*/
export declare enum Format {
Arb = "arb",
Json = "json",
LegacyMigrate = "legacy-migrate",
Xlf = "xlf",
Xlf2 = "xlf2",
Xlif = "xlif",
Xliff = "xliff",
Xliff2 = "xliff2",
Xmb = "xmb"
}

View file

@ -0,0 +1,20 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.Format = void 0;
/**
* Output format for the generated file.
*/
var Format;
(function (Format) {
Format["Arb"] = "arb";
Format["Json"] = "json";
Format["LegacyMigrate"] = "legacy-migrate";
Format["Xlf"] = "xlf";
Format["Xlf2"] = "xlf2";
Format["Xlif"] = "xlif";
Format["Xliff"] = "xliff";
Format["Xliff2"] = "xliff2";
Format["Xmb"] = "xmb";
})(Format || (exports.Format = Format = {}));

View file

@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Extract i18n Target",
"description": "Extract i18n target options for Build Facade.",
"type": "object",
"properties": {
"browserTarget": {
"type": "string",
"description": "A browser builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$",
"x-deprecated": "Use 'buildTarget' instead."
},
"buildTarget": {
"type": "string",
"description": "A builder target to extract i18n messages in the format of `project:target[:configuration]`. You can also pass in more than one configuration name as a comma-separated list. Example: `project:target:production,staging`.",
"pattern": "^[^:\\s]+:[^:\\s]+(:[^\\s]+)?$"
},
"format": {
"type": "string",
"description": "Output format for the generated file.",
"default": "xlf",
"enum": ["xmb", "xlf", "xlif", "xliff", "xlf2", "xliff2", "json", "arb", "legacy-migrate"]
},
"progress": {
"type": "boolean",
"description": "Log progress to the console.",
"default": true
},
"outputPath": {
"type": "string",
"description": "Path where output will be placed."
},
"outFile": {
"type": "string",
"description": "Name of the file to output."
}
},
"additionalProperties": false,
"anyOf": [{ "required": ["buildTarget"] }, { "required": ["browserTarget"] }]
}

View file

@ -0,0 +1,21 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import type { ɵParsedMessage as LocalizeMessage } from '@angular/localize';
import { BuilderContext } from '@angular-devkit/architect';
import { BuildResult } from '@angular-devkit/build-webpack';
import webpack from 'webpack';
import { ExecutionTransformer } from '../../transforms';
import { NormalizedExtractI18nOptions } from './options';
export declare function extractMessages(options: NormalizedExtractI18nOptions, builderName: string, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
}): Promise<{
builderResult: BuildResult;
basePath: string;
messages: LocalizeMessage[];
useLegacyIds: boolean;
}>;

View file

@ -0,0 +1,99 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractMessages = void 0;
const build_webpack_1 = require("@angular-devkit/build-webpack");
const rxjs_1 = require("rxjs");
const webpack_1 = __importDefault(require("webpack"));
const configs_1 = require("../../tools/webpack/configs");
const stats_1 = require("../../tools/webpack/utils/stats");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
const schema_1 = require("../browser/schema");
class NoEmitPlugin {
apply(compiler) {
compiler.hooks.shouldEmit.tap('angular-no-emit', () => false);
}
}
async function extractMessages(options, builderName, context, transforms = {}) {
const messages = [];
let useLegacyIds = true;
const browserOptions = await context.validateOptions(await context.getTargetOptions(options.buildTarget), builderName);
const builderOptions = {
...browserOptions,
optimization: false,
sourceMap: {
scripts: true,
styles: false,
vendor: true,
},
buildOptimizer: false,
aot: true,
progress: options.progress,
budgets: [],
assets: [],
scripts: [],
styles: [],
deleteOutputPath: false,
extractLicenses: false,
subresourceIntegrity: false,
outputHashing: schema_1.OutputHashing.None,
namedChunks: true,
allowedCommonJsDependencies: undefined,
};
const { config } = await (0, webpack_browser_config_1.generateBrowserWebpackConfigFromContext)(builderOptions, context, (wco) => {
// Default value for legacy message ids is currently true
useLegacyIds = wco.tsConfig.options.enableI18nLegacyMessageIdFormat ?? true;
const partials = [
{ plugins: [new NoEmitPlugin()] },
(0, configs_1.getCommonConfig)(wco),
];
// Add Ivy application file extractor support
partials.unshift({
module: {
rules: [
{
test: /\.[cm]?[tj]sx?$/,
loader: require.resolve('./ivy-extract-loader'),
options: {
messageHandler: (fileMessages) => messages.push(...fileMessages),
},
},
],
},
});
// Replace all stylesheets with empty content
partials.push({
module: {
rules: [
{
test: /\.(css|scss|sass|less)$/,
loader: require.resolve('./empty-loader'),
},
],
},
});
return partials;
},
// During extraction we don't need specific browser support.
{ supportedBrowsers: undefined });
const builderResult = await (0, rxjs_1.lastValueFrom)((0, build_webpack_1.runWebpack)((await transforms?.webpackConfiguration?.(config)) || config, context, {
logging: (0, stats_1.createWebpackLoggingCallback)(builderOptions, context.logger),
webpackFactory: webpack_1.default,
}));
return {
builderResult,
basePath: config.context || options.projectRoot,
messages,
useLegacyIds,
};
}
exports.extractMessages = extractMessages;

View file

@ -0,0 +1,11 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Schema as JestBuilderSchema } from './schema';
/** Main execution function for the Jest builder. */
declare const _default: import("../../../../architect/src/internal").Builder<JestBuilderSchema & import("../../../../core/src").JsonObject>;
export default _default;

View file

@ -0,0 +1,160 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const architect_1 = require("@angular-devkit/architect");
const child_process_1 = require("child_process");
const path = __importStar(require("path"));
const util_1 = require("util");
const color_1 = require("../../utils/color");
const test_files_1 = require("../../utils/test-files");
const application_1 = require("../application");
const schema_1 = require("../browser-esbuild/schema");
const options_1 = require("./options");
const execFile = (0, util_1.promisify)(child_process_1.execFile);
/** Main execution function for the Jest builder. */
exports.default = (0, architect_1.createBuilder)(async (schema, context) => {
context.logger.warn('NOTE: The Jest builder is currently EXPERIMENTAL and not ready for production use.');
const options = (0, options_1.normalizeOptions)(schema);
const testOut = 'dist/test-out'; // TODO(dgp1130): Hide in temp directory.
// Verify Jest installation and get the path to it's binary.
// We need to `node_modules/.bin/jest`, but there is no means to resolve that directly. Fortunately Jest's `package.json` exports the
// same file at `bin/jest`, so we can just resolve that instead.
const jest = resolveModule('jest/bin/jest');
if (!jest) {
return {
success: false,
// TODO(dgp1130): Display a more accurate message for non-NPM users.
error: 'Jest is not installed, most likely you need to run `npm install jest --save-dev` in your project.',
};
}
// Verify that JSDom is installed in the project.
const environment = resolveModule('jest-environment-jsdom');
if (!environment) {
return {
success: false,
// TODO(dgp1130): Display a more accurate message for non-NPM users.
error: '`jest-environment-jsdom` is not installed. Install it with `npm install jest-environment-jsdom --save-dev`.',
};
}
// Build all the test files.
const testFiles = await (0, test_files_1.findTestFiles)(options.include, options.exclude, context.workspaceRoot);
const jestGlobal = path.join(__dirname, 'jest-global.mjs');
const initTestBed = path.join(__dirname, 'init-test-bed.mjs');
const buildResult = await build(context, {
// Build all the test files and also the `jest-global` and `init-test-bed` scripts.
entryPoints: new Set([...testFiles, jestGlobal, initTestBed]),
tsConfig: options.tsConfig,
polyfills: options.polyfills ?? ['zone.js', 'zone.js/testing'],
outputPath: testOut,
aot: false,
index: false,
outputHashing: schema_1.OutputHashing.None,
outExtension: 'mjs', // Force native ESM.
optimization: false,
sourceMap: {
scripts: true,
styles: false,
vendor: false,
},
});
if (!buildResult.success) {
return buildResult;
}
// Execute Jest on the built output directory.
const jestProc = execFile(process.execPath, [
'--experimental-vm-modules',
jest,
`--rootDir="${path.join(testOut, 'browser')}"`,
'--testEnvironment=jsdom',
// TODO(dgp1130): Enable cache once we have a mechanism for properly clearing / disabling it.
'--no-cache',
// Run basically all files in the output directory, any excluded files were already dropped by the build.
`--testMatch="<rootDir>/**/*.mjs"`,
// Load polyfills and initialize the environment before executing each test file.
// IMPORTANT: Order matters here.
// First, we execute `jest-global.mjs` to initialize the `jest` global variable.
// Second, we execute user polyfills, including `zone.js` and `zone.js/testing`. This is dependent on the Jest global so it can patch
// the environment for fake async to work correctly.
// Third, we initialize `TestBed`. This is dependent on fake async being set up correctly beforehand.
`--setupFilesAfterEnv="<rootDir>/jest-global.mjs"`,
...(options.polyfills ? [`--setupFilesAfterEnv="<rootDir>/polyfills.mjs"`] : []),
`--setupFilesAfterEnv="<rootDir>/init-test-bed.mjs"`,
// Don't run any infrastructure files as tests, they are manually loaded where needed.
`--testPathIgnorePatterns="<rootDir>/jest-global\\.mjs"`,
...(options.polyfills ? [`--testPathIgnorePatterns="<rootDir>/polyfills\\.mjs"`] : []),
`--testPathIgnorePatterns="<rootDir>/init-test-bed\\.mjs"`,
// Skip shared chunks, as they are not entry points to tests.
`--testPathIgnorePatterns="<rootDir>/chunk-.*\\.mjs"`,
// Optionally enable color.
...(color_1.colors.enabled ? ['--colors'] : []),
]);
// Stream test output to the terminal.
jestProc.child.stdout?.on('data', (chunk) => {
context.logger.info(chunk);
});
jestProc.child.stderr?.on('data', (chunk) => {
// Write to stderr directly instead of `context.logger.error(chunk)` because the logger will overwrite Jest's coloring information.
process.stderr.write(chunk);
});
try {
await jestProc;
}
catch (error) {
// No need to propagate error message, already piped to terminal output.
// TODO(dgp1130): Handle process spawning failures.
return { success: false };
}
return { success: true };
});
async function build(context, options) {
try {
for await (const _ of (0, application_1.buildApplicationInternal)(options, context)) {
// Nothing to do for each event, just wait for the whole build.
}
return { success: true };
}
catch (err) {
return {
success: false,
error: err.message,
};
}
}
/** Safely resolves the given Node module string. */
function resolveModule(module) {
try {
return require.resolve(module);
}
catch {
return undefined;
}
}

View file

@ -0,0 +1,18 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
// TODO(dgp1130): These imports likely don't resolve in stricter package environments like `pnpm`, since they are resolved relative to
// `@angular-devkit/build-angular` rather than the user's workspace. Should look into virtual modules to support those use cases.
import { getTestBed } from '@angular/core/testing';
import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
errorOnUnknownElements: true,
errorOnUnknownProperties: true,
});

View file

@ -0,0 +1,19 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @fileoverview Zone.js requires the `jest` global to be initialized in order to know that it must patch the environment to support Jest
* execution. When running ESM code, Jest does _not_ inject the global `jest` symbol, so Zone.js would not normally know it is running
* within Jest as users are supposed to import from `@jest/globals` or use `import.meta.jest`. Zone.js is not currently aware of this, so we
* manually set this global to get Zone.js to run correctly.
*
* TODO(dgp1130): Update Zone.js to directly support Jest ESM executions so we can drop this.
*/
// eslint-disable-next-line no-undef
globalThis.jest = import.meta.jest;

View file

@ -0,0 +1,21 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { Schema as JestBuilderSchema } from './schema';
/**
* Options supported for the Jest builder. The schema is an approximate
* representation of the options type, but this is a more precise version.
*/
export type JestBuilderOptions = JestBuilderSchema & {
include: string[];
exclude: string[];
};
/**
* Normalizes input options validated by the schema to a more precise and useful
* options type in {@link JestBuilderOptions}.
*/
export declare function normalizeOptions(schema: JestBuilderSchema): JestBuilderOptions;

View file

@ -0,0 +1,25 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeOptions = void 0;
/**
* Normalizes input options validated by the schema to a more precise and useful
* options type in {@link JestBuilderOptions}.
*/
function normalizeOptions(schema) {
return {
// Options with default values can't actually be null, even if the types say so.
/* eslint-disable @typescript-eslint/no-non-null-assertion */
include: schema.include,
exclude: schema.exclude,
/* eslint-enable @typescript-eslint/no-non-null-assertion */
...schema,
};
}
exports.normalizeOptions = normalizeOptions;

View file

@ -0,0 +1,22 @@
/**
* Jest target options
*/
export interface Schema {
/**
* Globs of files to exclude, relative to the project root.
*/
exclude?: string[];
/**
* Globs of files to include, relative to project root.
*/
include?: string[];
/**
* A list of polyfills to include in the build. Can be a full path for a file, relative to
* the current workspace or module specifier. Example: 'zone.js'.
*/
polyfills?: string[];
/**
* The name of the TypeScript configuration file.
*/
tsConfig: string;
}

View file

@ -0,0 +1,4 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });

View file

@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Jest browser schema for Build Facade.",
"description": "Jest target options",
"type": "object",
"properties": {
"include": {
"type": "array",
"items": {
"type": "string"
},
"default": ["**/*.spec.ts"],
"description": "Globs of files to include, relative to project root."
},
"exclude": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "Globs of files to exclude, relative to the project root."
},
"tsConfig": {
"type": "string",
"description": "The name of the TypeScript configuration file."
},
"polyfills": {
"type": "array",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"items": {
"type": "string",
"uniqueItems": true
},
"default": []
}
},
"additionalProperties": false,
"required": ["tsConfig"]
}

View file

@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import type { Compiler } from 'webpack';
export interface FindTestsPluginOptions {
include?: string[];
exclude?: string[];
workspaceRoot: string;
projectSourceRoot: string;
}
export declare class FindTestsPlugin {
private options;
private compilation;
constructor(options: FindTestsPluginOptions);
apply(compiler: Compiler): void;
}

View file

@ -0,0 +1,152 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FindTestsPlugin = void 0;
const assert_1 = __importDefault(require("assert"));
const fast_glob_1 = __importStar(require("fast-glob"));
const fs_1 = require("fs");
const mini_css_extract_plugin_1 = require("mini-css-extract-plugin");
const path_1 = require("path");
/**
* The name of the plugin provided to Webpack when tapping Webpack compiler hooks.
*/
const PLUGIN_NAME = 'angular-find-tests-plugin';
class FindTestsPlugin {
options;
compilation;
constructor(options) {
this.options = options;
}
apply(compiler) {
const { include = ['**/*.spec.ts'], exclude = [], projectSourceRoot, workspaceRoot, } = this.options;
const webpackOptions = compiler.options;
const entry = typeof webpackOptions.entry === 'function' ? webpackOptions.entry() : webpackOptions.entry;
let originalImport;
// Add tests files are part of the entry-point.
webpackOptions.entry = async () => {
const specFiles = await findTests(include, exclude, workspaceRoot, projectSourceRoot);
const entrypoints = await entry;
const entrypoint = entrypoints['main'];
if (!entrypoint.import) {
throw new Error(`Cannot find 'main' entrypoint.`);
}
if (specFiles.length) {
originalImport ??= entrypoint.import;
entrypoint.import = [...originalImport, ...specFiles];
}
else {
(0, assert_1.default)(this.compilation, 'Compilation cannot be undefined.');
this.compilation
.getLogger(mini_css_extract_plugin_1.pluginName)
.error(`Specified patterns: "${include.join(', ')}" did not match any spec files.`);
}
return entrypoints;
};
compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
this.compilation = compilation;
compilation.contextDependencies.add(projectSourceRoot);
});
}
}
exports.FindTestsPlugin = FindTestsPlugin;
// go through all patterns and find unique list of files
async function findTests(include, exclude, workspaceRoot, projectSourceRoot) {
const matchingTestsPromises = include.map((pattern) => findMatchingTests(pattern, exclude, workspaceRoot, projectSourceRoot));
const files = await Promise.all(matchingTestsPromises);
// Unique file names
return [...new Set(files.flat())];
}
const normalizePath = (path) => path.replace(/\\/g, '/');
const removeLeadingSlash = (pattern) => {
if (pattern.charAt(0) === '/') {
return pattern.substring(1);
}
return pattern;
};
const removeRelativeRoot = (path, root) => {
if (path.startsWith(root)) {
return path.substring(root.length);
}
return path;
};
async function findMatchingTests(pattern, ignore, workspaceRoot, projectSourceRoot) {
// normalize pattern, glob lib only accepts forward slashes
let normalizedPattern = normalizePath(pattern);
normalizedPattern = removeLeadingSlash(normalizedPattern);
const relativeProjectRoot = normalizePath((0, path_1.relative)(workspaceRoot, projectSourceRoot) + '/');
// remove relativeProjectRoot to support relative paths from root
// such paths are easy to get when running scripts via IDEs
normalizedPattern = removeRelativeRoot(normalizedPattern, relativeProjectRoot);
// special logic when pattern does not look like a glob
if (!(0, fast_glob_1.isDynamicPattern)(normalizedPattern)) {
if (await isDirectory((0, path_1.join)(projectSourceRoot, normalizedPattern))) {
normalizedPattern = `${normalizedPattern}/**/*.spec.@(ts|tsx)`;
}
else {
// see if matching spec file exists
const fileExt = (0, path_1.extname)(normalizedPattern);
// Replace extension to `.spec.ext`. Example: `src/app/app.component.ts`-> `src/app/app.component.spec.ts`
const potentialSpec = (0, path_1.join)(projectSourceRoot, (0, path_1.dirname)(normalizedPattern), `${(0, path_1.basename)(normalizedPattern, fileExt)}.spec${fileExt}`);
if (await exists(potentialSpec)) {
return [potentialSpec];
}
}
}
// normalize the patterns in the ignore list
const normalizedIgnorePatternList = ignore.map((pattern) => removeRelativeRoot(removeLeadingSlash(normalizePath(pattern)), relativeProjectRoot));
return (0, fast_glob_1.default)(normalizedPattern, {
cwd: projectSourceRoot,
absolute: true,
ignore: ['**/node_modules/**', ...normalizedIgnorePatternList],
});
}
async function isDirectory(path) {
try {
const stats = await fs_1.promises.stat(path);
return stats.isDirectory();
}
catch {
return false;
}
}
async function exists(path) {
try {
await fs_1.promises.access(path, fs_1.constants.F_OK);
return true;
}
catch {
return false;
}
}

View file

@ -0,0 +1,27 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import type { ConfigOptions } from 'karma';
import { Observable } from 'rxjs';
import { Configuration } from 'webpack';
import { ExecutionTransformer } from '../../transforms';
import { Schema as KarmaBuilderOptions } from './schema';
export type KarmaConfigOptions = ConfigOptions & {
buildWebpack?: unknown;
configFile?: string;
};
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: KarmaBuilderOptions, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<Configuration>;
karmaOptions?: (options: KarmaConfigOptions) => KarmaConfigOptions;
}): Observable<BuilderOutput>;
export { KarmaBuilderOptions };
declare const _default: import("../../../../architect/src/internal").Builder<Record<string, string> & KarmaBuilderOptions & import("@angular-devkit/core").JsonObject>;
export default _default;

View file

@ -0,0 +1,218 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const architect_1 = require("@angular-devkit/architect");
const core_1 = require("@angular-devkit/core");
const module_1 = require("module");
const path = __importStar(require("path"));
const rxjs_1 = require("rxjs");
const configs_1 = require("../../tools/webpack/configs");
const purge_cache_1 = require("../../utils/purge-cache");
const version_1 = require("../../utils/version");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
const schema_1 = require("../browser/schema");
const find_tests_plugin_1 = require("./find-tests-plugin");
async function initialize(options, context, webpackConfigurationTransformer) {
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
const { config } = await (0, webpack_browser_config_1.generateBrowserWebpackConfigFromContext)(
// only two properties are missing:
// * `outputPath` which is fixed for tests
// * `budgets` which might be incorrect due to extra dev libs
{
...options,
outputPath: '',
budgets: undefined,
optimization: false,
buildOptimizer: false,
aot: false,
vendorChunk: true,
namedChunks: true,
extractLicenses: false,
outputHashing: schema_1.OutputHashing.None,
// The webpack tier owns the watch behavior so we want to force it in the config.
// When not in watch mode, webpack-dev-middleware will call `compiler.watch` anyway.
// https://github.com/webpack/webpack-dev-middleware/blob/698c9ae5e9bb9a013985add6189ff21c1a1ec185/src/index.js#L65
// https://github.com/webpack/webpack/blob/cde1b73e12eb8a77eb9ba42e7920c9ec5d29c2c9/lib/Compiler.js#L379-L388
watch: true,
}, context, (wco) => [(0, configs_1.getCommonConfig)(wco), (0, configs_1.getStylesConfig)(wco)]);
const karma = await Promise.resolve().then(() => __importStar(require('karma')));
return [karma, (await webpackConfigurationTransformer?.(config)) ?? config];
}
/**
* @experimental Direct usage of this function is considered experimental.
*/
function execute(options, context, transforms = {}) {
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(context.workspaceRoot);
let singleRun;
if (options.watch !== undefined) {
singleRun = !options.watch;
}
return (0, rxjs_1.from)(initialize(options, context, transforms.webpackConfiguration)).pipe((0, rxjs_1.switchMap)(async ([karma, webpackConfig]) => {
// Determine project name from builder context target
const projectName = context.target?.project;
if (!projectName) {
throw new Error(`The 'karma' builder requires a target to be specified.`);
}
const karmaOptions = options.karmaConfig
? {}
: getBuiltInKarmaConfig(context.workspaceRoot, projectName);
karmaOptions.singleRun = singleRun;
// Convert browsers from a string to an array
if (typeof options.browsers === 'string' && options.browsers) {
karmaOptions.browsers = options.browsers.split(',');
}
else if (options.browsers === false) {
karmaOptions.browsers = [];
}
if (options.reporters) {
// Split along commas to make it more natural, and remove empty strings.
const reporters = options.reporters
.reduce((acc, curr) => acc.concat(curr.split(',')), [])
.filter((x) => !!x);
if (reporters.length > 0) {
karmaOptions.reporters = reporters;
}
}
if (!options.main) {
webpackConfig.entry ??= {};
if (typeof webpackConfig.entry === 'object' && !Array.isArray(webpackConfig.entry)) {
if (Array.isArray(webpackConfig.entry['main'])) {
webpackConfig.entry['main'].push(getBuiltInMainFile());
}
else {
webpackConfig.entry['main'] = [getBuiltInMainFile()];
}
}
}
const projectMetadata = await context.getProjectMetadata(projectName);
const sourceRoot = (projectMetadata.sourceRoot ?? projectMetadata.root ?? '');
webpackConfig.plugins ??= [];
webpackConfig.plugins.push(new find_tests_plugin_1.FindTestsPlugin({
include: options.include,
exclude: options.exclude,
workspaceRoot: context.workspaceRoot,
projectSourceRoot: path.join(context.workspaceRoot, sourceRoot),
}));
karmaOptions.buildWebpack = {
options,
webpackConfig,
logger: context.logger,
};
const parsedKarmaConfig = await karma.config.parseConfig(options.karmaConfig && path.resolve(context.workspaceRoot, options.karmaConfig), transforms.karmaOptions ? transforms.karmaOptions(karmaOptions) : karmaOptions, { promiseConfig: true, throwErrors: true });
return [karma, parsedKarmaConfig];
}), (0, rxjs_1.switchMap)(([karma, karmaConfig]) => new rxjs_1.Observable((subscriber) => {
// Pass onto Karma to emit BuildEvents.
karmaConfig.buildWebpack ??= {};
if (typeof karmaConfig.buildWebpack === 'object') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
karmaConfig.buildWebpack.failureCb ??= () => subscriber.next({ success: false });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
karmaConfig.buildWebpack.successCb ??= () => subscriber.next({ success: true });
}
// Complete the observable once the Karma server returns.
const karmaServer = new karma.Server(karmaConfig, (exitCode) => {
subscriber.next({ success: exitCode === 0 });
subscriber.complete();
});
const karmaStart = karmaServer.start();
// Cleanup, signal Karma to exit.
return () => {
void karmaStart.then(() => karmaServer.stop());
};
})), (0, rxjs_1.defaultIfEmpty)({ success: false }));
}
exports.execute = execute;
function getBuiltInKarmaConfig(workspaceRoot, projectName) {
let coverageFolderName = projectName.charAt(0) === '@' ? projectName.slice(1) : projectName;
if (/[A-Z]/.test(coverageFolderName)) {
coverageFolderName = core_1.strings.dasherize(coverageFolderName);
}
const workspaceRootRequire = (0, module_1.createRequire)(workspaceRoot + '/');
// Any changes to the config here need to be synced to: packages/schematics/angular/config/files/karma.conf.js.template
return {
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
'karma-jasmine',
'karma-chrome-launcher',
'karma-jasmine-html-reporter',
'karma-coverage',
'@angular-devkit/build-angular/plugins/karma',
].map((p) => workspaceRootRequire(p)),
client: {
clearContext: false, // leave Jasmine Spec Runner output visible in browser
},
jasmineHtmlReporter: {
suppressAll: true, // removes the duplicated traces
},
coverageReporter: {
dir: path.join(workspaceRoot, 'coverage', coverageFolderName),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress', 'kjhtml'],
browsers: ['Chrome'],
customLaunchers: {
// Chrome configured to run in a bazel sandbox.
// Disable the use of the gpu and `/dev/shm` because it causes Chrome to
// crash on some environments.
// See:
// https://github.com/puppeteer/puppeteer/blob/v1.0.0/docs/troubleshooting.md#tips
// https://stackoverflow.com/questions/50642308/webdriverexception-unknown-error-devtoolsactiveport-file-doesnt-exist-while-t
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: ['--no-sandbox', '--headless', '--disable-gpu', '--disable-dev-shm-usage'],
},
},
restartOnFileChange: true,
};
}
exports.default = (0, architect_1.createBuilder)(execute);
function getBuiltInMainFile() {
const content = Buffer.from(`
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
// Initialize the Angular testing environment.
getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting(), {
errorOnUnknownElements: true,
errorOnUnknownProperties: true
});
`).toString('base64');
return `ng-virtual-main.js!=!data:text/javascript;base64,${content}`;
}

View file

@ -0,0 +1,200 @@
/**
* Karma target options for Build Facade.
*/
export interface Schema {
/**
* List of static application assets.
*/
assets?: AssetPattern[];
/**
* Override which browsers tests are run against. Set to `false` to not use any browser.
*/
browsers?: Browsers;
/**
* Output a code coverage report.
*/
codeCoverage?: boolean;
/**
* Globs to exclude from code coverage.
*/
codeCoverageExclude?: string[];
/**
* Globs of files to exclude, relative to the project root.
*/
exclude?: string[];
/**
* Replace compilation source files with other compilation source files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* Globs of files to include, relative to project root.
* There are 2 special cases:
* - when a path to directory is provided, all spec files ending ".spec.@(ts|tsx)" will be
* included
* - when a path to a file is provided, and a matching spec file exists it will be included
* instead.
*/
include?: string[];
/**
* The stylesheet language to use for the application's inline component styles.
*/
inlineStyleLanguage?: InlineStyleLanguage;
/**
* The name of the Karma configuration file.
*/
karmaConfig?: string;
/**
* The name of the main entry-point file.
*/
main?: string;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* Polyfills to be included in the build.
*/
polyfills?: Polyfills;
/**
* Do not use the real path when resolving modules. If unset then will default to `true` if
* NodeJS option --preserve-symlinks is set.
*/
preserveSymlinks?: boolean;
/**
* Log progress to the console while building.
*/
progress?: boolean;
/**
* Karma reporters to use. Directly passed to the karma runner.
*/
reporters?: string[];
/**
* Global scripts to be included in the build.
*/
scripts?: ScriptElement[];
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
sourceMap?: SourceMapUnion;
/**
* Options to pass to style preprocessors
*/
stylePreprocessorOptions?: StylePreprocessorOptions;
/**
* Global styles to be included in the build.
*/
styles?: StyleElement[];
/**
* The name of the TypeScript configuration file.
*/
tsConfig: string;
/**
* Run build when files change.
*/
watch?: boolean;
/**
* TypeScript configuration for Web Worker modules.
*/
webWorkerTsConfig?: string;
}
export type AssetPattern = AssetPatternClass | string;
export interface AssetPatternClass {
/**
* The pattern to match.
*/
glob: string;
/**
* An array of globs to ignore.
*/
ignore?: string[];
/**
* The input directory path in which to apply 'glob'. Defaults to the project root.
*/
input: string;
/**
* Absolute path within the output.
*/
output: string;
}
/**
* Override which browsers tests are run against. Set to `false` to not use any browser.
*/
export type Browsers = boolean | string;
export interface FileReplacement {
replace?: string;
replaceWith?: string;
src?: string;
with?: string;
}
/**
* The stylesheet language to use for the application's inline component styles.
*/
export declare enum InlineStyleLanguage {
Css = "css",
Less = "less",
Sass = "sass",
Scss = "scss"
}
/**
* Polyfills to be included in the build.
*/
export type Polyfills = string[] | string;
export type ScriptElement = ScriptClass | string;
export interface ScriptClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
export type SourceMapUnion = boolean | SourceMapClass;
export interface SourceMapClass {
/**
* Output source maps for all scripts.
*/
scripts?: boolean;
/**
* Output source maps for all styles.
*/
styles?: boolean;
/**
* Resolve vendor packages source maps.
*/
vendor?: boolean;
}
/**
* Options to pass to style preprocessors
*/
export interface StylePreprocessorOptions {
/**
* Paths to include. Paths will be resolved to workspace root.
*/
includePaths?: string[];
}
export type StyleElement = StyleClass | string;
export interface StyleClass {
/**
* The bundle name for this extra entry point.
*/
bundleName?: string;
/**
* If the bundle will be referenced in the HTML file.
*/
inject?: boolean;
/**
* The file to include.
*/
input: string;
}

View file

@ -0,0 +1,15 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });
exports.InlineStyleLanguage = void 0;
/**
* The stylesheet language to use for the application's inline component styles.
*/
var InlineStyleLanguage;
(function (InlineStyleLanguage) {
InlineStyleLanguage["Css"] = "css";
InlineStyleLanguage["Less"] = "less";
InlineStyleLanguage["Sass"] = "sass";
InlineStyleLanguage["Scss"] = "scss";
})(InlineStyleLanguage || (exports.InlineStyleLanguage = InlineStyleLanguage = {}));

View file

@ -0,0 +1,312 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Karma Target",
"description": "Karma target options for Build Facade.",
"type": "object",
"properties": {
"main": {
"type": "string",
"description": "The name of the main entry-point file."
},
"tsConfig": {
"type": "string",
"description": "The name of the TypeScript configuration file."
},
"karmaConfig": {
"type": "string",
"description": "The name of the Karma configuration file."
},
"polyfills": {
"description": "Polyfills to be included in the build.",
"oneOf": [
{
"type": "array",
"description": "A list of polyfills to include in the build. Can be a full path for a file, relative to the current workspace or module specifier. Example: 'zone.js'.",
"items": {
"type": "string",
"uniqueItems": true
},
"default": []
},
{
"type": "string",
"description": "The full path for the polyfills file, relative to the current workspace or a module specifier. Example: 'zone.js'."
}
]
},
"assets": {
"type": "array",
"description": "List of static application assets.",
"default": [],
"items": {
"$ref": "#/definitions/assetPattern"
}
},
"scripts": {
"description": "Global scripts to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.[cm]?jsx?$"
}
]
}
},
"styles": {
"description": "Global styles to be included in the build.",
"type": "array",
"default": [],
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"input": {
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
},
"bundleName": {
"type": "string",
"pattern": "^[\\w\\-.]*$",
"description": "The bundle name for this extra entry point."
},
"inject": {
"type": "boolean",
"description": "If the bundle will be referenced in the HTML file.",
"default": true
}
},
"additionalProperties": false,
"required": ["input"]
},
{
"type": "string",
"description": "The file to include.",
"pattern": "\\.(?:css|scss|sass|less)$"
}
]
}
},
"inlineStyleLanguage": {
"description": "The stylesheet language to use for the application's inline component styles.",
"type": "string",
"default": "css",
"enum": ["css", "less", "sass", "scss"]
},
"stylePreprocessorOptions": {
"description": "Options to pass to style preprocessors",
"type": "object",
"properties": {
"includePaths": {
"description": "Paths to include. Paths will be resolved to workspace root.",
"type": "array",
"items": {
"type": "string"
},
"default": []
}
},
"additionalProperties": false
},
"include": {
"type": "array",
"items": {
"type": "string"
},
"default": ["**/*.spec.ts"],
"description": "Globs of files to include, relative to project root. \nThere are 2 special cases:\n - when a path to directory is provided, all spec files ending \".spec.@(ts|tsx)\" will be included\n - when a path to a file is provided, and a matching spec file exists it will be included instead."
},
"exclude": {
"type": "array",
"items": {
"type": "string"
},
"default": [],
"description": "Globs of files to exclude, relative to the project root."
},
"sourceMap": {
"description": "Output source maps for scripts and styles. For more information, see https://angular.io/guide/workspace-config#source-map-configuration.",
"default": true,
"oneOf": [
{
"type": "object",
"properties": {
"scripts": {
"type": "boolean",
"description": "Output source maps for all scripts.",
"default": true
},
"styles": {
"type": "boolean",
"description": "Output source maps for all styles.",
"default": true
},
"vendor": {
"type": "boolean",
"description": "Resolve vendor packages source maps.",
"default": false
}
},
"additionalProperties": false
},
{
"type": "boolean"
}
]
},
"progress": {
"type": "boolean",
"description": "Log progress to the console while building.",
"default": true
},
"watch": {
"type": "boolean",
"description": "Run build when files change."
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
},
"preserveSymlinks": {
"type": "boolean",
"description": "Do not use the real path when resolving modules. If unset then will default to `true` if NodeJS option --preserve-symlinks is set."
},
"browsers": {
"description": "Override which browsers tests are run against. Set to `false` to not use any browser.",
"oneOf": [
{
"type": "string",
"description": "A comma seperate list of browsers to run tests against."
},
{
"const": false,
"type": "boolean",
"description": "Does use run tests against a browser."
}
]
},
"codeCoverage": {
"type": "boolean",
"description": "Output a code coverage report.",
"default": false
},
"codeCoverageExclude": {
"type": "array",
"description": "Globs to exclude from code coverage.",
"items": {
"type": "string"
},
"default": []
},
"fileReplacements": {
"description": "Replace compilation source files with other compilation source files in the build.",
"type": "array",
"items": {
"oneOf": [
{
"type": "object",
"properties": {
"src": {
"type": "string"
},
"replaceWith": {
"type": "string"
}
},
"additionalProperties": false,
"required": ["src", "replaceWith"]
},
{
"type": "object",
"properties": {
"replace": {
"type": "string"
},
"with": {
"type": "string"
}
},
"additionalProperties": false,
"required": ["replace", "with"]
}
]
},
"default": []
},
"reporters": {
"type": "array",
"description": "Karma reporters to use. Directly passed to the karma runner.",
"items": {
"type": "string"
}
},
"webWorkerTsConfig": {
"type": "string",
"description": "TypeScript configuration for Web Worker modules."
}
},
"additionalProperties": false,
"required": ["tsConfig"],
"definitions": {
"assetPattern": {
"oneOf": [
{
"type": "object",
"properties": {
"glob": {
"type": "string",
"description": "The pattern to match."
},
"input": {
"type": "string",
"description": "The input directory path in which to apply 'glob'. Defaults to the project root."
},
"output": {
"type": "string",
"description": "Absolute path within the output."
},
"ignore": {
"description": "An array of globs to ignore.",
"type": "array",
"items": {
"type": "string"
}
}
},
"additionalProperties": false,
"required": ["glob", "input", "output"]
},
{
"type": "string"
}
]
}
}
}

View file

@ -0,0 +1,17 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import { Observable } from 'rxjs';
import { Schema as NgPackagrBuilderOptions } from './schema';
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: NgPackagrBuilderOptions, context: BuilderContext): Observable<BuilderOutput>;
export { NgPackagrBuilderOptions };
declare const _default: import("../../../../architect/src/internal").Builder<Record<string, string> & NgPackagrBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;

View file

@ -0,0 +1,66 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const architect_1 = require("@angular-devkit/architect");
const path_1 = require("path");
const rxjs_1 = require("rxjs");
const normalize_cache_1 = require("../../utils/normalize-cache");
const purge_cache_1 = require("../../utils/purge-cache");
/**
* @experimental Direct usage of this function is considered experimental.
*/
function execute(options, context) {
return (0, rxjs_1.from)((async () => {
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
const root = context.workspaceRoot;
const packager = (await Promise.resolve().then(() => __importStar(require('ng-packagr')))).ngPackagr();
packager.forProject((0, path_1.resolve)(root, options.project));
if (options.tsConfig) {
packager.withTsConfig((0, path_1.resolve)(root, options.tsConfig));
}
const projectName = context.target?.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
const metadata = await context.getProjectMetadata(projectName);
const { enabled: cacheEnabled, path: cacheDirectory } = (0, normalize_cache_1.normalizeCacheOptions)(metadata, context.workspaceRoot);
const ngPackagrOptions = {
cacheEnabled,
cacheDirectory: (0, path_1.join)(cacheDirectory, 'ng-packagr'),
};
return { packager, ngPackagrOptions };
})()).pipe((0, rxjs_1.switchMap)(({ packager, ngPackagrOptions }) => options.watch ? packager.watch(ngPackagrOptions) : packager.build(ngPackagrOptions)), (0, rxjs_1.mapTo)({ success: true }), (0, rxjs_1.catchError)((err) => (0, rxjs_1.of)({ success: false, error: err.message })));
}
exports.execute = execute;
exports.default = (0, architect_1.createBuilder)(execute);

View file

@ -0,0 +1,17 @@
/**
* ng-packagr target options for Build Architect. Use to build library projects.
*/
export interface Schema {
/**
* The file path for the ng-packagr configuration file, relative to the current workspace.
*/
project: string;
/**
* The full path for the TypeScript configuration file, relative to the current workspace.
*/
tsConfig?: string;
/**
* Run build when files change.
*/
watch?: boolean;
}

View file

@ -0,0 +1,4 @@
"use strict";
// THIS FILE IS AUTOMATICALLY GENERATED. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
// CORRESPONDING JSON SCHEMA FILE, THEN RUN devkit-admin build (or bazel build ...).
Object.defineProperty(exports, "__esModule", { value: true });

View file

@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "ng-packagr Target",
"description": "ng-packagr target options for Build Architect. Use to build library projects.",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The file path for the ng-packagr configuration file, relative to the current workspace."
},
"tsConfig": {
"type": "string",
"description": "The full path for the TypeScript configuration file, relative to the current workspace."
},
"watch": {
"type": "boolean",
"description": "Run build when files change.",
"default": false
}
},
"additionalProperties": false,
"required": ["project"]
}

View file

@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
import { json } from '@angular-devkit/core';
import { Schema } from './schema';
type PrerenderBuilderOptions = Schema & json.JsonObject;
type PrerenderBuilderOutput = BuilderOutput;
/**
* Builds the browser and server, then renders each route in options.routes
* and writes them to prerender/<route>/index.html for each output path in
* the browser result.
*/
export declare function execute(options: PrerenderBuilderOptions, context: BuilderContext): Promise<PrerenderBuilderOutput>;
declare const _default: import("../../../../architect/src/internal").Builder<Schema & json.JsonObject>;
export default _default;

View file

@ -0,0 +1,221 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.execute = void 0;
const architect_1 = require("@angular-devkit/architect");
const fs = __importStar(require("fs"));
const promises_1 = require("node:fs/promises");
const ora_1 = __importDefault(require("ora"));
const path = __importStar(require("path"));
const piscina_1 = __importDefault(require("piscina"));
const utils_1 = require("../../utils");
const environment_options_1 = require("../../utils/environment-options");
const error_1 = require("../../utils/error");
const service_worker_1 = require("../../utils/service-worker");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
class RoutesSet extends Set {
add(value) {
return super.add(value.charAt(0) === '/' ? value.slice(1) : value);
}
}
async function getRoutes(indexFile, outputPath, serverBundlePath, options, workspaceRoot) {
const { routes: extraRoutes = [], routesFile, discoverRoutes } = options;
const routes = new RoutesSet(extraRoutes);
if (routesFile) {
const routesFromFile = (await (0, promises_1.readFile)(path.join(workspaceRoot, routesFile), 'utf8')).split(/\r?\n/);
for (const route of routesFromFile) {
routes.add(route);
}
}
if (discoverRoutes) {
const renderWorker = new piscina_1.default({
filename: require.resolve('./routes-extractor-worker'),
maxThreads: 1,
workerData: {
indexFile,
outputPath,
serverBundlePath,
zonePackage: require.resolve('zone.js', { paths: [workspaceRoot] }),
},
});
const extractedRoutes = await renderWorker
.run({})
.finally(() => void renderWorker.destroy());
for (const route of extractedRoutes) {
routes.add(route);
}
}
if (routes.size === 0) {
throw new Error('Could not find any routes to prerender.');
}
return [...routes];
}
/**
* Schedules the server and browser builds and returns their results if both builds are successful.
*/
async function _scheduleBuilds(options, context) {
const browserTarget = (0, architect_1.targetFromTargetString)(options.browserTarget);
const serverTarget = (0, architect_1.targetFromTargetString)(options.serverTarget);
const browserTargetRun = await context.scheduleTarget(browserTarget, {
watch: false,
serviceWorker: false,
// todo: handle service worker augmentation
});
if (browserTargetRun.info.builderName === '@angular-devkit/build-angular:application') {
return {
success: false,
error: '"@angular-devkit/build-angular:application" has built-in prerendering capabilities. ' +
'The "prerender" option should be used instead.',
};
}
const serverTargetRun = await context.scheduleTarget(serverTarget, {
watch: false,
});
try {
const [browserResult, serverResult] = await Promise.all([
browserTargetRun.result,
serverTargetRun.result,
]);
const success = browserResult.success && serverResult.success && browserResult.baseOutputPath !== undefined;
const error = browserResult.error || serverResult.error;
return { success, error, browserResult, serverResult };
}
catch (e) {
(0, error_1.assertIsError)(e);
return { success: false, error: e.message };
}
finally {
await Promise.all([browserTargetRun.stop(), serverTargetRun.stop()]);
}
}
/**
* Renders each route and writes them to
* <route>/index.html for each output path in the browser result.
*/
async function _renderUniversal(options, context, browserResult, serverResult, browserOptions) {
const projectName = context.target && context.target.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = path.join(context.workspaceRoot, projectMetadata.root ?? '');
// Users can specify a different base html file e.g. "src/home.html"
const indexFile = (0, webpack_browser_config_1.getIndexOutputFile)(browserOptions.index);
const { styles: normalizedStylesOptimization } = (0, utils_1.normalizeOptimization)(browserOptions.optimization);
const zonePackage = require.resolve('zone.js', { paths: [context.workspaceRoot] });
const { baseOutputPath = '' } = serverResult;
const worker = new piscina_1.default({
filename: path.join(__dirname, 'render-worker.js'),
maxThreads: environment_options_1.maxWorkers,
workerData: { zonePackage },
});
let routes;
try {
// We need to render the routes for each locale from the browser output.
for (const { path: outputPath } of browserResult.outputs) {
const localeDirectory = path.relative(browserResult.baseOutputPath, outputPath);
const serverBundlePath = path.join(baseOutputPath, localeDirectory, 'main.js');
if (!fs.existsSync(serverBundlePath)) {
throw new Error(`Could not find the main bundle: ${serverBundlePath}`);
}
routes ??= await getRoutes(indexFile, outputPath, serverBundlePath, options, context.workspaceRoot);
const spinner = (0, ora_1.default)(`Prerendering ${routes.length} route(s) to ${outputPath}...`).start();
try {
const results = (await Promise.all(routes.map((route) => {
const options = {
indexFile,
deployUrl: browserOptions.deployUrl || '',
inlineCriticalCss: !!normalizedStylesOptimization.inlineCritical,
minifyCss: !!normalizedStylesOptimization.minify,
outputPath,
route,
serverBundlePath,
};
return worker.run(options);
})));
let numErrors = 0;
for (const { errors, warnings } of results) {
spinner.stop();
errors?.forEach((e) => context.logger.error(e));
warnings?.forEach((e) => context.logger.warn(e));
spinner.start();
numErrors += errors?.length ?? 0;
}
if (numErrors > 0) {
throw Error(`Rendering failed with ${numErrors} worker errors.`);
}
}
catch (error) {
spinner.fail(`Prerendering routes to ${outputPath} failed.`);
(0, error_1.assertIsError)(error);
return { success: false, error: error.message };
}
spinner.succeed(`Prerendering routes to ${outputPath} complete.`);
if (browserOptions.serviceWorker) {
spinner.start('Generating service worker...');
try {
await (0, service_worker_1.augmentAppWithServiceWorker)(projectRoot, context.workspaceRoot, outputPath, browserOptions.baseHref || '/', browserOptions.ngswConfigPath);
}
catch (error) {
spinner.fail('Service worker generation failed.');
(0, error_1.assertIsError)(error);
return { success: false, error: error.message };
}
spinner.succeed('Service worker generation complete.');
}
}
}
finally {
void worker.destroy();
}
return browserResult;
}
/**
* Builds the browser and server, then renders each route in options.routes
* and writes them to prerender/<route>/index.html for each output path in
* the browser result.
*/
async function execute(options, context) {
const browserTarget = (0, architect_1.targetFromTargetString)(options.browserTarget);
const browserOptions = (await context.getTargetOptions(browserTarget));
const result = await _scheduleBuilds(options, context);
const { success, error, browserResult, serverResult } = result;
if (!success || !browserResult || !serverResult) {
return { success, error };
}
return _renderUniversal(options, context, browserResult, serverResult, browserOptions);
}
exports.execute = execute;
exports.default = (0, architect_1.createBuilder)(execute);

View file

@ -0,0 +1,30 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export interface RenderOptions {
indexFile: string;
deployUrl: string;
inlineCriticalCss: boolean;
minifyCss: boolean;
outputPath: string;
serverBundlePath: string;
route: string;
}
export interface RenderResult {
errors?: string[];
warnings?: string[];
}
/**
* Renders each route in routes and writes them to <outputPath>/<route>/index.html.
*/
declare function render({ indexFile, deployUrl, minifyCss, outputPath, serverBundlePath, route, inlineCriticalCss, }: RenderOptions): Promise<RenderResult>;
/**
* The default export will be the promise returned by the initialize function.
* This is awaited by piscina prior to using the Worker.
*/
declare const _default: Promise<typeof render>;
export default _default;

View file

@ -0,0 +1,128 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = __importDefault(require("node:assert"));
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const node_worker_threads_1 = require("node:worker_threads");
/**
* The fully resolved path to the zone.js package that will be loaded during worker initialization.
* This is passed as workerData when setting up the worker via the `piscina` package.
*/
const { zonePackage } = node_worker_threads_1.workerData;
/**
* Renders each route in routes and writes them to <outputPath>/<route>/index.html.
*/
async function render({ indexFile, deployUrl, minifyCss, outputPath, serverBundlePath, route, inlineCriticalCss, }) {
const result = {};
const browserIndexOutputPath = path.join(outputPath, indexFile);
const outputFolderPath = path.join(outputPath, route);
const outputIndexPath = path.join(outputFolderPath, 'index.html');
const { ɵSERVER_CONTEXT, AppServerModule, renderModule, renderApplication, default: bootstrapAppFn, } = (await Promise.resolve(`${serverBundlePath}`).then(s => __importStar(require(s))));
(0, node_assert_1.default)(ɵSERVER_CONTEXT, `ɵSERVER_CONTEXT was not exported from: ${serverBundlePath}.`);
const indexBaseName = fs.existsSync(path.join(outputPath, 'index.original.html'))
? 'index.original.html'
: indexFile;
const browserIndexInputPath = path.join(outputPath, indexBaseName);
const document = await fs.promises.readFile(browserIndexInputPath, 'utf8');
const platformProviders = [
{
provide: ɵSERVER_CONTEXT,
useValue: 'ssg',
},
];
let html;
// Render platform server module
if (isBootstrapFn(bootstrapAppFn)) {
(0, node_assert_1.default)(renderApplication, `renderApplication was not exported from: ${serverBundlePath}.`);
html = await renderApplication(bootstrapAppFn, {
document,
url: route,
platformProviders,
});
}
else {
(0, node_assert_1.default)(renderModule, `renderModule was not exported from: ${serverBundlePath}.`);
const moduleClass = bootstrapAppFn || AppServerModule;
(0, node_assert_1.default)(moduleClass, `Neither an AppServerModule nor a bootstrapping function was exported from: ${serverBundlePath}.`);
html = await renderModule(moduleClass, {
document,
url: route,
extraProviders: platformProviders,
});
}
if (inlineCriticalCss) {
const { InlineCriticalCssProcessor } = await Promise.resolve().then(() => __importStar(require('../../utils/index-file/inline-critical-css')));
const inlineCriticalCssProcessor = new InlineCriticalCssProcessor({
deployUrl: deployUrl,
minify: minifyCss,
});
const { content, warnings, errors } = await inlineCriticalCssProcessor.process(html, {
outputPath,
});
result.errors = errors;
result.warnings = warnings;
html = content;
}
// This case happens when we are prerendering "/".
if (browserIndexOutputPath === outputIndexPath) {
const browserIndexOutputPathOriginal = path.join(outputPath, 'index.original.html');
fs.renameSync(browserIndexOutputPath, browserIndexOutputPathOriginal);
}
fs.mkdirSync(outputFolderPath, { recursive: true });
fs.writeFileSync(outputIndexPath, html);
return result;
}
function isBootstrapFn(value) {
// We can differentiate between a module and a bootstrap function by reading compiler-generated `ɵmod` static property:
return typeof value === 'function' && !('ɵmod' in value);
}
/**
* Initializes the worker when it is first created by loading the Zone.js package
* into the worker instance.
*
* @returns A promise resolving to the render function of the worker.
*/
async function initialize() {
// Setup Zone.js
await Promise.resolve(`${zonePackage}`).then(s => __importStar(require(s)));
// Return the render function for use
return render;
}
/**
* The default export will be the promise returned by the initialize function.
* This is awaited by piscina prior to using the Worker.
*/
exports.default = initialize();

View file

@ -0,0 +1,20 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export interface RoutesExtractorWorkerData {
zonePackage: string;
indexFile: string;
outputPath: string;
serverBundlePath: string;
}
declare function extract(): Promise<string[]>;
/**
* The default export will be the promise returned by the initialize function.
* This is awaited by piscina prior to using the Worker.
*/
declare const _default: Promise<typeof extract>;
export default _default;

View file

@ -0,0 +1,70 @@
"use strict";
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = __importDefault(require("node:assert"));
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const node_worker_threads_1 = require("node:worker_threads");
const { zonePackage, serverBundlePath, outputPath, indexFile } = node_worker_threads_1.workerData;
async function extract() {
const { AppServerModule, extractRoutes, default: bootstrapAppFn, } = (await Promise.resolve(`${serverBundlePath}`).then(s => __importStar(require(s))));
const browserIndexInputPath = path.join(outputPath, indexFile);
const document = await fs.promises.readFile(browserIndexInputPath, 'utf8');
const bootstrapAppFnOrModule = bootstrapAppFn || AppServerModule;
(0, node_assert_1.default)(bootstrapAppFnOrModule, `The file "${serverBundlePath}" does not have a default export for an AppServerModule or a bootstrapping function.`);
const routes = [];
for await (const { route, success } of extractRoutes(bootstrapAppFnOrModule, document)) {
if (success) {
routes.push(route);
}
}
return routes;
}
/**
* Initializes the worker when it is first created by loading the Zone.js package
* into the worker instance.
*
* @returns A promise resolving to the extract function of the worker.
*/
async function initialize() {
// Setup Zone.js
await Promise.resolve(`${zonePackage}`).then(s => __importStar(require(s)));
return extract;
}
/**
* The default export will be the promise returned by the initialize function.
* This is awaited by piscina prior to using the Worker.
*/
exports.default = initialize();

Some files were not shown because too many files have changed in this diff Show more