From 9edd484e41848e7cc963d6b0bb628dcdd71d46a7 Mon Sep 17 00:00:00 2001 From: Adam Plumer Date: Tue, 19 Feb 2019 00:07:05 -0600 Subject: [PATCH 1/2] feat(express-engine): add ModuleMapLoaderModule to the app server imports during ng-add --- .../schematics/install/index.spec.ts | 9 ++- .../schematics/install/index.ts | 65 ++++++++++++++++++- .../schematics/install/utils.ts | 54 +++++++++++++++ 3 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 modules/express-engine/schematics/install/utils.ts diff --git a/modules/express-engine/schematics/install/index.spec.ts b/modules/express-engine/schematics/install/index.spec.ts index bd6c4c803..f3fcb4d49 100644 --- a/modules/express-engine/schematics/install/index.spec.ts +++ b/modules/express-engine/schematics/install/index.spec.ts @@ -88,8 +88,15 @@ describe('Universal Schematic', () => { noUniversal.skipUniversal = true; const tree = schematicRunner.runSchematic('ng-add', noUniversal, appTree); - const filePath = '/src/server.main.ts'; + const filePath = '/projects/bar/src/main.server.ts'; const contents = tree.readContent(filePath); expect(contents).toMatch(''); }); + + it('should add module map loader to server module imports', () => { + const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree); + const filePath = '/projects/bar/src/app/app.server.module.ts'; + const contents = tree.readContent(filePath); + expect(contents).toContain('ModuleMapLoaderModule'); + }); }); diff --git a/modules/express-engine/schematics/install/index.ts b/modules/express-engine/schematics/install/index.ts index 08e2c8985..538ab3581 100644 --- a/modules/express-engine/schematics/install/index.ts +++ b/modules/express-engine/schematics/install/index.ts @@ -5,7 +5,7 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ -import {experimental, strings} from '@angular-devkit/core'; +import {experimental, strings, normalize} from '@angular-devkit/core'; import { apply, chain, @@ -28,6 +28,15 @@ import { NodeDependencyType, } from '@schematics/angular/utility/dependencies'; import {BrowserBuilderOptions} from '@schematics/angular/utility/workspace-models'; +import {getProject} from '@schematics/angular/utility/project'; +import { + getProjectTargets, + targetBuildNotFoundError, +} from '@schematics/angular/utility/project-targets'; +import {InsertChange} from '@schematics/angular/utility/change'; +import {addSymbolToNgModuleMetadata, insertImport} from '@schematics/angular/utility/ast-utils'; +import * as ts from 'typescript'; +import {findAppServerModulePath} from './utils'; // TODO(CaerusKaru): make these configurable const BROWSER_DIST = 'dist/browser'; @@ -141,6 +150,59 @@ function updateConfigFile(options: UniversalOptions): Rule { }; } +function addModuleMapLoader(options: UniversalOptions): Rule { + return (host: Tree) => { + const clientProject = getProject(host, options.clientProject); + const clientTargets = getProjectTargets(clientProject); + if (!clientTargets.server) { + // If they skipped Universal schematics and don't have a server target, + // just get out + return; + } + const mainPath = normalize('/' + clientTargets.server.options.main); + + const appServerModuleRelativePath = findAppServerModulePath(host, mainPath); + const modulePath = normalize( + `/${clientProject.root}/src/${appServerModuleRelativePath}.ts`); + + // Add the module map loader import + let moduleSource = getTsSourceFile(host, modulePath); + const importModule = 'ModuleMapLoaderModule'; + const importPath = '@nguniversal/module-map-ngfactory-loader'; + const moduleMapImportChange = insertImport(moduleSource, modulePath, importModule, + importPath) as InsertChange; + if (moduleMapImportChange) { + const recorder = host.beginUpdate(modulePath); + recorder.insertLeft(moduleMapImportChange.pos, moduleMapImportChange.toAdd); + host.commitUpdate(recorder); + } + + // Add the module map loader module to the imports + const importText = 'ModuleMapLoaderModule'; + moduleSource = getTsSourceFile(host, modulePath); + const metadataChanges = addSymbolToNgModuleMetadata( + moduleSource, modulePath, 'imports', importText); + if (metadataChanges) { + const recorder = host.beginUpdate(modulePath); + metadataChanges.forEach((change: InsertChange) => { + recorder.insertRight(change.pos, change.toAdd); + }); + host.commitUpdate(recorder); + } + }; +} + +function getTsSourceFile(host: Tree, path: string): ts.SourceFile { + const buffer = host.read(path); + if (!buffer) { + throw new SchematicsException(`Could not read file (${path}).`); + } + const content = buffer.toString(); + const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true); + + return source; +} + export default function (options: UniversalOptions): Rule { return (host: Tree, context: SchematicContext) => { const clientProject = getClientProject(host, options); @@ -171,6 +233,7 @@ export default function (options: UniversalOptions): Rule { updateConfigFile(options), mergeWith(rootSource), addDependenciesAndScripts(options), + addModuleMapLoader(options), ]); }; } diff --git a/modules/express-engine/schematics/install/utils.ts b/modules/express-engine/schematics/install/utils.ts new file mode 100644 index 000000000..5a0eb0ae1 --- /dev/null +++ b/modules/express-engine/schematics/install/utils.ts @@ -0,0 +1,54 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import { SchematicsException, Tree } from '@angular-devkit/schematics'; +import * as ts from 'typescript'; +import {getSourceNodes} from '@schematics/angular/utility/ast-utils'; + +export function findAppServerModuleExport(host: Tree, + mainPath: string): ts.ExportDeclaration | null { + const mainBuffer = host.read(mainPath); + if (!mainBuffer) { + throw new SchematicsException(`Main file (${mainPath}) not found`); + } + const mainText = mainBuffer.toString('utf-8'); + const source = ts.createSourceFile(mainPath, mainText, ts.ScriptTarget.Latest, true); + + const allNodes = getSourceNodes(source); + + let exportDeclaration: ts.ExportDeclaration | null = null; + + for (const node of allNodes) { + + let exportDeclarationNode: ts.Node | null = node; + + // Walk up the parent until ExportDeclaration is found. + while (exportDeclarationNode && exportDeclarationNode.parent + && exportDeclarationNode.parent.kind !== ts.SyntaxKind.ExportDeclaration) { + exportDeclarationNode = exportDeclarationNode.parent; + } + + if (exportDeclarationNode !== null && + exportDeclarationNode.parent !== undefined && + exportDeclarationNode.parent.kind === ts.SyntaxKind.ExportDeclaration) { + exportDeclaration = exportDeclarationNode.parent as ts.ExportDeclaration; + break; + } + } + + return exportDeclaration; +} + +export function findAppServerModulePath(host: Tree, mainPath: string): string { + const exportDeclaration = findAppServerModuleExport(host, mainPath); + if (!exportDeclaration) { + throw new SchematicsException('Export declaration not found'); + } + + const moduleSpecifier = exportDeclaration.moduleSpecifier.getText(); + return moduleSpecifier.substring(1, moduleSpecifier.length - 1); +} From ade9b793cd0554c40233caec1623541a4e1a780d Mon Sep 17 00:00:00 2001 From: Adam Plumer Date: Wed, 6 Mar 2019 21:02:14 -0600 Subject: [PATCH 2/2] fixup! feat(express-engine): add ModuleMapLoaderModule to the app server imports during ng-add --- modules/express-engine/schematics/install/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/express-engine/schematics/install/utils.ts b/modules/express-engine/schematics/install/utils.ts index 5a0eb0ae1..b5c0c7a64 100644 --- a/modules/express-engine/schematics/install/utils.ts +++ b/modules/express-engine/schematics/install/utils.ts @@ -46,7 +46,7 @@ export function findAppServerModuleExport(host: Tree, export function findAppServerModulePath(host: Tree, mainPath: string): string { const exportDeclaration = findAppServerModuleExport(host, mainPath); if (!exportDeclaration) { - throw new SchematicsException('Export declaration not found'); + throw new SchematicsException('Could not find app server module export'); } const moduleSpecifier = exportDeclaration.moduleSpecifier.getText();