Skip to content

Commit 83af31c

Browse files
Alanhansl
authored andcommitted
fix(@schematics/angular): wrapping bootstrap code in a DOMContentLoaded in variable declaration
Fixes #13042
1 parent bff2790 commit 83af31c

File tree

2 files changed

+76
-4
lines changed

2 files changed

+76
-4
lines changed

packages/schematics/angular/universal/index.ts

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,63 @@ function wrapBootstrapCall(options: UniversalOptions): Rule {
115115
let currentCall = bootstrapCall;
116116
while (bootstrapCallExpression === null && currentCall.parent) {
117117
currentCall = currentCall.parent;
118-
if (currentCall.kind === ts.SyntaxKind.ExpressionStatement) {
118+
if (ts.isExpressionStatement(currentCall) || ts.isVariableStatement(currentCall)) {
119119
bootstrapCallExpression = currentCall;
120120
}
121121
}
122122
bootstrapCall = currentCall;
123123

124+
// In case the bootstrap code is a variable statement
125+
// we need to determine it's usage
126+
if (bootstrapCallExpression && ts.isVariableStatement(bootstrapCallExpression)) {
127+
const declaration = bootstrapCallExpression.declarationList.declarations[0];
128+
const bootstrapVar = (declaration.name as ts.Identifier).text;
129+
const sf = bootstrapCallExpression.getSourceFile();
130+
bootstrapCall = findCallExpressionNode(sf, bootstrapVar) || currentCall;
131+
}
132+
133+
// indent contents
134+
const triviaWidth = bootstrapCall.getLeadingTriviaWidth();
135+
const beforeText = `document.addEventListener('DOMContentLoaded', () => {\n`
136+
+ ' '.repeat(triviaWidth > 2 ? triviaWidth + 1 : triviaWidth);
137+
const afterText = `\n${triviaWidth > 2 ? ' '.repeat(triviaWidth - 1) : ''});`;
138+
139+
// in some cases we need to cater for a trailing semicolon such as;
140+
// bootstrap().catch(err => console.log(err));
141+
const lastToken = bootstrapCall.parent.getLastToken();
142+
let endPos = bootstrapCall.getEnd();
143+
if (lastToken && lastToken.kind === ts.SyntaxKind.SemicolonToken) {
144+
endPos = lastToken.getEnd();
145+
}
146+
124147
const recorder = host.beginUpdate(mainPath);
125-
const beforeText = `document.addEventListener('DOMContentLoaded', () => {\n `;
126-
const afterText = `\n});`;
127148
recorder.insertLeft(bootstrapCall.getStart(), beforeText);
128-
recorder.insertRight(bootstrapCall.getEnd(), afterText);
149+
recorder.insertRight(endPos, afterText);
129150
host.commitUpdate(recorder);
130151
};
131152
}
132153

154+
function findCallExpressionNode(node: ts.Node, text: string): ts.Node | null {
155+
if (
156+
ts.isCallExpression(node)
157+
&& ts.isIdentifier(node.expression)
158+
&& node.expression.text === text
159+
) {
160+
return node;
161+
}
162+
163+
let foundNode: ts.Node | null = null;
164+
ts.forEachChild(node, childNode => {
165+
foundNode = findCallExpressionNode(childNode, text);
166+
167+
if (foundNode) {
168+
return true;
169+
}
170+
});
171+
172+
return foundNode;
173+
}
174+
133175
function addServerTransition(options: UniversalOptions): Rule {
134176
return (host: Tree) => {
135177
const clientProject = getProject(host, options.clientProject);

packages/schematics/angular/universal/index_spec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,36 @@ describe('Universal Schematic', () => {
153153
expect(contents).toMatch(/document.addEventListener\('DOMContentLoaded', \(\) => {/);
154154
});
155155

156+
it('should wrap the bootstrap decleration in a DOMContentLoaded event handler', () => {
157+
const filePath = '/projects/bar/src/main.ts';
158+
appTree.overwrite(
159+
filePath,
160+
`
161+
import { enableProdMode } from '@angular/core';
162+
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
163+
import { AppModule } from './app/app.module';
164+
import { environment } from './environments/environment';
165+
import { hmrBootstrap } from './hmr';
166+
167+
if (environment.production) {
168+
enableProdMode();
169+
}
170+
171+
const bootstrap = () => platformBrowserDynamic().bootstrapModule(AppModule);
172+
173+
if (!hmrBootstrap) {
174+
bootstrap().catch(err => console.log(err));
175+
}
176+
`,
177+
);
178+
179+
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
180+
const contents = tree.readContent(filePath);
181+
expect(contents).toMatch(
182+
/document.addEventListener\('DOMContentLoaded', \(\) => {[\n\r\s]+bootstrap\(\)/,
183+
);
184+
});
185+
156186
it('should install npm dependencies', () => {
157187
schematicRunner.runSchematic('universal', defaultOptions, appTree);
158188
expect(schematicRunner.tasks.length).toBe(1);

0 commit comments

Comments
 (0)