Skip to content

Commit 81af213

Browse files
CaerusKaruvikerman
authored andcommitted
feat(hapi-engine): add ModuleMapLoaderModule to the app server imports during ng-add (#1143)
1 parent d8be7af commit 81af213

File tree

4 files changed

+126
-2
lines changed

4 files changed

+126
-2
lines changed

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

+7
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,11 @@ describe('Universal Schematic', () => {
115115
const contents = tree.readContent(filePath);
116116
expect(contents).toMatch('');
117117
});
118+
119+
it('should add module map loader to server module imports', () => {
120+
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
121+
const filePath = '/projects/bar/src/app/app.server.module.ts';
122+
const contents = tree.readContent(filePath);
123+
expect(contents).toContain('ModuleMapLoaderModule');
124+
});
118125
});

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

+61-1
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
Rule,
1111
SchematicContext,
@@ -28,6 +28,12 @@ import {
2828
addPackageJsonDependency,
2929
NodeDependencyType,
3030
} from '@schematics/angular/utility/dependencies';
31+
import {getProject} from '@schematics/angular/utility/project';
32+
import {getProjectTargets} from '@schematics/angular/utility/project-targets';
33+
import * as ts from 'typescript';
34+
import {findAppServerModulePath} from './utils';
35+
import {addSymbolToNgModuleMetadata, insertImport} from '@schematics/angular/utility/ast-utils';
36+
import {InsertChange} from '@schematics/angular/utility/change';
3137

3238
// TODO(CaerusKaru): make these configurable
3339
const BROWSER_DIST = 'dist/browser';
@@ -146,6 +152,59 @@ function updateConfigFile(options: UniversalOptions): Rule {
146152
};
147153
}
148154

155+
function addModuleMapLoader(options: UniversalOptions): Rule {
156+
return (host: Tree) => {
157+
const clientProject = getProject(host, options.clientProject);
158+
const clientTargets = getProjectTargets(clientProject);
159+
if (!clientTargets.server) {
160+
// If they skipped Universal schematics and don't have a server target,
161+
// just get out
162+
return;
163+
}
164+
const mainPath = normalize('/' + clientTargets.server.options.main);
165+
166+
const appServerModuleRelativePath = findAppServerModulePath(host, mainPath);
167+
const modulePath = normalize(
168+
`/${clientProject.root}/src/${appServerModuleRelativePath}.ts`);
169+
170+
// Add the module map loader import
171+
let moduleSource = getTsSourceFile(host, modulePath);
172+
const importModule = 'ModuleMapLoaderModule';
173+
const importPath = '@nguniversal/module-map-ngfactory-loader';
174+
const moduleMapImportChange = insertImport(moduleSource, modulePath, importModule,
175+
importPath) as InsertChange;
176+
if (moduleMapImportChange) {
177+
const recorder = host.beginUpdate(modulePath);
178+
recorder.insertLeft(moduleMapImportChange.pos, moduleMapImportChange.toAdd);
179+
host.commitUpdate(recorder);
180+
}
181+
182+
// Add the module map loader module to the imports
183+
const importText = 'ModuleMapLoaderModule';
184+
moduleSource = getTsSourceFile(host, modulePath);
185+
const metadataChanges = addSymbolToNgModuleMetadata(
186+
moduleSource, modulePath, 'imports', importText);
187+
if (metadataChanges) {
188+
const recorder = host.beginUpdate(modulePath);
189+
metadataChanges.forEach((change: InsertChange) => {
190+
recorder.insertRight(change.pos, change.toAdd);
191+
});
192+
host.commitUpdate(recorder);
193+
}
194+
};
195+
}
196+
197+
function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
198+
const buffer = host.read(path);
199+
if (!buffer) {
200+
throw new SchematicsException(`Could not read file (${path}).`);
201+
}
202+
const content = buffer.toString();
203+
const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);
204+
205+
return source;
206+
}
207+
149208
export default function (options: UniversalOptions): Rule {
150209
return (host: Tree, context: SchematicContext) => {
151210
const clientProject = getClientProject(host, options);
@@ -176,6 +235,7 @@ export default function (options: UniversalOptions): Rule {
176235
updateConfigFile(options),
177236
mergeWith(rootSource),
178237
addDependenciesAndScripts(options),
238+
addModuleMapLoader(options),
179239
]);
180240
};
181241
}
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+
}

modules/hapi-engine/schematics/tsconfig.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,8 @@
1717
"exclude": [
1818
"**/*.spec.ts",
1919
"*/files/**/*"
20-
]
20+
],
21+
"bazelOptions": {
22+
"suppressTsconfigOverrideWarnings": true
23+
}
2124
}

0 commit comments

Comments
 (0)