Skip to content

Commit 5500ad2

Browse files
committed
feat(@angular-devkit/build-angular): add Server Builder v2
Using the new Architect API. Including moving the tests.
1 parent 32a32d6 commit 5500ad2

3 files changed

Lines changed: 236 additions & 60 deletions

File tree

packages/angular_devkit/build_angular/builders.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"description": "Run tslint over a TS project."
4242
},
4343
"server": {
44+
"implementation": "./src/server/index2",
4445
"class": "./src/server",
4546
"schema": "./src/server/schema.json",
4647
"description": "Build a server Angular application."
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. 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 { createBuilder } from '@angular-devkit/architect/src/index2';
9+
import {
10+
Path,
11+
experimental,
12+
getSystemPath,
13+
json,
14+
logging,
15+
normalize,
16+
resolve, schema, virtualFs,
17+
} from '@angular-devkit/core';
18+
import { NodeJsSyncHost } from '@angular-devkit/core/node';
19+
import { Stats } from 'fs';
20+
import { from, of } from 'rxjs';
21+
import { concatMap, map } from 'rxjs/operators';
22+
import * as ts from 'typescript'; // tslint:disable-line:no-implicit-dependencies
23+
import { runWebpack } from '../../../build_webpack/src/webpack/index2';
24+
import { WebpackConfigOptions } from '../angular-cli-files/models/build-options';
25+
import {
26+
getAotConfig,
27+
getCommonConfig,
28+
getNonAotConfig,
29+
getServerConfig,
30+
getStatsConfig,
31+
getStylesConfig,
32+
} from '../angular-cli-files/models/webpack-configs';
33+
import { readTsconfig } from '../angular-cli-files/utilities/read-tsconfig';
34+
import { requireProjectModule } from '../angular-cli-files/utilities/require-project-module';
35+
import {
36+
NormalizedWebpackServerBuilderSchema, defaultProgress,
37+
deleteOutputDir,
38+
normalizeWebpackServerSchema,
39+
} from '../utils';
40+
import { Schema as BuildWebpackServerSchema } from './schema';
41+
const webpackMerge = require('webpack-merge');
42+
43+
44+
export default createBuilder<json.JsonObject & BuildWebpackServerSchema>((options, context) => {
45+
const host = new virtualFs.AliasHost(new NodeJsSyncHost());
46+
const root = context.workspaceRoot;
47+
48+
async function setup() {
49+
const registry = new schema.CoreSchemaRegistry();
50+
registry.addPostTransform(schema.transforms.addUndefinedDefaults);
51+
52+
const workspace = await experimental.workspace.Workspace.fromPath(
53+
host,
54+
normalize(context.workspaceRoot),
55+
registry,
56+
);
57+
const projectName = context.target ? context.target.project : workspace.getDefaultProjectName();
58+
59+
if (!projectName) {
60+
throw new Error('Must either have a target from the context or a default project.');
61+
}
62+
const projectRoot = resolve(
63+
workspace.root,
64+
normalize(workspace.getProject(projectName).root),
65+
);
66+
const workspaceSourceRoot = workspace.getProject(projectName).sourceRoot;
67+
const sourceRoot = workspaceSourceRoot !== undefined ? resolve(
68+
workspace.root,
69+
normalize(workspaceSourceRoot),
70+
) : undefined;
71+
72+
const normalizedOptions = normalizeWebpackServerSchema(
73+
host,
74+
normalize(root),
75+
projectRoot,
76+
sourceRoot,
77+
options,
78+
);
79+
80+
return { normalizedOptions, projectRoot };
81+
}
82+
83+
return from(setup()).pipe(
84+
concatMap(v => {
85+
if (options.deleteOutputPath) {
86+
return deleteOutputDir(normalize(root), normalize(options.outputPath), host).pipe(
87+
map(() => v),
88+
);
89+
} else {
90+
return of(v);
91+
}
92+
}),
93+
concatMap(({ normalizedOptions, projectRoot }) => {
94+
const webpackConfig = buildServerWebpackConfig(
95+
normalize(root),
96+
projectRoot,
97+
host,
98+
normalizedOptions,
99+
context.logger.createChild('webpack'),
100+
);
101+
102+
return runWebpack(webpackConfig, context);
103+
}),
104+
);
105+
});
106+
107+
export function buildServerWebpackConfig(
108+
root: Path,
109+
projectRoot: Path,
110+
_host: virtualFs.Host<Stats>,
111+
options: NormalizedWebpackServerBuilderSchema,
112+
logger: logging.Logger,
113+
) {
114+
let wco: WebpackConfigOptions;
115+
116+
// TODO: make target defaults into configurations instead
117+
// options = this.addTargetDefaults(options);
118+
119+
const tsConfigPath = getSystemPath(normalize(resolve(root, normalize(options.tsConfig))));
120+
const tsConfig = readTsconfig(tsConfigPath);
121+
122+
const projectTs = requireProjectModule(getSystemPath(projectRoot), 'typescript') as typeof ts;
123+
124+
const supportES2015 = tsConfig.options.target !== projectTs.ScriptTarget.ES3
125+
&& tsConfig.options.target !== projectTs.ScriptTarget.ES5;
126+
127+
const buildOptions: typeof wco['buildOptions'] = {
128+
...options as {} as typeof wco['buildOptions'],
129+
};
130+
131+
wco = {
132+
root: getSystemPath(root),
133+
projectRoot: getSystemPath(projectRoot),
134+
// TODO: use only this.options, it contains all flags and configs items already.
135+
buildOptions: {
136+
...buildOptions,
137+
buildOptimizer: false,
138+
aot: true,
139+
platform: 'server',
140+
scripts: [],
141+
styles: [],
142+
},
143+
tsConfig,
144+
tsConfigPath,
145+
supportES2015,
146+
logger,
147+
};
148+
149+
wco.buildOptions.progress = defaultProgress(wco.buildOptions.progress);
150+
151+
const webpackConfigs: {}[] = [
152+
getCommonConfig(wco),
153+
getServerConfig(wco),
154+
getStylesConfig(wco),
155+
getStatsConfig(wco),
156+
];
157+
158+
if (wco.buildOptions.main || wco.buildOptions.polyfills) {
159+
const typescriptConfigPartial = wco.buildOptions.aot
160+
? getAotConfig(wco)
161+
: getNonAotConfig(wco);
162+
webpackConfigs.push(typescriptConfigPartial);
163+
}
164+
165+
return webpackMerge(webpackConfigs);
166+
}

packages/angular_devkit/build_angular/test/server/base_spec_large.ts

Lines changed: 69 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -6,76 +6,80 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import { runTargetSpec } from '@angular-devkit/architect/testing';
9+
import { Architect } from '@angular-devkit/architect/src/index2';
1010
import { join, normalize, virtualFs } from '@angular-devkit/core';
1111
import { take, tap } from 'rxjs/operators';
12-
import { BuildWebpackServerSchema } from '../../src/server/schema';
13-
import { host } from '../utils';
12+
import { BrowserBuilderOutput } from '../../src/browser/index2';
13+
import { Schema as BuildWebpackServerSchema } from '../../src/server/schema';
14+
import { createArchitect, host } from '../utils';
1415

1516

1617
describe('Server Builder', () => {
17-
const outputPath = normalize('dist-server');
18+
const target = { project: 'app', target: 'server' };
19+
let architect: Architect;
1820

19-
beforeEach(done => host.initialize().toPromise().then(done, done.fail));
20-
afterEach(done => host.restore().toPromise().then(done, done.fail));
21+
beforeEach(async () => {
22+
await host.initialize().toPromise();
23+
architect = (await createArchitect(host.root())).architect;
24+
});
25+
afterEach(async () => host.restore().toPromise());
2126

22-
it('works (base)', (done) => {
23-
const overrides = { };
27+
const outputPath = normalize('dist-server');
2428

25-
runTargetSpec(host, { project: 'app', target: 'server' }, overrides).pipe(
26-
tap((buildEvent) => {
27-
expect(buildEvent.success).toBe(true);
29+
it('works (base)', async () => {
30+
const run = await architect.scheduleTarget(target);
31+
const output = await run.result as BrowserBuilderOutput;
32+
expect(output.success).toBe(true);
2833

29-
const fileName = join(outputPath, 'main.js');
30-
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
31-
expect(content).toMatch(/AppServerModuleNgFactory/);
32-
}),
33-
).toPromise().then(done, done.fail);
34+
const fileName = join(outputPath, 'main.js');
35+
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
36+
expect(content).toMatch(/AppServerModuleNgFactory/);
37+
38+
await run.stop();
3439
});
3540

36-
it('supports sourcemaps', (done) => {
41+
it('supports sourcemaps', async () => {
3742
const overrides = { sourceMap: true };
3843

39-
runTargetSpec(host, { project: 'app', target: 'server' }, overrides).pipe(
40-
tap((buildEvent) => {
41-
expect(buildEvent.success).toBe(true);
44+
const run = await architect.scheduleTarget(target, overrides);
45+
const output = await run.result as BrowserBuilderOutput;
46+
expect(output.success).toBe(true);
4247

43-
const fileName = join(outputPath, 'main.js');
44-
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
45-
expect(content).toMatch(/AppServerModuleNgFactory/);
46-
expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBeTruthy();
47-
}),
48-
).toPromise().then(done, done.fail);
48+
const fileName = join(outputPath, 'main.js');
49+
const content = virtualFs.fileBufferToString(host.scopedSync().read(normalize(fileName)));
50+
expect(content).toMatch(/AppServerModuleNgFactory/);
51+
expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBeTruthy();
52+
53+
await run.stop();
4954
});
5055

51-
it('supports scripts only sourcemaps', (done) => {
52-
const overrides: Partial<BuildWebpackServerSchema> = {
56+
it('supports scripts only sourcemaps', async () => {
57+
host.writeMultipleFiles({
58+
'src/app/app.component.css': `p { color: red; }`,
59+
});
60+
61+
const run = await architect.scheduleTarget(target, {
5362
sourceMap: {
5463
styles: false,
5564
scripts: true,
5665
},
57-
};
58-
59-
host.writeMultipleFiles({
60-
'src/app/app.component.css': `p { color: red; }`,
6166
});
67+
const output = await run.result as BrowserBuilderOutput;
68+
expect(output.success).toBe(true);
6269

63-
runTargetSpec(host, { project: 'app', target: 'server' }, overrides).pipe(
64-
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
65-
tap(() => {
66-
expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true);
70+
expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true);
6771

68-
const scriptContent = virtualFs.fileBufferToString(
69-
host.scopedSync().read(join(outputPath, 'main.js')),
70-
);
71-
expect(scriptContent).toContain('sourceMappingURL=main.js.map');
72-
expect(scriptContent).not.toContain('sourceMappingURL=data:application/json');
73-
}),
74-
).toPromise().then(done, done.fail);
75-
});
72+
const scriptContent = virtualFs.fileBufferToString(
73+
host.scopedSync().read(join(outputPath, 'main.js')),
74+
);
75+
expect(scriptContent).toContain('sourceMappingURL=main.js.map');
76+
expect(scriptContent).not.toContain('sourceMappingURL=data:application/json');
7677

77-
it('supports component styles sourcemaps', (done) => {
78-
const overrides: Partial<BuildWebpackServerSchema> = {
78+
await run.stop();
79+
});
80+
//
81+
it('supports component styles sourcemaps', async () => {
82+
const overrides = {
7983
sourceMap: {
8084
styles: true,
8185
scripts: true,
@@ -86,24 +90,27 @@ describe('Server Builder', () => {
8690
'src/app/app.component.css': `p { color: red; }`,
8791
});
8892

89-
runTargetSpec(host, { project: 'app', target: 'server' }, overrides).pipe(
90-
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
91-
tap(() => {
92-
expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true);
93+
const run = await architect.scheduleTarget(target, overrides);
94+
const output = await run.result as BrowserBuilderOutput;
95+
expect(output.success).toBe(true);
9396

94-
const scriptContent = virtualFs.fileBufferToString(
95-
host.scopedSync().read(join(outputPath, 'main.js')),
96-
);
97-
expect(scriptContent).toContain('sourceMappingURL=main.js.map');
98-
expect(scriptContent).toContain('sourceMappingURL=data:application/json');
99-
}),
100-
).toPromise().then(done, done.fail);
97+
expect(host.scopedSync().exists(join(outputPath, 'main.js.map'))).toBe(true);
98+
99+
const scriptContent = virtualFs.fileBufferToString(
100+
host.scopedSync().read(join(outputPath, 'main.js')),
101+
);
102+
expect(scriptContent).toContain('sourceMappingURL=main.js.map');
103+
expect(scriptContent).toContain('sourceMappingURL=data:application/json');
104+
105+
await run.stop();
101106
});
102107

103-
it('runs watch mode', (done) => {
108+
it('runs watch mode', async () => {
104109
const overrides = { watch: true };
105110

106-
runTargetSpec(host, { project: 'app', target: 'server' }, overrides).pipe(
111+
const run = await architect.scheduleTarget(target, overrides);
112+
113+
await run.output.pipe(
107114
tap((buildEvent) => {
108115
expect(buildEvent.success).toBe(true);
109116

@@ -112,6 +119,8 @@ describe('Server Builder', () => {
112119
expect(content).toMatch(/AppServerModuleNgFactory/);
113120
}),
114121
take(1),
115-
).subscribe(undefined, done.fail, done);
122+
).toPromise();
123+
124+
await run.stop();
116125
});
117126
});

0 commit comments

Comments
 (0)