@angular/build
Version:
Official build system for Angular
181 lines (180 loc) • 8.28 kB
JavaScript
;
/**
* @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.dev/license
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.replaceBootstrap = replaceBootstrap;
exports.elideImports = elideImports;
const typescript_1 = __importDefault(require("typescript"));
/**
* The name of the Angular platform that should be replaced within
* bootstrap call expressions to support AOT.
*/
const PLATFORM_BROWSER_DYNAMIC_NAME = 'platformBrowserDynamic';
function replaceBootstrap(getTypeChecker) {
return (context) => {
let bootstrapImport;
let bootstrapNamespace;
const replacedNodes = [];
const nodeFactory = context.factory;
const visitNode = (node) => {
if (typescript_1.default.isCallExpression(node) && typescript_1.default.isIdentifier(node.expression)) {
const target = node.expression;
if (target.text === PLATFORM_BROWSER_DYNAMIC_NAME) {
if (!bootstrapNamespace) {
bootstrapNamespace = nodeFactory.createUniqueName('__NgCli_bootstrap_');
bootstrapImport = nodeFactory.createImportDeclaration(undefined, nodeFactory.createImportClause(false, undefined, nodeFactory.createNamespaceImport(bootstrapNamespace)), nodeFactory.createStringLiteral('@angular/platform-browser'));
}
replacedNodes.push(target);
return nodeFactory.updateCallExpression(node, nodeFactory.createPropertyAccessExpression(bootstrapNamespace, 'platformBrowser'), node.typeArguments, node.arguments);
}
}
return typescript_1.default.visitEachChild(node, visitNode, context);
};
return (sourceFile) => {
if (!sourceFile.text.includes(PLATFORM_BROWSER_DYNAMIC_NAME)) {
return sourceFile;
}
let updatedSourceFile = typescript_1.default.visitEachChild(sourceFile, visitNode, context);
if (bootstrapImport) {
// Remove any unused platform browser dynamic imports
const removals = elideImports(updatedSourceFile, replacedNodes, getTypeChecker, context.getCompilerOptions());
if (removals.size > 0) {
updatedSourceFile = typescript_1.default.visitEachChild(updatedSourceFile, (node) => (removals.has(node) ? undefined : node), context);
}
// Add new platform browser import
return nodeFactory.updateSourceFile(updatedSourceFile, typescript_1.default.setTextRange(nodeFactory.createNodeArray([bootstrapImport, ...updatedSourceFile.statements]), sourceFile.statements));
}
else {
return updatedSourceFile;
}
};
};
}
// Remove imports for which all identifiers have been removed.
// Needs type checker, and works even if it's not the first transformer.
// Works by removing imports for symbols whose identifiers have all been removed.
// Doesn't use the `symbol.declarations` because that previous transforms might have removed nodes
// but the type checker doesn't know.
// See https://github.com/Microsoft/TypeScript/issues/17552 for more information.
function elideImports(sourceFile, removedNodes, getTypeChecker, compilerOptions) {
const importNodeRemovals = new Set();
if (removedNodes.length === 0) {
return importNodeRemovals;
}
const typeChecker = getTypeChecker();
// Collect all imports and used identifiers
const usedSymbols = new Set();
const imports = [];
typescript_1.default.forEachChild(sourceFile, function visit(node) {
// Skip removed nodes.
if (removedNodes.includes(node)) {
return;
}
// Consider types for 'implements' as unused.
// A HeritageClause token can also be an 'AbstractKeyword'
// which in that case we should not elide the import.
if (typescript_1.default.isHeritageClause(node) && node.token === typescript_1.default.SyntaxKind.ImplementsKeyword) {
return;
}
// Record import and skip
if (typescript_1.default.isImportDeclaration(node)) {
if (!node.importClause?.isTypeOnly) {
imports.push(node);
}
return;
}
// Type reference imports do not need to be emitted when emitDecoratorMetadata is disabled.
if (typescript_1.default.isTypeReferenceNode(node) && !compilerOptions.emitDecoratorMetadata) {
return;
}
let symbol;
switch (node.kind) {
case typescript_1.default.SyntaxKind.Identifier:
if (node.parent && typescript_1.default.isShorthandPropertyAssignment(node.parent)) {
const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(node.parent);
if (shorthandSymbol) {
symbol = shorthandSymbol;
}
}
else {
symbol = typeChecker.getSymbolAtLocation(node);
}
break;
case typescript_1.default.SyntaxKind.ExportSpecifier:
symbol = typeChecker.getExportSpecifierLocalTargetSymbol(node);
break;
case typescript_1.default.SyntaxKind.ShorthandPropertyAssignment:
symbol = typeChecker.getShorthandAssignmentValueSymbol(node);
break;
}
if (symbol) {
usedSymbols.add(symbol);
}
typescript_1.default.forEachChild(node, visit);
});
if (imports.length === 0) {
return importNodeRemovals;
}
const isUnused = (node) => {
// Do not remove JSX factory imports
if (node.text === compilerOptions.jsxFactory) {
return false;
}
const symbol = typeChecker.getSymbolAtLocation(node);
return symbol && !usedSymbols.has(symbol);
};
for (const node of imports) {
if (!node.importClause) {
// "import 'abc';"
continue;
}
const namedBindings = node.importClause.namedBindings;
if (namedBindings && typescript_1.default.isNamespaceImport(namedBindings)) {
// "import * as XYZ from 'abc';"
if (isUnused(namedBindings.name)) {
importNodeRemovals.add(node);
}
}
else {
const specifierNodeRemovals = [];
let clausesCount = 0;
// "import { XYZ, ... } from 'abc';"
if (namedBindings && typescript_1.default.isNamedImports(namedBindings)) {
let removedClausesCount = 0;
clausesCount += namedBindings.elements.length;
for (const specifier of namedBindings.elements) {
if (specifier.isTypeOnly || isUnused(specifier.name)) {
removedClausesCount++;
// in case we don't have any more namedImports we should remove the parent ie the {}
const nodeToRemove = clausesCount === removedClausesCount ? specifier.parent : specifier;
specifierNodeRemovals.push(nodeToRemove);
}
}
}
// "import XYZ from 'abc';"
if (node.importClause.name) {
clausesCount++;
if (node.importClause.isTypeOnly || isUnused(node.importClause.name)) {
specifierNodeRemovals.push(node.importClause.name);
}
}
if (specifierNodeRemovals.length === clausesCount) {
importNodeRemovals.add(node);
}
else {
for (const specifierNodeRemoval of specifierNodeRemovals) {
importNodeRemovals.add(specifierNodeRemoval);
}
}
}
}
return importNodeRemovals;
}