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

Commit

Permalink
fix(express-engine, hapi-engine): remove `@nguniversal/module-map-ngf…
Browse files Browse the repository at this point in the history
…actory-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
  • Loading branch information
alan-agius4 authored and vikerman committed Oct 8, 2019
1 parent 9674ebf commit 5798f19
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 5 deletions.
1 change: 1 addition & 0 deletions modules/common/schematics/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ ts_library(
"@npm//@angular-devkit/schematics",
"@npm//@schematics/angular",
"@npm//rxjs",
"@npm//typescript",
],
)

Expand Down
46 changes: 46 additions & 0 deletions modules/common/schematics/migrations/update-9/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,40 @@ describe('Migration to version 9', () => {
tree.create('/projects/test-app/server.ts', 'server content');
tree.create('/projects/test-app/webpack.server.config.js', 'webpack config content');

tree.overwrite('/projects/test-app/src/main.server.ts', `
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
export { AppServerModule } from './app/app.server.module';
export { renderModule, renderModuleFactory } from '@angular/platform-server';
export { ngExpressEngine } from '@nguniversal/express-engine';
export { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
`);

tree.overwrite('/projects/test-app/src/app/app.server.module.ts', `
import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';
@NgModule({
imports: [
AppModule,
ServerModule,
ModuleMapLoaderModule,
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
`);

const pkg = JSON.parse(tree.readContent('/package.json'));
const scripts = pkg.scripts;
scripts['compile:server'] = 'old compile:server';
Expand Down Expand Up @@ -69,4 +103,16 @@ describe('Migration to version 9', () => {
expect(scripts['build:ssr']).toBeUndefined();
expect(scripts['build:ssr_bak']).toBeUndefined();
});

it(`should remove '@nguniversal/module-map-ngfactory-loader' references`, async () => {
const newTree = await schematicRunner.callRule(version9UpdateRule(''), tree).toPromise();

const appServerModule =
newTree.read('/projects/test-app/src/app/app.server.module.ts')!.toString();
expect(appServerModule).not.toContain(`from '@nguniversal/module-map-ngfactory-loader';`);
expect(appServerModule).not.toContain('ModuleMapLoaderModule');

const mainServer = newTree.read('/projects/test-app/src/main.server.ts')!.toString();
expect(mainServer).not.toContain(`from '@nguniversal/module-map-ngfactory-loader';`);
});
});
119 changes: 114 additions & 5 deletions modules/common/schematics/migrations/update-9/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ import {
SchematicsException,
chain,
externalSchematic,
noop,
} from '@angular-devkit/schematics';
import * as ts from 'typescript';
import {getWorkspace} from '@schematics/angular/utility/workspace';
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
import {Builders} from '@schematics/angular/utility/workspace-models';
import {normalize, join} from '@angular-devkit/core';
import {normalize, join, Path} from '@angular-devkit/core';
import {Schema as UniversalOptions} from '@schematics/angular/universal/schema';
import {getDecoratorMetadata, getMetadataField} from '@schematics/angular/utility/ast-utils';
import {removePackageJsonDependency} from '@schematics/angular/utility/dependencies';

export function version9UpdateRule(collectionPath: string): Rule {
return async host => {
Expand Down Expand Up @@ -97,13 +101,118 @@ function updateProjectsStructureRule(collectionPath: string): Rule {
skipInstall: true,
};

if (!collectionPath) {
continue;
}
// Run the install schematic again so that we re-create the entire stucture.
installRules.push(externalSchematic(collectionPath, 'ng-add', installOptions));
installRules.push(
removeModuleMapNgfactoryLoaderRule(normalize(projectDefinition.sourceRoot)),
collectionPath
? externalSchematic(collectionPath, 'ng-add', installOptions)
: noop(),
);
}

return chain(installRules);
};
}

function removeModuleMapNgfactoryLoaderRule(sourceRoot: Path): Rule {
return tree => {
const moduleMapLoaderPackageName = '@nguniversal/module-map-ngfactory-loader';

// Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which
// which breaks the CLI UpdateRecorder.
// See: https://github.com/angular/angular/pull/30719
const createSourceFile = (path: string) => ts.createSourceFile(
path,
tree.read(path).toString().replace(/^\uFEFF/, ''),
ts.ScriptTarget.Latest,
true,
);

// Update main.server file
const mainServerPath = join(sourceRoot, 'main.server.ts');
if (tree.exists(mainServerPath)) {
const recorder = tree.beginUpdate(mainServerPath);

// Remove exports of '@nguniversal/module-map-ngfactory-loader'
createSourceFile(mainServerPath)
.statements
.filter(s => (
ts.isExportDeclaration(s) &&
s.moduleSpecifier &&
ts.isStringLiteral(s.moduleSpecifier) &&
s.moduleSpecifier.text === moduleMapLoaderPackageName
))
.forEach(node => {
const index = node.getFullStart();
const length = node.getFullWidth();
recorder.remove(index, length);
});
tree.commitUpdate(recorder);
}

// Update app.server.module file
const appServerModule = join(sourceRoot, 'app/app.server.module.ts');
if (tree.exists(appServerModule)) {
const recorder = tree.beginUpdate(appServerModule);
const appServerSourceFile = createSourceFile(appServerModule);

// Remove imports of '@nguniversal/module-map-ngfactory-loader'
appServerSourceFile
.statements
.filter(s => (
ts.isImportDeclaration(s) &&
s.moduleSpecifier &&
ts.isStringLiteral(s.moduleSpecifier) &&
s.moduleSpecifier.text === moduleMapLoaderPackageName
))
.forEach(node => {
const index = node.getFullStart();
const length = node.getFullWidth();
recorder.remove(index, length);
});


// Create a TS printer to get the text
const printer = ts.createPrinter();

// Remove 'ModuleMapLoaderModule' from 'NgModule' imports
getDecoratorMetadata(appServerSourceFile, 'NgModule', '@angular/core')
.forEach((metadata: ts.ObjectLiteralExpression) => {
const matchingProperties = getMetadataField(metadata, 'imports');

if (!matchingProperties) {
return;
}

const assignment = matchingProperties[0] as ts.PropertyAssignment;
if (!ts.isArrayLiteralExpression(assignment.initializer)) {
return;
}

const arrayLiteral = assignment.initializer;
const newImports = arrayLiteral.elements
.filter(n => !(ts.isIdentifier(n) && n.text === 'ModuleMapLoaderModule'));

if (arrayLiteral.elements.length !== newImports.length) {
const newImportsText = printer.printNode(
ts.EmitHint.Unspecified,
ts.updateArrayLiteral(arrayLiteral, newImports),
appServerSourceFile,
);

const index = arrayLiteral.getStart();
const length = arrayLiteral.getWidth();

recorder
.remove(index, length)
.insertLeft(index, newImportsText);
}
});

tree.commitUpdate(recorder);
}

// Remove package dependency
removePackageJsonDependency(tree, moduleMapLoaderPackageName);
};
}

0 comments on commit 5798f19

Please sign in to comment.