Skip to content

Commit 75ca7f9

Browse files
committed
fix(@ngtools/webpack): emit require in replace resources when using CommonJS as module
When using CommonJs as module format TypeScript will generate unreferenced `require` when using `ts.createImportDeclaration`. ```js const external_component_html_1 = require("!raw-loader!./external.component.html"); const core_1 = require("@angular/core"); let ExampleComponent = class ExampleComponent { }; ExampleComponent = __decorate([ core_1.Component({ selector: 'example-compoent', template: __NG_CLI_RESOURCE__0, }) ], ExampleComponent); ``` More context: microsoft/TypeScript#18369 (comment) Closes #18718 (cherry picked from commit 179b6ca)
1 parent 430c099 commit 75ca7f9

File tree

2 files changed

+85
-28
lines changed

2 files changed

+85
-28
lines changed

packages/ngtools/webpack/src/transformers/replace_resources.ts

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ export function replaceResources(
1515
return (context: ts.TransformationContext) => {
1616
const typeChecker = getTypeChecker();
1717
const resourceImportDeclarations: ts.ImportDeclaration[] = [];
18-
18+
const moduleKind = context.getCompilerOptions().module;
1919
const visitNode: ts.Visitor = (node: ts.Node) => {
2020
if (ts.isClassDeclaration(node)) {
2121
const decorators = ts.visitNodes(node.decorators, node =>
2222
ts.isDecorator(node)
23-
? visitDecorator(node, typeChecker, directTemplateLoading, resourceImportDeclarations)
23+
? visitDecorator(node, typeChecker, directTemplateLoading, resourceImportDeclarations, moduleKind)
2424
: node,
2525
);
2626

@@ -68,6 +68,7 @@ function visitDecorator(
6868
typeChecker: ts.TypeChecker,
6969
directTemplateLoading: boolean,
7070
resourceImportDeclarations: ts.ImportDeclaration[],
71+
moduleKind?: ts.ModuleKind,
7172
): ts.Decorator {
7273
if (!isComponentDecorator(node, typeChecker)) {
7374
return node;
@@ -90,7 +91,7 @@ function visitDecorator(
9091
// visit all properties
9192
let properties = ts.visitNodes(objectExpression.properties, node =>
9293
ts.isObjectLiteralElementLike(node)
93-
? visitComponentMetadata(node, styleReplacements, directTemplateLoading, resourceImportDeclarations)
94+
? visitComponentMetadata(node, styleReplacements, directTemplateLoading, resourceImportDeclarations, moduleKind)
9495
: node,
9596
);
9697

@@ -117,6 +118,7 @@ function visitComponentMetadata(
117118
styleReplacements: ts.Expression[],
118119
directTemplateLoading: boolean,
119120
resourceImportDeclarations: ts.ImportDeclaration[],
121+
moduleKind?: ts.ModuleKind,
120122
): ts.ObjectLiteralElementLike | undefined {
121123
if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name)) {
122124
return node;
@@ -128,7 +130,12 @@ function visitComponentMetadata(
128130
return undefined;
129131

130132
case 'templateUrl':
131-
const importName = createResourceImport(node.initializer, directTemplateLoading ? '!raw-loader!' : '', resourceImportDeclarations);
133+
const importName = createResourceImport(
134+
node.initializer,
135+
directTemplateLoading ? '!raw-loader!' : '',
136+
resourceImportDeclarations,
137+
moduleKind,
138+
);
132139
if (!importName) {
133140
return node;
134141
}
@@ -154,7 +161,7 @@ function visitComponentMetadata(
154161
return ts.createLiteral(node.text);
155162
}
156163

157-
return createResourceImport(node, undefined, resourceImportDeclarations) || node;
164+
return createResourceImport(node, undefined, resourceImportDeclarations, moduleKind) || node;
158165
});
159166

160167
// Styles should be placed first
@@ -170,28 +177,6 @@ function visitComponentMetadata(
170177
}
171178
}
172179

173-
export function createResourceImport(
174-
node: ts.Node,
175-
loader: string | undefined,
176-
resourceImportDeclarations: ts.ImportDeclaration[],
177-
): ts.Identifier | null {
178-
const url = getResourceUrl(node, loader);
179-
if (!url) {
180-
return null;
181-
}
182-
183-
const importName = ts.createIdentifier(`__NG_CLI_RESOURCE__${resourceImportDeclarations.length}`);
184-
185-
resourceImportDeclarations.push(ts.createImportDeclaration(
186-
undefined,
187-
undefined,
188-
ts.createImportClause(importName, undefined),
189-
ts.createLiteral(url),
190-
));
191-
192-
return importName;
193-
}
194-
195180
export function getResourceUrl(node: ts.Node, loader = ''): string | null {
196181
// only analyze strings
197182
if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) {
@@ -214,6 +199,41 @@ function isComponentDecorator(node: ts.Node, typeChecker: ts.TypeChecker): node
214199
return false;
215200
}
216201

202+
function createResourceImport(
203+
node: ts.Node,
204+
loader: string | undefined,
205+
resourceImportDeclarations: ts.ImportDeclaration[],
206+
moduleKind = ts.ModuleKind.ES2015,
207+
): ts.Identifier | ts.Expression | null {
208+
const url = getResourceUrl(node, loader);
209+
if (!url) {
210+
return null;
211+
}
212+
213+
const urlLiteral = ts.createLiteral(url);
214+
215+
if (moduleKind < ts.ModuleKind.ES2015) {
216+
return ts.createPropertyAccess(
217+
ts.createCall(
218+
ts.createIdentifier('require'),
219+
[],
220+
[urlLiteral],
221+
),
222+
'default',
223+
);
224+
} else {
225+
const importName = ts.createIdentifier(`__NG_CLI_RESOURCE__${resourceImportDeclarations.length}`);
226+
resourceImportDeclarations.push(ts.createImportDeclaration(
227+
undefined,
228+
undefined,
229+
ts.createImportClause(importName, undefined),
230+
urlLiteral,
231+
));
232+
233+
return importName;
234+
}
235+
}
236+
217237
interface DecoratorOrigin {
218238
name: string;
219239
module: string;

packages/ngtools/webpack/src/transformers/replace_resources_spec.ts

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88
import { tags } from '@angular-devkit/core'; // tslint:disable-line:no-implicit-dependencies
9+
import * as ts from 'typescript';
910
import { replaceResources } from './replace_resources';
1011
import { createTypescriptContext, transformTypescript } from './spec_helpers';
1112

@@ -14,8 +15,9 @@ function transform(
1415
shouldTransform = true,
1516
directTemplateLoading = true,
1617
importHelpers = true,
18+
module: ts.ModuleKind = ts.ModuleKind.ESNext,
1719
) {
18-
const { program, compilerHost } = createTypescriptContext(input, undefined, undefined, { importHelpers });
20+
const { program, compilerHost } = createTypescriptContext(input, undefined, undefined, { importHelpers, module });
1921
const getTypeChecker = () => program.getTypeChecker();
2022
const transformer = replaceResources(
2123
() => shouldTransform, getTypeChecker, directTemplateLoading);
@@ -67,6 +69,41 @@ describe('@ngtools/webpack transformers', () => {
6769
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
6870
});
6971

72+
it('should replace resources with `require()` when module is CommonJs', () => {
73+
const input = tags.stripIndent`
74+
import { Component } from '@angular/core';
75+
76+
@Component({
77+
selector: 'app-root',
78+
templateUrl: './app.component.html',
79+
styleUrls: ['./app.component.css', './app.component.2.css']
80+
})
81+
export class AppComponent {
82+
title = 'app';
83+
}
84+
`;
85+
const output = tags.stripIndent`
86+
"use strict";
87+
Object.defineProperty(exports, "__esModule", { value: true });
88+
89+
exports.AppComponent = void 0;
90+
const tslib_1 = require("tslib");
91+
const core_1 = require("@angular/core");
92+
let AppComponent = class AppComponent {
93+
constructor() { this.title = 'app'; }
94+
};
95+
AppComponent = tslib_1.__decorate([
96+
core_1.Component({
97+
selector: 'app-root',
98+
template: require("!raw-loader!./app.component.html").default,
99+
styles: [require("./app.component.css").default, require("./app.component.2.css").default] }) ], AppComponent);
100+
exports.AppComponent = AppComponent;
101+
`;
102+
103+
const result = transform(input, true, true, true, ts.ModuleKind.CommonJS);
104+
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
105+
});
106+
70107
it('should not replace resources when directTemplateLoading is false', () => {
71108
const input = tags.stripIndent`
72109
import { Component } from '@angular/core';

0 commit comments

Comments
 (0)