Changing front

This commit is contained in:
2023-01-16 17:49:38 +01:00
parent 0b8a93b256
commit 4fe4be7730
48586 changed files with 4725790 additions and 17464 deletions
@@ -0,0 +1,23 @@
/**
* @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';
}
declare module '@babel/traverse' {
export { default } from '@types/babel__traverse';
}
declare module '@babel/template' {
export { default } from '@types/babel__template';
}
@@ -0,0 +1,34 @@
/**
* @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
*/
declare module 'babel-loader' {
type BabelLoaderCustomizer<T> = (babel: typeof import('@babel/core')) => {
customOptions?(
this: import('webpack').loader.LoaderContext,
loaderOptions: Record<string, unknown>,
loaderArguments: { source: string; map?: unknown },
): Promise<{ custom?: T; loader: Record<string, unknown> }>;
config?(
this: import('webpack').loader.LoaderContext,
configuration: import('@babel/core').PartialConfig,
loaderArguments: { source: string; map?: unknown; customOptions: T },
): import('@babel/core').TransformOptions;
result?(
this: import('webpack').loader.LoaderContext,
result: import('@babel/core').BabelFileResult,
context: {
source: string;
map?: unknown;
customOptions: T;
configuration: import('@babel/core').PartialConfig;
options: import('@babel/core').TransformOptions;
},
): import('@babel/core').BabelFileResult;
};
function custom<T>(customizer: BabelLoaderCustomizer<T>): import('webpack').loader.Loader;
}
@@ -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 { PluginObj } from '@babel/core';
/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
export declare function getKeywords(): Iterable<string>;
/**
* A babel plugin factory function for adjusting classes; primarily with Angular metadata.
* The adjustments include wrapping classes with known safe or no side effects with pure
* annotations to support dead code removal of unused classes. Angular compiler generated
* metadata static fields not required in AOT mode are also elided to better support bundler-
* level treeshaking.
*
* @returns A babel plugin object instance.
*/
export default function (): PluginObj;
@@ -0,0 +1,274 @@
"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.getKeywords = void 0;
const core_1 = require("@babel/core");
const helper_annotate_as_pure_1 = __importDefault(require("@babel/helper-annotate-as-pure"));
/**
* The name of the Typescript decorator helper function created by the TypeScript compiler.
*/
const TSLIB_DECORATE_HELPER_NAME = '__decorate';
/**
* The set of Angular static fields that should always be wrapped.
* These fields may appear to have side effects but are safe to remove if the associated class
* is otherwise unused within the output.
*/
const angularStaticsToWrap = new Set([
'ɵcmp',
'ɵdir',
'ɵfac',
'ɵinj',
'ɵmod',
'ɵpipe',
'ɵprov',
'INJECTOR_KEY',
]);
/**
* An object map of static fields and related value checks for discovery of Angular generated
* JIT related static fields.
*/
const angularStaticsToElide = {
'ctorParameters'(path) {
return path.isFunctionExpression() || path.isArrowFunctionExpression();
},
'decorators'(path) {
return path.isArrayExpression();
},
'propDecorators'(path) {
return path.isObjectExpression();
},
};
/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
function getKeywords() {
return ['class'];
}
exports.getKeywords = getKeywords;
/**
* Determines whether a property and its initializer value can be safely wrapped in a pure
* annotated IIFE. Values that may cause side effects are not considered safe to wrap.
* Wrapping such values may cause runtime errors and/or incorrect runtime behavior.
*
* @param propertyName The name of the property to analyze.
* @param assignmentValue The initializer value that will be assigned to the property.
* @returns If the property can be safely wrapped, then true; otherwise, false.
*/
function canWrapProperty(propertyName, assignmentValue) {
if (angularStaticsToWrap.has(propertyName)) {
return true;
}
const { leadingComments } = assignmentValue.node;
if (leadingComments === null || leadingComments === void 0 ? void 0 : leadingComments.some(
// `@pureOrBreakMyCode` is used by closure and is present in Angular code
({ value }) => value.includes('@__PURE__') ||
value.includes('#__PURE__') ||
value.includes('@pureOrBreakMyCode'))) {
return true;
}
return assignmentValue.isPure();
}
/**
* Analyze the sibling nodes of a class to determine if any downlevel elements should be
* wrapped in a pure annotated IIFE. Also determines if any elements have potential side
* effects.
*
* @param origin The starting NodePath location for analyzing siblings.
* @param classIdentifier The identifier node that represents the name of the class.
* @param allowWrappingDecorators Whether to allow decorators to be wrapped.
* @returns An object containing the results of the analysis.
*/
function analyzeClassSiblings(origin, classIdentifier, allowWrappingDecorators) {
var _a;
const wrapStatementPaths = [];
let hasPotentialSideEffects = false;
for (let i = 1;; ++i) {
const nextStatement = origin.getSibling(+origin.key + i);
if (!nextStatement.isExpressionStatement()) {
break;
}
// Valid sibling statements for class declarations are only assignment expressions
// and TypeScript decorator helper call expressions
const nextExpression = nextStatement.get('expression');
if (nextExpression.isCallExpression()) {
if (!core_1.types.isIdentifier(nextExpression.node.callee) ||
nextExpression.node.callee.name !== TSLIB_DECORATE_HELPER_NAME) {
break;
}
if (allowWrappingDecorators) {
wrapStatementPaths.push(nextStatement);
}
else {
// Statement cannot be safely wrapped which makes wrapping the class unneeded.
// The statement will prevent even a wrapped class from being optimized away.
hasPotentialSideEffects = true;
}
continue;
}
else if (!nextExpression.isAssignmentExpression()) {
break;
}
// Valid assignment expressions should be member access expressions using the class
// name as the object and an identifier as the property for static fields or only
// the class name for decorators.
const left = nextExpression.get('left');
if (left.isIdentifier()) {
if (!left.scope.bindingIdentifierEquals(left.node.name, classIdentifier) ||
!core_1.types.isCallExpression(nextExpression.node.right) ||
!core_1.types.isIdentifier(nextExpression.node.right.callee) ||
nextExpression.node.right.callee.name !== TSLIB_DECORATE_HELPER_NAME) {
break;
}
if (allowWrappingDecorators) {
wrapStatementPaths.push(nextStatement);
}
else {
// Statement cannot be safely wrapped which makes wrapping the class unneeded.
// The statement will prevent even a wrapped class from being optimized away.
hasPotentialSideEffects = true;
}
continue;
}
else if (!left.isMemberExpression() ||
!core_1.types.isIdentifier(left.node.object) ||
!left.scope.bindingIdentifierEquals(left.node.object.name, classIdentifier) ||
!core_1.types.isIdentifier(left.node.property)) {
break;
}
const propertyName = left.node.property.name;
const assignmentValue = nextExpression.get('right');
if ((_a = angularStaticsToElide[propertyName]) === null || _a === void 0 ? void 0 : _a.call(angularStaticsToElide, assignmentValue)) {
nextStatement.remove();
--i;
}
else if (canWrapProperty(propertyName, assignmentValue)) {
wrapStatementPaths.push(nextStatement);
}
else {
// Statement cannot be safely wrapped which makes wrapping the class unneeded.
// The statement will prevent even a wrapped class from being optimized away.
hasPotentialSideEffects = true;
}
}
return { hasPotentialSideEffects, wrapStatementPaths };
}
/**
* The set of classed already visited and analyzed during the plugin's execution.
* This is used to prevent adjusted classes from being repeatedly analyzed which can lead
* to an infinite loop.
*/
const visitedClasses = new WeakSet();
/**
* A babel plugin factory function for adjusting classes; primarily with Angular metadata.
* The adjustments include wrapping classes with known safe or no side effects with pure
* annotations to support dead code removal of unused classes. Angular compiler generated
* metadata static fields not required in AOT mode are also elided to better support bundler-
* level treeshaking.
*
* @returns A babel plugin object instance.
*/
function default_1() {
return {
visitor: {
ClassDeclaration(path, state) {
const { node: classNode, parentPath } = path;
const { wrapDecorators } = state.opts;
if (visitedClasses.has(classNode)) {
return;
}
// Analyze sibling statements for elements of the class that were downleveled
const hasExport = parentPath.isExportNamedDeclaration() || parentPath.isExportDefaultDeclaration();
const origin = hasExport ? parentPath : path;
const { wrapStatementPaths, hasPotentialSideEffects } = analyzeClassSiblings(origin, classNode.id, wrapDecorators);
visitedClasses.add(classNode);
if (hasPotentialSideEffects || wrapStatementPaths.length === 0) {
return;
}
const wrapStatementNodes = [];
for (const statementPath of wrapStatementPaths) {
wrapStatementNodes.push(statementPath.node);
statementPath.remove();
}
// Wrap class and safe static assignments in a pure annotated IIFE
const container = core_1.types.arrowFunctionExpression([], core_1.types.blockStatement([
classNode,
...wrapStatementNodes,
core_1.types.returnStatement(core_1.types.cloneNode(classNode.id)),
]));
const replacementInitializer = core_1.types.callExpression(core_1.types.parenthesizedExpression(container), []);
(0, helper_annotate_as_pure_1.default)(replacementInitializer);
// Replace class with IIFE wrapped class
const declaration = core_1.types.variableDeclaration('let', [
core_1.types.variableDeclarator(core_1.types.cloneNode(classNode.id), replacementInitializer),
]);
if (parentPath.isExportDefaultDeclaration()) {
// When converted to a variable declaration, the default export must be moved
// to a subsequent statement to prevent a JavaScript syntax error.
parentPath.replaceWithMultiple([
declaration,
core_1.types.exportNamedDeclaration(undefined, [
core_1.types.exportSpecifier(core_1.types.cloneNode(classNode.id), core_1.types.identifier('default')),
]),
]);
}
else {
path.replaceWith(declaration);
}
},
ClassExpression(path, state) {
const { node: classNode, parentPath } = path;
const { wrapDecorators } = state.opts;
// Class expressions are used by TypeScript to represent downlevel class/constructor decorators.
// If not wrapping decorators, they do not need to be processed.
if (!wrapDecorators || visitedClasses.has(classNode)) {
return;
}
if (!classNode.id ||
!parentPath.isVariableDeclarator() ||
!core_1.types.isIdentifier(parentPath.node.id) ||
parentPath.node.id.name !== classNode.id.name) {
return;
}
const origin = parentPath.parentPath;
if (!origin.isVariableDeclaration() || origin.node.declarations.length !== 1) {
return;
}
const { wrapStatementPaths, hasPotentialSideEffects } = analyzeClassSiblings(origin, parentPath.node.id, wrapDecorators);
visitedClasses.add(classNode);
if (hasPotentialSideEffects || wrapStatementPaths.length === 0) {
return;
}
const wrapStatementNodes = [];
for (const statementPath of wrapStatementPaths) {
wrapStatementNodes.push(statementPath.node);
statementPath.remove();
}
// Wrap class and safe static assignments in a pure annotated IIFE
const container = core_1.types.arrowFunctionExpression([], core_1.types.blockStatement([
core_1.types.variableDeclaration('let', [
core_1.types.variableDeclarator(core_1.types.cloneNode(classNode.id), classNode),
]),
...wrapStatementNodes,
core_1.types.returnStatement(core_1.types.cloneNode(classNode.id)),
]));
const replacementInitializer = core_1.types.callExpression(core_1.types.parenthesizedExpression(container), []);
(0, helper_annotate_as_pure_1.default)(replacementInitializer);
// Add the wrapped class directly to the variable declaration
parentPath.get('init').replaceWith(replacementInitializer);
},
},
};
}
exports.default = default_1;
@@ -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 { PluginObj } from '@babel/core';
/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
export declare function getKeywords(): Iterable<string>;
/**
* A babel plugin factory function for adjusting TypeScript emitted enums.
*
* @returns A babel plugin object instance.
*/
export default function (): PluginObj;
@@ -0,0 +1,129 @@
"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.getKeywords = void 0;
const core_1 = require("@babel/core");
const helper_annotate_as_pure_1 = __importDefault(require("@babel/helper-annotate-as-pure"));
/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
function getKeywords() {
return ['var'];
}
exports.getKeywords = getKeywords;
/**
* A babel plugin factory function for adjusting TypeScript emitted enums.
*
* @returns A babel plugin object instance.
*/
function default_1() {
return {
visitor: {
VariableDeclaration(path, state) {
const { parentPath, node } = path;
const { loose } = state.opts;
if (node.kind !== 'var' || node.declarations.length !== 1) {
return;
}
const declaration = path.get('declarations')[0];
if (declaration.node.init) {
return;
}
const declarationId = declaration.node.id;
if (!core_1.types.isIdentifier(declarationId)) {
return;
}
const hasExport = parentPath.isExportNamedDeclaration() || parentPath.isExportDefaultDeclaration();
const origin = hasExport ? parentPath : path;
const nextStatement = origin.getSibling(+origin.key + 1);
if (!nextStatement.isExpressionStatement()) {
return;
}
const nextExpression = nextStatement.get('expression');
if (!nextExpression.isCallExpression() || nextExpression.node.arguments.length !== 1) {
return;
}
const enumCallArgument = nextExpression.node.arguments[0];
if (!core_1.types.isLogicalExpression(enumCallArgument, { operator: '||' })) {
return;
}
// Check if identifiers match var declaration
if (!core_1.types.isIdentifier(enumCallArgument.left) ||
!nextExpression.scope.bindingIdentifierEquals(enumCallArgument.left.name, declarationId)) {
return;
}
const enumCallee = nextExpression.get('callee');
if (!enumCallee.isFunctionExpression() || enumCallee.node.params.length !== 1) {
return;
}
const enumCalleeParam = enumCallee.node.params[0];
const isEnumCalleeMatching = core_1.types.isIdentifier(enumCalleeParam) && enumCalleeParam.name === declarationId.name;
// Loose mode rewrites the enum to a shorter but less TypeScript-like form
// Note: We only can apply the `loose` mode transformation if the callee parameter matches
// with the declaration identifier name. This is necessary in case the the declaration id has
// been renamed to avoid collisions, as the loose transform would then break the enum assignments
// which rely on the differently-named callee identifier name.
let enumAssignments;
if (loose && isEnumCalleeMatching) {
enumAssignments = [];
}
// Check if all enum member values are pure.
// If not, leave as-is due to potential side efects
let hasElements = false;
for (const enumStatement of enumCallee.get('body').get('body')) {
if (!enumStatement.isExpressionStatement()) {
return;
}
const enumValueAssignment = enumStatement.get('expression');
if (!enumValueAssignment.isAssignmentExpression() ||
!enumValueAssignment.get('right').isPure()) {
return;
}
hasElements = true;
enumAssignments === null || enumAssignments === void 0 ? void 0 : enumAssignments.push(enumStatement.node);
}
// If there are no enum elements then there is nothing to wrap
if (!hasElements) {
return;
}
// Remove existing enum initializer
const enumInitializer = nextExpression.node;
nextExpression.remove();
// Create IIFE block contents
let blockContents;
if (enumAssignments) {
// Loose mode
blockContents = [
core_1.types.expressionStatement(core_1.types.assignmentExpression('=', core_1.types.cloneNode(declarationId), core_1.types.logicalExpression('||', core_1.types.cloneNode(declarationId), core_1.types.objectExpression([])))),
...enumAssignments,
];
}
else {
blockContents = [core_1.types.expressionStatement(enumInitializer)];
}
// Wrap existing enum initializer in a pure annotated IIFE
const container = core_1.types.arrowFunctionExpression([], core_1.types.blockStatement([
...blockContents,
core_1.types.returnStatement(core_1.types.cloneNode(declarationId)),
]));
const replacementInitializer = core_1.types.callExpression(core_1.types.parenthesizedExpression(container), []);
(0, helper_annotate_as_pure_1.default)(replacementInitializer);
// Add the wrapped enum initializer directly to the variable declaration
declaration.get('init').replaceWith(replacementInitializer);
},
},
};
}
exports.default = default_1;
@@ -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 { PluginObj } from '@babel/core';
/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
export declare function getKeywords(): Iterable<string>;
/**
* A babel plugin factory function for eliding the Angular class metadata function (`ɵsetClassMetadata`).
*
* @returns A babel plugin object instance.
*/
export default function (): PluginObj;
@@ -0,0 +1,68 @@
"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.getKeywords = void 0;
const core_1 = require("@babel/core");
/**
* The name of the Angular class metadata function created by the Angular compiler.
*/
const SET_CLASS_METADATA_NAME = 'ɵsetClassMetadata';
/**
* Provides one or more keywords that if found within the content of a source file indicate
* that this plugin should be used with a source file.
*
* @returns An a string iterable containing one or more keywords.
*/
function getKeywords() {
return [SET_CLASS_METADATA_NAME];
}
exports.getKeywords = getKeywords;
/**
* A babel plugin factory function for eliding the Angular class metadata function (`ɵsetClassMetadata`).
*
* @returns A babel plugin object instance.
*/
function default_1() {
return {
visitor: {
CallExpression(path) {
var _a;
const callee = path.node.callee;
// The function being called must be the metadata function name
let calleeName;
if (core_1.types.isMemberExpression(callee) && core_1.types.isIdentifier(callee.property)) {
calleeName = callee.property.name;
}
else if (core_1.types.isIdentifier(callee)) {
calleeName = callee.name;
}
if (calleeName !== SET_CLASS_METADATA_NAME) {
return;
}
// There must be four arguments that meet the following criteria:
// * First must be an identifier
// * Second must be an array literal
const callArguments = path.node.arguments;
if (callArguments.length !== 4 ||
!core_1.types.isIdentifier(callArguments[0]) ||
!core_1.types.isArrayExpression(callArguments[1])) {
return;
}
// The metadata function is always emitted inside a function expression
if (!((_a = path.getFunctionParent()) === null || _a === void 0 ? void 0 : _a.isFunctionExpression())) {
return;
}
// Replace the metadata function with `void 0` which is the equivalent return value
// of the metadata function.
path.replaceWith(path.scope.buildUndefinedNode());
},
},
};
}
exports.default = default_1;
@@ -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 { PluginObj } from '@babel/core';
/**
* A babel plugin factory function for adding the PURE annotation to top-level new and call expressions.
*
* @returns A babel plugin object instance.
*/
export default function (): PluginObj;
@@ -0,0 +1,90 @@
"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 core_1 = require("@babel/core");
const helper_annotate_as_pure_1 = __importDefault(require("@babel/helper-annotate-as-pure"));
const tslib = __importStar(require("tslib"));
/**
* A cached set of TypeScript helper function names used by the helper name matcher utility function.
*/
const tslibHelpers = new Set(Object.keys(tslib).filter((h) => h.startsWith('__')));
/**
* Determinates whether an identifier name matches one of the TypeScript helper function names.
*
* @param name The identifier name to check.
* @returns True, if the name matches a TypeScript helper name; otherwise, false.
*/
function isTslibHelperName(name) {
const nameParts = name.split('$');
const originalName = nameParts[0];
if (nameParts.length > 2 || (nameParts.length === 2 && isNaN(+nameParts[1]))) {
return false;
}
return tslibHelpers.has(originalName);
}
/**
* A babel plugin factory function for adding the PURE annotation to top-level new and call expressions.
*
* @returns A babel plugin object instance.
*/
function default_1() {
return {
visitor: {
CallExpression(path) {
// If the expression has a function parent, it is not top-level
if (path.getFunctionParent()) {
return;
}
const callee = path.node.callee;
if (core_1.types.isFunctionExpression(callee) && path.node.arguments.length !== 0) {
return;
}
// Do not annotate TypeScript helpers emitted by the TypeScript compiler.
// TypeScript helpers are intended to cause side effects.
if (core_1.types.isIdentifier(callee) && isTslibHelperName(callee.name)) {
return;
}
(0, helper_annotate_as_pure_1.default)(path);
},
NewExpression(path) {
// If the expression has a function parent, it is not top-level
if (!path.getFunctionParent()) {
(0, helper_annotate_as_pure_1.default)(path);
}
},
},
};
}
exports.default = default_1;
@@ -0,0 +1,50 @@
/**
* @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 { ɵParsedTranslation } from '@angular/localize';
import type { makeEs2015TranslatePlugin, makeLocalePlugin } from '@angular/localize/tools';
export declare type DiagnosticReporter = (type: 'error' | 'warning' | 'info', message: string) => void;
/**
* An interface representing the factory functions for the `@angular/localize` translation Babel plugins.
* This must be provided for the ESM imports since dynamic imports are required to be asynchronous and
* Babel presets currently can only be synchronous.
*
*/
export interface I18nPluginCreators {
makeEs2015TranslatePlugin: typeof makeEs2015TranslatePlugin;
makeLocalePlugin: typeof makeLocalePlugin;
}
export interface ApplicationPresetOptions {
i18n?: {
locale: string;
missingTranslationBehavior?: 'error' | 'warning' | 'ignore';
translation?: Record<string, ɵParsedTranslation>;
translationFiles?: string[];
pluginCreators: I18nPluginCreators;
};
angularLinker?: {
shouldLink: boolean;
jitMode: boolean;
linkerPluginCreator: typeof import('@angular/compiler-cli/linker/babel').createEs2015LinkerPlugin;
};
forceAsyncTransformation?: boolean;
instrumentCode?: {
includedBasePath: string;
inputSourceMap: unknown;
};
optimize?: {
looseEnums: boolean;
pureTopLevel: boolean;
wrapDecorators: boolean;
};
supportedBrowsers?: string[];
diagnosticReporter?: DiagnosticReporter;
}
export default function (api: unknown, options: ApplicationPresetOptions): {
presets: any[][];
plugins: any[];
};
@@ -0,0 +1,200 @@
"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 assert_1 = require("assert");
const browserslist_1 = __importDefault(require("browserslist"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
/**
* List of browsers which are affected by a WebKit bug where class field
* initializers might have incorrect variable scopes.
*
* See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033
* See: https://github.com/WebKit/WebKit/commit/e8788a34b3d5f5b4edd7ff6450b80936bff396f2
*/
const safariClassFieldScopeBugBrowsers = new Set((0, browserslist_1.default)([
// Safari <15 is technically not supported via https://angular.io/guide/browser-support,
// but we apply the workaround if forcibly selected.
'Safari <=15',
'iOS <=15',
]));
function createI18nDiagnostics(reporter) {
const diagnostics = new (class {
constructor() {
this.messages = [];
this.hasErrors = false;
}
add(type, message) {
if (type === 'ignore') {
return;
}
this.messages.push({ type, message });
this.hasErrors || (this.hasErrors = type === 'error');
reporter === null || reporter === void 0 ? void 0 : reporter(type, message);
}
error(message) {
this.add('error', message);
}
warn(message) {
this.add('warning', message);
}
merge(other) {
for (const diagnostic of other.messages) {
this.add(diagnostic.type, diagnostic.message);
}
}
formatDiagnostics() {
assert_1.strict.fail('@angular/localize Diagnostics formatDiagnostics should not be called from within babel.');
}
})();
return diagnostics;
}
function createI18nPlugins(locale, translation, missingTranslationBehavior, diagnosticReporter, pluginCreators) {
const diagnostics = createI18nDiagnostics(diagnosticReporter);
const plugins = [];
const { makeEs2015TranslatePlugin, makeLocalePlugin } = pluginCreators;
if (translation) {
plugins.push(makeEs2015TranslatePlugin(diagnostics, translation, {
missingTranslation: missingTranslationBehavior,
}));
}
plugins.push(makeLocalePlugin(locale));
return plugins;
}
function createNgtscLogger(reporter) {
return {
level: 1,
debug(...args) { },
info(...args) {
reporter === null || reporter === void 0 ? void 0 : reporter('info', args.join());
},
warn(...args) {
reporter === null || reporter === void 0 ? void 0 : reporter('warning', args.join());
},
error(...args) {
reporter === null || reporter === void 0 ? void 0 : reporter('error', args.join());
},
};
}
function default_1(api, options) {
var _a, _b;
const presets = [];
const plugins = [];
let needRuntimeTransform = false;
if ((_a = options.angularLinker) === null || _a === void 0 ? void 0 : _a.shouldLink) {
plugins.push(options.angularLinker.linkerPluginCreator({
linkerJitMode: options.angularLinker.jitMode,
// This is a workaround until https://github.com/angular/angular/issues/42769 is fixed.
sourceMapping: false,
logger: createNgtscLogger(options.diagnosticReporter),
fileSystem: {
resolve: path.resolve,
exists: fs.existsSync,
dirname: path.dirname,
relative: path.relative,
readFile: fs.readFileSync,
// Node.JS types don't overlap the Compiler types.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
},
}));
}
// Applications code ES version can be controlled using TypeScript's `target` option.
// However, this doesn't effect libraries and hence we use preset-env to downlevel ES features
// based on the supported browsers in browserslist.
if (options.supportedBrowsers) {
const includePlugins = [];
// If a Safari browser affected by the class field scope bug is selected, we
// downlevel class properties by ensuring the class properties Babel plugin
// is always included- regardless of the preset-env targets.
if (options.supportedBrowsers.some((b) => safariClassFieldScopeBugBrowsers.has(b))) {
includePlugins.push('@babel/plugin-proposal-class-properties', '@babel/plugin-proposal-private-methods');
}
presets.push([
require('@babel/preset-env').default,
{
bugfixes: true,
modules: false,
targets: options.supportedBrowsers,
include: includePlugins,
exclude: ['transform-typeof-symbol'],
},
]);
needRuntimeTransform = true;
}
if (options.i18n) {
const { locale, missingTranslationBehavior, pluginCreators, translation } = options.i18n;
const i18nPlugins = createI18nPlugins(locale, translation, missingTranslationBehavior || 'ignore', options.diagnosticReporter, pluginCreators);
plugins.push(...i18nPlugins);
}
if (options.forceAsyncTransformation) {
// Always transform async/await to support Zone.js
plugins.push(require('@babel/plugin-transform-async-to-generator').default, require('@babel/plugin-proposal-async-generator-functions').default);
needRuntimeTransform = true;
}
if (options.optimize) {
if (options.optimize.pureTopLevel) {
plugins.push(require('../plugins/pure-toplevel-functions').default);
}
plugins.push(require('../plugins/elide-angular-metadata').default, [
require('../plugins/adjust-typescript-enums').default,
{ loose: options.optimize.looseEnums },
], [
require('../plugins/adjust-static-class-members').default,
{ wrapDecorators: options.optimize.wrapDecorators },
]);
}
if (options.instrumentCode) {
plugins.push([
require('babel-plugin-istanbul').default,
{
inputSourceMap: (_b = options.instrumentCode.inputSourceMap) !== null && _b !== void 0 ? _b : false,
cwd: options.instrumentCode.includedBasePath,
},
]);
}
if (needRuntimeTransform) {
// Babel equivalent to TypeScript's `importHelpers` option
plugins.push([
require('@babel/plugin-transform-runtime').default,
{
useESModules: true,
version: require('@babel/runtime/package.json').version,
absoluteRuntime: path.dirname(require.resolve('@babel/runtime/package.json')),
},
]);
}
return { presets, plugins };
}
exports.default = default_1;
@@ -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 { ApplicationPresetOptions } from './presets/application';
interface AngularCustomOptions extends Omit<ApplicationPresetOptions, 'instrumentCode'> {
instrumentCode?: {
/** node_modules and test files are always excluded. */
excludedPaths: Set<String>;
includedBasePath: string;
};
}
export declare type AngularBabelLoaderOptions = AngularCustomOptions & Record<string, unknown>;
export declare function requiresLinking(path: string, source: string): Promise<boolean>;
declare const _default: any;
export default _default;
@@ -0,0 +1,190 @@
"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.requiresLinking = void 0;
const babel_loader_1 = require("babel-loader");
const load_esm_1 = require("../utils/load-esm");
const package_version_1 = require("../utils/package-version");
/**
* Cached instance of the compiler-cli linker's needsLinking function.
*/
let needsLinking;
/**
* Cached instance of the compiler-cli linker's Babel plugin factory function.
*/
let linkerPluginCreator;
/**
* Cached instance of the localize Babel plugins factory functions.
*/
let i18nPluginCreators;
async function requiresLinking(path, source) {
// @angular/core and @angular/compiler will cause false positives
// Also, TypeScript files do not require linking
if (/[\\/]@angular[\\/](?:compiler|core)|\.tsx?$/.test(path)) {
return false;
}
if (!needsLinking) {
// Load ESM `@angular/compiler-cli/linker` 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 linkerModule = await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli/linker');
needsLinking = linkerModule.needsLinking;
}
return needsLinking(path, source);
}
exports.requiresLinking = requiresLinking;
// eslint-disable-next-line max-lines-per-function
exports.default = (0, babel_loader_1.custom)(() => {
const baseOptions = Object.freeze({
babelrc: false,
configFile: false,
compact: false,
cacheCompression: false,
sourceType: 'unambiguous',
inputSourceMap: false,
});
return {
async customOptions(options, { source, map }) {
var _a, _b;
const { i18n, aot, optimize, instrumentCode, supportedBrowsers, ...rawOptions } = options;
// Must process file if plugins are added
let shouldProcess = Array.isArray(rawOptions.plugins) && rawOptions.plugins.length > 0;
const customOptions = {
forceAsyncTransformation: false,
angularLinker: undefined,
i18n: undefined,
instrumentCode: undefined,
supportedBrowsers,
};
// Analyze file for linking
if (await requiresLinking(this.resourcePath, source)) {
// Load ESM `@angular/compiler-cli/linker/babel` 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.
linkerPluginCreator !== null && linkerPluginCreator !== void 0 ? linkerPluginCreator : (linkerPluginCreator = (await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli/linker/babel')).createEs2015LinkerPlugin);
customOptions.angularLinker = {
shouldLink: true,
jitMode: aot !== true,
linkerPluginCreator,
};
shouldProcess = true;
}
// Application code (TS files) will only contain native async if target is ES2017+.
// However, third-party libraries can regardless of the target option.
// APF packages with code in [f]esm2015 directories is downlevelled to ES2015 and
// will not have native async.
customOptions.forceAsyncTransformation =
!/[\\/][_f]?esm2015[\\/]/.test(this.resourcePath) && source.includes('async');
shouldProcess || (shouldProcess = customOptions.forceAsyncTransformation ||
customOptions.supportedBrowsers !== undefined ||
false);
// Analyze for i18n inlining
if (i18n &&
!/[\\/]@angular[\\/](?:compiler|localize)/.test(this.resourcePath) &&
source.includes('$localize')) {
// Load the i18n plugin creators from the new `@angular/localize/tools` entry point.
// This may fail during the transition to ESM due to the entry point not yet existing.
// During the transition, this will always attempt to load the entry point for each file.
// This will only occur during prerelease and will be automatically corrected once the new
// entry point exists.
if (i18nPluginCreators === undefined) {
// 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.
i18nPluginCreators = await (0, load_esm_1.loadEsmModule)('@angular/localize/tools');
}
customOptions.i18n = {
...i18n,
pluginCreators: i18nPluginCreators,
};
// Add translation files as dependencies of the file to support rebuilds
// Except for `@angular/core` which needs locale injection but has no translations
if (customOptions.i18n.translationFiles &&
!/[\\/]@angular[\\/]core/.test(this.resourcePath)) {
for (const file of customOptions.i18n.translationFiles) {
this.addDependency(file);
}
}
shouldProcess = true;
}
if (optimize) {
const angularPackage = /[\\/]node_modules[\\/]@angular[\\/]/.test(this.resourcePath);
customOptions.optimize = {
// Angular packages provide additional tested side effects guarantees and can use
// otherwise unsafe optimizations.
looseEnums: angularPackage,
pureTopLevel: angularPackage,
// JavaScript modules that are marked as side effect free are considered to have
// no decorators that contain non-local effects.
wrapDecorators: !!((_b = (_a = this._module) === null || _a === void 0 ? void 0 : _a.factoryMeta) === null || _b === void 0 ? void 0 : _b.sideEffectFree),
};
shouldProcess = true;
}
if (instrumentCode &&
!instrumentCode.excludedPaths.has(this.resourcePath) &&
!/\.(e2e|spec)\.tsx?$|[\\/]node_modules[\\/]/.test(this.resourcePath) &&
this.resourcePath.startsWith(instrumentCode.includedBasePath)) {
// `babel-plugin-istanbul` has it's own includes but we do the below so that we avoid running the the loader.
customOptions.instrumentCode = {
includedBasePath: instrumentCode.includedBasePath,
inputSourceMap: map,
};
shouldProcess = true;
}
// Add provided loader options to default base options
const loaderOptions = {
...baseOptions,
...rawOptions,
cacheIdentifier: JSON.stringify({
buildAngular: package_version_1.VERSION,
customOptions,
baseOptions,
rawOptions,
}),
};
// Skip babel processing if no actions are needed
if (!shouldProcess) {
// Force the current file to be ignored
loaderOptions.ignore = [() => true];
}
return { custom: customOptions, loader: loaderOptions };
},
config(configuration, { customOptions }) {
var _a;
return {
...configuration.options,
// Using `false` disables babel from attempting to locate sourcemaps or process any inline maps.
// The babel types do not include the false option even though it is valid
// eslint-disable-next-line @typescript-eslint/no-explicit-any
inputSourceMap: (_a = configuration.options.inputSourceMap) !== null && _a !== void 0 ? _a : false,
presets: [
...(configuration.options.presets || []),
[
require('./presets/application').default,
{
...customOptions,
diagnosticReporter: (type, message) => {
switch (type) {
case 'error':
this.emitError(message);
break;
case 'info':
// Webpack does not currently have an informational diagnostic
case 'warning':
this.emitWarning(message);
break;
}
},
},
],
],
};
},
};
});
@@ -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("@angular-devkit/architect/src/internal").Builder<BuildWebpackAppShellSchema & JsonObject>;
export default _default;
@@ -0,0 +1,170 @@
"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 inline_critical_css_1 = require("../../utils/index-file/inline-critical-css");
const service_worker_1 = require("../../utils/service-worker");
const spinner_1 = require("../../utils/spinner");
async function _renderUniversal(options, context, browserResult, serverResult, spinner) {
var _a;
// 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, (_a = projectMetadata.root) !== null && _a !== void 0 ? _a : '');
const { styles } = (0, utils_1.normalizeOptimization)(browserOptions.optimization);
const inlineCriticalCssProcessor = styles.inlineCritical
? new inline_critical_css_1.InlineCriticalCssProcessor({
minify: styles.minify,
deployUrl: browserOptions.deployUrl,
})
: undefined;
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 !== null && baseHref !== void 0 ? 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;
const browserTargetRun = await context.scheduleTarget(browserTarget, {
watch: false,
serviceWorker: false,
optimization: optimization,
});
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 === null || spinner === void 0 ? void 0 : 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);
@@ -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 | undefined;
}
/**
* 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;
@@ -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 __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 { AppServerModule, renderModule, ɵSERVER_CONTEXT } = (await Promise.resolve().then(() => __importStar(require(serverBundlePath))));
(0, node_assert_1.default)(renderModule, `renderModule was not exported from: ${serverBundlePath}.`);
(0, node_assert_1.default)(AppServerModule, `AppServerModule was not exported from: ${serverBundlePath}.`);
(0, node_assert_1.default)(ɵSERVER_CONTEXT, `ɵSERVER_CONTEXT was not exported from: ${serverBundlePath}.`);
// Render platform server module
const html = await renderModule(AppServerModule, {
document,
url,
extraProviders: [
{
provide: ɵSERVER_CONTEXT,
useValue: 'app-shell',
},
],
});
return html;
}
/**
* 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().then(() => __importStar(require(zonePackage)));
// 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();
@@ -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;
}
@@ -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 });
@@ -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"]
}
@@ -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 type { Plugin } from 'esbuild';
import ts from 'typescript';
import { BundleStylesheetOptions } from './stylesheets';
export declare class SourceFileCache extends Map<string, ts.SourceFile> {
readonly modifiedFiles: Set<string>;
readonly babelFileCache: Map<string, Uint8Array>;
readonly typeScriptFileCache: Map<string, Uint8Array>;
invalidate(files: Iterable<string>): void;
}
export interface CompilerPluginOptions {
sourcemap: boolean;
tsconfig: string;
advancedOptimizations?: boolean;
thirdPartySourcemaps?: boolean;
fileReplacements?: Record<string, string>;
sourceFileCache?: SourceFileCache;
}
export declare function createCompilerPlugin(pluginOptions: CompilerPluginOptions, styleOptions: BundleStylesheetOptions): Plugin;
@@ -0,0 +1,520 @@
"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.createCompilerPlugin = exports.SourceFileCache = void 0;
const core_1 = require("@babel/core");
const assert = __importStar(require("node:assert"));
const fs = __importStar(require("node:fs/promises"));
const node_os_1 = require("node:os");
const path = __importStar(require("node:path"));
const node_url_1 = require("node:url");
const typescript_1 = __importDefault(require("typescript"));
const application_1 = __importDefault(require("../../babel/presets/application"));
const webpack_loader_1 = require("../../babel/webpack-loader");
const load_esm_1 = require("../../utils/load-esm");
const profiling_1 = require("./profiling");
const stylesheets_1 = require("./stylesheets");
/**
* Converts TypeScript Diagnostic related information into an esbuild compatible note object.
* Related information is a subset of a full TypeScript Diagnostic and also used for diagnostic
* notes associated with the main Diagnostic.
* @param info The TypeScript diagnostic relative information to convert.
* @param host A TypeScript FormatDiagnosticsHost instance to use during conversion.
* @returns An esbuild diagnostic message as a PartialMessage object
*/
function convertTypeScriptDiagnosticInfo(info, host, textPrefix) {
let text = typescript_1.default.flattenDiagnosticMessageText(info.messageText, host.getNewLine());
if (textPrefix) {
text = textPrefix + text;
}
const note = { text };
if (info.file) {
note.location = {
file: info.file.fileName,
length: info.length,
};
// Calculate the line/column location and extract the full line text that has the diagnostic
if (info.start) {
const { line, character } = typescript_1.default.getLineAndCharacterOfPosition(info.file, info.start);
note.location.line = line + 1;
note.location.column = character;
// The start position for the slice is the first character of the error line
const lineStartPosition = typescript_1.default.getPositionOfLineAndCharacter(info.file, line, 0);
// The end position for the slice is the first character of the next line or the length of
// the entire file if the line is the last line of the file (getPositionOfLineAndCharacter
// will error if a nonexistent line is passed).
const { line: lastLineOfFile } = typescript_1.default.getLineAndCharacterOfPosition(info.file, info.file.text.length - 1);
const lineEndPosition = line < lastLineOfFile
? typescript_1.default.getPositionOfLineAndCharacter(info.file, line + 1, 0)
: info.file.text.length;
note.location.lineText = info.file.text.slice(lineStartPosition, lineEndPosition).trimEnd();
}
}
return note;
}
/**
* Converts a TypeScript Diagnostic message into an esbuild compatible message object.
* @param diagnostic The TypeScript diagnostic to convert.
* @param host A TypeScript FormatDiagnosticsHost instance to use during conversion.
* @returns An esbuild diagnostic message as a PartialMessage object
*/
function convertTypeScriptDiagnostic(diagnostic, host) {
var _a;
let codePrefix = 'TS';
let code = `${diagnostic.code}`;
if (diagnostic.source === 'ngtsc') {
codePrefix = 'NG';
// Remove `-99` Angular prefix from diagnostic code
code = code.slice(3);
}
const message = {
...convertTypeScriptDiagnosticInfo(diagnostic, host, `${codePrefix}${code}: `),
// Store original diagnostic for reference if needed downstream
detail: diagnostic,
};
if ((_a = diagnostic.relatedInformation) === null || _a === void 0 ? void 0 : _a.length) {
message.notes = diagnostic.relatedInformation.map((info) => convertTypeScriptDiagnosticInfo(info, host));
}
return message;
}
const USING_WINDOWS = (0, node_os_1.platform)() === 'win32';
const WINDOWS_SEP_REGEXP = new RegExp(`\\${path.win32.sep}`, 'g');
class SourceFileCache extends Map {
constructor() {
super(...arguments);
this.modifiedFiles = new Set();
this.babelFileCache = new Map();
this.typeScriptFileCache = new Map();
}
invalidate(files) {
this.modifiedFiles.clear();
for (let file of files) {
this.babelFileCache.delete(file);
this.typeScriptFileCache.delete((0, node_url_1.pathToFileURL)(file).href);
// Normalize separators to allow matching TypeScript Host paths
if (USING_WINDOWS) {
file = file.replace(WINDOWS_SEP_REGEXP, path.posix.sep);
}
this.delete(file);
this.modifiedFiles.add(file);
}
}
}
exports.SourceFileCache = SourceFileCache;
// This is a non-watch version of the compiler code from `@ngtools/webpack` augmented for esbuild
// eslint-disable-next-line max-lines-per-function
function createCompilerPlugin(pluginOptions, styleOptions) {
return {
name: 'angular-compiler',
// eslint-disable-next-line max-lines-per-function
async setup(build) {
var _a, _b;
var _c;
let setupWarnings;
// This uses a wrapped dynamic import to load `@angular/compiler-cli` which is ESM.
// Once TypeScript provides support for retaining dynamic imports this workaround can be dropped.
const { GLOBAL_DEFS_FOR_TERSER_WITH_AOT, NgtscProgram, OptimizeFor, readConfiguration } = await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli');
// Temporary deep import for transformer support
const { mergeTransformers, replaceBootstrap, } = require('@ngtools/webpack/src/ivy/transformation');
// Setup defines based on the values provided by the Angular compiler-cli
(_a = (_c = build.initialOptions).define) !== null && _a !== void 0 ? _a : (_c.define = {});
for (const [key, value] of Object.entries(GLOBAL_DEFS_FOR_TERSER_WITH_AOT)) {
if (key in build.initialOptions.define) {
// Skip keys that have been manually provided
continue;
}
if (key === 'ngDevMode') {
// ngDevMode is already set based on the builder's script optimization option
continue;
}
// esbuild requires values to be a string (actual strings need to be quoted).
// In this case, all provided values are booleans.
build.initialOptions.define[key] = value.toString();
}
// The tsconfig is loaded in setup instead of in start to allow the esbuild target build option to be modified.
// esbuild build options can only be modified in setup prior to starting the build.
const { options: compilerOptions, rootNames, errors: configurationDiagnostics, } = (0, profiling_1.profileSync)('NG_READ_CONFIG', () => readConfiguration(pluginOptions.tsconfig, {
noEmitOnError: false,
suppressOutputPathCheck: true,
outDir: undefined,
inlineSources: pluginOptions.sourcemap,
inlineSourceMap: pluginOptions.sourcemap,
sourceMap: false,
mapRoot: undefined,
sourceRoot: undefined,
declaration: false,
declarationMap: false,
allowEmptyCodegenFiles: false,
annotationsAs: 'decorators',
enableResourceInlining: false,
}));
if (compilerOptions.target === undefined || compilerOptions.target < typescript_1.default.ScriptTarget.ES2022) {
// If 'useDefineForClassFields' is already defined in the users project leave the value as is.
// Otherwise fallback to false due to https://github.com/microsoft/TypeScript/issues/45995
// which breaks the deprecated `@Effects` NGRX decorator and potentially other existing code as well.
compilerOptions.target = typescript_1.default.ScriptTarget.ES2022;
(_b = compilerOptions.useDefineForClassFields) !== null && _b !== void 0 ? _b : (compilerOptions.useDefineForClassFields = false);
(setupWarnings !== null && setupWarnings !== void 0 ? setupWarnings : (setupWarnings = [])).push({
text: 'TypeScript compiler options "target" and "useDefineForClassFields" are set to "ES2022" and ' +
'"false" respectively by the Angular CLI.',
location: { file: pluginOptions.tsconfig },
notes: [
{
text: 'To control ECMA version and features use the Browerslist configuration. ' +
'For more information, see https://angular.io/guide/build#configuring-browser-compatibility',
},
],
});
}
// The file emitter created during `onStart` that will be used during the build in `onLoad` callbacks for TS files
let fileEmitter;
// The stylesheet resources from component stylesheets that will be added to the build results output files
let stylesheetResourceFiles;
let previousBuilder;
let previousAngularProgram;
const babelDataCache = new Map();
const diagnosticCache = new WeakMap();
build.onStart(async () => {
const result = {
warnings: setupWarnings,
};
// Reset the setup warnings so that they are only shown during the first build.
setupWarnings = undefined;
// Reset debug performance tracking
(0, profiling_1.resetCumulativeDurations)();
// Reset stylesheet resource output files
stylesheetResourceFiles = [];
// Create TypeScript compiler host
const host = typescript_1.default.createIncrementalCompilerHost(compilerOptions);
// Temporarily process external resources via readResource.
// The AOT compiler currently requires this hook to allow for a transformResource hook.
// Once the AOT compiler allows only a transformResource hook, this can be reevaluated.
host.readResource = async function (fileName) {
var _a, _b, _c;
// Template resources (.html/.svg) files are not bundled or transformed
if (fileName.endsWith('.html') || fileName.endsWith('.svg')) {
return (_a = this.readFile(fileName)) !== null && _a !== void 0 ? _a : '';
}
const { contents, resourceFiles, errors, warnings } = await (0, stylesheets_1.bundleStylesheetFile)(fileName, styleOptions);
((_b = result.errors) !== null && _b !== void 0 ? _b : (result.errors = [])).push(...errors);
((_c = result.warnings) !== null && _c !== void 0 ? _c : (result.warnings = [])).push(...warnings);
stylesheetResourceFiles.push(...resourceFiles);
return contents;
};
// Add an AOT compiler resource transform hook
host.transformResource = async function (data, context) {
var _a, _b, _c;
// Only inline style resources are transformed separately currently
if (context.resourceFile || context.type !== 'style') {
return null;
}
// The file with the resource content will either be an actual file (resourceFile)
// or the file containing the inline component style text (containingFile).
const file = (_a = context.resourceFile) !== null && _a !== void 0 ? _a : context.containingFile;
const { contents, resourceFiles, errors, warnings } = await (0, stylesheets_1.bundleStylesheetText)(data, {
resolvePath: path.dirname(file),
virtualName: file,
}, styleOptions);
((_b = result.errors) !== null && _b !== void 0 ? _b : (result.errors = [])).push(...errors);
((_c = result.warnings) !== null && _c !== void 0 ? _c : (result.warnings = [])).push(...warnings);
stylesheetResourceFiles.push(...resourceFiles);
return { content: contents };
};
// Temporary deep import for host augmentation support
const { augmentHostWithCaching, augmentHostWithReplacements, augmentProgramWithVersioning, } = require('@ngtools/webpack/src/ivy/host');
// Augment TypeScript Host for file replacements option
if (pluginOptions.fileReplacements) {
augmentHostWithReplacements(host, pluginOptions.fileReplacements);
}
// Augment TypeScript Host with source file caching if provided
if (pluginOptions.sourceFileCache) {
augmentHostWithCaching(host, pluginOptions.sourceFileCache);
// Allow the AOT compiler to request the set of changed templates and styles
host.getModifiedResourceFiles = function () {
var _a;
return (_a = pluginOptions.sourceFileCache) === null || _a === void 0 ? void 0 : _a.modifiedFiles;
};
}
// Create the Angular specific program that contains the Angular compiler
const angularProgram = (0, profiling_1.profileSync)('NG_CREATE_PROGRAM', () => new NgtscProgram(rootNames, compilerOptions, host, previousAngularProgram));
previousAngularProgram = angularProgram;
const angularCompiler = angularProgram.compiler;
const typeScriptProgram = angularProgram.getTsProgram();
augmentProgramWithVersioning(typeScriptProgram);
const builder = typescript_1.default.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, previousBuilder, configurationDiagnostics);
previousBuilder = builder;
await (0, profiling_1.profileAsync)('NG_ANALYZE_PROGRAM', () => angularCompiler.analyzeAsync());
const affectedFiles = (0, profiling_1.profileSync)('NG_FIND_AFFECTED', () => findAffectedFiles(builder, angularCompiler));
if (pluginOptions.sourceFileCache) {
for (const affected of affectedFiles) {
pluginOptions.sourceFileCache.typeScriptFileCache.delete((0, node_url_1.pathToFileURL)(affected.fileName).href);
}
}
function* collectDiagnostics() {
// Collect program level diagnostics
yield* builder.getConfigFileParsingDiagnostics();
yield* angularCompiler.getOptionDiagnostics();
yield* builder.getOptionsDiagnostics();
yield* builder.getGlobalDiagnostics();
// Collect source file specific diagnostics
const optimizeFor = affectedFiles.size > 1 ? OptimizeFor.WholeProgram : OptimizeFor.SingleFile;
for (const sourceFile of builder.getSourceFiles()) {
if (angularCompiler.ignoreForDiagnostics.has(sourceFile)) {
continue;
}
// TypeScript will use cached diagnostics for files that have not been
// changed or affected for this build when using incremental building.
yield* (0, profiling_1.profileSync)('NG_DIAGNOSTICS_SYNTACTIC', () => builder.getSyntacticDiagnostics(sourceFile), true);
yield* (0, profiling_1.profileSync)('NG_DIAGNOSTICS_SEMANTIC', () => builder.getSemanticDiagnostics(sourceFile), true);
// Declaration files cannot have template diagnostics
if (sourceFile.isDeclarationFile) {
continue;
}
// Only request Angular template diagnostics for affected files to avoid
// overhead of template diagnostics for unchanged files.
if (affectedFiles.has(sourceFile)) {
const angularDiagnostics = (0, profiling_1.profileSync)('NG_DIAGNOSTICS_TEMPLATE', () => angularCompiler.getDiagnosticsForFile(sourceFile, optimizeFor), true);
diagnosticCache.set(sourceFile, angularDiagnostics);
yield* angularDiagnostics;
}
else {
const angularDiagnostics = diagnosticCache.get(sourceFile);
if (angularDiagnostics) {
yield* angularDiagnostics;
}
}
}
}
(0, profiling_1.profileSync)('NG_DIAGNOSTICS_TOTAL', () => {
var _a, _b;
for (const diagnostic of collectDiagnostics()) {
const message = convertTypeScriptDiagnostic(diagnostic, host);
if (diagnostic.category === typescript_1.default.DiagnosticCategory.Error) {
((_a = result.errors) !== null && _a !== void 0 ? _a : (result.errors = [])).push(message);
}
else {
((_b = result.warnings) !== null && _b !== void 0 ? _b : (result.warnings = [])).push(message);
}
}
});
fileEmitter = createFileEmitter(builder, mergeTransformers(angularCompiler.prepareEmit().transformers, {
before: [replaceBootstrap(() => builder.getProgram().getTypeChecker())],
}), (sourceFile) => angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile));
return result;
});
build.onLoad({ filter: compilerOptions.allowJs ? /\.[cm]?[jt]sx?$/ : /\.[cm]?tsx?$/ }, (args) => (0, profiling_1.profileAsync)('NG_EMIT_TS*', async () => {
var _a, _b, _c, _d, _e, _f;
assert.ok(fileEmitter, 'Invalid plugin execution order');
const request = (_b = (_a = pluginOptions.fileReplacements) === null || _a === void 0 ? void 0 : _a[args.path]) !== null && _b !== void 0 ? _b : args.path;
// The filename is currently used as a cache key. Since the cache is memory only,
// the options cannot change and do not need to be represented in the key. If the
// cache is later stored to disk, then the options that affect transform output
// would need to be added to the key as well as a check for any change of content.
let contents = (_c = pluginOptions.sourceFileCache) === null || _c === void 0 ? void 0 : _c.typeScriptFileCache.get((0, node_url_1.pathToFileURL)(request).href);
if (contents === undefined) {
const typescriptResult = await fileEmitter(request);
if (!typescriptResult) {
// No TS result indicates the file is not part of the TypeScript program.
// If allowJs is enabled and the file is JS then defer to the next load hook.
if (compilerOptions.allowJs && /\.[cm]?js$/.test(request)) {
return undefined;
}
// Otherwise return an error
return {
errors: [
createMissingFileError(request, args.path, (_d = build.initialOptions.absWorkingDir) !== null && _d !== void 0 ? _d : ''),
],
};
}
const data = (_e = typescriptResult.content) !== null && _e !== void 0 ? _e : '';
// The pre-transformed data is used as a cache key. Since the cache is memory only,
// the options cannot change and do not need to be represented in the key. If the
// cache is later stored to disk, then the options that affect transform output
// would need to be added to the key as well.
contents = babelDataCache.get(data);
if (contents === undefined) {
const transformedData = await transformWithBabel(request, data, pluginOptions);
contents = Buffer.from(transformedData, 'utf-8');
babelDataCache.set(data, contents);
}
(_f = pluginOptions.sourceFileCache) === null || _f === void 0 ? void 0 : _f.typeScriptFileCache.set((0, node_url_1.pathToFileURL)(request).href, contents);
}
return {
contents,
loader: 'js',
};
}, true));
build.onLoad({ filter: /\.[cm]?js$/ }, (args) => (0, profiling_1.profileAsync)('NG_EMIT_JS*', async () => {
var _a, _b;
// The filename is currently used as a cache key. Since the cache is memory only,
// the options cannot change and do not need to be represented in the key. If the
// cache is later stored to disk, then the options that affect transform output
// would need to be added to the key as well as a check for any change of content.
let contents = (_a = pluginOptions.sourceFileCache) === null || _a === void 0 ? void 0 : _a.babelFileCache.get(args.path);
if (contents === undefined) {
const data = await fs.readFile(args.path, 'utf-8');
const transformedData = await transformWithBabel(args.path, data, pluginOptions);
contents = Buffer.from(transformedData, 'utf-8');
(_b = pluginOptions.sourceFileCache) === null || _b === void 0 ? void 0 : _b.babelFileCache.set(args.path, contents);
}
return {
contents,
loader: 'js',
};
}, true));
build.onEnd((result) => {
var _a;
if (stylesheetResourceFiles.length) {
(_a = result.outputFiles) === null || _a === void 0 ? void 0 : _a.push(...stylesheetResourceFiles);
}
(0, profiling_1.logCumulativeDurations)();
});
},
};
}
exports.createCompilerPlugin = createCompilerPlugin;
function createFileEmitter(program, transformers = {}, onAfterEmit) {
return async (file) => {
const sourceFile = program.getSourceFile(file);
if (!sourceFile) {
return undefined;
}
let content;
program.emit(sourceFile, (filename, data) => {
if (/\.[cm]?js$/.test(filename)) {
content = data;
}
}, undefined /* cancellationToken */, undefined /* emitOnlyDtsFiles */, transformers);
onAfterEmit === null || onAfterEmit === void 0 ? void 0 : onAfterEmit(sourceFile);
return { content, dependencies: [] };
};
}
async function transformWithBabel(filename, data, pluginOptions) {
var _a;
const forceAsyncTransformation = !/[\\/][_f]?esm2015[\\/]/.test(filename) && /async\s+function\s*\*/.test(data);
const shouldLink = await (0, webpack_loader_1.requiresLinking)(filename, data);
const useInputSourcemap = pluginOptions.sourcemap &&
(!!pluginOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
// If no additional transformations are needed, return the data directly
if (!forceAsyncTransformation && !pluginOptions.advancedOptimizations && !shouldLink) {
// Strip sourcemaps if they should not be used
return useInputSourcemap ? data : data.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '');
}
const angularPackage = /[\\/]node_modules[\\/]@angular[\\/]/.test(filename);
const linkerPluginCreator = shouldLink
? (await (0, load_esm_1.loadEsmModule)('@angular/compiler-cli/linker/babel')).createEs2015LinkerPlugin
: undefined;
const result = await (0, core_1.transformAsync)(data, {
filename,
inputSourceMap: (useInputSourcemap ? undefined : false),
sourceMaps: pluginOptions.sourcemap ? 'inline' : false,
compact: false,
configFile: false,
babelrc: false,
browserslistConfigFile: false,
plugins: [],
presets: [
[
application_1.default,
{
angularLinker: {
shouldLink,
jitMode: false,
linkerPluginCreator,
},
forceAsyncTransformation,
optimize: pluginOptions.advancedOptimizations && {
looseEnums: angularPackage,
pureTopLevel: angularPackage,
},
},
],
],
});
return (_a = result === null || result === void 0 ? void 0 : result.code) !== null && _a !== void 0 ? _a : data;
}
function findAffectedFiles(builder, { ignoreForDiagnostics, ignoreForEmit, incrementalCompilation }) {
const affectedFiles = new Set();
// eslint-disable-next-line no-constant-condition
while (true) {
const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
// If the affected file is a TTC shim, add the shim's original source file.
// This ensures that changes that affect TTC are typechecked even when the changes
// are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
// For example, changing @Input property types of a directive used in another component's
// template.
// A TTC shim is a file that has been ignored for diagnostics and has a filename ending in `.ngtypecheck.ts`.
if (ignoreForDiagnostics.has(sourceFile) && sourceFile.fileName.endsWith('.ngtypecheck.ts')) {
// This file name conversion relies on internal compiler logic and should be converted
// to an official method when available. 15 is length of `.ngtypecheck.ts`
const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
const originalSourceFile = builder.getSourceFile(originalFilename);
if (originalSourceFile) {
affectedFiles.add(originalSourceFile);
}
return true;
}
return false;
});
if (!result) {
break;
}
affectedFiles.add(result.affected);
}
// A file is also affected if the Angular compiler requires it to be emitted
for (const sourceFile of builder.getSourceFiles()) {
if (ignoreForEmit.has(sourceFile) || incrementalCompilation.safeToSkipEmit(sourceFile)) {
continue;
}
affectedFiles.add(sourceFile);
}
return affectedFiles;
}
function createMissingFileError(request, original, root) {
const error = {
text: `File '${path.relative(root, request)}' is missing from the TypeScript compilation.`,
notes: [
{
text: `Ensure the file is part of the TypeScript program via the 'files' or 'include' property.`,
},
],
};
if (request !== original) {
error.notes.push({
text: `File is requested from a file replacement of '${path.relative(root, original)}'.`,
});
}
return error;
}
@@ -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 { Plugin } from 'esbuild';
/**
* Creates an esbuild {@link Plugin} that loads all CSS url token references using the
* built-in esbuild `file` loader. A plugin is used to allow for all file extensions
* and types to be supported without needing to manually specify all extensions
* within the build configuration.
*
* @returns An esbuild {@link Plugin} instance.
*/
export declare function createCssResourcePlugin(): Plugin;
@@ -0,0 +1,67 @@
"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.createCssResourcePlugin = void 0;
const promises_1 = require("fs/promises");
/**
* Symbol marker used to indicate CSS resource resolution is being attempted.
* This is used to prevent an infinite loop within the plugin's resolve hook.
*/
const CSS_RESOURCE_RESOLUTION = Symbol('CSS_RESOURCE_RESOLUTION');
/**
* Creates an esbuild {@link Plugin} that loads all CSS url token references using the
* built-in esbuild `file` loader. A plugin is used to allow for all file extensions
* and types to be supported without needing to manually specify all extensions
* within the build configuration.
*
* @returns An esbuild {@link Plugin} instance.
*/
function createCssResourcePlugin() {
return {
name: 'angular-css-resource',
setup(build) {
build.onResolve({ filter: /.*/ }, async (args) => {
var _a;
// Only attempt to resolve url tokens which only exist inside CSS.
// Also, skip this plugin if already attempting to resolve the url-token.
if (args.kind !== 'url-token' || ((_a = args.pluginData) === null || _a === void 0 ? void 0 : _a[CSS_RESOURCE_RESOLUTION])) {
return null;
}
// If root-relative, absolute or protocol relative url, mark as external to leave the
// path/URL in place.
if (/^((?:\w+:)?\/\/|data:|chrome:|#|\/)/.test(args.path)) {
return {
path: args.path,
external: true,
};
}
const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
pluginData[CSS_RESOURCE_RESOLUTION] = true;
const result = await build.resolve(args.path, {
importer,
kind,
namespace,
pluginData,
resolveDir,
});
return {
...result,
namespace: 'css-resource',
};
});
build.onLoad({ filter: /.*/, namespace: 'css-resource' }, async (args) => {
return {
contents: await (0, promises_1.readFile)(args.path),
loader: 'file',
};
});
},
};
}
exports.createCssResourcePlugin = createCssResourcePlugin;
@@ -0,0 +1,37 @@
/**
* @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 { BuildFailure, BuildInvalidate, BuildOptions, BuildResult, Message, OutputFile } from 'esbuild';
import { FileInfo } from '../../utils/index-file/augment-index-html';
/**
* Determines if an unknown value is an esbuild BuildFailure error object thrown by esbuild.
* @param value A potential esbuild BuildFailure error object.
* @returns `true` if the object is determined to be a BuildFailure object; otherwise, `false`.
*/
export declare function isEsBuildFailure(value: unknown): value is BuildFailure;
/**
* Executes the esbuild build function and normalizes the build result in the event of a
* build failure that results in no output being generated.
* All builds use the `write` option with a value of `false` to allow for the output files
* build result array to be populated.
*
* @param optionsOrInvalidate The esbuild options object to use when building or the invalidate object
* returned from an incremental build to perform an additional incremental build.
* @returns If output files are generated, the full esbuild BuildResult; if not, the
* warnings and errors for the attempted build.
*/
export declare function bundle(workspaceRoot: string, optionsOrInvalidate: BuildOptions | BuildInvalidate): Promise<(BuildResult & {
outputFiles: OutputFile[];
initialFiles: FileInfo[];
}) | (BuildFailure & {
outputFiles?: never;
})>;
export declare function logMessages(context: BuilderContext, { errors, warnings }: {
errors: Message[];
warnings: Message[];
}): Promise<void>;
@@ -0,0 +1,86 @@
"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.logMessages = exports.bundle = exports.isEsBuildFailure = void 0;
const esbuild_1 = require("esbuild");
const node_path_1 = require("node:path");
/**
* Determines if an unknown value is an esbuild BuildFailure error object thrown by esbuild.
* @param value A potential esbuild BuildFailure error object.
* @returns `true` if the object is determined to be a BuildFailure object; otherwise, `false`.
*/
function isEsBuildFailure(value) {
return !!value && typeof value === 'object' && 'errors' in value && 'warnings' in value;
}
exports.isEsBuildFailure = isEsBuildFailure;
/**
* Executes the esbuild build function and normalizes the build result in the event of a
* build failure that results in no output being generated.
* All builds use the `write` option with a value of `false` to allow for the output files
* build result array to be populated.
*
* @param optionsOrInvalidate The esbuild options object to use when building or the invalidate object
* returned from an incremental build to perform an additional incremental build.
* @returns If output files are generated, the full esbuild BuildResult; if not, the
* warnings and errors for the attempted build.
*/
async function bundle(workspaceRoot, optionsOrInvalidate) {
var _a, _b;
let result;
try {
if (typeof optionsOrInvalidate === 'function') {
result = (await optionsOrInvalidate());
}
else {
result = await (0, esbuild_1.build)({
...optionsOrInvalidate,
metafile: true,
write: false,
});
}
}
catch (failure) {
// Build failures will throw an exception which contains errors/warnings
if (isEsBuildFailure(failure)) {
return failure;
}
else {
throw failure;
}
}
const initialFiles = [];
for (const outputFile of result.outputFiles) {
// Entries in the metafile are relative to the `absWorkingDir` option which is set to the workspaceRoot
const relativeFilePath = (0, node_path_1.relative)(workspaceRoot, outputFile.path);
const entryPoint = (_b = (_a = result.metafile) === null || _a === void 0 ? void 0 : _a.outputs[relativeFilePath]) === null || _b === void 0 ? void 0 : _b.entryPoint;
outputFile.path = relativeFilePath;
if (entryPoint) {
// An entryPoint value indicates an initial file
initialFiles.push({
file: outputFile.path,
// The first part of the filename is the name of file (e.g., "polyfills" for "polyfills.7S5G3MDY.js")
name: (0, node_path_1.basename)(outputFile.path).split('.')[0],
extension: (0, node_path_1.extname)(outputFile.path),
});
}
}
return { ...result, initialFiles };
}
exports.bundle = bundle;
async function logMessages(context, { errors, warnings }) {
if (warnings.length) {
const warningMessages = await (0, esbuild_1.formatMessages)(warnings, { kind: 'warning', color: true });
context.logger.warn(warningMessages.join('\n'));
}
if (errors.length) {
const errorMessages = await (0, esbuild_1.formatMessages)(errors, { kind: 'error', color: true });
context.logger.error(errorMessages.join('\n'));
}
}
exports.logMessages = logMessages;
@@ -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 '../browser/schema';
export declare function logExperimentalWarnings(options: BrowserBuilderOptions, context: BuilderContext): void;
@@ -0,0 +1,58 @@
"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.logExperimentalWarnings = void 0;
const UNSUPPORTED_OPTIONS = [
'allowedCommonJsDependencies',
'budgets',
'extractLicenses',
'progress',
'scripts',
'statsJson',
// * i18n support
'localize',
// The following two have no effect when localize is not enabled
// 'i18nDuplicateTranslation',
// 'i18nMissingTranslation',
// * Stylesheet preprocessor support
'inlineStyleLanguage',
// The following option has no effect until preprocessors are supported
// 'stylePreprocessorOptions',
// * Deprecated
'deployUrl',
// * Always enabled with esbuild
// 'commonChunk',
// * Currently unsupported by esbuild
'namedChunks',
'vendorChunk',
'webWorkerTsConfig',
];
function logExperimentalWarnings(options, context) {
// Warn about experimental status of this builder
context.logger.warn(`The esbuild browser application builder ('browser-esbuild') is currently experimental.`);
// Validate supported options
// Currently only a subset of the Webpack-based browser builder options are supported.
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 === 'inlineStyleLanguage' && value === 'css') {
continue;
}
context.logger.warn(`The '${unsupportedOption}' option is currently unsupported by this experimental builder and will be ignored.`);
}
}
exports.logExperimentalWarnings = logExperimentalWarnings;
@@ -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 { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
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 initialOptions 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(initialOptions: BrowserBuilderOptions, context: BuilderContext): AsyncIterable<BuilderOutput>;
declare const _default: import("@angular-devkit/architect/src/internal").Builder<BrowserBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;
@@ -0,0 +1,417 @@
"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.buildEsbuildBrowser = void 0;
const architect_1 = require("@angular-devkit/architect");
const node_assert_1 = __importDefault(require("node:assert"));
const fs = __importStar(require("node:fs/promises"));
const path = __importStar(require("node:path"));
const utils_1 = require("../../utils");
const copy_assets_1 = require("../../utils/copy-assets");
const error_1 = require("../../utils/error");
const esbuild_targets_1 = require("../../utils/esbuild-targets");
const index_html_generator_1 = require("../../utils/index-file/index-html-generator");
const service_worker_1 = require("../../utils/service-worker");
const supported_browsers_1 = require("../../utils/supported-browsers");
const compiler_plugin_1 = require("./compiler-plugin");
const esbuild_1 = require("./esbuild");
const experimental_warnings_1 = require("./experimental-warnings");
const options_1 = require("./options");
const sass_plugin_1 = require("./sass-plugin");
const stylesheets_1 = require("./stylesheets");
const watcher_1 = require("./watcher");
/**
* Represents the result of a single builder execute call.
*/
class ExecutionResult {
constructor(success, codeRebuild, globalStylesRebuild, codeBundleCache) {
this.success = success;
this.codeRebuild = codeRebuild;
this.globalStylesRebuild = globalStylesRebuild;
this.codeBundleCache = codeBundleCache;
}
get output() {
return {
success: this.success,
};
}
createRebuildState(fileChanges) {
var _a;
(_a = this.codeBundleCache) === null || _a === void 0 ? void 0 : _a.invalidate([...fileChanges.modified, ...fileChanges.removed]);
return {
codeRebuild: this.codeRebuild,
globalStylesRebuild: this.globalStylesRebuild,
codeBundleCache: this.codeBundleCache,
fileChanges,
};
}
dispose() {
var _a;
(_a = this.codeRebuild) === null || _a === void 0 ? void 0 : _a.dispose();
}
}
async function execute(options, context, rebuildState) {
var _a, _b, _c, _d;
const startTime = process.hrtime.bigint();
const { projectRoot, workspaceRoot, optimizationOptions, outputPath, assets, serviceWorkerOptions, indexHtmlOptions, } = options;
const target = (0, esbuild_targets_1.transformSupportedBrowsersToTargets)((0, supported_browsers_1.getSupportedBrowsers)(projectRoot, context.logger));
const codeBundleCache = options.watch
? (_a = rebuildState === null || rebuildState === void 0 ? void 0 : rebuildState.codeBundleCache) !== null && _a !== void 0 ? _a : new compiler_plugin_1.SourceFileCache()
: undefined;
const [codeResults, styleResults] = await Promise.all([
// Execute esbuild to bundle the application code
(0, esbuild_1.bundle)(workspaceRoot, (_b = rebuildState === null || rebuildState === void 0 ? void 0 : rebuildState.codeRebuild) !== null && _b !== void 0 ? _b : createCodeBundleOptions(options, target, codeBundleCache)),
// Execute esbuild to bundle the global stylesheets
(0, esbuild_1.bundle)(workspaceRoot, (_c = rebuildState === null || rebuildState === void 0 ? void 0 : rebuildState.globalStylesRebuild) !== null && _c !== void 0 ? _c : createGlobalStylesBundleOptions(options, target)),
]);
// Log all warnings and errors generated during bundling
await (0, esbuild_1.logMessages)(context, {
errors: [...codeResults.errors, ...styleResults.errors],
warnings: [...codeResults.warnings, ...styleResults.warnings],
});
// Return if the bundling failed to generate output files or there are errors
if (!codeResults.outputFiles || codeResults.errors.length) {
return new ExecutionResult(false, rebuildState === null || rebuildState === void 0 ? void 0 : rebuildState.codeRebuild, (_d = (styleResults.outputFiles && styleResults.rebuild)) !== null && _d !== void 0 ? _d : rebuildState === null || rebuildState === void 0 ? void 0 : rebuildState.globalStylesRebuild, codeBundleCache);
}
// Return if the global stylesheet bundling has errors
if (!styleResults.outputFiles || styleResults.errors.length) {
return new ExecutionResult(false, codeResults.rebuild, rebuildState === null || rebuildState === void 0 ? void 0 : rebuildState.globalStylesRebuild, codeBundleCache);
}
// Filter global stylesheet initial files
styleResults.initialFiles = styleResults.initialFiles.filter(({ name }) => { var _a; return (_a = options.globalStyles.find((style) => style.name === name)) === null || _a === void 0 ? void 0 : _a.initial; });
// Combine the bundling output files
const initialFiles = [...codeResults.initialFiles, ...styleResults.initialFiles];
const outputFiles = [...codeResults.outputFiles, ...styleResults.outputFiles];
// Generate index HTML file
if (indexHtmlOptions) {
// Create an index HTML generator that reads from the in-memory output files
const indexHtmlGenerator = new index_html_generator_1.IndexHtmlGenerator({
indexPath: indexHtmlOptions.input,
entrypoints: indexHtmlOptions.insertionOrder,
sri: options.subresourceIntegrity,
optimization: optimizationOptions,
crossOrigin: options.crossOrigin,
});
/** Virtual output path to support reading in-memory files. */
const virtualOutputPath = '/';
indexHtmlGenerator.readAsset = async function (filePath) {
// Remove leading directory separator
const relativefilePath = path.relative(virtualOutputPath, filePath);
const file = outputFiles.find((file) => file.path === relativefilePath);
if (file) {
return file.text;
}
throw new Error(`Output file does not exist: ${path}`);
};
const { content, warnings, errors } = await indexHtmlGenerator.process({
baseHref: options.baseHref,
lang: undefined,
outputPath: virtualOutputPath,
files: initialFiles,
});
for (const error of errors) {
context.logger.error(error);
}
for (const warning of warnings) {
context.logger.warn(warning);
}
outputFiles.push(createOutputFileFromText(indexHtmlOptions.output, content));
}
// Copy assets
if (assets) {
await (0, copy_assets_1.copyAssets)(assets, [outputPath], workspaceRoot);
}
// Write output files
await Promise.all(outputFiles.map((file) => fs.writeFile(path.join(outputPath, file.path), file.contents)));
// Augment the application with service worker support
// TODO: This should eventually operate on the in-memory files prior to writing the output files
if (serviceWorkerOptions) {
try {
await (0, service_worker_1.augmentAppWithServiceWorkerEsbuild)(workspaceRoot, serviceWorkerOptions, outputPath, options.baseHref || '/');
}
catch (error) {
context.logger.error(error instanceof Error ? error.message : `${error}`);
return new ExecutionResult(false, codeResults.rebuild, styleResults.rebuild, codeBundleCache);
}
}
const buildTime = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
context.logger.info(`Complete. [${buildTime.toFixed(3)} seconds]`);
return new ExecutionResult(true, codeResults.rebuild, styleResults.rebuild, codeBundleCache);
}
function createOutputFileFromText(path, text) {
return {
path,
text,
get contents() {
return Buffer.from(this.text, 'utf-8');
},
};
}
function createCodeBundleOptions(options, target, sourceFileCache) {
const { workspaceRoot, entryPoints, optimizationOptions, sourcemapOptions, tsconfig, outputNames, fileReplacements, externalDependencies, preserveSymlinks, stylePreprocessorOptions, advancedOptimizations, } = options;
return {
absWorkingDir: workspaceRoot,
bundle: true,
incremental: options.watch,
format: 'esm',
entryPoints,
entryNames: outputNames.bundles,
assetNames: outputNames.media,
target,
supported: getFeatureSupport(target),
mainFields: ['es2020', 'browser', 'module', 'main'],
conditions: ['es2020', 'es2015', 'module'],
resolveExtensions: ['.ts', '.tsx', '.mjs', '.js'],
logLevel: options.verbose ? 'debug' : 'silent',
minify: optimizationOptions.scripts,
pure: ['forwardRef'],
outdir: workspaceRoot,
sourcemap: sourcemapOptions.scripts && (sourcemapOptions.hidden ? 'external' : true),
splitting: true,
tsconfig,
external: externalDependencies,
write: false,
platform: 'browser',
preserveSymlinks,
plugins: [
(0, compiler_plugin_1.createCompilerPlugin)(
// JS/TS options
{
sourcemap: !!sourcemapOptions.scripts,
thirdPartySourcemaps: sourcemapOptions.vendor,
tsconfig,
advancedOptimizations,
fileReplacements,
sourceFileCache,
},
// Component stylesheet options
{
workspaceRoot,
optimization: !!optimizationOptions.styles.minify,
sourcemap:
// Hidden component stylesheet sourcemaps are inaccessible which is effectively
// the same as being disabled. Disabling has the advantage of avoiding the overhead
// of sourcemap processing.
!!sourcemapOptions.styles && (sourcemapOptions.hidden ? false : 'inline'),
outputNames,
includePaths: stylePreprocessorOptions === null || stylePreprocessorOptions === void 0 ? void 0 : stylePreprocessorOptions.includePaths,
externalDependencies,
target,
}),
],
define: {
// Only set to false when script optimizations are enabled. It should not be set to true because
// Angular turns `ngDevMode` into an object for development debugging purposes when not defined
// which a constant true value would break.
...(optimizationOptions.scripts ? { 'ngDevMode': 'false' } : undefined),
// Only AOT mode is supported currently
'ngJitMode': 'false',
},
};
}
/**
* Generates a syntax feature object map for Angular applications based on a list of targets.
* A full set of feature names can be found here: https://esbuild.github.io/api/#supported
* @param target An array of browser/engine targets in the format accepted by the esbuild `target` option.
* @returns An object that can be used with the esbuild build `supported` option.
*/
function getFeatureSupport(target) {
const supported = {
// Native async/await is not supported with Zone.js. Disabling support here will cause
// esbuild to downlevel async/await and for await...of to a Zone.js supported form. However, esbuild
// does not currently support downleveling async generators. Instead babel is used within the JS/TS
// loader to perform the downlevel transformation.
// NOTE: If esbuild adds support in the future, the babel support for async generators can be disabled.
'async-await': false,
// V8 currently has a performance defect involving object spread operations that can cause signficant
// degradation in runtime performance. By not supporting the language feature here, a downlevel form
// will be used instead which provides a workaround for the performance issue.
// For more details: https://bugs.chromium.org/p/v8/issues/detail?id=11536
'object-rest-spread': false,
};
// Detect Safari browser versions that have a class field behavior bug
// See: https://github.com/angular/angular-cli/issues/24355#issuecomment-1333477033
// See: https://github.com/WebKit/WebKit/commit/e8788a34b3d5f5b4edd7ff6450b80936bff396f2
let safariClassFieldScopeBug = false;
for (const browser of target) {
let majorVersion;
if (browser.startsWith('ios')) {
majorVersion = Number(browser.slice(3, 5));
}
else if (browser.startsWith('safari')) {
majorVersion = Number(browser.slice(6, 8));
}
else {
continue;
}
// Technically, 14.0 is not broken but rather does not have support. However, the behavior
// is identical since it would be set to false by esbuild if present as a target.
if (majorVersion === 14 || majorVersion === 15) {
safariClassFieldScopeBug = true;
break;
}
}
// If class field support cannot be used set to false; otherwise leave undefined to allow
// esbuild to use `target` to determine support.
if (safariClassFieldScopeBug) {
supported['class-field'] = false;
supported['class-static-field'] = false;
}
return supported;
}
function createGlobalStylesBundleOptions(options, target) {
const { workspaceRoot, optimizationOptions, sourcemapOptions, outputNames, globalStyles, preserveSymlinks, externalDependencies, stylePreprocessorOptions, watch, } = options;
const buildOptions = (0, stylesheets_1.createStylesheetBundleOptions)({
workspaceRoot,
optimization: !!optimizationOptions.styles.minify,
sourcemap: !!sourcemapOptions.styles,
preserveSymlinks,
target,
externalDependencies,
outputNames,
includePaths: stylePreprocessorOptions === null || stylePreprocessorOptions === void 0 ? void 0 : stylePreprocessorOptions.includePaths,
});
buildOptions.incremental = watch;
const namespace = 'angular:styles/global';
buildOptions.entryPoints = {};
for (const { name } of globalStyles) {
buildOptions.entryPoints[name] = `${namespace};${name}`;
}
buildOptions.plugins.unshift({
name: 'angular-global-styles',
setup(build) {
build.onResolve({ filter: /^angular:styles\/global;/ }, (args) => {
if (args.kind !== 'entry-point') {
return null;
}
return {
path: args.path.split(';', 2)[1],
namespace,
};
});
build.onLoad({ filter: /./, namespace }, (args) => {
var _a;
const files = (_a = globalStyles.find(({ name }) => name === args.path)) === null || _a === void 0 ? void 0 : _a.files;
(0, node_assert_1.default)(files, `global style name should always be found [${args.path}]`);
return {
contents: files.map((file) => `@import '${file.replace(/\\/g, '/')}';`).join('\n'),
loader: 'css',
resolveDir: workspaceRoot,
};
});
},
});
return buildOptions;
}
/**
* Main execution function for the esbuild-based application builder.
* The options are compatible with the Webpack-based builder.
* @param initialOptions 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(initialOptions, context) {
var _a;
// Only AOT is currently supported
if (initialOptions.aot !== true) {
context.logger.error('JIT mode is currently not supported by this experimental builder. AOT mode must be used.');
return { success: false };
}
// Inform user of experimental status of builder and options
(0, experimental_warnings_1.logExperimentalWarnings)(initialOptions, context);
// Determine project name from builder context target
const projectName = (_a = context.target) === null || _a === void 0 ? void 0 : _a.project;
if (!projectName) {
context.logger.error(`The 'browser-esbuild' builder requires a target to be specified.`);
return { success: false };
}
const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, initialOptions);
// Clean output path if enabled
if (initialOptions.deleteOutputPath) {
(0, utils_1.deleteOutputDir)(normalizedOptions.workspaceRoot, initialOptions.outputPath);
}
// Create output directory if needed
try {
await fs.mkdir(normalizedOptions.outputPath, { recursive: true });
}
catch (e) {
(0, error_1.assertIsError)(e);
context.logger.error('Unable to create output directory: ' + e.message);
return { success: false };
}
// Initial build
let result = await execute(normalizedOptions, context);
yield result.output;
// Finish if watch mode is not enabled
if (!initialOptions.watch) {
(0, sass_plugin_1.shutdownSassWorkerPool)();
return;
}
context.logger.info('Watch mode enabled. Watching for file changes...');
// Setup a watcher
const watcher = (0, watcher_1.createWatcher)({
polling: typeof initialOptions.poll === 'number',
interval: initialOptions.poll,
// Ignore the output and cache paths to avoid infinite rebuild cycles
ignored: [normalizedOptions.outputPath, normalizedOptions.cacheOptions.basePath],
});
// Temporarily watch the entire project
watcher.add(normalizedOptions.projectRoot);
// Watch workspace root node modules
// Includes Yarn PnP manifest files (https://yarnpkg.com/advanced/pnp-spec/)
watcher.add(path.join(normalizedOptions.workspaceRoot, 'node_modules'));
watcher.add(path.join(normalizedOptions.workspaceRoot, '.pnp.cjs'));
watcher.add(path.join(normalizedOptions.workspaceRoot, '.pnp.data.json'));
// Wait for changes and rebuild as needed
try {
for await (const changes of watcher) {
context.logger.info('Changes detected. Rebuilding...');
if (initialOptions.verbose) {
context.logger.info(changes.toDebugString());
}
result = await execute(normalizedOptions, context, result.createRebuildState(changes));
yield result.output;
}
}
finally {
// Stop the watcher
await watcher.close();
// Cleanup incremental rebuild state
result.dispose();
(0, sass_plugin_1.shutdownSassWorkerPool)();
}
}
exports.buildEsbuildBrowser = buildEsbuildBrowser;
exports.default = (0, architect_1.createBuilder)(buildEsbuildBrowser);
@@ -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 } from '@angular-devkit/architect';
import { Schema as BrowserBuilderOptions } from './schema';
export declare type NormalizedBrowserOptions = 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: BrowserBuilderOptions): Promise<{
advancedOptimizations: boolean | undefined;
baseHref: string | undefined;
cacheOptions: import("../../utils/normalize-cache").NormalizedCachedOptions;
crossOrigin: import("./schema").CrossOrigin | undefined;
externalDependencies: string[] | undefined;
poll: number | undefined;
preserveSymlinks: boolean;
stylePreprocessorOptions: import("./schema").StylePreprocessorOptions | undefined;
subresourceIntegrity: boolean | undefined;
verbose: boolean | undefined;
watch: boolean | undefined;
workspaceRoot: string;
entryPoints: Record<string, string>;
optimizationOptions: import("../../utils").NormalizedOptimizationOptions;
outputPath: string;
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;
}[];
serviceWorkerOptions: string | undefined;
indexHtmlOptions: {
input: string;
output: string;
insertionOrder: import("../../utils/package-chunk-sort").EntryPointsType[];
} | undefined;
}>;
@@ -0,0 +1,155 @@
"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.normalizeOptions = void 0;
const path = __importStar(require("path"));
const utils_1 = require("../../utils");
const normalize_cache_1 = require("../../utils/normalize-cache");
const normalize_polyfills_1 = require("../../utils/normalize-polyfills");
const package_chunk_sort_1 = require("../../utils/package-chunk-sort");
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
const helpers_1 = require("../../webpack/utils/helpers");
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) {
var _a, _b, _c, _d, _e, _f, _g;
const workspaceRoot = context.workspaceRoot;
const projectMetadata = await context.getProjectMetadata(projectName);
const projectRoot = path.join(workspaceRoot, (_a = projectMetadata.root) !== null && _a !== void 0 ? _a : '');
const projectSourceRoot = path.join(workspaceRoot, (_b = projectMetadata.sourceRoot) !== null && _b !== void 0 ? _b : 'src');
const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(projectMetadata, workspaceRoot);
const mainEntryPoint = path.join(workspaceRoot, options.main);
// Currently esbuild do not support multiple files per entry-point
const [polyfillsEntryPoint, ...remainingPolyfills] = (0, normalize_polyfills_1.normalizePolyfills)(options.polyfills, workspaceRoot);
if (remainingPolyfills.length) {
context.logger.warn(`The 'polyfills' option currently does not support multiple entries by this experimental builder. The first entry will be used.`);
}
const tsconfig = path.join(workspaceRoot, options.tsConfig);
const outputPath = path.join(workspaceRoot, options.outputPath);
const optimizationOptions = (0, utils_1.normalizeOptimization)(options.optimization);
const sourcemapOptions = (0, utils_1.normalizeSourceMaps)((_c = options.sourceMap) !== null && _c !== void 0 ? _c : false);
const assets = ((_d = options.assets) === null || _d === void 0 ? void 0 : _d.length)
? (0, utils_1.normalizeAssetPatterns)(options.assets, workspaceRoot, projectRoot, projectSourceRoot)
: undefined;
const outputNames = {
bundles: options.outputHashing === schema_1.OutputHashing.All || options.outputHashing === schema_1.OutputHashing.Bundles
? '[name].[hash]'
: '[name]',
media: options.outputHashing === schema_1.OutputHashing.All || options.outputHashing === schema_1.OutputHashing.Media
? '[name].[hash]'
: '[name]',
};
if (options.resourcesOutputPath) {
outputNames.media = path.join(options.resourcesOutputPath, outputNames.media);
}
let fileReplacements;
if (options.fileReplacements) {
for (const replacement of options.fileReplacements) {
fileReplacements !== null && fileReplacements !== void 0 ? fileReplacements : (fileReplacements = {});
fileReplacements[path.join(workspaceRoot, replacement.replace)] = path.join(workspaceRoot, replacement.with);
}
}
const globalStyles = [];
if ((_e = options.styles) === null || _e === void 0 ? void 0 : _e.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) });
}
}
let serviceWorkerOptions;
if (options.serviceWorker) {
// If ngswConfigPath is not specified, the default is 'ngsw-config.json' within the project root
serviceWorkerOptions = options.ngswConfigPath
? path.join(workspaceRoot, options.ngswConfigPath)
: path.join(projectRoot, 'ngsw-config.json');
}
// Setup bundler entry points
const entryPoints = {
main: mainEntryPoint,
};
if (polyfillsEntryPoint) {
entryPoints['polyfills'] = polyfillsEntryPoint;
}
let indexHtmlOptions;
if (options.index) {
indexHtmlOptions = {
input: path.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: (_f = options.scripts) !== null && _f !== void 0 ? _f : [],
styles: (_g = options.styles) !== null && _g !== void 0 ? _g : [],
}),
};
}
// Initial options to keep
const { baseHref, buildOptimizer, crossOrigin, externalDependencies, poll, preserveSymlinks, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, } = options;
// Return all the normalized options
return {
advancedOptimizations: buildOptimizer,
baseHref,
cacheOptions,
crossOrigin,
externalDependencies,
poll,
// If not explicitly set, default to the Node.js process argument
preserveSymlinks: preserveSymlinks !== null && preserveSymlinks !== void 0 ? preserveSymlinks : process.execArgv.includes('--preserve-symlinks'),
stylePreprocessorOptions,
subresourceIntegrity,
verbose,
watch,
workspaceRoot,
entryPoints,
optimizationOptions,
outputPath,
sourcemapOptions,
tsconfig,
projectRoot,
assets,
outputNames,
fileReplacements,
globalStyles,
serviceWorkerOptions,
indexHtmlOptions,
};
}
exports.normalizeOptions = normalizeOptions;
@@ -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
*/
export declare function resetCumulativeDurations(): void;
export declare function logCumulativeDurations(): void;
export declare function profileAsync<T>(name: string, action: () => Promise<T>, cumulative?: boolean): Promise<T>;
export declare function profileSync<T>(name: string, action: () => T, cumulative?: boolean): T;
@@ -0,0 +1,79 @@
"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.profileSync = exports.profileAsync = exports.logCumulativeDurations = exports.resetCumulativeDurations = void 0;
const environment_options_1 = require("../../utils/environment-options");
let cumulativeDurations;
function resetCumulativeDurations() {
cumulativeDurations === null || cumulativeDurations === void 0 ? void 0 : cumulativeDurations.clear();
}
exports.resetCumulativeDurations = resetCumulativeDurations;
function logCumulativeDurations() {
if (!environment_options_1.debugPerformance || !cumulativeDurations) {
return;
}
for (const [name, durations] of cumulativeDurations) {
let total = 0;
let min;
let max;
for (const duration of durations) {
total += duration;
if (min === undefined || duration < min) {
min = duration;
}
if (max === undefined || duration > max) {
max = duration;
}
}
const average = total / durations.length;
// eslint-disable-next-line no-console
console.log(`DURATION[${name}]: ${total.toFixed(9)}s [count: ${durations.length}; avg: ${average.toFixed(9)}s; min: ${min === null || min === void 0 ? void 0 : min.toFixed(9)}s; max: ${max === null || max === void 0 ? void 0 : max.toFixed(9)}s]`);
}
}
exports.logCumulativeDurations = logCumulativeDurations;
function recordDuration(name, startTime, cumulative) {
var _a;
const duration = Number(process.hrtime.bigint() - startTime) / 10 ** 9;
if (cumulative) {
cumulativeDurations !== null && cumulativeDurations !== void 0 ? cumulativeDurations : (cumulativeDurations = new Map());
const durations = (_a = cumulativeDurations.get(name)) !== null && _a !== void 0 ? _a : [];
durations.push(duration);
cumulativeDurations.set(name, durations);
}
else {
// eslint-disable-next-line no-console
console.log(`DURATION[${name}]: ${duration.toFixed(9)}s`);
}
}
async function profileAsync(name, action, cumulative) {
if (!environment_options_1.debugPerformance) {
return action();
}
const startTime = process.hrtime.bigint();
try {
return await action();
}
finally {
recordDuration(name, startTime, cumulative);
}
}
exports.profileAsync = profileAsync;
function profileSync(name, action, cumulative) {
if (!environment_options_1.debugPerformance) {
return action();
}
const startTime = process.hrtime.bigint();
try {
return action();
}
finally {
recordDuration(name, startTime, cumulative);
}
}
exports.profileSync = profileSync;
@@ -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
*/
import type { Plugin } from 'esbuild';
export declare function shutdownSassWorkerPool(): void;
export declare function createSassPlugin(options: {
sourcemap: boolean;
loadPaths?: string[];
}): Plugin;
@@ -0,0 +1,135 @@
"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.createSassPlugin = exports.shutdownSassWorkerPool = void 0;
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const node_url_1 = require("node:url");
const sass_service_1 = require("../../sass/sass-service");
let sassWorkerPool;
function isSassException(error) {
return !!error && typeof error === 'object' && 'sassMessage' in error;
}
function shutdownSassWorkerPool() {
sassWorkerPool === null || sassWorkerPool === void 0 ? void 0 : sassWorkerPool.close();
sassWorkerPool = undefined;
}
exports.shutdownSassWorkerPool = shutdownSassWorkerPool;
function createSassPlugin(options) {
return {
name: 'angular-sass',
setup(build) {
const resolveUrl = async (url, previousResolvedModules) => {
let result = await build.resolve(url, {
kind: 'import-rule',
// This should ideally be the directory of the importer file from Sass
// but that is not currently available from the Sass importer API.
resolveDir: build.initialOptions.absWorkingDir,
});
// Workaround to support Yarn PnP without access to the importer file from Sass
if (!result.path && (previousResolvedModules === null || previousResolvedModules === void 0 ? void 0 : previousResolvedModules.size)) {
for (const previous of previousResolvedModules) {
result = await build.resolve(url, {
kind: 'import-rule',
resolveDir: previous,
});
if (result.path) {
break;
}
}
}
return result;
};
build.onLoad({ filter: /\.s[ac]ss$/ }, async (args) => {
// Lazily load Sass when a Sass file is found
sassWorkerPool !== null && sassWorkerPool !== void 0 ? sassWorkerPool : (sassWorkerPool = new sass_service_1.SassWorkerImplementation(true));
const warnings = [];
try {
const data = await (0, promises_1.readFile)(args.path, 'utf-8');
const { css, sourceMap, loadedUrls } = await sassWorkerPool.compileStringAsync(data, {
url: (0, node_url_1.pathToFileURL)(args.path),
style: 'expanded',
loadPaths: options.loadPaths,
sourceMap: options.sourcemap,
sourceMapIncludeSources: options.sourcemap,
quietDeps: true,
importers: [
{
findFileUrl: async (url, { previousResolvedModules }) => {
const result = await resolveUrl(url, previousResolvedModules);
// Check for package deep imports
if (!result.path) {
const parts = url.split('/');
const hasScope = parts.length >= 2 && parts[0].startsWith('@');
const [nameOrScope, nameOrFirstPath, ...pathPart] = parts;
const packageName = hasScope
? `${nameOrScope}/${nameOrFirstPath}`
: nameOrScope;
const packageResult = await resolveUrl(packageName + '/package.json', previousResolvedModules);
if (packageResult.path) {
return (0, node_url_1.pathToFileURL)((0, node_path_1.join)((0, node_path_1.dirname)(packageResult.path), !hasScope ? nameOrFirstPath : '', ...pathPart));
}
}
return result.path ? (0, node_url_1.pathToFileURL)(result.path) : null;
},
},
],
logger: {
warn: (text, { deprecation, span }) => {
warnings.push({
text: deprecation ? 'Deprecation' : text,
location: span && {
file: span.url && (0, node_url_1.fileURLToPath)(span.url),
lineText: span.context,
// Sass line numbers are 0-based while esbuild's are 1-based
line: span.start.line + 1,
column: span.start.column,
},
notes: deprecation ? [{ text }] : undefined,
});
},
},
});
return {
loader: 'css',
contents: sourceMap
? `${css}\n${sourceMapToUrlComment(sourceMap, (0, node_path_1.dirname)(args.path))}`
: css,
watchFiles: loadedUrls.map((url) => (0, node_url_1.fileURLToPath)(url)),
warnings,
};
}
catch (error) {
if (isSassException(error)) {
const file = error.span.url ? (0, node_url_1.fileURLToPath)(error.span.url) : undefined;
return {
loader: 'css',
errors: [
{
text: error.message,
},
],
warnings,
watchFiles: file ? [file] : undefined,
};
}
throw error;
}
});
},
};
}
exports.createSassPlugin = createSassPlugin;
function sourceMapToUrlComment(sourceMap, root) {
// Remove `file` protocol from all sourcemap sources and adjust to be relative to the input file.
// This allows esbuild to correctly process the paths.
sourceMap.sources = sourceMap.sources.map((source) => (0, node_path_1.relative)(root, (0, node_url_1.fileURLToPath)(source)));
const urlSourceMap = Buffer.from(JSON.stringify(sourceMap), 'utf-8').toString('base64');
return `/*# sourceMappingURL=data:application/json;charset=utf-8;base64,${urlSourceMap} */`;
}
@@ -0,0 +1,430 @@
/**
* Browser target options
*/
export interface Schema {
/**
* A list of CommonJS packages that are allowed to be used without a build time warning.
*/
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.
* By default, writes output to a folder named dist/ in the current project.
*/
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 declare 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 declare 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;
}
/**
* 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 declare 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 declare 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 declare 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 declare 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 declare type Polyfills = string[] | string;
export declare 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 declare 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 declare 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;
}
@@ -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 || (exports.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 || (exports.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 || (exports.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 || (exports.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 || (exports.OutputHashing = {}));
@@ -0,0 +1,537 @@
{
"$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 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
},
"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.\nBy default, writes output to a folder named dist/ in the current project."
},
"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 packages that are allowed to be used without a build time warning.",
"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"]
}
}
}
@@ -0,0 +1,58 @@
/**
* @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 { BuildOptions, OutputFile } from 'esbuild';
export interface BundleStylesheetOptions {
workspaceRoot: string;
optimization: boolean;
preserveSymlinks?: boolean;
sourcemap: boolean | 'external' | 'inline';
outputNames?: {
bundles?: string;
media?: string;
};
includePaths?: string[];
externalDependencies?: string[];
target: string[];
}
export declare function createStylesheetBundleOptions(options: BundleStylesheetOptions): BuildOptions & {
plugins: NonNullable<BuildOptions['plugins']>;
};
/**
* Bundle a stylesheet that exists as a file on the filesystem.
*
* @param filename The path to the file to bundle.
* @param options The stylesheet bundling options to use.
* @returns The bundle result object.
*/
export declare function bundleStylesheetFile(filename: string, options: BundleStylesheetOptions): Promise<{
errors: import("esbuild").Message[];
warnings: import("esbuild").Message[];
contents: string;
map: string | undefined;
path: string | undefined;
resourceFiles: OutputFile[];
}>;
/**
* Bundle stylesheet text data from a string.
*
* @param data The string content of a stylesheet to bundle.
* @param dataOptions The options to use to resolve references and name output of the stylesheet data.
* @param bundleOptions The stylesheet bundling options to use.
* @returns The bundle result object.
*/
export declare function bundleStylesheetText(data: string, dataOptions: {
resolvePath: string;
virtualName?: string;
}, bundleOptions: BundleStylesheetOptions): Promise<{
errors: import("esbuild").Message[];
warnings: import("esbuild").Message[];
contents: string;
map: string | undefined;
path: string | undefined;
resourceFiles: OutputFile[];
}>;
@@ -0,0 +1,129 @@
"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.bundleStylesheetText = exports.bundleStylesheetFile = exports.createStylesheetBundleOptions = void 0;
const path = __importStar(require("path"));
const css_resource_plugin_1 = require("./css-resource-plugin");
const esbuild_1 = require("./esbuild");
const sass_plugin_1 = require("./sass-plugin");
function createStylesheetBundleOptions(options) {
var _a, _b;
return {
absWorkingDir: options.workspaceRoot,
bundle: true,
entryNames: (_a = options.outputNames) === null || _a === void 0 ? void 0 : _a.bundles,
assetNames: (_b = options.outputNames) === null || _b === void 0 ? void 0 : _b.media,
logLevel: 'silent',
minify: options.optimization,
sourcemap: options.sourcemap,
outdir: options.workspaceRoot,
write: false,
platform: 'browser',
target: options.target,
preserveSymlinks: options.preserveSymlinks,
external: options.externalDependencies,
conditions: ['style', 'sass'],
mainFields: ['style', 'sass'],
plugins: [
(0, sass_plugin_1.createSassPlugin)({ sourcemap: !!options.sourcemap, loadPaths: options.includePaths }),
(0, css_resource_plugin_1.createCssResourcePlugin)(),
],
};
}
exports.createStylesheetBundleOptions = createStylesheetBundleOptions;
async function bundleStylesheet(entry, options) {
// Execute esbuild
const result = await (0, esbuild_1.bundle)(options.workspaceRoot, {
...createStylesheetBundleOptions(options),
...entry,
});
// Extract the result of the bundling from the output files
let contents = '';
let map;
let outputPath;
const resourceFiles = [];
if (result.outputFiles) {
for (const outputFile of result.outputFiles) {
const filename = path.basename(outputFile.path);
if (filename.endsWith('.css')) {
outputPath = outputFile.path;
contents = outputFile.text;
}
else if (filename.endsWith('.css.map')) {
map = outputFile.text;
}
else {
// The output files could also contain resources (images/fonts/etc.) that were referenced
resourceFiles.push(outputFile);
}
}
}
return {
errors: result.errors,
warnings: result.warnings,
contents,
map,
path: outputPath,
resourceFiles,
};
}
/**
* Bundle a stylesheet that exists as a file on the filesystem.
*
* @param filename The path to the file to bundle.
* @param options The stylesheet bundling options to use.
* @returns The bundle result object.
*/
async function bundleStylesheetFile(filename, options) {
return bundleStylesheet({ entryPoints: [filename] }, options);
}
exports.bundleStylesheetFile = bundleStylesheetFile;
/**
* Bundle stylesheet text data from a string.
*
* @param data The string content of a stylesheet to bundle.
* @param dataOptions The options to use to resolve references and name output of the stylesheet data.
* @param bundleOptions The stylesheet bundling options to use.
* @returns The bundle result object.
*/
async function bundleStylesheetText(data, dataOptions, bundleOptions) {
const result = bundleStylesheet({
stdin: {
contents: data,
sourcefile: dataOptions.virtualName,
resolveDir: dataOptions.resolvePath,
loader: 'css',
},
}, bundleOptions);
return result;
}
exports.bundleStylesheetText = bundleStylesheetText;
@@ -0,0 +1,23 @@
/**
* @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 declare class ChangedFiles {
readonly added: Set<string>;
readonly modified: Set<string>;
readonly removed: Set<string>;
toDebugString(): string;
}
export interface BuildWatcher extends AsyncIterableIterator<ChangedFiles> {
add(paths: string | string[]): void;
remove(paths: string | string[]): void;
close(): Promise<void>;
}
export declare function createWatcher(options?: {
polling?: boolean;
interval?: number;
ignored?: string[];
}): BuildWatcher;
@@ -0,0 +1,104 @@
"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.createWatcher = exports.ChangedFiles = void 0;
const chokidar_1 = require("chokidar");
class ChangedFiles {
constructor() {
this.added = new Set();
this.modified = new Set();
this.removed = new Set();
}
toDebugString() {
const content = {
added: Array.from(this.added),
modified: Array.from(this.modified),
removed: Array.from(this.removed),
};
return JSON.stringify(content, null, 2);
}
}
exports.ChangedFiles = ChangedFiles;
function createWatcher(options) {
const watcher = new chokidar_1.FSWatcher({
...options,
disableGlobbing: true,
ignoreInitial: true,
});
const nextQueue = [];
let currentChanges;
let nextWaitTimeout;
watcher.on('all', (event, path) => {
switch (event) {
case 'add':
currentChanges !== null && currentChanges !== void 0 ? currentChanges : (currentChanges = new ChangedFiles());
currentChanges.added.add(path);
break;
case 'change':
currentChanges !== null && currentChanges !== void 0 ? currentChanges : (currentChanges = new ChangedFiles());
currentChanges.modified.add(path);
break;
case 'unlink':
currentChanges !== null && currentChanges !== void 0 ? currentChanges : (currentChanges = new ChangedFiles());
currentChanges.removed.add(path);
break;
default:
return;
}
// Wait 250ms from next change to better capture groups of file save operations.
if (!nextWaitTimeout) {
nextWaitTimeout = setTimeout(() => {
nextWaitTimeout = undefined;
const next = nextQueue.shift();
if (next) {
const value = currentChanges;
currentChanges = undefined;
next(value);
}
}, 250);
nextWaitTimeout === null || nextWaitTimeout === void 0 ? void 0 : nextWaitTimeout.unref();
}
});
return {
[Symbol.asyncIterator]() {
return this;
},
async next() {
if (currentChanges && nextQueue.length === 0) {
const result = { value: currentChanges };
currentChanges = undefined;
return result;
}
return new Promise((resolve) => {
nextQueue.push((value) => resolve(value ? { value } : { done: true, value }));
});
},
add(paths) {
watcher.add(paths);
},
remove(paths) {
watcher.unwatch(paths);
},
async close() {
try {
await watcher.close();
if (nextWaitTimeout) {
clearTimeout(nextWaitTimeout);
}
}
finally {
let next;
while ((next = nextQueue.shift()) !== undefined) {
next();
}
}
},
};
}
exports.createWatcher = createWatcher;
@@ -0,0 +1,59 @@
"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
// */
// import type ng from '@angular/compiler-cli';
// import { Worker } from 'node:worker_threads';
// import { AngularHostOptions } from './angular-host';
// interface Message {
// id: number;
// }
// interface InitializeRequest extends Message {
// tsconfig: string;
// tsCallbackId: number;
// tcoCallbackId?: number;
// }
// // eslint-disable-next-line @typescript-eslint/no-explicit-any
// type ResponseCallback = (...args: any[]) => void;
// let callbackIds = 0;
// export class WorkerAngularCompilation {
// #worker: Worker;
// #callbacks = new Map<number, ResponseCallback>();
// constructor() {
// this.#worker = new Worker(require.resolve('./compilation-worker'));
// }
// async initialize(
// tsconfig: string,
// hostOptions: AngularHostOptions,
// transformCompilerOptions?: (compilerOptions: ng.CompilerOptions) => ng.CompilerOptions,
// ): Promise<{ compilerOptions: ng.CompilerOptions }> {
// const request: InitializeRequest = {
// id: ++callbackIds,
// tsconfig,
// tsCallbackId: ++callbackIds,
// };
// this.#callbacks.set(request.tsCallbackId, () => {
// hostOptions.transformStylesheet();
// });
// if (transformCompilerOptions) {
// request.tcoCallbackId = ++callbackIds;
// this.#callbacks.set(request.tcoCallbackId, (id: number, compilerOptions) => {
// try {
// transformCompilerOptions(compilerOptions);
// } catch (e) {
// }
// });
// }
// const result = new Promise<{ compilerOptions: ng.CompilerOptions }>((resolve, reject) => {
// this.#callbacks.set(request.id, () => {
// resolve({});
// });
// });
// this.#worker.postMessage(request);
// return result;
// }
// }
@@ -0,0 +1,50 @@
/**
* @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 { ExecutionTransformer } from '../../transforms';
import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
import { BuildEventStats } from '../../webpack/utils/stats';
import { Schema as BrowserBuilderSchema } from './schema';
/**
* @experimental Direct usage of this type is considered experimental.
*/
export declare type BrowserBuilderOutput = BuilderOutput & {
stats: BuildEventStats;
baseOutputPath: string;
/**
* @deprecated in version 14. Use 'outputs' instead.
*/
outputPaths: string[];
/**
* @deprecated in version 9. Use 'outputs' instead.
*/
outputPath: 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("@angular-devkit/architect/src/internal").Builder<BrowserBuilderSchema & import("../../../../core/src").JsonObject>;
export default _default;
@@ -0,0 +1,348 @@
"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 fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
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");
const configs_1 = require("../../webpack/configs");
const async_chunks_1 = require("../../webpack/utils/async-chunks");
const helpers_1 = require("../../webpack/utils/helpers");
const stats_1 = require("../../webpack/utils/stats");
/**
* Maximum time in milliseconds for single build/rebuild
* This accounts for CI variability.
*/
exports.BUILD_TIMEOUT = 30000;
async function initialize(options, context, webpackConfigurationTransform) {
var _a, _b;
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),
]);
// Validate asset option values if processed directly
if (((_a = options.assets) === null || _a === void 0 ? void 0 : _a.length) && !((_b = adjustedOptions.assets) === null || _b === void 0 ? void 0 : _b.length)) {
(0, utils_1.normalizeAssetPatterns)(options.assets, context.workspaceRoot, projectRoot, projectSourceRoot).forEach(({ output }) => {
if (output.startsWith('..')) {
throw new Error('An asset cannot be written to a location outside of the output path.');
}
});
}
let transformedConfig;
if (webpackConfigurationTransform) {
transformedConfig = await webpackConfigurationTransform(config);
}
if (options.deleteOutputPath) {
(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 = {}) {
var _a;
const projectName = (_a = context.target) === null || _a === void 0 ? void 0 : _a.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, operators_1.switchMap)(async (projectMetadata) => {
var _a;
var _b;
// 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));
(_a = (_b = initialization.config).plugins) !== null && _a !== void 0 ? _a : (_b.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, operators_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, operators_1.concatMap)(
// eslint-disable-next-line max-lines-per-function
async (buildEvent) => {
var _a, _b, _c, _d, _e, _f, _g, _h;
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 === null || budgets === void 0 ? void 0 : budgets.length) {
budgetFailures = [...(0, bundle_calculator_1.checkBudgets)(budgets, webpackStats)];
for (const { severity, message } of budgetFailures) {
switch (severity) {
case bundle_calculator_1.ThresholdSeverity.Warning:
(_a = webpackStats.warnings) === null || _a === void 0 ? void 0 : _a.push({ message });
break;
case bundle_calculator_1.ThresholdSeverity.Error:
(_b = webpackStats.errors) === null || _b === void 0 ? void 0 : _b.push({ message });
break;
default:
assertNever(severity);
}
}
}
const buildSuccess = success && !(0, stats_1.statsHasErrors)(webpackStats);
if (buildSuccess) {
// Copy assets
if (!options.watch && ((_c = options.assets) === null || _c === void 0 ? void 0 : _c.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: (_d = options.scripts) !== null && _d !== void 0 ? _d : [],
styles: (_e = options.styles) !== null && _e !== void 0 ? _e : [],
});
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,
});
let hasErrors = false;
for (const [locale, outputPath] of outputPaths.entries()) {
try {
const { content, warnings, errors } = await indexHtmlGenerator.process({
baseHref: (_f = getLocaleBaseHref(i18n, locale)) !== null && _f !== void 0 ? _f : 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, (_h = (_g = getLocaleBaseHref(i18n, locale)) !== null && _g !== void 0 ? _g : options.baseHref) !== null && _h !== void 0 ? _h : '/', 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, operators_1.map)(({ output: event, webpackStats }) => ({
...event,
stats: (0, stats_1.generateBuildEventStats)(webpackStats, options),
baseOutputPath,
outputPath: baseOutputPath,
outputPaths: (outputPaths && Array.from(outputPaths.values())) || [baseOutputPath],
outputs: (outputPaths &&
[...outputPaths.entries()].map(([locale, path]) => {
var _a;
return ({
locale,
path,
baseHref: (_a = getLocaleBaseHref(i18n, locale)) !== null && _a !== void 0 ? _a : options.baseHref,
});
})) || {
path: baseOutputPath,
baseHref: options.baseHref,
},
})));
}));
function getLocaleBaseHref(i18n, locale) {
var _a, _b;
if (i18n.locales[locale] && ((_a = i18n.locales[locale]) === null || _a === void 0 ? void 0 : _a.baseHref) !== '') {
return (0, utils_1.urlJoin)(options.baseHref || '', (_b = i18n.locales[locale].baseHref) !== null && _b !== void 0 ? _b : `/${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);
@@ -0,0 +1,427 @@
/**
* Browser target options
*/
export interface Schema {
/**
* A list of CommonJS packages that are allowed to be used without a build time warning.
*/
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.
* By default, writes output to a folder named dist/ in the current project.
*/
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 declare 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 declare 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;
}
/**
* 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 declare 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 declare 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 declare 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 declare 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 declare type Polyfills = string[] | string;
export declare 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 declare 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 declare 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;
}
@@ -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 || (exports.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 || (exports.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 || (exports.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 || (exports.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 || (exports.OutputHashing = {}));
@@ -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.\nBy default, writes output to a folder named dist/ in the current project."
},
"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 packages that are allowed to be used without a build time warning.",
"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"]
}
}
}
@@ -0,0 +1,40 @@
/**
* @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 { json } from '@angular-devkit/core';
import { Observable } from 'rxjs';
import webpack from 'webpack';
import { ExecutionTransformer } from '../../transforms';
import { IndexHtmlTransform } from '../../utils/index-file/index-html-generator';
import { BuildEventStats } from '../../webpack/utils/stats';
import { Schema } from './schema';
export declare type DevServerBuilderOptions = Schema;
/**
* @experimental Direct usage of this type is considered experimental.
*/
export declare type DevServerBuilderOutput = DevServerBuildOutput & {
baseUrl: string;
stats: BuildEventStats;
};
/**
* Reusable implementation of the Angular Webpack development server builder.
* @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).
*
* @experimental Direct usage of this function is considered experimental.
*/
export declare function serveWebpackBrowser(options: DevServerBuilderOptions, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
logging?: WebpackLoggingCallback;
indexHtml?: IndexHtmlTransform;
}): Observable<DevServerBuilderOutput>;
declare const _default: import("@angular-devkit/architect/src/internal").Builder<Schema & json.JsonObject>;
export default _default;
@@ -0,0 +1,336 @@
"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 architect_1 = require("@angular-devkit/architect");
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 operators_1 = require("rxjs/operators");
const url = __importStar(require("url"));
const utils_1 = require("../../utils");
const check_port_1 = require("../../utils/check-port");
const color_1 = require("../../utils/color");
const i18n_options_1 = require("../../utils/i18n-options");
const load_translations_1 = require("../../utils/load-translations");
const normalize_cache_1 = require("../../utils/normalize-cache");
const package_chunk_sort_1 = require("../../utils/package-chunk-sort");
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 webpack_diagnostics_1 = require("../../utils/webpack-diagnostics");
const configs_1 = require("../../webpack/configs");
const index_html_webpack_plugin_1 = require("../../webpack/plugins/index-html-webpack-plugin");
const service_worker_plugin_1 = require("../../webpack/plugins/service-worker-plugin");
const stats_1 = require("../../webpack/utils/stats");
const schema_1 = require("../browser/schema");
/**
* Reusable implementation of the Angular Webpack development server builder.
* @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).
*
* @experimental Direct usage of this function is considered experimental.
*/
// eslint-disable-next-line max-lines-per-function
function serveWebpackBrowser(options, context, transforms = {}) {
// Check Angular version.
const { logger, workspaceRoot } = context;
(0, version_1.assertCompatibleAngularVersion)(workspaceRoot);
const browserTarget = (0, architect_1.targetFromTargetString)(options.browserTarget);
async function setup() {
var _a, _b, _c, _d;
const projectName = (_a = context.target) === null || _a === void 0 ? void 0 : _a.project;
if (!projectName) {
throw new Error('The builder requires a target.');
}
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
options.port = await (0, check_port_1.checkPort)((_b = options.port) !== null && _b !== void 0 ? _b : 4200, options.host || 'localhost');
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.`);
}
if (!options.disableHostCheck &&
options.host &&
!/^127\.\d+\.\d+\.\d+/g.test(options.host) &&
options.host !== 'localhost') {
logger.warn(core_1.tags.stripIndent `
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 (options.disableHostCheck) {
logger.warn(core_1.tags.oneLine `
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.
`);
}
// Get the browser configuration from the target name.
const rawBrowserOptions = (await context.getTargetOptions(browserTarget));
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 metadata = await context.getProjectMetadata(projectName);
const cacheOptions = (0, normalize_cache_1.normalizeCacheOptions)(metadata, context.workspaceRoot);
const browserName = await context.getBuilderNameForTarget(browserTarget);
// Issue a warning that the dev-server does not currently support the experimental esbuild-
// based builder and will use Webpack.
if (browserName === '@angular-devkit/build-angular:browser-esbuild') {
logger.warn('WARNING: The experimental esbuild-based builder is not currently supported ' +
'by the dev-server. The stable Webpack-based builder will be used instead.');
}
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,
}, browserName));
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, projectRoot, 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, cacheOptions, context);
}
if (transforms.webpackConfiguration) {
webpackConfig = await transforms.webpackConfiguration(webpackConfig);
}
(_c = webpackConfig.plugins) !== null && _c !== void 0 ? _c : (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: !!((_d = webpackConfig.devServer) === null || _d === void 0 ? void 0 : _d.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: 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,
ngswConfigPath: browserOptions.ngswConfigPath,
}));
}
return {
browserOptions,
webpackConfig,
projectRoot,
};
}
return (0, rxjs_1.from)(setup()).pipe((0, operators_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, operators_1.concatMap)(async (buildEvent, index) => {
var _a, _b;
const webpackRawStats = buildEvent.webpackStats;
if (!webpackRawStats) {
throw new Error('Webpack stats build result is required.');
}
// Resolve serve address.
const publicPath = (_b = (_a = webpackConfig.devServer) === null || _a === void 0 ? void 0 : _a.devMiddleware) === null || _b === void 0 ? void 0 : _b.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) {
var _a;
const localeDescription = i18n.locales[locale];
// Modify main entrypoint to include locale data
if ((localeDescription === null || localeDescription === void 0 ? void 0 : 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 === null || localeDescription === void 0 ? void 0 : localeDescription.translation) || {};
if (locale === i18n.sourceLocale) {
missingTranslationBehavior = 'ignore';
translation = {};
}
const i18nLoaderOptions = {
locale,
missingTranslationBehavior,
translation: i18n.shouldInline ? translation : undefined,
translationFiles: localeDescription === null || localeDescription === void 0 ? void 0 : localeDescription.files.map((file) => path.resolve(context.workspaceRoot, file.path)),
};
const i18nRule = {
test: /\.[cm]?[tj]sx?$/,
enforce: 'post',
use: [
{
loader: require.resolve('../../babel/webpack-loader'),
options: {
cacheDirectory: (cacheOptions.enabled && path.join(cacheOptions.path, 'babel-dev-server-i18n')) ||
false,
cacheIdentifier: JSON.stringify({
locale,
translationIntegrity: localeDescription === null || localeDescription === void 0 ? void 0 : localeDescription.files.map((file) => file.integrity),
}),
i18n: i18nLoaderOptions,
},
},
],
};
// Get the rules and ensure the Webpack configuration is setup properly
const rules = ((_a = webpackConfig.module) === null || _a === void 0 ? void 0 : _a.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) => {
var _a;
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 = (_a = localeDescription.translation) !== null && _a !== void 0 ? _a : {};
}
compilation.hooks.finishModules.tap('build-angular', () => {
// After loaders are finished, clear out the now unneeded translations
i18nLoaderOptions.translation = undefined;
});
});
},
});
}
exports.default = (0, architect_1.createBuilder)(serveWebpackBrowser);
@@ -0,0 +1,84 @@
/**
* 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`.
*/
browserTarget: string;
/**
* Don't verify connected clients are part of allowed hosts.
*/
disableHostCheck?: 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;
}
@@ -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 });
@@ -0,0 +1,102 @@
{
"$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]+)?$"
},
"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."
}
},
"additionalProperties": false,
"required": ["browserTarget"]
}
@@ -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;
@@ -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;
@@ -0,0 +1,22 @@
/**
* @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 { BuildResult } from '@angular-devkit/build-webpack';
import { JsonObject } from '@angular-devkit/core';
import webpack from 'webpack';
import { ExecutionTransformer } from '../../transforms';
import { Schema } from './schema';
export declare type ExtractI18nBuilderOptions = Schema;
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: ExtractI18nBuilderOptions, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
}): Promise<BuildResult>;
declare const _default: import("@angular-devkit/architect/src/internal").Builder<Schema & JsonObject>;
export default _default;
@@ -0,0 +1,258 @@
"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 build_webpack_1 = require("@angular-devkit/build-webpack");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const webpack_1 = __importDefault(require("webpack"));
const i18n_options_1 = require("../../utils/i18n-options");
const load_esm_1 = require("../../utils/load-esm");
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 configs_1 = require("../../webpack/configs");
const stats_1 = require("../../webpack/utils/stats");
const schema_1 = require("../browser/schema");
const schema_2 = require("./schema");
function getI18nOutfile(format) {
switch (format) {
case 'xmb':
return 'messages.xmb';
case 'xlf':
case 'xlif':
case 'xliff':
case 'xlf2':
case 'xliff2':
return 'messages.xlf';
case 'json':
case 'legacy-migrate':
return 'messages.json';
case 'arb':
return 'messages.arb';
default:
throw new Error(`Unsupported format "${format}"`);
}
}
async function getSerializer(localizeToolsModule, format, sourceLocale, basePath, useLegacyIds, diagnostics) {
const { XmbTranslationSerializer, LegacyMessageIdMigrationSerializer, ArbTranslationSerializer, Xliff1TranslationSerializer, Xliff2TranslationSerializer, SimpleJsonTranslationSerializer, } = localizeToolsModule;
switch (format) {
case schema_2.Format.Xmb:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new XmbTranslationSerializer(basePath, useLegacyIds);
case schema_2.Format.Xlf:
case schema_2.Format.Xlif:
case schema_2.Format.Xliff:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Xliff1TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
case schema_2.Format.Xlf2:
case schema_2.Format.Xliff2:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new Xliff2TranslationSerializer(sourceLocale, basePath, useLegacyIds, {});
case schema_2.Format.Json:
return new SimpleJsonTranslationSerializer(sourceLocale);
case schema_2.Format.LegacyMigrate:
return new LegacyMessageIdMigrationSerializer(diagnostics);
case schema_2.Format.Arb:
const fileSystem = {
relative(from, to) {
return path.relative(from, to);
},
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return new ArbTranslationSerializer(sourceLocale, basePath, fileSystem);
}
}
function normalizeFormatOption(options) {
let format = options.format;
switch (format) {
case schema_2.Format.Xlf:
case schema_2.Format.Xlif:
case schema_2.Format.Xliff:
format = schema_2.Format.Xlf;
break;
case schema_2.Format.Xlf2:
case schema_2.Format.Xliff2:
format = schema_2.Format.Xlf2;
break;
}
// Default format is xliff1
return format !== null && format !== void 0 ? format : schema_2.Format.Xlf;
}
class NoEmitPlugin {
apply(compiler) {
compiler.hooks.shouldEmit.tap('angular-no-emit', () => false);
}
}
/**
* @experimental Direct usage of this function is considered experimental.
*/
async function execute(options, context, transforms) {
var _a;
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(context.workspaceRoot);
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
const browserTarget = (0, architect_1.targetFromTargetString)(options.browserTarget);
const browserOptions = await context.validateOptions(await context.getTargetOptions(browserTarget), await context.getBuilderNameForTarget(browserTarget));
const format = normalizeFormatOption(options);
// We need to determine the outFile name so that AngularCompiler can retrieve it.
let outFile = options.outFile || getI18nOutfile(format);
if (options.outputPath) {
// AngularCompilerPlugin doesn't support genDir so we have to adjust outFile instead.
outFile = path.join(options.outputPath, outFile);
}
outFile = path.resolve(context.workspaceRoot, outFile);
if (!context.target || !context.target.project) {
throw new Error('The builder requires a target.');
}
try {
require.resolve('@angular/localize');
}
catch {
return {
success: false,
error: `i18n extraction requires the '@angular/localize' package.`,
outputPath: outFile,
};
}
const metadata = await context.getProjectMetadata(context.target);
const i18n = (0, i18n_options_1.createI18nOptions)(metadata);
let useLegacyIds = true;
const ivyMessages = [];
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, projectRoot } = await (0, webpack_browser_config_1.generateBrowserWebpackConfigFromContext)(builderOptions, context, (wco) => {
var _a;
// Default value for legacy message ids is currently true
useLegacyIds = (_a = wco.tsConfig.options.enableI18nLegacyMessageIdFormat) !== null && _a !== void 0 ? _a : 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: (messages) => ivyMessages.push(...messages),
},
},
],
},
});
// 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 });
// 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.
const localizeToolsModule = await (0, load_esm_1.loadEsmModule)('@angular/localize/tools');
const webpackResult = await (0, build_webpack_1.runWebpack)((await ((_a = transforms === null || transforms === void 0 ? void 0 : transforms.webpackConfiguration) === null || _a === void 0 ? void 0 : _a.call(transforms, config))) || config, context, {
logging: (0, stats_1.createWebpackLoggingCallback)(builderOptions, context.logger),
webpackFactory: webpack_1.default,
}).toPromise();
// Set the outputPath to the extraction output location for downstream consumers
webpackResult.outputPath = outFile;
// Complete if Webpack build failed
if (!webpackResult.success) {
return webpackResult;
}
const basePath = config.context || projectRoot;
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 path.relative(from, to);
},
};
const diagnostics = checkDuplicateMessages(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
checkFileSystem, ivyMessages, 'warning',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
basePath);
if (diagnostics.messages.length > 0) {
context.logger.warn(diagnostics.formatDiagnostics(''));
}
// Serialize all extracted messages
const serializer = await getSerializer(localizeToolsModule, format, i18n.sourceLocale, basePath, useLegacyIds, diagnostics);
const content = serializer.serialize(ivyMessages);
// Ensure directory exists
const outputPath = path.dirname(outFile);
if (!fs.existsSync(outputPath)) {
fs.mkdirSync(outputPath, { recursive: true });
}
// Write translation file
fs.writeFileSync(outFile, content);
return webpackResult;
}
exports.execute = execute;
exports.default = (0, architect_1.createBuilder)(execute);
@@ -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
*/
declare 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 {};
@@ -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 === null || mapObject === void 0 ? void 0 : 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 === null || options === void 0 ? void 0 : options.messageHandler(messages);
}
}
@@ -0,0 +1,41 @@
/**
* 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`.
*/
browserTarget: 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"
}
@@ -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 || (exports.Format = {}));
@@ -0,0 +1,34 @@
{
"$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]+)?$"
},
"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,
"required": ["browserTarget"]
}
@@ -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 type { Compiler } from 'webpack';
export interface FindTestsPluginOptions {
include?: string[];
workspaceRoot: string;
projectSourceRoot: string;
}
export declare class FindTestsPlugin {
private options;
private compilation;
constructor(options: FindTestsPluginOptions);
apply(compiler: Compiler): void;
}
@@ -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 __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 fs_1 = require("fs");
const glob_1 = __importStar(require("glob"));
const path_1 = require("path");
const util_1 = require("util");
const webpack_diagnostics_1 = require("../../utils/webpack-diagnostics");
const globPromise = (0, util_1.promisify)(glob_1.default);
/**
* The name of the plugin provided to Webpack when tapping Webpack compiler hooks.
*/
const PLUGIN_NAME = 'angular-find-tests-plugin';
class FindTestsPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
const { include = ['**/*.spec.ts'], 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, workspaceRoot, projectSourceRoot);
if (!specFiles.length) {
(0, assert_1.default)(this.compilation, 'Compilation cannot be undefined.');
(0, webpack_diagnostics_1.addError)(this.compilation, `Specified patterns: "${include.join(', ')}" did not match any spec files.`);
}
const entrypoints = await entry;
const entrypoint = entrypoints['main'];
if (!entrypoint.import) {
throw new Error(`Cannot find 'main' entrypoint.`);
}
originalImport !== null && originalImport !== void 0 ? originalImport : (originalImport = entrypoint.import);
entrypoint.import = [...originalImport, ...specFiles];
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(patterns, workspaceRoot, projectSourceRoot) {
const matchingTestsPromises = patterns.map((pattern) => findMatchingTests(pattern, workspaceRoot, projectSourceRoot));
const files = await Promise.all(matchingTestsPromises);
// Unique file names
return [...new Set(files.flat())];
}
const normalizePath = (path) => path.replace(/\\/g, '/');
async function findMatchingTests(pattern, workspaceRoot, projectSourceRoot) {
// normalize pattern, glob lib only accepts forward slashes
let normalizedPattern = normalizePath(pattern);
if (normalizedPattern.charAt(0) === '/') {
normalizedPattern = normalizedPattern.substring(1);
}
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
if (normalizedPattern.startsWith(relativeProjectRoot)) {
normalizedPattern = normalizedPattern.substring(relativeProjectRoot.length);
}
// special logic when pattern does not look like a glob
if (!(0, glob_1.hasMagic)(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];
}
}
}
return globPromise(normalizedPattern, {
cwd: projectSourceRoot,
root: projectSourceRoot,
nomount: true,
absolute: true,
ignore: ['**/node_modules/**'],
});
}
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;
}
}
@@ -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 declare 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("@angular-devkit/architect/src/internal").Builder<Record<string, string> & KarmaBuilderOptions & import("@angular-devkit/core").JsonObject>;
export default _default;
@@ -0,0 +1,208 @@
"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 operators_1 = require("rxjs/operators");
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 configs_1 = require("../../webpack/configs");
const schema_1 = require("../browser/schema");
const find_tests_plugin_1 = require("./find-tests-plugin");
async function initialize(options, context, webpackConfigurationTransformer) {
var _a;
// 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, (_a = (await (webpackConfigurationTransformer === null || webpackConfigurationTransformer === void 0 ? void 0 : webpackConfigurationTransformer(config)))) !== null && _a !== void 0 ? _a : 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, operators_1.switchMap)(async ([karma, webpackConfig]) => {
var _a, _b, _c, _d, _e;
// Determine project name from builder context target
const projectName = (_a = context.target) === null || _a === void 0 ? void 0 : _a.project;
if (!projectName) {
throw new Error(`The 'karma' builder requires a target to be specified.`);
}
const karmaOptions = options.karmaConfig
? {}
: getBuiltInKarmaConfig(karma, context.workspaceRoot, projectName);
karmaOptions.singleRun = singleRun;
// Convert browsers from a string to an array
if (options.browsers) {
karmaOptions.browsers = options.browsers.split(',');
}
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) {
(_b = webpackConfig.entry) !== null && _b !== void 0 ? _b : (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 = ((_d = (_c = projectMetadata.sourceRoot) !== null && _c !== void 0 ? _c : projectMetadata.root) !== null && _d !== void 0 ? _d : '');
(_e = webpackConfig.plugins) !== null && _e !== void 0 ? _e : (webpackConfig.plugins = []);
webpackConfig.plugins.push(new find_tests_plugin_1.FindTestsPlugin({
include: options.include,
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, operators_1.switchMap)(([karma, karmaConfig]) => new rxjs_1.Observable((subscriber) => {
var _a, _b, _c;
var _d, _e;
// Pass onto Karma to emit BuildEvents.
(_a = karmaConfig.buildWebpack) !== null && _a !== void 0 ? _a : (karmaConfig.buildWebpack = {});
if (typeof karmaConfig.buildWebpack === 'object') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(_b = (_d = karmaConfig.buildWebpack).failureCb) !== null && _b !== void 0 ? _b : (_d.failureCb = () => subscriber.next({ success: false }));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(_c = (_e = karmaConfig.buildWebpack).successCb) !== null && _c !== void 0 ? _c : (_e.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 () => karmaStart.then(() => karmaServer.stop());
})), (0, operators_1.defaultIfEmpty)({ success: false }));
}
exports.execute = execute;
function getBuiltInKarmaConfig(karma, 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 + '/');
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'],
port: 9876,
colors: true,
logLevel: karma.constants.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
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}`;
}
@@ -0,0 +1,192 @@
/**
* Karma target options for Build Facade.
*/
export interface Schema {
/**
* List of static application assets.
*/
assets?: AssetPattern[];
/**
* Override which browsers tests are run against.
*/
browsers?: string;
/**
* Output a code coverage report.
*/
codeCoverage?: boolean;
/**
* Globs to exclude from code coverage.
*/
codeCoverageExclude?: string[];
/**
* Replace compilation source files with other compilation source files in the build.
*/
fileReplacements?: FileReplacement[];
/**
* Globs of files to include, relative to workspace or 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 declare 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;
}
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 declare type Polyfills = string[] | string;
export declare 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 declare 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 declare 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;
}
@@ -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 || (exports.InlineStyleLanguage = {}));
@@ -0,0 +1,294 @@
{
"$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 workspace or 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."
},
"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": {
"type": "string",
"description": "Override which browsers tests are run against."
},
"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"
}
]
}
}
}
@@ -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("@angular-devkit/architect/src/internal").Builder<Record<string, string> & NgPackagrBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;
@@ -0,0 +1,68 @@
"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 operators_1 = require("rxjs/operators");
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 () => {
var _a;
// 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 = (_a = context.target) === null || _a === void 0 ? void 0 : _a.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, operators_1.switchMap)(({ packager, ngPackagrOptions }) => options.watch ? packager.watch(ngPackagrOptions) : packager.build(ngPackagrOptions)), (0, operators_1.mapTo)({ success: true }), (0, operators_1.catchError)((err) => (0, rxjs_1.of)({ success: false, error: err.message })));
}
exports.execute = execute;
exports.default = (0, architect_1.createBuilder)(execute);
@@ -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;
}
@@ -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 });
@@ -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"]
}
@@ -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 { json } from '@angular-devkit/core';
import { Schema as ProtractorBuilderOptions } from './schema';
export { ProtractorBuilderOptions };
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: ProtractorBuilderOptions, context: BuilderContext): Promise<BuilderOutput>;
declare const _default: import("@angular-devkit/architect/src/internal").Builder<ProtractorBuilderOptions & json.JsonObject>;
export default _default;
@@ -0,0 +1,167 @@
"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 path_1 = require("path");
const url = __importStar(require("url"));
const utils_1 = require("../../utils");
const error_1 = require("../../utils/error");
function runProtractor(root, options) {
const additionalProtractorConfig = {
baseUrl: options.baseUrl,
specs: options.specs && options.specs.length ? options.specs : undefined,
suite: options.suite,
jasmineNodeOpts: {
grep: options.grep,
invertGrep: options.invertGrep,
},
};
// TODO: Protractor manages process.exit itself, so this target will allways quit the
// process. To work around this we run it in a subprocess.
// https://github.com/angular/protractor/issues/4160
return (0, utils_1.runModuleAsObservableFork)(root, 'protractor/built/launcher', 'init', [
(0, path_1.resolve)(root, options.protractorConfig),
additionalProtractorConfig,
]).toPromise();
}
async function updateWebdriver() {
// The webdriver-manager update command can only be accessed via a deep import.
const webdriverDeepImport = 'webdriver-manager/built/lib/cmds/update';
let path;
try {
const protractorPath = require.resolve('protractor');
path = require.resolve(webdriverDeepImport, { paths: [protractorPath] });
}
catch (error) {
(0, error_1.assertIsError)(error);
if (error.code !== 'MODULE_NOT_FOUND') {
throw error;
}
}
if (!path) {
throw new Error(core_1.tags.stripIndents `
Cannot automatically find webdriver-manager to update.
Update webdriver-manager manually and run 'ng e2e --no-webdriver-update' instead.
`);
}
const webdriverUpdate = await Promise.resolve().then(() => __importStar(require(path)));
// const webdriverUpdate = await import(path) as typeof import ('webdriver-manager/built/lib/cmds/update');
// run `webdriver-manager update --standalone false --gecko false --quiet`
// if you change this, update the command comment in prev line
return webdriverUpdate.program.run({
standalone: false,
gecko: false,
quiet: true,
});
}
/**
* @experimental Direct usage of this function is considered experimental.
*/
async function execute(options, context) {
context.logger.warn('Protractor has been deprecated including its support in the Angular CLI. For additional information and alternatives, please see https://github.com/angular/protractor/issues/5502.');
// ensure that only one of these options is used
if (options.devServerTarget && options.baseUrl) {
throw new Error(core_1.tags.stripIndents `
The 'baseUrl' option cannot be used with 'devServerTarget'.
When present, 'devServerTarget' will be used to automatically setup 'baseUrl' for Protractor.
`);
}
if (options.webdriverUpdate) {
await updateWebdriver();
}
let baseUrl = options.baseUrl;
let server;
try {
if (options.devServerTarget) {
const target = (0, architect_1.targetFromTargetString)(options.devServerTarget);
const serverOptions = await context.getTargetOptions(target);
const overrides = {
watch: false,
liveReload: false,
};
if (options.host !== undefined) {
overrides.host = options.host;
}
else if (typeof serverOptions.host === 'string') {
options.host = serverOptions.host;
}
else {
options.host = overrides.host = 'localhost';
}
if (options.port !== undefined) {
overrides.port = options.port;
}
else if (typeof serverOptions.port === 'number') {
options.port = serverOptions.port;
}
server = await context.scheduleTarget(target, overrides);
const result = await server.result;
if (!result.success) {
return { success: false };
}
if (typeof serverOptions.publicHost === 'string') {
let publicHost = serverOptions.publicHost;
if (!/^\w+:\/\//.test(publicHost)) {
publicHost = `${serverOptions.ssl ? 'https' : 'http'}://${publicHost}`;
}
const clientUrl = url.parse(publicHost);
baseUrl = url.format(clientUrl);
}
else if (typeof result.baseUrl === 'string') {
baseUrl = result.baseUrl;
}
else if (typeof result.port === 'number') {
baseUrl = url.format({
protocol: serverOptions.ssl ? 'https' : 'http',
hostname: options.host,
port: result.port.toString(),
});
}
}
// Like the baseUrl in protractor config file when using the API we need to add
// a trailing slash when provide to the baseUrl.
if (baseUrl && !baseUrl.endsWith('/')) {
baseUrl += '/';
}
return await runProtractor(context.workspaceRoot, { ...options, baseUrl });
}
catch {
return { success: false };
}
finally {
await (server === null || server === void 0 ? void 0 : server.stop());
}
}
exports.execute = execute;
exports.default = (0, architect_1.createBuilder)(execute);
@@ -0,0 +1,47 @@
/**
* Protractor target options for Build Facade.
*/
export interface Schema {
/**
* Base URL for protractor to connect to.
*/
baseUrl?: string;
/**
* A dev-server builder target to run tests against 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`.
*/
devServerTarget?: string;
/**
* Execute specs whose names match the pattern, which is internally compiled to a RegExp.
*/
grep?: string;
/**
* Host to listen on.
*/
host?: string;
/**
* Invert the selection specified by the 'grep' option.
*/
invertGrep?: boolean;
/**
* The port to use to serve the application.
*/
port?: number;
/**
* The name of the Protractor configuration file.
*/
protractorConfig: string;
/**
* Override specs in the protractor config.
*/
specs?: string[];
/**
* Override suite in the protractor config.
*/
suite?: string;
/**
* Try to update webdriver.
*/
webdriverUpdate?: boolean;
}
@@ -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 });
@@ -0,0 +1,58 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"title": "Protractor Target",
"description": "Protractor target options for Build Facade.",
"type": "object",
"properties": {
"protractorConfig": {
"type": "string",
"description": "The name of the Protractor configuration file."
},
"devServerTarget": {
"type": "string",
"description": "A dev-server builder target to run tests against 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]+)?)?$"
},
"grep": {
"type": "string",
"description": "Execute specs whose names match the pattern, which is internally compiled to a RegExp."
},
"invertGrep": {
"type": "boolean",
"description": "Invert the selection specified by the 'grep' option.",
"default": false
},
"specs": {
"type": "array",
"description": "Override specs in the protractor config.",
"default": [],
"items": {
"type": "string",
"description": "Spec name."
}
},
"suite": {
"type": "string",
"description": "Override suite in the protractor config."
},
"webdriverUpdate": {
"type": "boolean",
"description": "Try to update webdriver.",
"default": true
},
"port": {
"type": "number",
"description": "The port to use to serve the application."
},
"host": {
"type": "string",
"description": "Host to listen on."
},
"baseUrl": {
"type": "string",
"description": "Base URL for protractor to connect to."
}
},
"additionalProperties": false,
"required": ["protractorConfig"]
}
@@ -0,0 +1,39 @@
/**
* @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 webpack from 'webpack';
import { ExecutionTransformer } from '../../transforms';
import { Schema as ServerBuilderOptions } from './schema';
/**
* @experimental Direct usage of this type is considered experimental.
*/
export declare type ServerBuilderOutput = BuilderOutput & {
baseOutputPath: string;
/**
* @deprecated in version 14. Use 'outputs' instead.
*/
outputPaths: string[];
/**
* @deprecated in version 9. Use 'outputs' instead.
*/
outputPath: string;
outputs: {
locale?: string;
path: string;
}[];
};
export { ServerBuilderOptions };
/**
* @experimental Direct usage of this function is considered experimental.
*/
export declare function execute(options: ServerBuilderOptions, context: BuilderContext, transforms?: {
webpackConfiguration?: ExecutionTransformer<webpack.Configuration>;
}): Observable<ServerBuilderOutput>;
declare const _default: import("@angular-devkit/architect/src/internal").Builder<ServerBuilderOptions & import("../../../../core/src").JsonObject>;
export default _default;
@@ -0,0 +1,146 @@
"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 build_webpack_1 = require("@angular-devkit/build-webpack");
const path = __importStar(require("path"));
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
const utils_1 = require("../../utils");
const i18n_inlining_1 = require("../../utils/i18n-inlining");
const output_paths_1 = require("../../utils/output-paths");
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 configs_1 = require("../../webpack/configs");
const helpers_1 = require("../../webpack/utils/helpers");
const stats_1 = require("../../webpack/utils/stats");
/**
* @experimental Direct usage of this function is considered experimental.
*/
function execute(options, context, transforms = {}) {
const root = context.workspaceRoot;
// Check Angular version.
(0, version_1.assertCompatibleAngularVersion)(root);
const baseOutputPath = path.resolve(root, options.outputPath);
let outputPaths;
return (0, rxjs_1.from)(initialize(options, context, transforms.webpackConfiguration)).pipe((0, operators_1.concatMap)(({ config, i18n }) => {
return (0, build_webpack_1.runWebpack)(config, context, {
webpackFactory: require('webpack'),
logging: (stats, config) => {
if (options.verbose) {
context.logger.info(stats.toString(config.stats));
}
},
}).pipe((0, operators_1.concatMap)(async (output) => {
const { emittedFiles = [], outputPath, webpackStats } = output;
if (!webpackStats) {
throw new Error('Webpack stats build result is required.');
}
let success = output.success;
if (success && i18n.shouldInline) {
outputPaths = (0, output_paths_1.ensureOutputPaths)(baseOutputPath, i18n);
success = await (0, i18n_inlining_1.i18nInlineEmittedFiles)(context, emittedFiles, i18n, baseOutputPath, Array.from(outputPaths.values()), [], outputPath, options.i18nMissingTranslation);
}
(0, stats_1.webpackStatsLogger)(context.logger, webpackStats, config);
return { ...output, success };
}));
}), (0, operators_1.map)((output) => {
if (!output.success) {
return output;
}
return {
...output,
baseOutputPath,
outputPath: baseOutputPath,
outputPaths: outputPaths || [baseOutputPath],
outputs: (outputPaths &&
[...outputPaths.entries()].map(([locale, path]) => ({
locale,
path,
}))) || {
path: baseOutputPath,
},
};
}));
}
exports.execute = execute;
exports.default = (0, architect_1.createBuilder)(execute);
async function initialize(options, context, webpackConfigurationTransform) {
var _a;
// Purge old build disk cache.
await (0, purge_cache_1.purgeStaleBuildCache)(context);
const browserslist = (await Promise.resolve().then(() => __importStar(require('browserslist')))).default;
const originalOutputPath = options.outputPath;
const { config, i18n } = await (0, webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext)({
...options,
buildOptimizer: false,
aot: true,
platform: 'server',
}, context, (wco) => {
var _a;
var _b;
// We use the platform to determine the JavaScript syntax output.
(_a = (_b = wco.buildOptions).supportedBrowsers) !== null && _a !== void 0 ? _a : (_b.supportedBrowsers = []);
wco.buildOptions.supportedBrowsers.push(...browserslist('maintained node versions'));
return [getPlatformServerExportsConfig(wco), (0, configs_1.getCommonConfig)(wco), (0, configs_1.getStylesConfig)(wco)];
});
if (options.deleteOutputPath) {
(0, utils_1.deleteOutputDir)(context.workspaceRoot, originalOutputPath);
}
const transformedConfig = (_a = (await (webpackConfigurationTransform === null || webpackConfigurationTransform === void 0 ? void 0 : webpackConfigurationTransform(config)))) !== null && _a !== void 0 ? _a : config;
return { config: transformedConfig, i18n };
}
/**
* Add `@angular/platform-server` exports.
* This is needed so that DI tokens can be referenced and set at runtime outside of the bundle.
*/
function getPlatformServerExportsConfig(wco) {
// Add `@angular/platform-server` exports.
// This is needed so that DI tokens can be referenced and set at runtime outside of the bundle.
// Only add `@angular/platform-server` exports when it is installed.
// In some cases this builder is used when `@angular/platform-server` is not installed.
// Example: when using `@nguniversal/common/clover` which does not need `@angular/platform-server`.
return (0, helpers_1.isPlatformServerInstalled)(wco.root)
? {
module: {
rules: [
{
loader: require.resolve('./platform-server-exports-loader'),
include: [path.resolve(wco.root, wco.buildOptions.main)],
},
],
},
}
: {};
}
@@ -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
*/
/**
* This loader is needed to add additional exports and is a workaround for a Webpack bug that doesn't
* allow exports from multiple files in the same entry.
* @see https://github.com/webpack/webpack/issues/15936.
*/
export default function (this: import('webpack').LoaderContext<{}>, content: string, map: Parameters<import('webpack').LoaderDefinitionFunction>[1]): void;
@@ -0,0 +1,24 @@
"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 });
/**
* This loader is needed to add additional exports and is a workaround for a Webpack bug that doesn't
* allow exports from multiple files in the same entry.
* @see https://github.com/webpack/webpack/issues/15936.
*/
function default_1(content, map) {
const source = `${content}
// EXPORTS added by @angular-devkit/build-angular
export { renderModule, ɵSERVER_CONTEXT } from '@angular/platform-server';
`;
this.callback(null, source, map);
return;
}
exports.default = default_1;
@@ -0,0 +1,198 @@
export interface Schema {
/**
* Delete the output path before building.
*/
deleteOutputPath?: boolean;
/**
* URL where files will be deployed.
* @deprecated Use "baseHref" browser builder 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, in the case of production builds only.
*/
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;
/**
* 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 name of the main entry-point file.
*/
main: string;
/**
* Use file name for lazy loaded chunks.
*/
namedChunks?: boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking and dead-code elimination. For more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
optimization?: OptimizationUnion;
/**
* Define the output filename cache-busting hashing mode.
*/
outputHashing?: OutputHashing;
/**
* Path where output will be placed.
*/
outputPath: string;
/**
* Enable and define the file watching poll time period in milliseconds.
*/
poll?: number;
/**
* 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;
/**
* 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;
/**
* The name of the TypeScript configuration file.
*/
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;
}
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"
}
/**
* 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 declare type Localize = string[] | boolean;
/**
* Enables optimization of the build output. Including minification of scripts and styles,
* tree-shaking and dead-code elimination. For more information, see
* https://angular.io/guide/workspace-config#optimization-configuration.
*/
export declare type OptimizationUnion = boolean | OptimizationClass;
export interface OptimizationClass {
/**
* Enables optimization of the scripts output.
*/
scripts?: boolean;
/**
* Enables optimization of the styles output.
*/
styles?: boolean;
}
/**
* Define the output filename cache-busting hashing mode.
*/
export declare enum OutputHashing {
All = "all",
Bundles = "bundles",
Media = "media",
None = "none"
}
/**
* Output source maps for scripts and styles. For more information, see
* https://angular.io/guide/workspace-config#source-map-configuration.
*/
export declare 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[];
}
@@ -0,0 +1,36 @@
"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 = void 0;
/**
* 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 || (exports.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 || (exports.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 || (exports.OutputHashing = {}));
@@ -0,0 +1,250 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "BuildAngularWebpackServerSchema",
"title": "Universal Target",
"type": "object",
"properties": {
"main": {
"type": "string",
"description": "The name of the main entry-point file."
},
"tsConfig": {
"type": "string",
"default": "tsconfig.app.json",
"description": "The name of the TypeScript configuration file."
},
"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 and dead-code elimination. 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": {
"type": "boolean",
"description": "Enables optimization of the styles output.",
"default": true
}
},
"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": "Path where output will be placed."
},
"resourcesOutputPath": {
"type": "string",
"description": "The path where style resources will be placed, relative to outputPath."
},
"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"
}
]
},
"deployUrl": {
"type": "string",
"description": "URL where files will be deployed.",
"x-deprecated": "Use \"baseHref\" browser builder option, \"APP_BASE_HREF\" DI token or a combination of both instead. For more information, see https://angular.io/guide/deployment#the-deploy-url."
},
"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
},
"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})+)?$"
}
}
]
},
"outputHashing": {
"type": "string",
"description": "Define the output filename cache-busting hashing mode.",
"default": "none",
"enum": ["none", "all", "media", "bundles"]
},
"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, in the case of production builds only.",
"default": true
},
"namedChunks": {
"type": "boolean",
"description": "Use file name for lazy loaded chunks.",
"default": 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": []
},
"statsJson": {
"type": "boolean",
"description": "Generates a 'stats.json' file which can be analyzed using tools such as 'webpack-bundle-analyzer'.",
"default": false
},
"watch": {
"type": "boolean",
"description": "Run build when files change.",
"default": false
},
"poll": {
"type": "number",
"description": "Enable and define the file watching poll time period in milliseconds."
}
},
"additionalProperties": false,
"required": ["outputPath", "main", "tsConfig"],
"definitions": {
"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"]
}
]
}
}
}
+16
View File
@@ -0,0 +1,16 @@
/**
* @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 * from './transforms';
export { AssetPattern, AssetPatternClass as AssetPatternObject, Budget, CrossOrigin, FileReplacement, OptimizationClass as OptimizationObject, OptimizationUnion, OutputHashing, Schema as BrowserBuilderOptions, SourceMapClass as SourceMapObject, SourceMapUnion, StylePreprocessorOptions, Type, } from './builders/browser/schema';
export { buildWebpackBrowser as executeBrowserBuilder, BrowserBuilderOutput, } from './builders/browser';
export { serveWebpackBrowser as executeDevServerBuilder, DevServerBuilderOptions, DevServerBuilderOutput, } from './builders/dev-server';
export { execute as executeExtractI18nBuilder, ExtractI18nBuilderOptions, } from './builders/extract-i18n';
export { execute as executeKarmaBuilder, KarmaBuilderOptions, KarmaConfigOptions, } from './builders/karma';
export { execute as executeProtractorBuilder, ProtractorBuilderOptions, } from './builders/protractor';
export { execute as executeServerBuilder, ServerBuilderOptions, ServerBuilderOutput, } from './builders/server';
export { execute as executeNgPackagrBuilder, NgPackagrBuilderOptions } from './builders/ng-packagr';
+43
View File
@@ -0,0 +1,43 @@
"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 __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeNgPackagrBuilder = exports.executeServerBuilder = exports.executeProtractorBuilder = exports.executeKarmaBuilder = exports.executeExtractI18nBuilder = exports.executeDevServerBuilder = exports.executeBrowserBuilder = exports.Type = exports.OutputHashing = exports.CrossOrigin = void 0;
__exportStar(require("./transforms"), exports);
var schema_1 = require("./builders/browser/schema");
Object.defineProperty(exports, "CrossOrigin", { enumerable: true, get: function () { return schema_1.CrossOrigin; } });
Object.defineProperty(exports, "OutputHashing", { enumerable: true, get: function () { return schema_1.OutputHashing; } });
Object.defineProperty(exports, "Type", { enumerable: true, get: function () { return schema_1.Type; } });
var browser_1 = require("./builders/browser");
Object.defineProperty(exports, "executeBrowserBuilder", { enumerable: true, get: function () { return browser_1.buildWebpackBrowser; } });
var dev_server_1 = require("./builders/dev-server");
Object.defineProperty(exports, "executeDevServerBuilder", { enumerable: true, get: function () { return dev_server_1.serveWebpackBrowser; } });
var extract_i18n_1 = require("./builders/extract-i18n");
Object.defineProperty(exports, "executeExtractI18nBuilder", { enumerable: true, get: function () { return extract_i18n_1.execute; } });
var karma_1 = require("./builders/karma");
Object.defineProperty(exports, "executeKarmaBuilder", { enumerable: true, get: function () { return karma_1.execute; } });
var protractor_1 = require("./builders/protractor");
Object.defineProperty(exports, "executeProtractorBuilder", { enumerable: true, get: function () { return protractor_1.execute; } });
var server_1 = require("./builders/server");
Object.defineProperty(exports, "executeServerBuilder", { enumerable: true, get: function () { return server_1.execute; } });
var ng_packagr_1 = require("./builders/ng-packagr");
Object.defineProperty(exports, "executeNgPackagrBuilder", { enumerable: true, get: function () { return ng_packagr_1.execute; } });
@@ -0,0 +1,98 @@
/**
* @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="node" />
import { RawSourceMap } from '@ampproject/remapping';
import { Dirent } from 'node:fs';
import type { FileImporter, Importer, ImporterResult } from 'sass';
/**
* A Sass Importer base class that provides the load logic to rebase all `url()` functions
* within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler
* reflect the final filesystem location of the output CSS file.
*
* This class provides the core of the rebasing functionality. To ensure that each file is processed
* by this importer's load implementation, the Sass compiler requires the importer's canonicalize
* function to return a non-null value with the resolved location of the requested stylesheet.
* Concrete implementations of this class must provide this canonicalize functionality for rebasing
* to be effective.
*/
declare abstract class UrlRebasingImporter implements Importer<'sync'> {
private entryDirectory;
private rebaseSourceMaps?;
/**
* @param entryDirectory The directory of the entry stylesheet that was passed to the Sass compiler.
* @param rebaseSourceMaps When provided, rebased files will have an intermediate sourcemap added to the Map
* which can be used to generate a final sourcemap that contains original sources.
*/
constructor(entryDirectory: string, rebaseSourceMaps?: Map<string, RawSourceMap> | undefined);
abstract canonicalize(url: string, options: {
fromImport: boolean;
}): URL | null;
load(canonicalUrl: URL): ImporterResult | null;
}
/**
* Provides the Sass importer logic to resolve relative stylesheet imports via both import and use rules
* and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that
* the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file.
*/
export declare class RelativeUrlRebasingImporter extends UrlRebasingImporter {
private directoryCache;
constructor(entryDirectory: string, directoryCache?: Map<string, Dirent[]>, rebaseSourceMaps?: Map<string, RawSourceMap>);
canonicalize(url: string, options: {
fromImport: boolean;
}): URL | null;
/**
* Attempts to resolve a provided URL to a stylesheet file using the Sass compiler's resolution algorithm.
* Based on https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart
* @param url The file protocol URL to resolve.
* @param fromImport If true, URL was from an import rule; otherwise from a use rule.
* @param checkDirectory If true, try checking for a directory with the base name containing an index file.
* @returns A full resolved URL of the stylesheet file or `null` if not found.
*/
private resolveImport;
/**
* Checks an array of potential stylesheet files to determine if there is a valid
* stylesheet file. More than one discovered file may indicate an error.
* @param found An array of discovered stylesheet files.
* @returns A fully resolved URL for a stylesheet file or `null` if not found.
* @throws If there are ambiguous files discovered.
*/
private checkFound;
}
/**
* Provides the Sass importer logic to resolve module (npm package) stylesheet imports via both import and
* use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that
* the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file.
*/
export declare class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter {
private finder;
constructor(entryDirectory: string, directoryCache: Map<string, Dirent[]>, rebaseSourceMaps: Map<string, RawSourceMap> | undefined, finder: FileImporter<'sync'>['findFileUrl']);
canonicalize(url: string, options: {
fromImport: boolean;
}): URL | null;
}
/**
* Provides the Sass importer logic to resolve load paths located stylesheet imports via both import and
* use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that
* the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file.
*/
export declare class LoadPathsUrlRebasingImporter extends RelativeUrlRebasingImporter {
private loadPaths;
constructor(entryDirectory: string, directoryCache: Map<string, Dirent[]>, rebaseSourceMaps: Map<string, RawSourceMap> | undefined, loadPaths: Iterable<string>);
canonicalize(url: string, options: {
fromImport: boolean;
}): URL | null;
}
/**
* Workaround for Sass not calling instance methods with `this`.
* The `canonicalize` and `load` methods will be bound to the class instance.
* @param importer A Sass importer to bind.
* @returns The bound Sass importer.
*/
export declare function sassBindWorkaround<T extends Importer>(importer: T): T;
export {};
@@ -0,0 +1,425 @@
"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.sassBindWorkaround = exports.LoadPathsUrlRebasingImporter = exports.ModuleUrlRebasingImporter = exports.RelativeUrlRebasingImporter = void 0;
const magic_string_1 = __importDefault(require("magic-string"));
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const node_url_1 = require("node:url");
/**
* A Sass Importer base class that provides the load logic to rebase all `url()` functions
* within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler
* reflect the final filesystem location of the output CSS file.
*
* This class provides the core of the rebasing functionality. To ensure that each file is processed
* by this importer's load implementation, the Sass compiler requires the importer's canonicalize
* function to return a non-null value with the resolved location of the requested stylesheet.
* Concrete implementations of this class must provide this canonicalize functionality for rebasing
* to be effective.
*/
class UrlRebasingImporter {
/**
* @param entryDirectory The directory of the entry stylesheet that was passed to the Sass compiler.
* @param rebaseSourceMaps When provided, rebased files will have an intermediate sourcemap added to the Map
* which can be used to generate a final sourcemap that contains original sources.
*/
constructor(entryDirectory, rebaseSourceMaps) {
this.entryDirectory = entryDirectory;
this.rebaseSourceMaps = rebaseSourceMaps;
}
load(canonicalUrl) {
const stylesheetPath = (0, node_url_1.fileURLToPath)(canonicalUrl);
const stylesheetDirectory = (0, node_path_1.dirname)(stylesheetPath);
let contents = (0, node_fs_1.readFileSync)(stylesheetPath, 'utf-8');
// Rebase any URLs that are found
let updatedContents;
for (const { start, end, value } of findUrls(contents)) {
// Skip if value is empty or a Sass variable
if (value.length === 0 || value.startsWith('$')) {
continue;
}
// Skip if root-relative, absolute or protocol relative url
if (/^((?:\w+:)?\/\/|data:|chrome:|#|\/)/.test(value)) {
continue;
}
const rebasedPath = (0, node_path_1.relative)(this.entryDirectory, (0, node_path_1.join)(stylesheetDirectory, value));
// Normalize path separators and escape characters
// https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax
const rebasedUrl = './' + rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&');
updatedContents !== null && updatedContents !== void 0 ? updatedContents : (updatedContents = new magic_string_1.default(contents));
updatedContents.update(start, end, rebasedUrl);
}
if (updatedContents) {
contents = updatedContents.toString();
if (this.rebaseSourceMaps) {
// Generate an intermediate source map for the rebasing changes
const map = updatedContents.generateMap({
hires: true,
includeContent: true,
source: canonicalUrl.href,
});
this.rebaseSourceMaps.set(canonicalUrl.href, map);
}
}
let syntax;
switch ((0, node_path_1.extname)(stylesheetPath).toLowerCase()) {
case 'css':
syntax = 'css';
break;
case 'sass':
syntax = 'indented';
break;
default:
syntax = 'scss';
break;
}
return {
contents,
syntax,
sourceMapUrl: canonicalUrl,
};
}
}
/**
* Determines if a unicode code point is a CSS whitespace character.
* @param code The unicode code point to test.
* @returns true, if the code point is CSS whitespace; false, otherwise.
*/
function isWhitespace(code) {
// Based on https://www.w3.org/TR/css-syntax-3/#whitespace
switch (code) {
case 0x0009: // tab
case 0x0020: // space
case 0x000a: // line feed
case 0x000c: // form feed
case 0x000d: // carriage return
return true;
default:
return false;
}
}
/**
* Scans a CSS or Sass file and locates all valid url function values as defined by the CSS
* syntax specification.
* @param contents A string containing a CSS or Sass file to scan.
* @returns An iterable that yields each CSS url function value found.
*/
function* findUrls(contents) {
let pos = 0;
let width = 1;
let current = -1;
const next = () => {
var _a;
pos += width;
current = (_a = contents.codePointAt(pos)) !== null && _a !== void 0 ? _a : -1;
width = current > 0xffff ? 2 : 1;
return current;
};
// Based on https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token
while ((pos = contents.indexOf('url(', pos)) !== -1) {
// Set to position of the (
pos += 3;
width = 1;
// Consume all leading whitespace
while (isWhitespace(next())) {
/* empty */
}
// Initialize URL state
const url = { start: pos, end: -1, value: '' };
let complete = false;
// If " or ', then consume the value as a string
if (current === 0x0022 || current === 0x0027) {
const ending = current;
// Based on https://www.w3.org/TR/css-syntax-3/#consume-string-token
while (!complete) {
switch (next()) {
case -1: // EOF
return;
case 0x000a: // line feed
case 0x000c: // form feed
case 0x000d: // carriage return
// Invalid
complete = true;
break;
case 0x005c: // \ -- character escape
// If not EOF or newline, add the character after the escape
switch (next()) {
case -1:
return;
case 0x000a: // line feed
case 0x000c: // form feed
case 0x000d: // carriage return
// Skip when inside a string
break;
default:
// TODO: Handle hex escape codes
url.value += String.fromCodePoint(current);
break;
}
break;
case ending:
// Full string position should include the quotes for replacement
url.end = pos + 1;
complete = true;
yield url;
break;
default:
url.value += String.fromCodePoint(current);
break;
}
}
next();
continue;
}
// Based on https://www.w3.org/TR/css-syntax-3/#consume-url-token
while (!complete) {
switch (current) {
case -1: // EOF
return;
case 0x0022: // "
case 0x0027: // '
case 0x0028: // (
// Invalid
complete = true;
break;
case 0x0029: // )
// URL is valid and complete
url.end = pos;
complete = true;
break;
case 0x005c: // \ -- character escape
// If not EOF or newline, add the character after the escape
switch (next()) {
case -1: // EOF
return;
case 0x000a: // line feed
case 0x000c: // form feed
case 0x000d: // carriage return
// Invalid
complete = true;
break;
default:
// TODO: Handle hex escape codes
url.value += String.fromCodePoint(current);
break;
}
break;
default:
if (isWhitespace(current)) {
while (isWhitespace(next())) {
/* empty */
}
// Unescaped whitespace is only valid before the closing )
if (current === 0x0029) {
// URL is valid
url.end = pos;
}
complete = true;
}
else {
// Add the character to the url value
url.value += String.fromCodePoint(current);
}
break;
}
next();
}
// An end position indicates a URL was found
if (url.end !== -1) {
yield url;
}
}
}
/**
* Provides the Sass importer logic to resolve relative stylesheet imports via both import and use rules
* and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that
* the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file.
*/
class RelativeUrlRebasingImporter extends UrlRebasingImporter {
constructor(entryDirectory, directoryCache = new Map(), rebaseSourceMaps) {
super(entryDirectory, rebaseSourceMaps);
this.directoryCache = directoryCache;
}
canonicalize(url, options) {
return this.resolveImport(url, options.fromImport, true);
}
/**
* Attempts to resolve a provided URL to a stylesheet file using the Sass compiler's resolution algorithm.
* Based on https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart
* @param url The file protocol URL to resolve.
* @param fromImport If true, URL was from an import rule; otherwise from a use rule.
* @param checkDirectory If true, try checking for a directory with the base name containing an index file.
* @returns A full resolved URL of the stylesheet file or `null` if not found.
*/
resolveImport(url, fromImport, checkDirectory) {
var _a;
let stylesheetPath;
try {
stylesheetPath = (0, node_url_1.fileURLToPath)(url);
}
catch {
// Only file protocol URLs are supported by this importer
return null;
}
const directory = (0, node_path_1.dirname)(stylesheetPath);
const extension = (0, node_path_1.extname)(stylesheetPath);
const hasStyleExtension = extension === '.scss' || extension === '.sass' || extension === '.css';
// Remove the style extension if present to allow adding the `.import` suffix
const filename = (0, node_path_1.basename)(stylesheetPath, hasStyleExtension ? extension : undefined);
let entries;
try {
entries = this.directoryCache.get(directory);
if (!entries) {
entries = (0, node_fs_1.readdirSync)(directory, { withFileTypes: true });
this.directoryCache.set(directory, entries);
}
}
catch {
return null;
}
const importPotentials = new Set();
const defaultPotentials = new Set();
if (hasStyleExtension) {
if (fromImport) {
importPotentials.add(filename + '.import' + extension);
importPotentials.add('_' + filename + '.import' + extension);
}
defaultPotentials.add(filename + extension);
defaultPotentials.add('_' + filename + extension);
}
else {
if (fromImport) {
importPotentials.add(filename + '.import.scss');
importPotentials.add(filename + '.import.sass');
importPotentials.add(filename + '.import.css');
importPotentials.add('_' + filename + '.import.scss');
importPotentials.add('_' + filename + '.import.sass');
importPotentials.add('_' + filename + '.import.css');
}
defaultPotentials.add(filename + '.scss');
defaultPotentials.add(filename + '.sass');
defaultPotentials.add(filename + '.css');
defaultPotentials.add('_' + filename + '.scss');
defaultPotentials.add('_' + filename + '.sass');
defaultPotentials.add('_' + filename + '.css');
}
const foundDefaults = [];
const foundImports = [];
let hasPotentialIndex = false;
for (const entry of entries) {
// Record if the name should be checked as a directory with an index file
if (checkDirectory && !hasStyleExtension && entry.name === filename && entry.isDirectory()) {
hasPotentialIndex = true;
}
if (!entry.isFile()) {
continue;
}
if (importPotentials.has(entry.name)) {
foundImports.push((0, node_path_1.join)(directory, entry.name));
}
if (defaultPotentials.has(entry.name)) {
foundDefaults.push((0, node_path_1.join)(directory, entry.name));
}
}
// `foundImports` will only contain elements if `options.fromImport` is true
const result = (_a = this.checkFound(foundImports)) !== null && _a !== void 0 ? _a : this.checkFound(foundDefaults);
if (result === null && hasPotentialIndex) {
// Check for index files using filename as a directory
return this.resolveImport(url + '/index', fromImport, false);
}
return result;
}
/**
* Checks an array of potential stylesheet files to determine if there is a valid
* stylesheet file. More than one discovered file may indicate an error.
* @param found An array of discovered stylesheet files.
* @returns A fully resolved URL for a stylesheet file or `null` if not found.
* @throws If there are ambiguous files discovered.
*/
checkFound(found) {
if (found.length === 0) {
// Not found
return null;
}
// More than one found file may be an error
if (found.length > 1) {
// Presence of CSS files alongside a Sass file does not cause an error
const foundWithoutCss = found.filter((element) => (0, node_path_1.extname)(element) !== '.css');
// If the length is zero then there are two or more css files
// If the length is more than one than there are two or more sass/scss files
if (foundWithoutCss.length !== 1) {
throw new Error('Ambiguous import detected.');
}
// Return the non-CSS file (sass/scss files have priority)
// https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart#L44-L47
return (0, node_url_1.pathToFileURL)(foundWithoutCss[0]);
}
return (0, node_url_1.pathToFileURL)(found[0]);
}
}
exports.RelativeUrlRebasingImporter = RelativeUrlRebasingImporter;
/**
* Provides the Sass importer logic to resolve module (npm package) stylesheet imports via both import and
* use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that
* the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file.
*/
class ModuleUrlRebasingImporter extends RelativeUrlRebasingImporter {
constructor(entryDirectory, directoryCache, rebaseSourceMaps, finder) {
super(entryDirectory, directoryCache, rebaseSourceMaps);
this.finder = finder;
}
canonicalize(url, options) {
if (url.startsWith('file://')) {
return super.canonicalize(url, options);
}
const result = this.finder(url, options);
return result ? super.canonicalize(result.href, options) : null;
}
}
exports.ModuleUrlRebasingImporter = ModuleUrlRebasingImporter;
/**
* Provides the Sass importer logic to resolve load paths located stylesheet imports via both import and
* use rules and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that
* the URLs in the output of the Sass compiler reflect the final filesystem location of the output CSS file.
*/
class LoadPathsUrlRebasingImporter extends RelativeUrlRebasingImporter {
constructor(entryDirectory, directoryCache, rebaseSourceMaps, loadPaths) {
super(entryDirectory, directoryCache, rebaseSourceMaps);
this.loadPaths = loadPaths;
}
canonicalize(url, options) {
if (url.startsWith('file://')) {
return super.canonicalize(url, options);
}
let result = null;
for (const loadPath of this.loadPaths) {
result = super.canonicalize((0, node_url_1.pathToFileURL)((0, node_path_1.join)(loadPath, url)).href, options);
if (result !== null) {
break;
}
}
return result;
}
}
exports.LoadPathsUrlRebasingImporter = LoadPathsUrlRebasingImporter;
/**
* Workaround for Sass not calling instance methods with `this`.
* The `canonicalize` and `load` methods will be bound to the class instance.
* @param importer A Sass importer to bind.
* @returns The bound Sass importer.
*/
function sassBindWorkaround(importer) {
importer.canonicalize = importer.canonicalize.bind(importer);
importer.load = importer.load.bind(importer);
return importer;
}
exports.sassBindWorkaround = sassBindWorkaround;
@@ -0,0 +1,51 @@
/**
* @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 { LegacyResult as CompileResult, LegacyException as Exception, LegacyOptions as Options } from 'sass';
/**
* The callback type for the `dart-sass` asynchronous render function.
*/
declare type RenderCallback = (error?: Exception, result?: CompileResult) => void;
/**
* A Sass renderer implementation that provides an interface that can be used by Webpack's
* `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering
* with the `dart-sass` package. The `dart-sass` synchronous render function is used within
* the worker which can be up to two times faster than the asynchronous variant.
*/
export declare class SassLegacyWorkerImplementation {
private readonly workers;
private readonly availableWorkers;
private readonly requests;
private readonly workerPath;
private idCounter;
private nextWorkerIndex;
/**
* Provides information about the Sass implementation.
* This mimics enough of the `dart-sass` value to be used with the `sass-loader`.
*/
get info(): string;
/**
* The synchronous render function is not used by the `sass-loader`.
*/
renderSync(): never;
/**
* Asynchronously request a Sass stylesheet to be renderered.
*
* @param options The `dart-sass` options to use when rendering the stylesheet.
* @param callback The function to execute when the rendering is complete.
*/
render(options: Options<'async'>, callback: RenderCallback): void;
/**
* Shutdown the Sass render worker.
* Executing this method will stop any pending render requests.
*/
close(): void;
private createWorker;
private processImporters;
private createRequest;
}
export {};
@@ -0,0 +1,175 @@
"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.SassLegacyWorkerImplementation = void 0;
const path_1 = require("path");
const worker_threads_1 = require("worker_threads");
const environment_options_1 = require("../utils/environment-options");
/**
* The maximum number of Workers that will be created to execute render requests.
*/
const MAX_RENDER_WORKERS = environment_options_1.maxWorkers;
/**
* A Sass renderer implementation that provides an interface that can be used by Webpack's
* `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering
* with the `dart-sass` package. The `dart-sass` synchronous render function is used within
* the worker which can be up to two times faster than the asynchronous variant.
*/
class SassLegacyWorkerImplementation {
constructor() {
this.workers = [];
this.availableWorkers = [];
this.requests = new Map();
this.workerPath = (0, path_1.join)(__dirname, './worker-legacy.js');
this.idCounter = 1;
this.nextWorkerIndex = 0;
}
/**
* Provides information about the Sass implementation.
* This mimics enough of the `dart-sass` value to be used with the `sass-loader`.
*/
get info() {
return 'dart-sass\tworker';
}
/**
* The synchronous render function is not used by the `sass-loader`.
*/
renderSync() {
throw new Error('Sass renderSync is not supported.');
}
/**
* Asynchronously request a Sass stylesheet to be renderered.
*
* @param options The `dart-sass` options to use when rendering the stylesheet.
* @param callback The function to execute when the rendering is complete.
*/
render(options, callback) {
// The `functions`, `logger` and `importer` options are JavaScript functions that cannot be transferred.
// If any additional function options are added in the future, they must be excluded as well.
const { functions, importer, logger, ...serializableOptions } = options;
// The CLI's configuration does not use or expose the ability to defined custom Sass functions
if (functions && Object.keys(functions).length > 0) {
throw new Error('Sass custom functions are not supported.');
}
let workerIndex = this.availableWorkers.pop();
if (workerIndex === undefined) {
if (this.workers.length < MAX_RENDER_WORKERS) {
workerIndex = this.workers.length;
this.workers.push(this.createWorker());
}
else {
workerIndex = this.nextWorkerIndex++;
if (this.nextWorkerIndex >= this.workers.length) {
this.nextWorkerIndex = 0;
}
}
}
const request = this.createRequest(workerIndex, callback, importer);
this.requests.set(request.id, request);
this.workers[workerIndex].postMessage({
id: request.id,
hasImporter: !!importer,
options: serializableOptions,
});
}
/**
* Shutdown the Sass render worker.
* Executing this method will stop any pending render requests.
*/
close() {
for (const worker of this.workers) {
try {
void worker.terminate();
}
catch { }
}
this.requests.clear();
}
createWorker() {
const { port1: mainImporterPort, port2: workerImporterPort } = new worker_threads_1.MessageChannel();
const importerSignal = new Int32Array(new SharedArrayBuffer(4));
const worker = new worker_threads_1.Worker(this.workerPath, {
workerData: { workerImporterPort, importerSignal },
transferList: [workerImporterPort],
});
worker.on('message', (response) => {
const request = this.requests.get(response.id);
if (!request) {
return;
}
this.requests.delete(response.id);
this.availableWorkers.push(request.workerIndex);
if (response.result) {
// The results are expected to be Node.js `Buffer` objects but will each be transferred as
// a Uint8Array that does not have the expected `toString` behavior of a `Buffer`.
const { css, map, stats } = response.result;
const result = {
// This `Buffer.from` override will use the memory directly and avoid making a copy
css: Buffer.from(css.buffer, css.byteOffset, css.byteLength),
stats,
};
if (map) {
// This `Buffer.from` override will use the memory directly and avoid making a copy
result.map = Buffer.from(map.buffer, map.byteOffset, map.byteLength);
}
request.callback(undefined, result);
}
else {
request.callback(response.error);
}
});
mainImporterPort.on('message', ({ id, url, prev, fromImport, }) => {
const request = this.requests.get(id);
if (!(request === null || request === void 0 ? void 0 : request.importers)) {
mainImporterPort.postMessage(null);
Atomics.store(importerSignal, 0, 1);
Atomics.notify(importerSignal, 0);
return;
}
this.processImporters(request.importers, url, prev, fromImport)
.then((result) => {
mainImporterPort.postMessage(result);
})
.catch((error) => {
mainImporterPort.postMessage(error);
})
.finally(() => {
Atomics.store(importerSignal, 0, 1);
Atomics.notify(importerSignal, 0);
});
});
mainImporterPort.unref();
return worker;
}
async processImporters(importers, url, prev, fromImport) {
let result = null;
for (const importer of importers) {
result = await new Promise((resolve) => {
// Importers can be both sync and async
const innerResult = importer.call({ fromImport }, url, prev, resolve);
if (innerResult !== undefined) {
resolve(innerResult);
}
});
if (result) {
break;
}
}
return result;
}
createRequest(workerIndex, callback, importer) {
return {
id: this.idCounter++,
workerIndex,
callback,
importers: !importer || Array.isArray(importer) ? importer : [importer],
};
}
}
exports.SassLegacyWorkerImplementation = SassLegacyWorkerImplementation;
@@ -0,0 +1,60 @@
/**
* @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 { CompileResult, FileImporter, StringOptionsWithImporter, StringOptionsWithoutImporter } from 'sass';
declare type FileImporterOptions = Parameters<FileImporter['findFileUrl']>[1];
export interface FileImporterWithRequestContextOptions extends FileImporterOptions {
/**
* This is a custom option and is required as SASS does not provide context from which the file is being resolved.
* This breaks Yarn PNP as transitive deps cannot be resolved from the workspace root.
*
* Workaround until https://github.com/sass/sass/issues/3247 is addressed.
*/
previousResolvedModules?: Set<string>;
}
/**
* A Sass renderer implementation that provides an interface that can be used by Webpack's
* `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering
* with the `dart-sass` package. The `dart-sass` synchronous render function is used within
* the worker which can be up to two times faster than the asynchronous variant.
*/
export declare class SassWorkerImplementation {
private rebase;
private readonly workers;
private readonly availableWorkers;
private readonly requests;
private readonly workerPath;
private idCounter;
private nextWorkerIndex;
constructor(rebase?: boolean);
/**
* Provides information about the Sass implementation.
* This mimics enough of the `dart-sass` value to be used with the `sass-loader`.
*/
get info(): string;
/**
* The synchronous render function is not used by the `sass-loader`.
*/
compileString(): never;
/**
* Asynchronously request a Sass stylesheet to be renderered.
*
* @param source The contents to compile.
* @param options The `dart-sass` options to use when rendering the stylesheet.
*/
compileStringAsync(source: string, options: StringOptionsWithImporter<'async'> | StringOptionsWithoutImporter<'async'>): Promise<CompileResult>;
/**
* Shutdown the Sass render worker.
* Executing this method will stop any pending render requests.
*/
close(): void;
private createWorker;
private processImporters;
private createRequest;
private isImporter;
}
export {};
@@ -0,0 +1,216 @@
"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.SassWorkerImplementation = void 0;
const node_path_1 = require("node:path");
const node_url_1 = require("node:url");
const node_worker_threads_1 = require("node:worker_threads");
const environment_options_1 = require("../utils/environment-options");
/**
* The maximum number of Workers that will be created to execute render requests.
*/
const MAX_RENDER_WORKERS = environment_options_1.maxWorkers;
/**
* A Sass renderer implementation that provides an interface that can be used by Webpack's
* `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering
* with the `dart-sass` package. The `dart-sass` synchronous render function is used within
* the worker which can be up to two times faster than the asynchronous variant.
*/
class SassWorkerImplementation {
constructor(rebase = false) {
this.rebase = rebase;
this.workers = [];
this.availableWorkers = [];
this.requests = new Map();
this.workerPath = (0, node_path_1.join)(__dirname, './worker.js');
this.idCounter = 1;
this.nextWorkerIndex = 0;
}
/**
* Provides information about the Sass implementation.
* This mimics enough of the `dart-sass` value to be used with the `sass-loader`.
*/
get info() {
return 'dart-sass\tworker';
}
/**
* The synchronous render function is not used by the `sass-loader`.
*/
compileString() {
throw new Error('Sass compileString is not supported.');
}
/**
* Asynchronously request a Sass stylesheet to be renderered.
*
* @param source The contents to compile.
* @param options The `dart-sass` options to use when rendering the stylesheet.
*/
compileStringAsync(source, options) {
// The `functions`, `logger` and `importer` options are JavaScript functions that cannot be transferred.
// If any additional function options are added in the future, they must be excluded as well.
const { functions, importers, url, logger, ...serializableOptions } = options;
// The CLI's configuration does not use or expose the ability to defined custom Sass functions
if (functions && Object.keys(functions).length > 0) {
throw new Error('Sass custom functions are not supported.');
}
return new Promise((resolve, reject) => {
let workerIndex = this.availableWorkers.pop();
if (workerIndex === undefined) {
if (this.workers.length < MAX_RENDER_WORKERS) {
workerIndex = this.workers.length;
this.workers.push(this.createWorker());
}
else {
workerIndex = this.nextWorkerIndex++;
if (this.nextWorkerIndex >= this.workers.length) {
this.nextWorkerIndex = 0;
}
}
}
const callback = (error, result) => {
var _a;
if (error) {
const url = (_a = error.span) === null || _a === void 0 ? void 0 : _a.url;
if (url) {
error.span.url = (0, node_url_1.pathToFileURL)(url);
}
reject(error);
return;
}
if (!result) {
reject(new Error('No result.'));
return;
}
resolve(result);
};
const request = this.createRequest(workerIndex, callback, logger, importers);
this.requests.set(request.id, request);
this.workers[workerIndex].postMessage({
id: request.id,
source,
hasImporter: !!(importers === null || importers === void 0 ? void 0 : importers.length),
hasLogger: !!logger,
rebase: this.rebase,
options: {
...serializableOptions,
// URL is not serializable so to convert to string here and back to URL in the worker.
url: url ? (0, node_url_1.fileURLToPath)(url) : undefined,
},
});
});
}
/**
* Shutdown the Sass render worker.
* Executing this method will stop any pending render requests.
*/
close() {
for (const worker of this.workers) {
try {
void worker.terminate();
}
catch { }
}
this.requests.clear();
}
createWorker() {
const { port1: mainImporterPort, port2: workerImporterPort } = new node_worker_threads_1.MessageChannel();
const importerSignal = new Int32Array(new SharedArrayBuffer(4));
const worker = new node_worker_threads_1.Worker(this.workerPath, {
workerData: { workerImporterPort, importerSignal },
transferList: [workerImporterPort],
});
worker.on('message', (response) => {
var _a;
const request = this.requests.get(response.id);
if (!request) {
return;
}
this.requests.delete(response.id);
this.availableWorkers.push(request.workerIndex);
if (response.warnings && ((_a = request.logger) === null || _a === void 0 ? void 0 : _a.warn)) {
for (const { message, span, ...options } of response.warnings) {
request.logger.warn(message, {
...options,
span: span && {
...span,
url: span.url ? (0, node_url_1.pathToFileURL)(span.url) : undefined,
},
});
}
}
if (response.result) {
request.callback(undefined, {
...response.result,
// URL is not serializable so in the worker we convert to string and here back to URL.
loadedUrls: response.result.loadedUrls.map((p) => (0, node_url_1.pathToFileURL)(p)),
});
}
else {
request.callback(response.error);
}
});
mainImporterPort.on('message', ({ id, url, options }) => {
const request = this.requests.get(id);
if (!(request === null || request === void 0 ? void 0 : request.importers)) {
mainImporterPort.postMessage(null);
Atomics.store(importerSignal, 0, 1);
Atomics.notify(importerSignal, 0);
return;
}
this.processImporters(request.importers, url, {
...options,
previousResolvedModules: request.previousResolvedModules,
})
.then((result) => {
var _a;
if (result) {
(_a = request.previousResolvedModules) !== null && _a !== void 0 ? _a : (request.previousResolvedModules = new Set());
request.previousResolvedModules.add((0, node_path_1.dirname)(result));
}
mainImporterPort.postMessage(result);
})
.catch((error) => {
mainImporterPort.postMessage(error);
})
.finally(() => {
Atomics.store(importerSignal, 0, 1);
Atomics.notify(importerSignal, 0);
});
});
mainImporterPort.unref();
return worker;
}
async processImporters(importers, url, options) {
for (const importer of importers) {
if (this.isImporter(importer)) {
// Importer
throw new Error('Only File Importers are supported.');
}
// File importer (Can be sync or aync).
const result = await importer.findFileUrl(url, options);
if (result) {
return (0, node_url_1.fileURLToPath)(result);
}
}
return null;
}
createRequest(workerIndex, callback, logger, importers) {
return {
id: this.idCounter++,
workerIndex,
callback,
logger,
importers,
};
}
isImporter(value) {
return 'canonicalize' in value && 'load' in value;
}
}
exports.SassWorkerImplementation = SassWorkerImplementation;
@@ -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 {};
@@ -0,0 +1,44 @@
"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 });
const sass_1 = require("sass");
const worker_threads_1 = require("worker_threads");
if (!worker_threads_1.parentPort || !worker_threads_1.workerData) {
throw new Error('Sass worker must be executed as a Worker.');
}
// The importer variables are used to proxy import requests to the main thread
const { workerImporterPort, importerSignal } = worker_threads_1.workerData;
worker_threads_1.parentPort.on('message', ({ id, hasImporter, options }) => {
try {
if (hasImporter) {
// When a custom importer function is present, the importer request must be proxied
// back to the main thread where it can be executed.
// This process must be synchronous from the perspective of dart-sass. The `Atomics`
// functions combined with the shared memory `importSignal` and the Node.js
// `receiveMessageOnPort` function are used to ensure synchronous behavior.
options.importer = function (url, prev) {
var _a;
Atomics.store(importerSignal, 0, 0);
const { fromImport } = this;
workerImporterPort.postMessage({ id, url, prev, fromImport });
Atomics.wait(importerSignal, 0, 0);
return (_a = (0, worker_threads_1.receiveMessageOnPort)(workerImporterPort)) === null || _a === void 0 ? void 0 : _a.message;
};
}
// The synchronous Sass render function can be up to two times faster than the async variant
const result = (0, sass_1.renderSync)(options);
worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.postMessage({ id, result });
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}
catch (error) {
// Needed because V8 will only serialize the message and stack properties of an Error instance.
const { formatted, file, line, column, message, stack } = error;
worker_threads_1.parentPort === null || worker_threads_1.parentPort === void 0 ? void 0 : worker_threads_1.parentPort.postMessage({ id, error: { formatted, file, line, column, message, stack } });
}
});
@@ -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 {};

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