UNPKG

@ngtools/webpack

Version:

Webpack plugin that AoT compiles your Angular components and modules.

238 lines (237 loc) • 10.6 kB
"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.getResourceUrl = exports.replaceResources = exports.NG_COMPONENT_RESOURCE_QUERY = void 0; const ts = __importStar(require("typescript")); const inline_resource_1 = require("../loaders/inline-resource"); exports.NG_COMPONENT_RESOURCE_QUERY = 'ngResource'; function replaceResources(shouldTransform, getTypeChecker, inlineStyleFileExtension) { return (context) => { const typeChecker = getTypeChecker(); const resourceImportDeclarations = []; const moduleKind = context.getCompilerOptions().module; const nodeFactory = context.factory; const visitNode = (node) => { if (ts.isClassDeclaration(node)) { const decorators = ts.getDecorators(node); if (!decorators || decorators.length === 0) { return node; } return nodeFactory.updateClassDeclaration(node, [ ...decorators.map((current) => visitDecorator(nodeFactory, current, typeChecker, resourceImportDeclarations, moduleKind, inlineStyleFileExtension)), ...(ts.getModifiers(node) ?? []), ], node.name, node.typeParameters, node.heritageClauses, node.members); } return ts.visitEachChild(node, visitNode, context); }; return (sourceFile) => { if (!shouldTransform(sourceFile.fileName)) { return sourceFile; } const updatedSourceFile = ts.visitNode(sourceFile, visitNode); if (resourceImportDeclarations.length) { // Add resource imports return context.factory.updateSourceFile(updatedSourceFile, ts.setTextRange(context.factory.createNodeArray([ ...resourceImportDeclarations, ...updatedSourceFile.statements, ]), updatedSourceFile.statements)); } return updatedSourceFile; }; }; } exports.replaceResources = replaceResources; function visitDecorator(nodeFactory, node, typeChecker, resourceImportDeclarations, moduleKind, inlineStyleFileExtension) { if (!isComponentDecorator(node, typeChecker)) { return node; } if (!ts.isCallExpression(node.expression)) { return node; } const decoratorFactory = node.expression; const args = decoratorFactory.arguments; if (args.length !== 1 || !ts.isObjectLiteralExpression(args[0])) { // Unsupported component metadata return node; } const objectExpression = args[0]; const styleReplacements = []; // visit all properties let properties = ts.visitNodes(objectExpression.properties, (node) => ts.isObjectLiteralElementLike(node) ? visitComponentMetadata(nodeFactory, node, styleReplacements, resourceImportDeclarations, moduleKind, inlineStyleFileExtension) : node); // replace properties with updated properties if (styleReplacements.length > 0) { const styleProperty = nodeFactory.createPropertyAssignment(nodeFactory.createIdentifier('styles'), nodeFactory.createArrayLiteralExpression(styleReplacements)); properties = nodeFactory.createNodeArray([...properties, styleProperty]); } return nodeFactory.updateDecorator(node, nodeFactory.updateCallExpression(decoratorFactory, decoratorFactory.expression, decoratorFactory.typeArguments, [nodeFactory.updateObjectLiteralExpression(objectExpression, properties)])); } function visitComponentMetadata(nodeFactory, node, styleReplacements, resourceImportDeclarations, moduleKind = ts.ModuleKind.ES2015, inlineStyleFileExtension) { if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name)) { return node; } const name = node.name.text; switch (name) { case 'moduleId': return undefined; case 'templateUrl': const url = getResourceUrl(node.initializer); if (!url) { return node; } const importName = createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind); if (!importName) { return node; } return nodeFactory.updatePropertyAssignment(node, nodeFactory.createIdentifier('template'), importName); case 'styles': case 'styleUrl': case 'styleUrls': const isInlineStyle = name === 'styles'; let styles; if (ts.isStringLiteralLike(node.initializer)) { styles = [ transformInlineStyleLiteral(node.initializer, nodeFactory, isInlineStyle, inlineStyleFileExtension, resourceImportDeclarations, moduleKind), ]; } else if (ts.isArrayLiteralExpression(node.initializer)) { styles = ts.visitNodes(node.initializer.elements, (node) => transformInlineStyleLiteral(node, nodeFactory, isInlineStyle, inlineStyleFileExtension, resourceImportDeclarations, moduleKind)); } else { return node; } // Styles should be placed first if (isInlineStyle) { styleReplacements.unshift(...styles); } else { styleReplacements.push(...styles); } return undefined; default: return node; } } function transformInlineStyleLiteral(node, nodeFactory, isInlineStyle, inlineStyleFileExtension, resourceImportDeclarations, moduleKind) { if (!ts.isStringLiteralLike(node)) { return node; } // Don't transform empty strings as PostCSS will choke on them. No work to do anyways. if (node.text === '') { return node; } if (!isInlineStyle) { const url = getResourceUrl(node); return url ? createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind) : node; } if (!inlineStyleFileExtension) { return nodeFactory.createStringLiteral(node.text); } const data = Buffer.from(node.text).toString('base64'); const containingFile = node.getSourceFile().fileName; // app.component.ts.css?ngResource!=!@ngtools/webpack/src/loaders/inline-resource.js?data=...!app.component.ts const url = `${containingFile}.${inlineStyleFileExtension}?${exports.NG_COMPONENT_RESOURCE_QUERY}` + `!=!${inline_resource_1.InlineAngularResourceLoaderPath}?data=${encodeURIComponent(data)}!${containingFile}`; return createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind); } function getResourceUrl(node) { // only analyze strings if (!ts.isStringLiteralLike(node)) { return null; } return `${/^\.?\.\//.test(node.text) ? '' : './'}${node.text}?${exports.NG_COMPONENT_RESOURCE_QUERY}`; } exports.getResourceUrl = getResourceUrl; function isComponentDecorator(node, typeChecker) { if (!ts.isDecorator(node)) { return false; } const origin = getDecoratorOrigin(node, typeChecker); if (origin && origin.module === '@angular/core' && origin.name === 'Component') { return true; } return false; } function createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind) { const urlLiteral = nodeFactory.createStringLiteral(url); if (moduleKind < ts.ModuleKind.ES2015) { return nodeFactory.createCallExpression(nodeFactory.createIdentifier('require'), [], [urlLiteral]); } else { const importName = nodeFactory.createIdentifier(`__NG_CLI_RESOURCE__${resourceImportDeclarations.length}`); resourceImportDeclarations.push(nodeFactory.createImportDeclaration(undefined, nodeFactory.createImportClause(false, importName, undefined), urlLiteral)); return importName; } } function getDecoratorOrigin(decorator, typeChecker) { if (!ts.isCallExpression(decorator.expression)) { return null; } let identifier; let name = ''; if (ts.isPropertyAccessExpression(decorator.expression.expression)) { identifier = decorator.expression.expression.expression; name = decorator.expression.expression.name.text; } else if (ts.isIdentifier(decorator.expression.expression)) { identifier = decorator.expression.expression; } else { return null; } // NOTE: resolver.getReferencedImportDeclaration would work as well but is internal const symbol = typeChecker.getSymbolAtLocation(identifier); if (symbol && symbol.declarations && symbol.declarations.length > 0) { const declaration = symbol.declarations[0]; let module; if (ts.isImportSpecifier(declaration)) { name = (declaration.propertyName || declaration.name).text; module = declaration.parent.parent.parent.moduleSpecifier.text; } else if (ts.isNamespaceImport(declaration)) { // Use the name from the decorator namespace property access module = declaration.parent.parent.moduleSpecifier.text; } else if (ts.isImportClause(declaration)) { name = declaration.name.text; module = declaration.parent.moduleSpecifier.text; } else { return null; } return { name, module }; } return null; }