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

Commit 1909be1

Browse files
authored
feat(express-engine): add schematics (#1051)
* Adds schematics to the Express Engine that allow for easier installation. This is basically an enhanced version of the vanilla Universal schematic that is available by default, where this one adds the Express Engine, a sample Express server, as well as some other necessary dependencies * Credit to @devversion on the Material team for the entire Bazel setup Fixes #968
1 parent de33b02 commit 1909be1

20 files changed

Lines changed: 1577 additions & 281 deletions

BUILD.bazel

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,20 @@ alias(
1414
node_modules_filegroup(
1515
name = "node_modules",
1616
packages = [
17+
"bytebuffer",
1718
"express",
1819
"hapi",
1920
"jasmine",
21+
"protobufjs",
2022
"rxjs",
2123
"tsickle",
2224
"tslib",
2325
"tsutils",
2426
"typescript",
2527
"zone.js",
2628
"@angular",
29+
"@angular-devkit",
30+
"@schematics",
2731
"@types",
2832
],
2933
)

modules/express-engine/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ ng_package(
2121
":package.json",
2222
],
2323
entry_point = "modules/express-engine/index.js",
24+
packages = ["//modules/express-engine/schematics:npm_package"],
2425
readme_md = ":README.md",
2526
deps = [
2627
":express-engine",

modules/express-engine/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@angular/platform-server": "NG_VERSION",
1515
"express": "EXPRESS_VERSION"
1616
},
17+
"schematics": "./schematics/collection.json",
1718
"ng-update": {
1819
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
1920
},
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("//tools:defaults.bzl", "ts_library", "npm_package")
4+
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")
5+
6+
filegroup(
7+
name = "schematics_assets",
8+
srcs = glob([
9+
"**/files/**/*",
10+
"**/*.json",
11+
]) + [
12+
"README.md",
13+
],
14+
)
15+
16+
ts_library(
17+
name = "schematics",
18+
srcs = glob(
19+
["**/*.ts"],
20+
exclude = [
21+
"**/*.spec.ts",
22+
"**/files/**/*",
23+
"test-setup/**/*",
24+
],
25+
),
26+
module_name = "@nguniversal/express-engine/schematics",
27+
tsconfig = ":tsconfig.json",
28+
)
29+
30+
# This package is intended to be combined into the main @nguniversal/express-engine package as a dep.
31+
npm_package(
32+
name = "npm_package",
33+
srcs = [":schematics_assets"],
34+
deps = [":schematics"],
35+
)
36+
37+
### Testing rules
38+
39+
jasmine_node_test(
40+
name = "unit_tests",
41+
srcs = [":schematics_test_sources"],
42+
data = [":schematics_assets"],
43+
)
44+
45+
ts_library(
46+
name = "schematics_test_sources",
47+
testonly = True,
48+
srcs = glob(
49+
[
50+
"**/*.spec.ts",
51+
"test-setup/**/*.ts",
52+
],
53+
exclude = ["**/files/**/*"],
54+
),
55+
tsconfig = ":tsconfig.json",
56+
deps = [":schematics"],
57+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Angular Universal Express-Engine Schematics
2+
A collection of Schematics for Angular Universal Express-Engine.
3+
4+
## Collection
5+
6+
### Install
7+
Adds Angular Universal Express Engine and its dependencies and pre-configures the application.
8+
9+
- Runs the default Angular Universal schematic to add Universal capabilities to an application
10+
- Adds Express-Engine, NgModule-Factory-Loader, ts-loader, and webpack to `package.json`
11+
- Adds a sample Express server file
12+
13+
Command: `ng add @nguniversal/express-engine`
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
3+
"schematics": {
4+
"ng-add": {
5+
"description": "Adds Angular Universal Express Engine to the application without affecting any templates",
6+
"factory": "./install",
7+
"schema": "./install/schema.json",
8+
"aliases": ["express-engine-shell"]
9+
},
10+
}
11+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import 'zone.js/dist/zone-node';
2+
import 'reflect-metadata';
3+
import {enableProdMode} from '@angular/core';
4+
// Express Engine
5+
import {ngExpressEngine} from '@nguniversal/express-engine';
6+
// Import module map for lazy loading
7+
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';
8+
9+
import * as express from 'express';
10+
import {join} from 'path';
11+
12+
// Faster server renders w/ Prod mode (dev mode never needed)
13+
enableProdMode();
14+
15+
// Express server
16+
const app = express();
17+
18+
const PORT = process.env.PORT || <%= serverPort %>;
19+
const DIST_FOLDER = join(process.cwd(), 'dist');
20+
21+
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
22+
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./server/main');
23+
24+
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
25+
app.engine('html', ngExpressEngine({
26+
bootstrap: AppServerModuleNgFactory,
27+
providers: [
28+
provideModuleMap(LAZY_MODULE_MAP)
29+
]
30+
}));
31+
32+
app.set('view engine', 'html');
33+
app.set('views', join(DIST_FOLDER, 'browser'));
34+
35+
// Example Express Rest API endpoints
36+
// app.get('/api/**', (req, res) => { });
37+
// Server static files from /browser
38+
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'), {
39+
maxAge: '1y'
40+
}));
41+
42+
// All regular routes use the Universal engine
43+
app.get('*', (req, res) => {
44+
res.render('index', { req });
45+
});
46+
47+
// Start up the Node server
48+
app.listen(PORT, () => {
49+
console.log(`Node Express server listening on http://localhost:${PORT}`);
50+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"compileOnSave": false,
3+
"compilerOptions": {
4+
"outDir": "./dist",
5+
"sourceMap": true,
6+
"declaration": false,
7+
"moduleResolution": "node",
8+
"emitDecoratorMetadata": true,
9+
"experimentalDecorators": true,
10+
"target": "es5",
11+
"typeRoots": [
12+
"node_modules/@types"
13+
],
14+
"lib": [
15+
"es2017",
16+
"dom"
17+
]
18+
},
19+
"include": ["<%= stripTsExtension(serverFileName) %>.ts"]
20+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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 {SchematicTestRunner} from '@angular-devkit/schematics/testing';
9+
import {Schema as UniversalOptions} from './schema';
10+
import {collectionPath, createTestApp} from '../test-setup/test-app';
11+
import {Tree} from '@angular-devkit/schematics';
12+
13+
describe('Universal Schematic', () => {
14+
const defaultOptions: UniversalOptions = {
15+
clientProject: 'bar',
16+
};
17+
18+
let schematicRunner: SchematicTestRunner;
19+
let appTree: Tree;
20+
21+
beforeEach(() => {
22+
appTree = createTestApp();
23+
schematicRunner = new SchematicTestRunner('schematics', collectionPath);
24+
});
25+
26+
it('should add dependency: @nguniversal/module-map-ngfactory-loader', () => {
27+
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
28+
const filePath = '/package.json';
29+
const contents = tree.readContent(filePath);
30+
expect(contents).toMatch(/\"@nguniversal\/module-map-ngfactory-loader\": \"/);
31+
});
32+
33+
it('should add dependency: @nguniversal/express-engine', () => {
34+
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
35+
const filePath = '/package.json';
36+
const contents = tree.readContent(filePath);
37+
expect(contents).toMatch(/\"@nguniversal\/express-engine\": \"/);
38+
});
39+
40+
it('should add dependency: express', () => {
41+
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
42+
const filePath = '/package.json';
43+
const contents = tree.readContent(filePath);
44+
expect(contents).toMatch(/\"express\": \"/);
45+
});
46+
47+
it('should install npm dependencies', () => {
48+
schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
49+
expect(schematicRunner.tasks.length).toBe(2);
50+
expect(schematicRunner.tasks[0].name).toBe('node-package');
51+
expect((schematicRunner.tasks[0].options as {command: string}).command).toBe('install');
52+
});
53+
});
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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+
basename,
10+
experimental,
11+
join,
12+
normalize,
13+
parseJson,
14+
strings,
15+
} from '@angular-devkit/core';
16+
import {
17+
Rule,
18+
SchematicContext,
19+
SchematicsException,
20+
Tree,
21+
apply,
22+
chain,
23+
mergeWith,
24+
move,
25+
template,
26+
url,
27+
noop,
28+
filter,
29+
externalSchematic,
30+
} from '@angular-devkit/schematics';
31+
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
32+
import {getWorkspace} from '@schematics/angular/utility/config';
33+
import {Schema as UniversalOptions} from './schema';
34+
35+
36+
function getClientProject(
37+
host: Tree, options: UniversalOptions,
38+
): experimental.workspace.WorkspaceProject {
39+
const workspace = getWorkspace(host);
40+
const clientProject = workspace.projects[options.clientProject];
41+
if (!clientProject) {
42+
throw new SchematicsException(`Client app ${options.clientProject} not found.`);
43+
}
44+
45+
return clientProject;
46+
}
47+
48+
function getClientArchitect(
49+
host: Tree,
50+
options: UniversalOptions,
51+
): experimental.workspace.WorkspaceTool {
52+
const clientArchitect = getClientProject(host, options).architect;
53+
54+
if (!clientArchitect) {
55+
throw new Error('Client project architect not found.');
56+
}
57+
58+
return clientArchitect;
59+
}
60+
61+
function addDependenciesAndScripts(options: UniversalOptions): Rule {
62+
return (host: Tree) => {
63+
64+
const pkgPath = '/package.json';
65+
const buffer = host.read(pkgPath);
66+
if (buffer === null) {
67+
throw new SchematicsException('Could not find package.json');
68+
}
69+
70+
const pkg = JSON.parse(buffer.toString());
71+
72+
pkg.dependencies['@nguniversal/express-engine'] = '0.0.0-PLACEHOLDER';
73+
pkg.dependencies['@nguniversal/module-map-ngfactory-loader'] = '0.0.0-PLACEHOLDER';
74+
pkg.dependencies['express'] = 'EXPRESS_VERSION';
75+
76+
pkg.scripts['serve:ssr'] = 'node dist/server';
77+
pkg.scripts['build:ssr'] = 'npm run build:client-and-server-bundles && npm run compile:server';
78+
pkg.scripts['build:client-and-server-bundles'] =
79+
`ng build --prod && ng run ${options.clientProject}:server:production`;
80+
pkg.scripts['compile:server'] =
81+
`tsc -p ${options.serverFileName.replace(/\.ts$/, '')}.tsconfig.json`;
82+
83+
host.overwrite(pkgPath, JSON.stringify(pkg, null, 2));
84+
85+
return host;
86+
};
87+
}
88+
89+
export default function (options: UniversalOptions): Rule {
90+
return (host: Tree, context: SchematicContext) => {
91+
const clientProject = getClientProject(host, options);
92+
if (clientProject.projectType !== 'application') {
93+
throw new SchematicsException(`Universal requires a project type of "application".`);
94+
}
95+
const clientArchitect = getClientArchitect(host, options);
96+
const tsConfigExtends = basename(clientArchitect.build.options.tsConfig);
97+
const rootInSrc = clientProject.root === '';
98+
const tsConfigDirectory = join(normalize(clientProject.root), rootInSrc ? 'src' : '');
99+
100+
if (!options.skipInstall) {
101+
context.addTask(new NodePackageInstallTask());
102+
}
103+
104+
const rootSource = apply(url('./files/root'), [
105+
options.skipServer ? filter(path => !path.startsWith('__serverFileName')) : noop(),
106+
template({
107+
...strings,
108+
...options as object,
109+
stripTsExtension: (s: string) => { return s.replace(/\.ts$/, ''); },
110+
tsConfigExtends,
111+
rootInSrc,
112+
}),
113+
move(tsConfigDirectory),
114+
]);
115+
116+
return chain([
117+
externalSchematic('@schematics/angular', 'universal', options),
118+
mergeWith(rootSource),
119+
addDependenciesAndScripts(options),
120+
]);
121+
};
122+
}

0 commit comments

Comments
 (0)