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

Commit cf78cf4

Browse files
alan-agius4vikerman
authored andcommitted
feat: bundle and build using CLI server builder
1 parent 6aba64d commit cf78cf4

36 files changed

+686
-842
lines changed

modules/common/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ ng_package(
2323
name = "npm_package",
2424
srcs = [":package.json"],
2525
entry_point = ":index.ts",
26+
packages = ["//modules/common/schematics:npm_package"],
2627
readme_md = ":README.md",
2728
tags = ["release"],
2829
deps = [

modules/common/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"ng-update": {
1515
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
1616
},
17+
"schematics": "./schematics/collection.json",
1718
"repository": {
1819
"type": "git",
1920
"url": "https://github.com/angular/universal"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
load("//tools:defaults.bzl", "jasmine_node_test", "ng_test_library", "npm_package", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
filegroup(
6+
name = "schematics_assets",
7+
srcs = glob([
8+
"**/files/**/*",
9+
"**/*.json",
10+
]),
11+
)
12+
13+
ts_library(
14+
name = "schematics",
15+
srcs = glob(
16+
["**/*.ts"],
17+
exclude = [
18+
"**/*.spec.ts",
19+
"**/files/**/*",
20+
],
21+
),
22+
module_name = "@nguniversal/common/schematics",
23+
tsconfig = ":tsconfig.json",
24+
deps = [
25+
"@npm//@angular-devkit/core",
26+
"@npm//@angular-devkit/schematics",
27+
"@npm//@schematics/angular",
28+
"@npm//@types/jasmine",
29+
"@npm//rxjs",
30+
"@npm//typescript",
31+
],
32+
)
33+
34+
# This package is intended to be combined into the main @nguniversal/common package as a dep.
35+
npm_package(
36+
name = "npm_package",
37+
srcs = [":schematics_assets"],
38+
deps = [":schematics"],
39+
)
40+
41+
### Testing rules
42+
jasmine_node_test(
43+
name = "unit_tests",
44+
srcs = [":schematics_test_sources"],
45+
data = [":schematics_assets"],
46+
)
47+
48+
ng_test_library(
49+
name = "schematics_test_sources",
50+
srcs = glob(
51+
["**/*.spec.ts"],
52+
exclude = ["**/files/**/*"],
53+
),
54+
tsconfig = ":tsconfig.json",
55+
deps = [
56+
":schematics",
57+
"@npm//@angular-devkit/core",
58+
"@npm//@angular-devkit/schematics",
59+
"@npm//@schematics/angular",
60+
],
61+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"$schema": "../../node_modules/@angular-devkit/schematics/collection-schema.json",
3+
"schematics": {
4+
"install": {
5+
"description": "Adds Angular Universal to the application.",
6+
"factory": "./install",
7+
"schema": "./install/schema.json"
8+
}
9+
}
10+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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 {Tree} from '@angular-devkit/schematics';
9+
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
10+
11+
import {collectionPath, createTestApp} from '../testing/test-app';
12+
13+
import {Schema as UniversalOptions} from './schema';
14+
15+
describe('Universal Schematic', () => {
16+
const defaultOptions: UniversalOptions = {
17+
clientProject: 'bar',
18+
};
19+
20+
let schematicRunner: SchematicTestRunner;
21+
let appTree: Tree;
22+
23+
beforeEach(async () => {
24+
appTree = await createTestApp().toPromise();
25+
schematicRunner = new SchematicTestRunner('schematics', collectionPath);
26+
});
27+
28+
it('should update angular.json', async () => {
29+
const tree = await schematicRunner
30+
.runSchematicAsync('install', defaultOptions, appTree)
31+
.toPromise();
32+
const contents = JSON.parse(tree.readContent('angular.json'));
33+
const architect = contents.projects.bar.architect;
34+
expect(architect.build.configurations.production).toBeDefined();
35+
expect(architect.build.options.outputPath).toBe('dist/bar/browser');
36+
expect(architect.server.options.outputPath).toBe('dist/bar/server');
37+
38+
const productionConfig = architect.server.configurations.production;
39+
expect(productionConfig.fileReplacements).toBeDefined();
40+
expect(productionConfig.sourceMap).toBeDefined();
41+
expect(productionConfig.optimization).toBeDefined();
42+
});
43+
44+
it(`should update 'tsconfig.server.json' files with main file`, async () => {
45+
const tree = await schematicRunner
46+
.runSchematicAsync('install', defaultOptions, appTree)
47+
.toPromise();
48+
49+
const contents = JSON.parse(tree.readContent('/projects/bar/tsconfig.server.json'));
50+
expect(contents.files).toEqual([
51+
'src/main.server.ts',
52+
'server.ts',
53+
]);
54+
});
55+
});
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
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 {
9+
chain,
10+
externalSchematic,
11+
Rule,
12+
SchematicsException,
13+
} from '@angular-devkit/schematics';
14+
import {normalize, join, parseJsonAst, JsonParseMode} from '@angular-devkit/core';
15+
import {updateWorkspace} from '@schematics/angular/utility/workspace';
16+
import {
17+
findPropertyInAstObject,
18+
appendValueInAstArray,
19+
} from '@schematics/angular/utility/json-utils';
20+
import {Schema as UniversalOptions} from './schema';
21+
import {stripTsExtension, getDistPaths, getClientProject} from './utils';
22+
23+
function addScriptsRule(options: UniversalOptions): Rule {
24+
return async host => {
25+
const pkgPath = '/package.json';
26+
const buffer = host.read(pkgPath);
27+
if (buffer === null) {
28+
throw new SchematicsException('Could not find package.json');
29+
}
30+
31+
const {server: serverDist} = await getDistPaths(host, options.clientProject);
32+
const pkg = JSON.parse(buffer.toString());
33+
pkg.scripts = {
34+
...pkg.scripts,
35+
'serve:ssr': `node ${serverDist}/main.js`,
36+
'build:ssr': 'npm run build:client-and-server-bundles',
37+
// tslint:disable-next-line: max-line-length
38+
'build:client-and-server-bundles': `ng build --prod && ng run ${options.clientProject}:server:production`,
39+
};
40+
41+
host.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
42+
};
43+
}
44+
45+
function updateConfigFileRule(options: UniversalOptions): Rule {
46+
return host => {
47+
return updateWorkspace((async workspace => {
48+
const clientProject = workspace.projects.get(options.clientProject);
49+
if (clientProject) {
50+
const buildTarget = clientProject.targets.get('build');
51+
const serverTarget = clientProject.targets.get('server');
52+
53+
// We have to check if the project config has a server target, because
54+
// if the Universal step in this schematic isn't run, it can't be guaranteed
55+
// to exist
56+
if (!serverTarget || !buildTarget) {
57+
return;
58+
}
59+
60+
const distPaths = await getDistPaths(host, options.clientProject);
61+
62+
serverTarget.options = {
63+
...serverTarget.options,
64+
outputPath: distPaths.server,
65+
};
66+
67+
serverTarget.options.main = join(
68+
normalize(clientProject.root),
69+
stripTsExtension(options.serverFileName) + '.ts',
70+
);
71+
72+
buildTarget.options = {
73+
...buildTarget.options,
74+
outputPath: distPaths.browser,
75+
};
76+
}
77+
})) as unknown as Rule;
78+
};
79+
}
80+
81+
function updateServerTsConfigRule(options: UniversalOptions): Rule {
82+
return async host => {
83+
const clientProject = await getClientProject(host, options.clientProject);
84+
const serverTarget = clientProject.targets.get('server');
85+
if (!serverTarget || !serverTarget.options) {
86+
return;
87+
}
88+
89+
const tsConfigPath = serverTarget.options.tsConfig;
90+
if (!tsConfigPath || typeof tsConfigPath !== 'string') {
91+
// No tsconfig path
92+
return;
93+
}
94+
95+
const configBuffer = host.read(tsConfigPath);
96+
if (!configBuffer) {
97+
throw new SchematicsException(`Could not find (${tsConfigPath})`);
98+
}
99+
100+
const content = configBuffer.toString();
101+
const tsConfigAst = parseJsonAst(content, JsonParseMode.Loose);
102+
if (!tsConfigAst || tsConfigAst.kind !== 'object') {
103+
throw new SchematicsException(`Invalid JSON AST Object (${tsConfigPath})`);
104+
}
105+
106+
const filesAstNode = findPropertyInAstObject(tsConfigAst, 'files');
107+
108+
if (filesAstNode && filesAstNode.kind === 'array') {
109+
const recorder = host.beginUpdate(tsConfigPath);
110+
111+
appendValueInAstArray(
112+
recorder,
113+
filesAstNode,
114+
stripTsExtension(options.serverFileName) + '.ts',
115+
);
116+
117+
host.commitUpdate(recorder);
118+
}
119+
};
120+
}
121+
122+
export default function (options: UniversalOptions): Rule {
123+
return () => {
124+
return chain([
125+
externalSchematic('@schematics/angular', 'universal', options),
126+
addScriptsRule(options),
127+
updateServerTsConfigRule(options),
128+
updateConfigFileRule(options),
129+
]);
130+
};
131+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"$schema": "http://json-schema.org/schema",
3+
"id": "SchematicsCommonInstall",
4+
"title": "Common Install Options Schema",
5+
"type": "object",
6+
"properties": {
7+
"clientProject": {
8+
"type": "string",
9+
"description": "Name of related client app."
10+
},
11+
"appId": {
12+
"type": "string",
13+
"format": "html-selector",
14+
"description": "The appId to use withServerTransition.",
15+
"default": "serverApp"
16+
},
17+
"main": {
18+
"type": "string",
19+
"format": "path",
20+
"description": "The name of the main entry-point file.",
21+
"default": "main.server.ts"
22+
},
23+
"serverFileName": {
24+
"type": "string",
25+
"default": "server.ts",
26+
"description": "The name of the server file."
27+
},
28+
"tsconfigFileName": {
29+
"type": "string",
30+
"default": "tsconfig.server",
31+
"description": "The name of the TypeScript configuration file."
32+
},
33+
"appDir": {
34+
"type": "string",
35+
"format": "path",
36+
"description": "The name of the application directory.",
37+
"default": "app"
38+
},
39+
"rootModuleFileName": {
40+
"type": "string",
41+
"format": "path",
42+
"description": "The name of the root module file",
43+
"default": "app.server.module.ts"
44+
},
45+
"rootModuleClassName": {
46+
"type": "string",
47+
"description": "The name of the root module class.",
48+
"default": "AppServerModule"
49+
},
50+
"skipInstall": {
51+
"description": "Skip installing dependency packages.",
52+
"type": "boolean",
53+
"default": false
54+
}
55+
},
56+
"required": [
57+
"clientProject"
58+
]
59+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
9+
export interface Schema {
10+
/**
11+
* Name or index of related client app.
12+
*/
13+
clientProject: string;
14+
/**
15+
* The appId to use withServerTransition.
16+
*/
17+
appId?: string;
18+
/**
19+
* The name of the main entry-point file.
20+
*/
21+
main?: string;
22+
/**
23+
* The name of the server file.
24+
*/
25+
serverFileName?: string;
26+
/**
27+
* The name of the TypeScript configuration file.
28+
*/
29+
tsconfigFileName?: string;
30+
/**
31+
* The name of the application directory.
32+
*/
33+
appDir?: string;
34+
/**
35+
* The name of the root module file
36+
*/
37+
rootModuleFileName?: string;
38+
/**
39+
* The name of the root module class.
40+
*/
41+
rootModuleClassName?: string;
42+
/**
43+
* Skip installing dependency packages.
44+
*/
45+
skipInstall?: boolean;
46+
}

0 commit comments

Comments
 (0)