Skip to content

Commit b838df0

Browse files
committed
refactor(@schematics/angular): update universal to use new workspace rules
1 parent 0e1f31c commit b838df0

File tree

2 files changed

+87
-85
lines changed

2 files changed

+87
-85
lines changed

packages/schematics/angular/universal/index.ts

Lines changed: 56 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import {
99
Path,
1010
basename,
11-
experimental,
1211
join,
1312
normalize,
1413
parseJson,
@@ -32,52 +31,44 @@ import {
3231
import * as ts from '../third_party/github.com/Microsoft/TypeScript/lib/typescript';
3332
import { findNode, getDecoratorMetadata } from '../utility/ast-utils';
3433
import { InsertChange } from '../utility/change';
35-
import { getWorkspace, updateWorkspace } from '../utility/config';
3634
import { addPackageJsonDependency, getPackageJsonDependency } from '../utility/dependencies';
3735
import { findBootstrapModuleCall, findBootstrapModulePath } from '../utility/ng-ast-utils';
38-
import { getProject } from '../utility/project';
39-
import { getProjectTargets, targetBuildNotFoundError } from '../utility/project-targets';
40-
import { Builders, WorkspaceTargets } from '../utility/workspace-models';
36+
import { targetBuildNotFoundError } from '../utility/project-targets';
37+
import { getWorkspace, updateWorkspace } from '../utility/workspace';
38+
import { BrowserBuilderOptions, Builders } from '../utility/workspace-models';
4139
import { Schema as UniversalOptions } from './schema';
4240

43-
44-
function getFileReplacements(target: WorkspaceTargets) {
45-
const fileReplacements =
46-
target.build &&
47-
target.build.configurations &&
48-
target.build.configurations.production &&
49-
target.build.configurations.production.fileReplacements;
50-
51-
return fileReplacements || [];
52-
}
53-
5441
function updateConfigFile(options: UniversalOptions, tsConfigDirectory: Path): Rule {
55-
return (host: Tree) => {
56-
const workspace = getWorkspace(host);
57-
const clientProject = getProject(workspace, options.clientProject);
58-
const projectTargets = getProjectTargets(clientProject);
59-
60-
projectTargets.server = {
61-
builder: Builders.Server,
62-
options: {
63-
outputPath: `dist/${options.clientProject}-server`,
64-
main: `${clientProject.root}src/main.server.ts`,
65-
tsConfig: join(tsConfigDirectory, `${options.tsconfigFileName}.json`),
66-
},
67-
configurations: {
68-
production: {
69-
fileReplacements: getFileReplacements(projectTargets),
70-
sourceMap: false,
71-
optimization: {
72-
scripts: false,
73-
styles: true,
42+
return updateWorkspace(workspace => {
43+
const clientProject = workspace.projects.get(options.clientProject);
44+
if (clientProject) {
45+
const buildTarget = clientProject.targets.get('build');
46+
let fileReplacements;
47+
if (buildTarget && buildTarget.configurations && buildTarget.configurations.production) {
48+
fileReplacements = buildTarget.configurations.production.fileReplacements;
49+
}
50+
51+
clientProject.targets.add({
52+
name: 'server',
53+
builder: Builders.Server,
54+
options: {
55+
outputPath: `dist/${options.clientProject}-server`,
56+
main: `${clientProject.root}src/main.server.ts`,
57+
tsConfig: join(tsConfigDirectory, `${options.tsconfigFileName}.json`),
58+
},
59+
configurations: {
60+
production: {
61+
fileReplacements,
62+
sourceMap: false,
63+
optimization: {
64+
scripts: false,
65+
styles: true,
66+
},
7467
},
7568
},
76-
},
77-
};
78-
79-
return updateWorkspace(workspace);
80-
};
69+
});
70+
}
71+
});
8172
}
8273

8374
function findBrowserModuleImport(host: Tree, modulePath: string): ts.Node {
@@ -99,13 +90,9 @@ function findBrowserModuleImport(host: Tree, modulePath: string): ts.Node {
9990
return browserModuleNode;
10091
}
10192

102-
function wrapBootstrapCall(options: UniversalOptions): Rule {
93+
function wrapBootstrapCall(mainFile: string): Rule {
10394
return (host: Tree) => {
104-
const clientTargets = getProjectTargets(host, options.clientProject);
105-
if (!clientTargets.build) {
106-
throw targetBuildNotFoundError();
107-
}
108-
const mainPath = normalize('/' + clientTargets.build.options.main);
95+
const mainPath = normalize('/' + mainFile);
10996
let bootstrapCall: ts.Node | null = findBootstrapModuleCall(host, mainPath);
11097
if (bootstrapCall === null) {
11198
throw new SchematicsException('Bootstrap module not found.');
@@ -172,18 +159,17 @@ function findCallExpressionNode(node: ts.Node, text: string): ts.Node | null {
172159
return foundNode;
173160
}
174161

175-
function addServerTransition(options: UniversalOptions): Rule {
162+
function addServerTransition(
163+
options: UniversalOptions,
164+
mainFile: string,
165+
clientProjectRoot: string,
166+
): Rule {
176167
return (host: Tree) => {
177-
const clientProject = getProject(host, options.clientProject);
178-
const clientTargets = getProjectTargets(clientProject);
179-
if (!clientTargets.build) {
180-
throw targetBuildNotFoundError();
181-
}
182-
const mainPath = normalize('/' + clientTargets.build.options.main);
168+
const mainPath = normalize('/' + mainFile);
183169

184170
const bootstrapModuleRelativePath = findBootstrapModulePath(host, mainPath);
185171
const bootstrapModulePath = normalize(
186-
`/${clientProject.root}/src/${bootstrapModuleRelativePath}.ts`);
172+
`/${clientProjectRoot}/src/${bootstrapModuleRelativePath}.ts`);
187173

188174
const browserModuleImport = findBrowserModuleImport(host, bootstrapModulePath);
189175
const appId = options.appId;
@@ -214,8 +200,7 @@ function addDependencies(): Rule {
214200
};
215201
}
216202

217-
function getTsConfigOutDir(host: Tree, targets: experimental.workspace.WorkspaceTool): string {
218-
const tsConfigPath = targets.build.options.tsConfig;
203+
function getTsConfigOutDir(host: Tree, tsConfigPath: string): string {
219204
const tsConfigBuffer = host.read(tsConfigPath);
220205
if (!tsConfigBuffer) {
221206
throw new SchematicsException(`Could not read ${tsConfigPath}`);
@@ -233,18 +218,24 @@ function getTsConfigOutDir(host: Tree, targets: experimental.workspace.Workspace
233218
}
234219

235220
export default function (options: UniversalOptions): Rule {
236-
return (host: Tree, context: SchematicContext) => {
237-
const clientProject = getProject(host, options.clientProject);
238-
if (clientProject.projectType !== 'application') {
221+
return async (host: Tree, context: SchematicContext) => {
222+
const workspace = await getWorkspace(host);
223+
224+
const clientProject = workspace.projects.get(options.clientProject);
225+
if (!clientProject || clientProject.extensions.projectType !== 'application') {
239226
throw new SchematicsException(`Universal requires a project type of "application".`);
240227
}
241-
const clientTargets = getProjectTargets(clientProject);
242-
const outDir = getTsConfigOutDir(host, clientTargets);
243-
if (!clientTargets.build) {
228+
229+
const clientBuildTarget = clientProject.targets.get('build');
230+
if (!clientBuildTarget) {
244231
throw targetBuildNotFoundError();
245232
}
233+
const clientBuildOptions =
234+
(clientBuildTarget.options || {}) as unknown as BrowserBuilderOptions;
235+
236+
const outDir = getTsConfigOutDir(host, clientBuildOptions.tsConfig);
246237

247-
const clientTsConfig = normalize(clientTargets.build.options.tsConfig);
238+
const clientTsConfig = normalize(clientBuildOptions.tsConfig);
248239
const tsConfigExtends = basename(clientTsConfig);
249240
// this is needed because prior to version 8, tsconfig might have been in 'src'
250241
// and we don't want to break the 'ng add @nguniversal/express-engine schematics'
@@ -281,8 +272,8 @@ export default function (options: UniversalOptions): Rule {
281272
mergeWith(rootSource),
282273
addDependencies(),
283274
updateConfigFile(options, tsConfigDirectory),
284-
wrapBootstrapCall(options),
285-
addServerTransition(options),
275+
wrapBootstrapCall(clientBuildOptions.main),
276+
addServerTransition(options, clientBuildOptions.main, clientProject.root),
286277
]);
287278
};
288279
}

packages/schematics/angular/universal/index_spec.ts

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -57,22 +57,27 @@ describe('Universal Schematic', () => {
5757
appTree = schematicRunner.runSchematic('application', appOptions, appTree);
5858
});
5959

60-
it('should create a root module file', () => {
61-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
60+
it('should create a root module file', async () => {
61+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
62+
.toPromise();
6263
const filePath = '/projects/bar/src/app/app.server.module.ts';
6364
expect(tree.exists(filePath)).toEqual(true);
6465
});
6566

66-
it('should create a main file', () => {
67-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
67+
it('should create a main file', async () => {
68+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
69+
.toPromise();
6870
const filePath = '/projects/bar/src/main.server.ts';
6971
expect(tree.exists(filePath)).toEqual(true);
7072
const contents = tree.readContent(filePath);
7173
expect(contents).toMatch(/export { AppServerModule } from '\.\/app\/app\.server\.module'/);
7274
});
7375

74-
it('should create a tsconfig file for the workspace project', () => {
75-
const tree = schematicRunner.runSchematic('universal', workspaceUniversalOptions, appTree);
76+
it('should create a tsconfig file for the workspace project', async () => {
77+
const tree = await schematicRunner
78+
.runSchematicAsync('universal', workspaceUniversalOptions, appTree)
79+
.toPromise();
80+
debugger;
7681
const filePath = '/tsconfig.server.json';
7782
expect(tree.exists(filePath)).toEqual(true);
7883
const contents = tree.readContent(filePath);
@@ -90,8 +95,9 @@ describe('Universal Schematic', () => {
9095
.server.options.tsConfig).toEqual('tsconfig.server.json');
9196
});
9297

93-
it('should create a tsconfig file for a generated application', () => {
94-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
98+
it('should create a tsconfig file for a generated application', async () => {
99+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
100+
.toPromise();
95101
const filePath = '/projects/bar/tsconfig.server.json';
96102
expect(tree.exists(filePath)).toEqual(true);
97103
const contents = tree.readContent(filePath);
@@ -109,15 +115,17 @@ describe('Universal Schematic', () => {
109115
.server.options.tsConfig).toEqual('projects/bar/tsconfig.server.json');
110116
});
111117

112-
it('should add dependency: @angular/platform-server', () => {
113-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
118+
it('should add dependency: @angular/platform-server', async () => {
119+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
120+
.toPromise();
114121
const filePath = '/package.json';
115122
const contents = tree.readContent(filePath);
116123
expect(contents).toMatch(/\"@angular\/platform-server\": \"/);
117124
});
118125

119-
it('should update workspace with a server target', () => {
120-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
126+
it('should update workspace with a server target', async () => {
127+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
128+
.toPromise();
121129
const filePath = '/angular.json';
122130
const contents = tree.readContent(filePath);
123131
const config = JSON.parse(contents.toString());
@@ -137,22 +145,24 @@ describe('Universal Schematic', () => {
137145
expect(fileReplacements[0].with).toEqual('projects/bar/src/environments/environment.prod.ts');
138146
});
139147

140-
it('should add a server transition to BrowerModule import', () => {
141-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
148+
it('should add a server transition to BrowerModule import', async () => {
149+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
150+
.toPromise();
142151
const filePath = '/projects/bar/src/app/app.module.ts';
143152
const contents = tree.readContent(filePath);
144153
expect(contents).toMatch(/BrowserModule\.withServerTransition\({ appId: 'serverApp' }\)/);
145154
});
146155

147-
it('should wrap the bootstrap call in a DOMContentLoaded event handler', () => {
148-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
156+
it('should wrap the bootstrap call in a DOMContentLoaded event handler', async () => {
157+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
158+
.toPromise();
149159
const filePath = '/projects/bar/src/main.ts';
150160
const contents = tree.readContent(filePath);
151161
expect(contents)
152162
.toMatch(/document.addEventListener\('DOMContentLoaded', \(\) => {[\w\W]+;[\r\n]}\);/);
153163
});
154164

155-
it('should wrap the bootstrap declaration in a DOMContentLoaded event handler', () => {
165+
it('should wrap the bootstrap declaration in a DOMContentLoaded event handler', async () => {
156166
const filePath = '/projects/bar/src/main.ts';
157167
appTree.overwrite(
158168
filePath,
@@ -175,15 +185,16 @@ describe('Universal Schematic', () => {
175185
`,
176186
);
177187

178-
const tree = schematicRunner.runSchematic('universal', defaultOptions, appTree);
188+
const tree = await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree)
189+
.toPromise();
179190
const contents = tree.readContent(filePath);
180191
expect(contents).toMatch(
181192
/document.addEventListener\('DOMContentLoaded', \(\) => {[\n\r\s]+bootstrap\(\)/,
182193
);
183194
});
184195

185-
it('should install npm dependencies', () => {
186-
schematicRunner.runSchematic('universal', defaultOptions, appTree);
196+
it('should install npm dependencies', async () => {
197+
await schematicRunner.runSchematicAsync('universal', defaultOptions, appTree).toPromise();
187198
expect(schematicRunner.tasks.length).toBe(1);
188199
expect(schematicRunner.tasks[0].name).toBe('node-package');
189200
expect((schematicRunner.tasks[0].options as {command: string}).command).toBe('install');

0 commit comments

Comments
 (0)