Skip to content

Commit c9c27a6

Browse files
FrozenPandazBrocco
authored andcommitted
refactor(@ngtools/webpack): refactor bootstrap refactoring
1 parent 1e1160f commit c9c27a6

File tree

4 files changed

+81
-48
lines changed

4 files changed

+81
-48
lines changed

packages/@ngtools/webpack/src/loader.ts

+74-47
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,7 @@ function _removeDecorators(refactor: TypeScriptFileRefactor) {
187187
}
188188

189189

190-
function _replaceBootstrap(plugin: AotPlugin, refactor: TypeScriptFileRefactor) {
191-
// If bootstrapModule can't be found, bail out early.
192-
if (!refactor.sourceMatch(/\bbootstrapModule\b/)) {
193-
return;
194-
}
195-
190+
function _getNgFactoryPath(plugin: AotPlugin, refactor: TypeScriptFileRefactor) {
196191
// Calculate the base path.
197192
const basePath = path.normalize(plugin.basePath);
198193
const genDir = path.normalize(plugin.genDir);
@@ -202,44 +197,20 @@ function _replaceBootstrap(plugin: AotPlugin, refactor: TypeScriptFileRefactor)
202197
const relativeEntryModulePath = path.relative(basePath, entryModuleFileName);
203198
const fullEntryModulePath = path.resolve(genDir, relativeEntryModulePath);
204199
const relativeNgFactoryPath = path.relative(dirName, fullEntryModulePath);
205-
const ngFactoryPath = './' + relativeNgFactoryPath.replace(/\\/g, '/');
206-
207-
const allCalls = refactor.findAstNodes(refactor.sourceFile,
208-
ts.SyntaxKind.CallExpression, true) as ts.CallExpression[];
209-
210-
const bootstraps = allCalls
211-
.filter(call => call.expression.kind == ts.SyntaxKind.PropertyAccessExpression)
212-
.map(call => call.expression as ts.PropertyAccessExpression)
213-
.filter(access => {
214-
return access.name.kind == ts.SyntaxKind.Identifier
215-
&& access.name.text == 'bootstrapModule';
216-
});
200+
return './' + relativeNgFactoryPath.replace(/\\/g, '/');
201+
}
217202

218-
const calls: ts.CallExpression[] = bootstraps
219-
.reduce((previous, access) => {
220-
const expressions
221-
= refactor.findAstNodes(access, ts.SyntaxKind.CallExpression, true) as ts.CallExpression[];
222-
return previous.concat(expressions);
223-
}, [])
224-
.filter((call: ts.CallExpression) => call.expression.kind == ts.SyntaxKind.Identifier)
225-
.filter((call: ts.CallExpression) => {
226-
// Find if the expression matches one of the replacement targets
227-
return !!changeMap[(call.expression as ts.Identifier).text];
228-
});
229203

230-
if (calls.length == 0) {
231-
// Didn't find any dynamic bootstrapping going on.
232-
return;
233-
}
234-
235-
// Create the changes we need.
236-
allCalls
237-
.filter(call => bootstraps.some(bs => bs == call.expression))
238-
.forEach((call: ts.CallExpression) => {
239-
refactor.replaceNode(call.arguments[0], entryModule.className + 'NgFactory');
240-
});
204+
function _replacePlatform(
205+
refactor: TypeScriptFileRefactor, bootstrapCall: ts.PropertyAccessExpression) {
206+
const platforms = (refactor.findAstNodes(bootstrapCall,
207+
ts.SyntaxKind.CallExpression, true) as ts.CallExpression[])
208+
.filter(call => {
209+
return call.expression.kind == ts.SyntaxKind.Identifier;
210+
})
211+
.filter(call => !!changeMap[(call.expression as ts.Identifier).text]);
241212

242-
calls.forEach(call => {
213+
platforms.forEach(call => {
243214
const platform = changeMap[(call.expression as ts.Identifier).text];
244215

245216
// Replace with mapped replacement
@@ -248,14 +219,70 @@ function _replaceBootstrap(plugin: AotPlugin, refactor: TypeScriptFileRefactor)
248219
// Add the appropriate import
249220
refactor.insertImport(platform.name, platform.importLocation);
250221
});
222+
}
223+
224+
225+
function _replaceBootstrap(refactor: TypeScriptFileRefactor, call: ts.CallExpression) {
226+
// If bootstrapModule can't be found, bail out early.
227+
if (!call.getText().includes('bootstrapModule')) {
228+
return;
229+
}
230+
231+
if (call.expression.kind == ts.SyntaxKind.PropertyAccessExpression) {
232+
const access = call.expression as ts.PropertyAccessExpression;
233+
234+
if (access.name.text === 'bootstrapModule') {
235+
_replacePlatform(refactor, access);
236+
refactor.replaceNode(access.name, 'bootstrapModuleFactory');
237+
}
238+
}
239+
}
240+
241+
242+
function _getCaller(node: ts.Node): ts.CallExpression {
243+
while (node = node.parent) {
244+
if (node.kind === ts.SyntaxKind.CallExpression) {
245+
return node as ts.CallExpression;
246+
}
247+
}
248+
return null;
249+
}
250+
251251

252-
bootstraps
253-
.forEach((bs: ts.PropertyAccessExpression) => {
254-
// This changes the call.
255-
refactor.replaceNode(bs.name, 'bootstrapModuleFactory');
252+
function _replaceEntryModule(plugin: AotPlugin, refactor: TypeScriptFileRefactor) {
253+
const modules = refactor.findAstNodes(refactor.sourceFile, ts.SyntaxKind.Identifier, true)
254+
.filter(identifier => identifier.getText() === plugin.entryModule.className)
255+
.filter(identifier =>
256+
identifier.parent.kind === ts.SyntaxKind.CallExpression ||
257+
identifier.parent.kind === ts.SyntaxKind.PropertyAssignment)
258+
.filter(node => !!_getCaller(node));
259+
260+
if (modules.length == 0) {
261+
return;
262+
}
263+
264+
const factoryClassName = plugin.entryModule.className + 'NgFactory';
265+
266+
refactor.insertImport(factoryClassName, _getNgFactoryPath(plugin, refactor));
267+
268+
modules
269+
.forEach(reference => {
270+
refactor.replaceNode(reference, factoryClassName);
271+
_replaceBootstrap(refactor, _getCaller(reference));
256272
});
273+
}
274+
275+
276+
function _refactorBootstrap(plugin: AotPlugin, refactor: TypeScriptFileRefactor) {
277+
const genDir = path.normalize(plugin.genDir);
278+
const dirName = path.normalize(path.dirname(refactor.fileName));
279+
280+
// Bail if in the generated directory
281+
if (dirName.startsWith(genDir)) {
282+
return;
283+
}
257284

258-
refactor.insertImport(entryModule.className + 'NgFactory', ngFactoryPath);
285+
_replaceEntryModule(plugin, refactor);
259286
}
260287

261288
export function removeModuleIdOnlyForTesting(refactor: TypeScriptFileRefactor) {
@@ -410,7 +437,7 @@ export function ngcLoader(this: LoaderContext & { _compilation: any }) {
410437
if (!plugin.skipCodeGeneration) {
411438
return Promise.resolve()
412439
.then(() => _removeDecorators(refactor))
413-
.then(() => _replaceBootstrap(plugin, refactor));
440+
.then(() => _refactorBootstrap(plugin, refactor));
414441
} else {
415442
return Promise.resolve()
416443
.then(() => _replaceResources(refactor))

tests/e2e/assets/webpack/test-server-app/app/app.module.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ export class HomeView {}
2424
],
2525
bootstrap: [AppComponent]
2626
})
27-
export class AppModule { }
27+
export class AppModule {
28+
static testProp: string;
29+
}

tests/e2e/assets/webpack/test-server-app/app/main.ts

+2
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ import 'core-js/es7/reflect';
22
import {platformDynamicServer} from '@angular/platform-server';
33
import {AppModule} from './app.module';
44

5+
AppModule.testProp = 'testing';
6+
57
platformDynamicServer().bootstrapModule(AppModule);

tests/e2e/tests/packages/webpack/server.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,7 @@ export default function(skipCleaning: () => void) {
1616
+ 'type: undefined, decorators.*Inject.*args: .*DOCUMENT.*'))
1717
.then(() => expectFileToMatch('dist/app.main.js',
1818
new RegExp('AppComponent.ctorParameters = .*MyInjectable'))
19+
.then(() => expectFileToMatch('dist/app.main.js',
20+
/AppModule.*\.testProp = \'testing\'/)
1921
.then(() => skipCleaning());
2022
}

0 commit comments

Comments
 (0)