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

Commit e776dfc

Browse files
CaerusKaruvikerman
authored andcommitted
feat(express-engine): add ModuleMapLoaderModule to the app server imports during ng-add (#1130)
1 parent 87b6c06 commit e776dfc

3 files changed

Lines changed: 126 additions & 2 deletions

File tree

modules/express-engine/schematics/install/index.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,15 @@ describe('Universal Schematic', () => {
8888
noUniversal.skipUniversal = true;
8989

9090
const tree = schematicRunner.runSchematic('ng-add', noUniversal, appTree);
91-
const filePath = '/src/server.main.ts';
91+
const filePath = '/projects/bar/src/main.server.ts';
9292
const contents = tree.readContent(filePath);
9393
expect(contents).toMatch('');
9494
});
95+
96+
it('should add module map loader to server module imports', () => {
97+
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
98+
const filePath = '/projects/bar/src/app/app.server.module.ts';
99+
const contents = tree.readContent(filePath);
100+
expect(contents).toContain('ModuleMapLoaderModule');
101+
});
95102
});

modules/express-engine/schematics/install/index.ts

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {experimental, strings} from '@angular-devkit/core';
8+
import {experimental, strings, normalize} from '@angular-devkit/core';
99
import {
1010
apply,
1111
chain,
@@ -28,6 +28,15 @@ import {
2828
NodeDependencyType,
2929
} from '@schematics/angular/utility/dependencies';
3030
import {BrowserBuilderOptions} from '@schematics/angular/utility/workspace-models';
31+
import {getProject} from '@schematics/angular/utility/project';
32+
import {
33+
getProjectTargets,
34+
targetBuildNotFoundError,
35+
} from '@schematics/angular/utility/project-targets';
36+
import {InsertChange} from '@schematics/angular/utility/change';
37+
import {addSymbolToNgModuleMetadata, insertImport} from '@schematics/angular/utility/ast-utils';
38+
import * as ts from 'typescript';
39+
import {findAppServerModulePath} from './utils';
3140

3241
// TODO(CaerusKaru): make these configurable
3342
const BROWSER_DIST = 'dist/browser';
@@ -141,6 +150,59 @@ function updateConfigFile(options: UniversalOptions): Rule {
141150
};
142151
}
143152

153+
function addModuleMapLoader(options: UniversalOptions): Rule {
154+
return (host: Tree) => {
155+
const clientProject = getProject(host, options.clientProject);
156+
const clientTargets = getProjectTargets(clientProject);
157+
if (!clientTargets.server) {
158+
// If they skipped Universal schematics and don't have a server target,
159+
// just get out
160+
return;
161+
}
162+
const mainPath = normalize('/' + clientTargets.server.options.main);
163+
164+
const appServerModuleRelativePath = findAppServerModulePath(host, mainPath);
165+
const modulePath = normalize(
166+
`/${clientProject.root}/src/${appServerModuleRelativePath}.ts`);
167+
168+
// Add the module map loader import
169+
let moduleSource = getTsSourceFile(host, modulePath);
170+
const importModule = 'ModuleMapLoaderModule';
171+
const importPath = '@nguniversal/module-map-ngfactory-loader';
172+
const moduleMapImportChange = insertImport(moduleSource, modulePath, importModule,
173+
importPath) as InsertChange;
174+
if (moduleMapImportChange) {
175+
const recorder = host.beginUpdate(modulePath);
176+
recorder.insertLeft(moduleMapImportChange.pos, moduleMapImportChange.toAdd);
177+
host.commitUpdate(recorder);
178+
}
179+
180+
// Add the module map loader module to the imports
181+
const importText = 'ModuleMapLoaderModule';
182+
moduleSource = getTsSourceFile(host, modulePath);
183+
const metadataChanges = addSymbolToNgModuleMetadata(
184+
moduleSource, modulePath, 'imports', importText);
185+
if (metadataChanges) {
186+
const recorder = host.beginUpdate(modulePath);
187+
metadataChanges.forEach((change: InsertChange) => {
188+
recorder.insertRight(change.pos, change.toAdd);
189+
});
190+
host.commitUpdate(recorder);
191+
}
192+
};
193+
}
194+
195+
function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
196+
const buffer = host.read(path);
197+
if (!buffer) {
198+
throw new SchematicsException(`Could not read file (${path}).`);
199+
}
200+
const content = buffer.toString();
201+
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
202+
203+
return source;
204+
}
205+
144206
export default function (options: UniversalOptions): Rule {
145207
return (host: Tree, context: SchematicContext) => {
146208
const clientProject = getClientProject(host, options);
@@ -171,6 +233,7 @@ export default function (options: UniversalOptions): Rule {
171233
updateConfigFile(options),
172234
mergeWith(rootSource),
173235
addDependenciesAndScripts(options),
236+
addModuleMapLoader(options),
174237
]);
175238
};
176239
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import { SchematicsException, Tree } from '@angular-devkit/schematics';
9+
import * as ts from 'typescript';
10+
import {getSourceNodes} from '@schematics/angular/utility/ast-utils';
11+
12+
export function findAppServerModuleExport(host: Tree,
13+
mainPath: string): ts.ExportDeclaration | null {
14+
const mainBuffer = host.read(mainPath);
15+
if (!mainBuffer) {
16+
throw new SchematicsException(`Main file (${mainPath}) not found`);
17+
}
18+
const mainText = mainBuffer.toString('utf-8');
19+
const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true);
20+
21+
const allNodes = getSourceNodes(source);
22+
23+
let exportDeclaration: ts.ExportDeclaration | null = null;
24+
25+
for (const node of allNodes) {
26+
27+
let exportDeclarationNode: ts.Node | null = node;
28+
29+
// Walk up the parent until ExportDeclaration is found.
30+
while (exportDeclarationNode && exportDeclarationNode.parent
31+
&& exportDeclarationNode.parent.kind !== ts.SyntaxKind.ExportDeclaration) {
32+
exportDeclarationNode = exportDeclarationNode.parent;
33+
}
34+
35+
if (exportDeclarationNode !== null &&
36+
exportDeclarationNode.parent !== undefined &&
37+
exportDeclarationNode.parent.kind === ts.SyntaxKind.ExportDeclaration) {
38+
exportDeclaration = exportDeclarationNode.parent as ts.ExportDeclaration;
39+
break;
40+
}
41+
}
42+
43+
return exportDeclaration;
44+
}
45+
46+
export function findAppServerModulePath(host: Tree, mainPath: string): string {
47+
const exportDeclaration = findAppServerModuleExport(host, mainPath);
48+
if (!exportDeclaration) {
49+
throw new SchematicsException('Could not find app server module export');
50+
}
51+
52+
const moduleSpecifier = exportDeclaration.moduleSpecifier.getText();
53+
return moduleSpecifier.substring(1, moduleSpecifier.length - 1);
54+
}

0 commit comments

Comments
 (0)