@ngtools/webpack
Version:
Webpack plugin that AoT compiles your Angular components and modules.
238 lines (237 loc) • 10.6 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.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;
}
;