Skip to content
This repository was archived by the owner on Nov 22, 2024. It is now read-only.

Commit 5798f19

Browse files
alan-agius4vikerman
authored andcommitted
fix(express-engine, hapi-engine): remove @nguniversal/module-map-ngfactory-loader during ng update
String based lazy loading syntax is not support with Ivy and hence `@nguniversal/module-map-ngfactory-loader` is no longer required. When not removed the application is left in a broken state with the following runtime error ``` NullInjectorError: R3InjectorError[router_RouterModule -> router_Router -> NgModuleFactoryLoader -> InjectionToken MODULE_MAP -> InjectionToken MODULE_MAP -> InjectionToken MODULE_MAP]: ``` Fixes: #1272
1 parent 9674ebf commit 5798f19

File tree

3 files changed

+161
-5
lines changed

3 files changed

+161
-5
lines changed

modules/common/schematics/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ ts_library(
2626
"@npm//@angular-devkit/schematics",
2727
"@npm//@schematics/angular",
2828
"@npm//rxjs",
29+
"@npm//typescript",
2930
],
3031
)
3132

modules/common/schematics/migrations/update-9/index.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,40 @@ describe('Migration to version 9', () => {
3434
tree.create('/projects/test-app/server.ts', 'server content');
3535
tree.create('/projects/test-app/webpack.server.config.js', 'webpack config content');
3636

37+
tree.overwrite('/projects/test-app/src/main.server.ts', `
38+
import { enableProdMode } from '@angular/core';
39+
40+
import { environment } from './environments/environment';
41+
42+
if (environment.production) {
43+
enableProdMode();
44+
}
45+
46+
export { AppServerModule } from './app/app.server.module';
47+
export { renderModule, renderModuleFactory } from '@angular/platform-server';
48+
export { ngExpressEngine } from '@nguniversal/express-engine';
49+
export { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
50+
`);
51+
52+
tree.overwrite('/projects/test-app/src/app/app.server.module.ts', `
53+
import { NgModule } from '@angular/core';
54+
import { ServerModule } from '@angular/platform-server';
55+
56+
import { AppModule } from './app.module';
57+
import { AppComponent } from './app.component';
58+
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
59+
60+
@NgModule({
61+
imports: [
62+
AppModule,
63+
ServerModule,
64+
ModuleMapLoaderModule,
65+
],
66+
bootstrap: [AppComponent],
67+
})
68+
export class AppServerModule {}
69+
`);
70+
3771
const pkg = JSON.parse(tree.readContent('/package.json'));
3872
const scripts = pkg.scripts;
3973
scripts['compile:server'] = 'old compile:server';
@@ -69,4 +103,16 @@ describe('Migration to version 9', () => {
69103
expect(scripts['build:ssr']).toBeUndefined();
70104
expect(scripts['build:ssr_bak']).toBeUndefined();
71105
});
106+
107+
it(`should remove '@nguniversal/module-map-ngfactory-loader' references`, async () => {
108+
const newTree = await schematicRunner.callRule(version9UpdateRule(''), tree).toPromise();
109+
110+
const appServerModule =
111+
newTree.read('/projects/test-app/src/app/app.server.module.ts')!.toString();
112+
expect(appServerModule).not.toContain(`from '@nguniversal/module-map-ngfactory-loader';`);
113+
expect(appServerModule).not.toContain('ModuleMapLoaderModule');
114+
115+
const mainServer = newTree.read('/projects/test-app/src/main.server.ts')!.toString();
116+
expect(mainServer).not.toContain(`from '@nguniversal/module-map-ngfactory-loader';`);
117+
});
72118
});

modules/common/schematics/migrations/update-9/index.ts

Lines changed: 114 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,16 @@ import {
1111
SchematicsException,
1212
chain,
1313
externalSchematic,
14+
noop,
1415
} from '@angular-devkit/schematics';
16+
import * as ts from 'typescript';
1517
import {getWorkspace} from '@schematics/angular/utility/workspace';
1618
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
1719
import {Builders} from '@schematics/angular/utility/workspace-models';
18-
import {normalize, join} from '@angular-devkit/core';
20+
import {normalize, join, Path} from '@angular-devkit/core';
1921
import {Schema as UniversalOptions} from '@schematics/angular/universal/schema';
22+
import {getDecoratorMetadata, getMetadataField} from '@schematics/angular/utility/ast-utils';
23+
import {removePackageJsonDependency} from '@schematics/angular/utility/dependencies';
2024

