mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-26 01:01:13 +08:00
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.
This commit is contained in:
parent
439385fef6
commit
7e61677d0b
@ -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<ts.SourceFile> {
|
||||
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;
|
||||
|
@ -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 };
|
||||
|
Loading…
x
Reference in New Issue
Block a user