From 7e61677d0bccfac278f578a0a15e94e6b1e6b21c Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Mon, 17 Aug 2020 12:09:28 +0200 Subject: [PATCH] refactor(@ngtools/webpack): use ES modules imports instead of `require` for resources With this change we update the replace_resources transformer for styles and templates to use ES6 module import syntax instead of the CommonJs require syntax. --- .../src/transformers/replace_resources.ts | 120 +++++++++--------- .../transformers/replace_resources_spec.ts | 115 +++++++---------- 2 files changed, 108 insertions(+), 127 deletions(-) diff --git a/packages/ngtools/webpack/src/transformers/replace_resources.ts b/packages/ngtools/webpack/src/transformers/replace_resources.ts index c5e752f13f..f52d85cc10 100644 --- a/packages/ngtools/webpack/src/transformers/replace_resources.ts +++ b/packages/ngtools/webpack/src/transformers/replace_resources.ts @@ -7,18 +7,6 @@ */ import * as ts from 'typescript'; -// emit helper for `import Name from "foo"` -// importName is marked as an internal property but is needed for the tslib import. -const importDefaultHelper: ts.UnscopedEmitHelper & { importName?: string } = { - name: 'typescript:commonjsimportdefault', - importName: '__importDefault', - scoped: false, - text: ` - var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; - };`, -}; - export function replaceResources( shouldTransform: (fileName: string) => boolean, getTypeChecker: () => ts.TypeChecker, @@ -26,12 +14,13 @@ export function replaceResources( ): ts.TransformerFactory { return (context: ts.TransformationContext) => { const typeChecker = getTypeChecker(); + const resourceImportDeclarations: ts.ImportDeclaration[] = []; const visitNode: ts.Visitor = (node: ts.Node) => { if (ts.isClassDeclaration(node)) { - const decorators = ts.visitNodes(node.decorators, (node) => + const decorators = ts.visitNodes(node.decorators, node => ts.isDecorator(node) - ? visitDecorator(context, node, typeChecker, directTemplateLoading) + ? visitDecorator(node, typeChecker, directTemplateLoading, resourceImportDeclarations) : node, ); @@ -50,20 +39,35 @@ export function replaceResources( }; return (sourceFile: ts.SourceFile) => { - if (shouldTransform(sourceFile.fileName)) { - return ts.visitNode(sourceFile, visitNode); + if (!shouldTransform(sourceFile.fileName)) { + return sourceFile; } - return sourceFile; + const updatedSourceFile = ts.visitNode(sourceFile, visitNode); + if (resourceImportDeclarations.length) { + // Add resource imports + return ts.updateSourceFileNode( + updatedSourceFile, + ts.setTextRange( + ts.createNodeArray([ + ...resourceImportDeclarations, + ...updatedSourceFile.statements, + ]), + updatedSourceFile.statements, + ), + ); + } + + return updatedSourceFile; }; }; } function visitDecorator( - context: ts.TransformationContext, node: ts.Decorator, typeChecker: ts.TypeChecker, directTemplateLoading: boolean, + resourceImportDeclarations: ts.ImportDeclaration[], ): ts.Decorator { if (!isComponentDecorator(node, typeChecker)) { return node; @@ -84,9 +88,9 @@ function visitDecorator( const styleReplacements: ts.Expression[] = []; // visit all properties - let properties = ts.visitNodes(objectExpression.properties, (node) => + let properties = ts.visitNodes(objectExpression.properties, node => ts.isObjectLiteralElementLike(node) - ? visitComponentMetadata(context, node, styleReplacements, directTemplateLoading) + ? visitComponentMetadata(node, styleReplacements, directTemplateLoading, resourceImportDeclarations) : node, ); @@ -109,10 +113,10 @@ function visitDecorator( } function visitComponentMetadata( - context: ts.TransformationContext, node: ts.ObjectLiteralElementLike, styleReplacements: ts.Expression[], directTemplateLoading: boolean, + resourceImportDeclarations: ts.ImportDeclaration[], ): ts.ObjectLiteralElementLike | undefined { if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name)) { return node; @@ -124,16 +128,16 @@ function visitComponentMetadata( return undefined; case 'templateUrl': + const importName = createResourceImport(node.initializer, directTemplateLoading ? '!raw-loader!' : '', resourceImportDeclarations); + if (!importName) { + return node; + } + return ts.updatePropertyAssignment( node, ts.createIdentifier('template'), - createRequireExpression( - context, - node.initializer, - directTemplateLoading ? '!raw-loader!' : '', - ), + importName, ); - case 'styles': case 'styleUrls': if (!ts.isArrayLiteralExpression(node.initializer)) { @@ -141,14 +145,16 @@ function visitComponentMetadata( } const isInlineStyles = name === 'styles'; - const styles = ts.visitNodes(node.initializer.elements, (node) => { + const styles = ts.visitNodes(node.initializer.elements, node => { if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) { return node; } - return isInlineStyles - ? ts.createLiteral(node.text) - : createRequireExpression(context, node); + if (isInlineStyles) { + return ts.createLiteral(node.text); + } + + return createResourceImport(node, undefined, resourceImportDeclarations) || node; }); // Styles should be placed first @@ -159,12 +165,33 @@ function visitComponentMetadata( } return undefined; - default: return node; } } +export function createResourceImport( + node: ts.Node, + loader: string | undefined, + resourceImportDeclarations: ts.ImportDeclaration[], +): ts.Identifier | null { + const url = getResourceUrl(node, loader); + if (!url) { + return null; + } + + const importName = ts.createIdentifier(`__NG_CLI_RESOURCE__${resourceImportDeclarations.length}`); + + resourceImportDeclarations.push(ts.createImportDeclaration( + undefined, + undefined, + ts.createImportClause(importName, undefined), + ts.createLiteral(url), + )); + + return importName; +} + export function getResourceUrl(node: ts.Node, loader = ''): string | null { // only analyze strings if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) { @@ -187,35 +214,6 @@ function isComponentDecorator(node: ts.Node, typeChecker: ts.TypeChecker): node return false; } -function createRequireExpression( - context: ts.TransformationContext, - node: ts.Expression, - loader?: string, -): ts.Expression { - const url = getResourceUrl(node, loader); - if (!url) { - return node; - } - - context.requestEmitHelper(importDefaultHelper); - - const callExpression = ts.createCall(ts.createIdentifier('require'), undefined, [ - ts.createLiteral(url), - ]); - - return ts.createPropertyAccess( - ts.createCall( - ts.setEmitFlags( - ts.createIdentifier('__importDefault'), - ts.EmitFlags.HelperName | ts.EmitFlags.AdviseOnEmitNode, - ), - undefined, - [callExpression], - ), - 'default', - ); -} - interface DecoratorOrigin { name: string; module: string; diff --git a/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts b/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts index 2e092fe580..7d2bb4d4b0 100644 --- a/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts @@ -42,8 +42,12 @@ describe('@ngtools/webpack transformers', () => { } `; const output = tags.stripIndent` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "!raw-loader!./app.component.html"; + import __NG_CLI_RESOURCE__1 from "./app.component.css"; + import __NG_CLI_RESOURCE__2 from "./app.component.2.css"; import { Component } from '@angular/core'; + let AppComponent = class AppComponent { constructor() { this.title = 'app'; @@ -52,8 +56,8 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ Component({ selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.html")).default, - styles: [__importDefault(require("./app.component.css")).default, __importDefault(require("./app.component.2.css")).default] + template: __NG_CLI_RESOURCE__0, + styles: [__NG_CLI_RESOURCE__1, __NG_CLI_RESOURCE__2] }) ], AppComponent); export { AppComponent }; @@ -63,49 +67,6 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); - it(`should replace resources and add helper when 'importHelpers' is false`, () => { - const input = tags.stripIndent` - import { Component } from '@angular/core'; - - @Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css', './app.component.2.css'] - }) - export class AppComponent { - title = 'app'; - } - `; - const output = tags.stripIndent` - var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null - ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" - && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) - if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; - - var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; - - import { Component } from '@angular/core'; - - let AppComponent = class AppComponent { - constructor() { - this.title = 'app'; - } - }; - AppComponent = __decorate([ - Component({ - selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.html")).default, - styles: [__importDefault(require("./app.component.css")).default, __importDefault(require("./app.component.2.css")).default] - }) - ], AppComponent); - export { AppComponent }; - `; - - const result = transform(input, undefined, undefined, false); - expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); - }); - it('should not replace resources when directTemplateLoading is false', () => { const input = tags.stripIndent` import { Component } from '@angular/core'; @@ -123,7 +84,10 @@ describe('@ngtools/webpack transformers', () => { } `; const output = tags.stripIndent` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "./app.component.html"; + import __NG_CLI_RESOURCE__1 from "./app.component.css"; + import __NG_CLI_RESOURCE__2 from "./app.component.2.css"; import { Component } from '@angular/core'; let AppComponent = class AppComponent { constructor() { @@ -133,8 +97,8 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ Component({ selector: 'app-root', - template: __importDefault(require("./app.component.html")).default, - styles: [__importDefault(require("./app.component.css")).default, __importDefault(require("./app.component.2.css")).default] + template: __NG_CLI_RESOURCE__0, + styles: [__NG_CLI_RESOURCE__1, __NG_CLI_RESOURCE__2] }) ], AppComponent); export { AppComponent }; @@ -158,7 +122,8 @@ describe('@ngtools/webpack transformers', () => { } `; const output = tags.stripIndent` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "!raw-loader!./app.component.svg"; import { Component } from '@angular/core'; let AppComponent = class AppComponent { constructor() { @@ -168,7 +133,7 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ Component({ selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.svg")).default + template: __NG_CLI_RESOURCE__0 }) ], AppComponent); export { AppComponent }; @@ -193,8 +158,11 @@ describe('@ngtools/webpack transformers', () => { } `; const output = tags.stripIndent` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "!raw-loader!./app.component.html"; + import __NG_CLI_RESOURCE__1 from "./app.component.css"; import { Component } from '@angular/core'; + let AppComponent = class AppComponent { constructor() { this.title = 'app'; @@ -203,8 +171,8 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ Component({ selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.html")).default, - styles: ["a { color: red }", __importDefault(require("./app.component.css")).default] + template: __NG_CLI_RESOURCE__0, + styles: ["a { color: red }", __NG_CLI_RESOURCE__1] }) ], AppComponent); export { AppComponent }; @@ -228,7 +196,11 @@ describe('@ngtools/webpack transformers', () => { } `; const output = ` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "!raw-loader!./app.component.html"; + import __NG_CLI_RESOURCE__1 from "./app.component.css"; + import __NG_CLI_RESOURCE__2 from "./app.component.2.css"; + import { Component } from '@angular/core'; let AppComponent = class AppComponent { constructor() { @@ -238,8 +210,8 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ Component({ selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.html")).default, - styles: [__importDefault(require("./app.component.css")).default, __importDefault(require("./app.component.2.css")).default] + template: __NG_CLI_RESOURCE__0, + styles: [__NG_CLI_RESOURCE__1, __NG_CLI_RESOURCE__2] }) ], AppComponent); export { AppComponent }; @@ -263,8 +235,12 @@ describe('@ngtools/webpack transformers', () => { } `; const output = tags.stripIndent` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "!raw-loader!./app.component.html"; + import __NG_CLI_RESOURCE__1 from "./app.component.css"; + import __NG_CLI_RESOURCE__2 from "./app.component.2.css"; import { Component as NgComponent } from '@angular/core'; + let AppComponent = class AppComponent { constructor() { this.title = 'app'; @@ -273,8 +249,8 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ NgComponent({ selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.html")).default, - styles: [__importDefault(require("./app.component.css")).default, __importDefault(require("./app.component.2.css")).default] + template: __NG_CLI_RESOURCE__0, + styles: [__NG_CLI_RESOURCE__1, __NG_CLI_RESOURCE__2] }) ], AppComponent); export { AppComponent }; @@ -302,7 +278,11 @@ describe('@ngtools/webpack transformers', () => { } `; const output = tags.stripIndent` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "!raw-loader!./app.component.html"; + import __NG_CLI_RESOURCE__1 from "./app.component.css"; + import __NG_CLI_RESOURCE__2 from "./app.component.2.css"; + import * as ng from '@angular/core'; let AppComponent = class AppComponent { constructor() { @@ -312,8 +292,8 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ ng.Component({ selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.html")).default, - styles: [__importDefault(require("./app.component.css")).default, __importDefault(require("./app.component.2.css")).default] + template: __NG_CLI_RESOURCE__0, + styles: [__NG_CLI_RESOURCE__1, __NG_CLI_RESOURCE__2] }) ], AppComponent); export { AppComponent }; @@ -343,7 +323,10 @@ describe('@ngtools/webpack transformers', () => { `; const output = tags.stripIndent` - import { __decorate, __importDefault } from "tslib"; + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "!raw-loader!./app.component.html"; + import __NG_CLI_RESOURCE__1 from "./app.component.css"; + import { Component } from '@angular/core'; let AppComponent = class AppComponent { @@ -360,8 +343,8 @@ describe('@ngtools/webpack transformers', () => { AppComponent = __decorate([ Component({ selector: 'app-root', - template: __importDefault(require("!raw-loader!./app.component.html")).default, - styles: [__importDefault(require("./app.component.css")).default] + template: __NG_CLI_RESOURCE__0, + styles: [__NG_CLI_RESOURCE__1] }) ], AppComponent); export { AppComponent };