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

Commit 32cfef1

Browse files
Alan Agiusvikerman
authored andcommitted
feat(express-engine): update schematics to generate Ivy compatable code
With this change by default we support `ng add` only on Ivy applications. Users wishing to add this package in a non ivy project will need to this manually. **Note:**: for Ivy users are required to use `bundleDependencies` which is turned on by default in CLI version 9. Users opting out of `bundleDependencies` will need to run `ivy-ngcc` manually. The latter is rerquired because when not bundling the server using webpack during runtime Node will resolve the packages to their VE version.
1 parent 563e608 commit 32cfef1

File tree

4 files changed

+56
-136
lines changed

4 files changed

+56
-136
lines changed

modules/express-engine/schematics/install/files/root/__serverFileName@stripTsExtension__.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,11 @@ const PORT = process.env.PORT || <%= serverPort %>;
2727
const DIST_FOLDER = join(process.cwd(), '<%= getBrowserDistDirectory() %>');
2828

2929
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
30-
const {AppServerModuleNgFactory, LAZY_MODULE_MAP, ngExpressEngine, provideModuleMap} = require('./<%= getServerDistDirectory() %>/main');
30+
const {AppServerModule, ngExpressEngine} = require('./<%= getServerDistDirectory() %>/main');
3131

3232
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
3333
app.engine('html', ngExpressEngine({
34-
bootstrap: AppServerModuleNgFactory,
35-
providers: [
36-
provideModuleMap(LAZY_MODULE_MAP)
37-
]
34+
bootstrap: AppServerModule,
3835
}));
3936

4037
app.set('view engine', 'html');