2125
export function version9UpdateRule(collectionPath: string): Rule {
2226
return async host => {
@@ -97,13 +101,118 @@ function updateProjectsStructureRule(collectionPath: string): Rule {
97101
skipInstall: true,
98102
};
99103

100-
if (!collectionPath) {
101-
continue;
102-
}
103104
// Run the install schematic again so that we re-create the entire stucture.
104-
installRules.push(externalSchematic(collectionPath, 'ng-add', installOptions));
105+
installRules.push(
106+
removeModuleMapNgfactoryLoaderRule(normalize(projectDefinition.sourceRoot)),
107+
collectionPath
108+
? externalSchematic(collectionPath, 'ng-add', installOptions)
109+
: noop(),
110+
);
105111
}
106112

107113
return chain(installRules);
108114
};
109115
}
116+
117+
function removeModuleMapNgfactoryLoaderRule(sourceRoot: Path): Rule {
118+
return tree => {
119+
const moduleMapLoaderPackageName = '@nguniversal/module-map-ngfactory-loader';
120+
121+
// Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which
122+
// which breaks the CLI UpdateRecorder.
123+
// See: https://github.com/angular/angular/pull/30719
124+
const createSourceFile = (path: string) => ts.createSourceFile(
125+
path,
126+
tree.read(path).toString().replace(/^\uFEFF/, ''),
127+
ts.ScriptTarget.Latest,
128+
true,
129+
);
130+
131+
// Update main.server file
132+
const mainServerPath = join(sourceRoot, 'main.server.ts');
133+
if (tree.exists(mainServerPath)) {
134+
const recorder = tree.beginUpdate(mainServerPath);
135+
136+
// Remove exports of '@nguniversal/module-map-ngfactory-loader'
137+
createSourceFile(mainServerPath)
138+
.statements
139+
.filter(s => (
140+
ts.isExportDeclaration(s) &&
141+
s.moduleSpecifier &&
142+
ts.isStringLiteral(s.moduleSpecifier) &&
143+
s.moduleSpecifier.text === moduleMapLoaderPackageName
144+
))
145+
.forEach(node => {
146+
const index = node.getFullStart();
147+
const length = node.getFullWidth();
148+
recorder.remove(index, length);
149+
});
150+
tree.commitUpdate(recorder);
151+
}
152+
153+
// Update app.server.module file
154+
const appServerModule = join(sourceRoot, 'app/app.server.module.ts');
155+
if (tree.exists(appServerModule)) {
156+
const recorder = tree.beginUpdate(appServerModule);
157+
const appServerSourceFile = createSourceFile(appServerModule);
158+
159+
// Remove imports of '@nguniversal/module-map-ngfactory-loader'
160+
appServerSourceFile
161+
.statements
162+
.filter(s => (
163+
ts.isImportDeclaration(s) &&
164+
s.moduleSpecifier &&
165+
ts.isStringLiteral(s.moduleSpecifier) &&
166+
s.moduleSpecifier.text === moduleMapLoaderPackageName
167+
))
168+
.forEach(node => {
169+
const index = node.getFullStart();
170+
const length = node.getFullWidth();
171+
recorder.remove(index, length);
172+
});
173+
174+
175+
// Create a TS printer to get the text
176+
const printer = ts.createPrinter();
177+
178+
// Remove 'ModuleMapLoaderModule' from 'NgModule' imports
179+
getDecoratorMetadata(appServerSourceFile, 'NgModule', '@angular/core')
180+
.forEach((metadata: ts.ObjectLiteralExpression) => {
181+
const matchingProperties = getMetadataField(metadata, 'imports');
182+
183+
if (!matchingProperties) {
184+
return;
185+
}
186+
187+
const assignment = matchingProperties[0] as ts.PropertyAssignment;
188+
if (!ts.isArrayLiteralExpression(assignment.initializer)) {
189+
return;
190+
}
191+
192+
const arrayLiteral = assignment.initializer;
193+
const newImports = arrayLiteral.elements
194+
.filter(n => !(ts.isIdentifier(n) && n.text === 'ModuleMapLoaderModule'));
195+
196+
if (arrayLiteral.elements.length !== newImports.length) {
197+
const newImportsText = printer.printNode(
198+
ts.EmitHint.Unspecified,
199+
ts.updateArrayLiteral(arrayLiteral, newImports),
200+
appServerSourceFile,
201+
);
202+
203+
const index = arrayLiteral.getStart();
204+
const length = arrayLiteral.getWidth();
205+
206+
recorder
207+
.remove(index, length)
208+
.insertLeft(index, newImportsText);
209+
}
210+
});
211+
212+
tree.commitUpdate(recorder);
213+
}
214+
215+
// Remove package dependency
216+
removePackageJsonDependency(tree, moduleMapLoaderPackageName);
217+
};
218+
}

0 commit comments

Comments
 (0)