modules/express-engine/schematics/install/files/root/webpack.server.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ module.exports = {
99
// This is our Express server for Dynamic universal
1010
server: './<%= stripTsExtension(serverFileName) %>.ts'
1111
},
12-
externals: {
13-
'./<%= getServerDistDirectory() %>/main': 'require("./server/main")'
14-
},
12+
externals: [
13+
'./<%= getServerDistDirectory() %>/main'
14+
],
1515
target: 'node',
1616
resolve: { extensions: ['.ts', '.js'] },
1717
optimization: {

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

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,6 @@ describe('Universal Schematic', () => {
2525
schematicRunner = new SchematicTestRunner('schematics', collectionPath);
2626
});
2727

28-
it('should add dependency: @nguniversal/module-map-ngfactory-loader',
29-
async () => {
30-
const tree = await schematicRunner
31-
.runSchematicAsync('ng-add', defaultOptions, appTree)
32-
.toPromise();
33-
const filePath = '/package.json';
34-
const contents = tree.readContent(filePath);
35-
expect(contents).toMatch(
36-
/\"@nguniversal\/module-map-ngfactory-loader\": \"/);
37-
});
38-
3928
it('should add dependency: @nguniversal/express-engine', async () => {
4029
const tree = await schematicRunner
4130
.runSchematicAsync('ng-add', defaultOptions, appTree)
@@ -116,23 +105,13 @@ describe('Universal Schematic', () => {
116105
expect(contents).toMatch('');
117106
});
118107

119-
it('should add module map loader to server module imports', async () => {
120-
const tree = await schematicRunner
121-
.runSchematicAsync('ng-add', defaultOptions, appTree)
122-
.toPromise();
123-
const filePath = '/projects/bar/src/app/app.server.module.ts';
124-
const contents = tree.readContent(filePath);
125-
expect(contents).toContain('ModuleMapLoaderModule');
126-
});
127-
128108
it('should add exports to main server file', async () => {
129109
const tree = await schematicRunner
130110
.runSchematicAsync('ng-add', defaultOptions, appTree)
131111
.toPromise();
132112
const filePath = '/projects/bar/src/main.server.ts';
133113
const contents = tree.readContent(filePath);
134114
expect(contents).toContain('ngExpressEngine');
135-
expect(contents).toContain('provideModuleMap');
136115
});
137116

138117
it('should update angular.json', async () => {
@@ -142,8 +121,8 @@ describe('Universal Schematic', () => {
142121
const contents = JSON.parse(tree.readContent('angular.json'));
143122
const architect = contents.projects.bar.architect;
144123
expect(architect.build.configurations.production).toBeDefined();
145-
expect(architect.build.options.outputPath).toBe('dist/browser');
146-
expect(architect.server.options.outputPath).toBe('dist/server');
124+
expect(architect.build.options.outputPath).toBe('dist/bar/browser');
125+
expect(architect.server.options.outputPath).toBe('dist/bar/server');
147126

148127
const productionConfig = architect.server.configurations.production;
149128
expect(productionConfig.fileReplacements).toBeDefined();

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

Lines changed: 49 additions & 105 deletions
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, normalize} from '@angular-devkit/core';
8+
import {strings, normalize, workspaces, join} from '@angular-devkit/core';
99
import {
1010
apply,
1111
chain,
@@ -21,53 +21,35 @@ import {
2121
url,
2222
} from '@angular-devkit/schematics';
2323
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
24-
import {getWorkspace} from '@schematics/angular/utility/config';
2524
import {Schema as UniversalOptions} from './schema';
2625
import {
2726
addPackageJsonDependency,
2827
NodeDependencyType,
2928
} from '@schematics/angular/utility/dependencies';
30-
import {getProject} from '@schematics/angular/utility/project';
31-
import {getProjectTargets} from '@schematics/angular/utility/project-targets';
3229
import {InsertChange} from '@schematics/angular/utility/change';
33-
import {
34-
addSymbolToNgModuleMetadata,
35-
findNodes,
36-
insertAfterLastOccurrence,
37-
insertImport
38-
} from '@schematics/angular/utility/ast-utils';
30+
import {insertAfterLastOccurrence, findNodes} from '@schematics/angular/utility/ast-utils';
3931
import * as ts from 'typescript';
40-
import {findAppServerModulePath, generateExport, getTsSourceFile, getTsSourceText} from './utils';
41-
import {updateWorkspace} from '@schematics/angular/utility/workspace';
42-
43-
// TODO(CaerusKaru): make these configurable
44-
const BROWSER_DIST = 'dist/browser';
45-
const SERVER_DIST = 'dist/server';
46-
47-
function getClientProject(
48-
host: Tree, options: UniversalOptions,
49-
): experimental.workspace.WorkspaceProject {
50-
const workspace = getWorkspace(host);
51-
const clientProject = workspace.projects[options.clientProject];
52-
if (!clientProject) {
53-
throw new SchematicsException(`Client app ${options.clientProject} not found.`);
32+
import {generateExport, getTsSourceFile, getTsSourceText} from './utils';
33+
import {getWorkspace, updateWorkspace} from '@schematics/angular/utility/workspace';
34+
35+
async function getClientProject(host, projectName: string): Promise<workspaces.ProjectDefinition> {
36+
const workspace = await getWorkspace(host);
37+
const clientProject = workspace.projects.get(projectName);
38+
39+
if (!clientProject || clientProject.extensions.projectType !== 'application') {
40+
throw new SchematicsException(`Universal requires a project type of "application".`);
5441
}
5542

5643
return clientProject;
5744
}
5845

59-
function addDependenciesAndScripts(options: UniversalOptions): Rule {
46+
function addDependenciesAndScripts(options: UniversalOptions, serverDist: string): Rule {
6047
return (host: Tree) => {
6148
addPackageJsonDependency(host, {
6249
type: NodeDependencyType.Default,
6350
name: '@nguniversal/express-engine',
6451
version: '0.0.0-PLACEHOLDER',
6552
});
66-
addPackageJsonDependency(host, {
67-
type: NodeDependencyType.Default,
68-
name: '@nguniversal/module-map-ngfactory-loader',
69-
version: '0.0.0-PLACEHOLDER',
70-
});
7153
addPackageJsonDependency(host, {
7254
type: NodeDependencyType.Default,
7355
name: 'express',
@@ -100,23 +82,24 @@ function addDependenciesAndScripts(options: UniversalOptions): Rule {
10082
}
10183

10284
const pkg = JSON.parse(buffer.toString());
103-
104-
pkg.scripts['compile:server'] = options.webpack ?
105-
'webpack --config webpack.server.config.js --progress --colors' :
106-
`tsc -p ${serverFileName}.tsconfig.json`;
107-
pkg.scripts['serve:ssr'] = `node dist/${serverFileName}`;
108-
pkg.scripts['build:ssr'] = 'npm run build:client-and-server-bundles && npm run compile:server';
109-
pkg.scripts['build:client-and-server-bundles'] =
110-
// tslint:disable:max-line-length
111-
`ng build --prod && ng run ${options.clientProject}:server:production --bundleDependencies all`;
85+
pkg.scripts = {
86+
...pkg.scripts,
87+
'compile:server': options.webpack
88+
? 'webpack --config webpack.server.config.js --progress --colors'
89+
: `tsc -p ${serverFileName}.tsconfig.json`,
90+
'serve:ssr': `node ${serverDist.substr(1)}/${serverFileName}`,
91+
'build:ssr': 'npm run build:client-and-server-bundles && npm run compile:server',
92+
// tslint:disable-next-line: max-line-length
93+
'build:client-and-server-bundles': `ng build --prod && ng run ${options.clientProject}:server:production`,
94+
};
11295

11396
host.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
11497

11598
return host;
11699
};
117100
}
118101

119-
function updateConfigFile(options: UniversalOptions) {
102+
function updateConfigFile(options: UniversalOptions, browserDist: string, serverDist: string) {
120103
return updateWorkspace((workspace => {
121104
const clientProject = workspace.projects.get(options.clientProject);
122105
if (clientProject) {
@@ -132,93 +115,54 @@ function updateConfigFile(options: UniversalOptions) {
132115

133116
serverTarget.options = {
134117
...serverTarget.options,
135-
outputPath: SERVER_DIST,
118+
outputPath: serverDist,
136119
};
137120

138121
buildTarget.options = {
139122
...buildTarget.options,
140-
outputPath: BROWSER_DIST,
123+
outputPath: browserDist,
141124
};
142125
}
143126
}));
144127
}
145128

146-
function addModuleMapLoader(options: UniversalOptions): Rule {
147-
return (host: Tree) => {
148-
const clientProject = getProject(host, options.clientProject);
149-
const clientTargets = getProjectTargets(clientProject);
150-
if (!clientTargets.server) {
151-
// If they skipped Universal schematics and don't have a server target,
152-
// just get out
153-
return;
154-
}
155-
const mainPath = normalize('/' + clientTargets.server.options.main);
156-
const appServerModuleRelativePath = findAppServerModulePath(host, mainPath);
157-
const modulePath = normalize(
158-
`/${clientProject.root}/src/${appServerModuleRelativePath}.ts`);
159-
160-
// Add the module map loader import
161-
let moduleSource = getTsSourceFile(host, modulePath);
162-
const importModule = 'ModuleMapLoaderModule';
163-
const importPath = '@nguniversal/module-map-ngfactory-loader';
164-
const moduleMapImportChange = insertImport(moduleSource, modulePath, importModule,
165-
importPath) as InsertChange;
166-
if (moduleMapImportChange) {
167-
const recorder = host.beginUpdate(modulePath);
168-
recorder.insertLeft(moduleMapImportChange.pos, moduleMapImportChange.toAdd);
169-
host.commitUpdate(recorder);
170-
}
171-
172-
// Add the module map loader module to the imports
173-
const importText = 'ModuleMapLoaderModule';
174-
moduleSource = getTsSourceFile(host, modulePath);
175-
const metadataChanges = addSymbolToNgModuleMetadata(
176-
moduleSource, modulePath, 'imports', importText);
177-
if (metadataChanges) {
178-
const recorder = host.beginUpdate(modulePath);
179-
metadataChanges.forEach((change: InsertChange) => {
180-
recorder.insertRight(change.pos, change.toAdd);
181-
});
182-
host.commitUpdate(recorder);
183-
}
184-
};
185-
}
186-
187129
function addExports(options: UniversalOptions): Rule {
188-
return (host: Tree) => {
189-
const clientProject = getProject(host, options.clientProject);
190-
const clientTargets = getProjectTargets(clientProject);
191-
192-
if (!clientTargets.server) {
130+
return async (host: Tree) => {
131+
const clientProject = await getClientProject(host, options.clientProject);
132+
const serverTarget = clientProject.targets.get('server');
133+
if (!serverTarget) {
193134
// If they skipped Universal schematics and don't have a server target,
194135
// just get out
195136
return;
196137
}
197138

198-
const mainPath = normalize('/' + clientTargets.server.options.main);
139+
const mainPath = normalize('/' + serverTarget.options.main);
199140
const mainSourceFile = getTsSourceFile(host, mainPath);
200141
let mainText = getTsSourceText(host, mainPath);
201142
const mainRecorder = host.beginUpdate(mainPath);
202143
const expressEngineExport = generateExport(mainSourceFile, ['ngExpressEngine'],
203144
'@nguniversal/express-engine');
204-
const moduleMapExport = generateExport(mainSourceFile, ['provideModuleMap'],
205-
'@nguniversal/module-map-ngfactory-loader');
145+
const addedExports = `\n${expressEngineExport}\n`;
206146
const exports = findNodes(mainSourceFile, ts.SyntaxKind.ExportDeclaration);
207-
const addedExports = `\n${expressEngineExport}\n${moduleMapExport}\n`;
208-
const exportChange = insertAfterLastOccurrence(exports, addedExports, mainText,
209-
0) as InsertChange;
147+
const exportChange =
148+
insertAfterLastOccurrence(exports, addedExports, mainText, 0) as InsertChange;
210149

211150
mainRecorder.insertLeft(exportChange.pos, exportChange.toAdd);
212151
host.commitUpdate(mainRecorder);
213152
};
214153
}
215154

216155
export default function (options: UniversalOptions): Rule {
217-
return (host: Tree, context: SchematicContext) => {
218-
const clientProject = getClientProject(host, options);
219-
if (clientProject.projectType !== 'application') {
220-
throw new SchematicsException(`Universal requires a project type of "application".`);
221-
}
156+
return async (host: Tree, context: SchematicContext) => {
157+
// Generate new output paths
158+
const clientProject = await getClientProject(host, options.clientProject);
159+
const {options: buildOptions} = clientProject.targets.get('build');
160+
const clientOutputPath = normalize(
161+
typeof buildOptions.outputPath === 'string' ? buildOptions.outputPath : 'dist'
162+
);
163+
164+
const browserDist = join(clientOutputPath, 'browser');
165+
const serverDist = join(clientOutputPath, 'server');
222166

223167
if (!options.skipInstall) {
224168
context.addTask(new NodePackageInstallTask());
@@ -230,20 +174,20 @@ export default function (options: UniversalOptions): Rule {
230174
filter(path => !path.includes('tsconfig')) : filter(path => !path.startsWith('webpack')),
231175
template({
232176
...strings,
233-
...options as object,
177+
...options,
234178
stripTsExtension: (s: string) => s.replace(/\.ts$/, ''),
235-
getBrowserDistDirectory: () => BROWSER_DIST,
236-
getServerDistDirectory: () => SERVER_DIST,
179+
// remove the leading slashes
180+
getBrowserDistDirectory: () => browserDist.substr(1),
181+
getServerDistDirectory: () => serverDist.substr(1),
237182
})
238183
]);
239184

240185
return chain([
241186
options.skipUniversal ?
242187
noop() : externalSchematic('@schematics/angular', 'universal', options),
243-
updateConfigFile(options),
188+
updateConfigFile(options, browserDist, serverDist),
244189
mergeWith(rootSource),
245-
addDependenciesAndScripts(options),
246-
addModuleMapLoader(options),
190+
addDependenciesAndScripts(options, serverDist),
247191
addExports(options),
248192
]);
249193
};

0 commit comments

Comments
 (0)