Skip to content
This repository was archived by the owner on Nov 22, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@ alias(
node_modules_filegroup(
name = "node_modules",
packages = [
"bytebuffer",
"express",
"hapi",
"jasmine",
"protobufjs",
"rxjs",
"tsickle",
"tslib",
"tsutils",
"typescript",
"zone.js",
"@angular",
"@angular-devkit",
"@schematics",
"@types",
],
)
1 change: 1 addition & 0 deletions modules/express-engine/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ ng_package(
":package.json",
],
entry_point = "modules/express-engine/index.js",
packages = ["//modules/express-engine/schematics:npm_package"],
readme_md = ":README.md",
deps = [
":express-engine",
Expand Down
1 change: 1 addition & 0 deletions modules/express-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@angular/platform-server": "NG_VERSION",
"express": "EXPRESS_VERSION"
},
"schematics": "./schematics/collection.json",
"ng-update": {
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
},
Expand Down
57 changes: 57 additions & 0 deletions modules/express-engine/schematics/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package(default_visibility = ["//visibility:public"])

load("//tools:defaults.bzl", "ts_library", "npm_package")
load("@build_bazel_rules_nodejs//:defs.bzl", "jasmine_node_test")

filegroup(
name = "schematics_assets",
srcs = glob([
"**/files/**/*",
"**/*.json",
]) + [
"README.md",
],
)

ts_library(
name = "schematics",
srcs = glob(
["**/*.ts"],
exclude = [
"**/*.spec.ts",
"**/files/**/*",
"test-setup/**/*",
],
),
module_name = "@nguniversal/express-engine/schematics",
tsconfig = ":tsconfig.json",
)

# This package is intended to be combined into the main @nguniversal/express-engine package as a dep.
npm_package(
name = "npm_package",
srcs = [":schematics_assets"],
deps = [":schematics"],
)

### Testing rules

jasmine_node_test(
name = "unit_tests",
srcs = [":schematics_test_sources"],
data = [":schematics_assets"],
)

ts_library(
name = "schematics_test_sources",
testonly = True,
srcs = glob(
[
"**/*.spec.ts",
"test-setup/**/*.ts",
],
exclude = ["**/files/**/*"],
),
tsconfig = ":tsconfig.json",
deps = [":schematics"],
)
13 changes: 13 additions & 0 deletions modules/express-engine/schematics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Angular Universal Express-Engine Schematics
A collection of Schematics for Angular Universal Express-Engine.

## Collection

### Install
Adds Angular Universal Express Engine and its dependencies and pre-configures the application.

- Runs the default Angular Universal schematic to add Universal capabilities to an application
- Adds Express-Engine, NgModule-Factory-Loader, ts-loader, and webpack to `package.json`
- Adds a sample Express server file

Command: `ng add @nguniversal/express-engine`
11 changes: 11 additions & 0 deletions modules/express-engine/schematics/collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
"schematics": {
"ng-add": {
"description": "Adds Angular Universal Express Engine to the application without affecting any templates",
"factory": "./install",
"schema": "./install/schema.json",
"aliases": ["express-engine-shell"]
},
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import {enableProdMode} from '@angular/core';
// Express Engine
import {ngExpressEngine} from '@nguniversal/express-engine';
// Import module map for lazy loading
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';

import * as express from 'express';
import {join} from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || <%= serverPort %>;
const DIST_FOLDER = join(process.cwd(), 'dist');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./server/main');

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
bootstrap: AppServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'), {
maxAge: '1y'
}));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});

// Start up the Node server
app.listen(PORT, () => {
console.log(`Node Express server listening on http://localhost:${PORT}`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compileOnSave": false,
"compilerOptions": {
"outDir": "./dist",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
]
},
"include": ["<%= stripTsExtension(serverFileName) %>.ts"]
}
53 changes: 53 additions & 0 deletions modules/express-engine/schematics/install/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More tests will be needed as behavior is finalized. These are the default tests, plus some added dependency checks. Will also need test runners that work with Bazel (Material has this already)

* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {SchematicTestRunner} from '@angular-devkit/schematics/testing';
import {Schema as UniversalOptions} from './schema';
import {collectionPath, createTestApp} from '../test-setup/test-app';
import {Tree} from '@angular-devkit/schematics';

describe('Universal Schematic', () => {
const defaultOptions: UniversalOptions = {
clientProject: 'bar',
};

let schematicRunner: SchematicTestRunner;
let appTree: Tree;

beforeEach(() => {
appTree = createTestApp();
schematicRunner = new SchematicTestRunner('schematics', collectionPath);
});

it('should add dependency: @nguniversal/module-map-ngfactory-loader', () => {
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
const filePath = '/package.json';
const contents = tree.readContent(filePath);
expect(contents).toMatch(/\"@nguniversal\/module-map-ngfactory-loader\": \"/);
});

it('should add dependency: @nguniversal/express-engine', () => {
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
const filePath = '/package.json';
const contents = tree.readContent(filePath);
expect(contents).toMatch(/\"@nguniversal\/express-engine\": \"/);
});

it('should add dependency: express', () => {
const tree = schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
const filePath = '/package.json';
const contents = tree.readContent(filePath);
expect(contents).toMatch(/\"express\": \"/);
});

it('should install npm dependencies', () => {
schematicRunner.runSchematic('ng-add', defaultOptions, appTree);
expect(schematicRunner.tasks.length).toBe(2);
expect(schematicRunner.tasks[0].name).toBe('node-package');
expect((schematicRunner.tasks[0].options as {command: string}).command).toBe('install');
});
});
122 changes: 122 additions & 0 deletions modules/express-engine/schematics/install/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
basename,
experimental,
join,
normalize,
parseJson,
strings,
} from '@angular-devkit/core';
import {
Rule,
SchematicContext,
SchematicsException,
Tree,
apply,
chain,
mergeWith,
move,
template,
url,
noop,
filter,
externalSchematic,
} from '@angular-devkit/schematics';
import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks';
import {getWorkspace} from '@schematics/angular/utility/config';
import {Schema as UniversalOptions} from './schema';


function getClientProject(
host: Tree, options: UniversalOptions,
): experimental.workspace.WorkspaceProject {
const workspace = getWorkspace(host);
const clientProject = workspace.projects[options.clientProject];
if (!clientProject) {
throw new SchematicsException(`Client app ${options.clientProject} not found.`);
}

return clientProject;
}

function getClientArchitect(
host: Tree,
options: UniversalOptions,
): experimental.workspace.WorkspaceTool {
const clientArchitect = getClientProject(host, options).architect;

if (!clientArchitect) {
throw new Error('Client project architect not found.');
}

return clientArchitect;
}

function addDependenciesAndScripts(options: UniversalOptions): Rule {
return (host: Tree) => {

const pkgPath = '/package.json';
const buffer = host.read(pkgPath);
if (buffer === null) {
throw new SchematicsException('Could not find package.json');
}

const pkg = JSON.parse(buffer.toString());

pkg.dependencies['@nguniversal/express-engine'] = '0.0.0-PLACEHOLDER';
pkg.dependencies['@nguniversal/module-map-ngfactory-loader'] = '0.0.0-PLACEHOLDER';
pkg.dependencies['express'] = 'EXPRESS_VERSION';

pkg.scripts['serve:ssr'] = 'node dist/server';
pkg.scripts['build:ssr'] = 'npm run build:client-and-server-bundles && npm run compile:server';
pkg.scripts['build:client-and-server-bundles'] =
`ng build --prod && ng run ${options.clientProject}:server:production`;
pkg.scripts['compile:server'] =
`tsc -p ${options.serverFileName.replace(/\.ts$/, '')}.tsconfig.json`;

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

return host;
};
}

export default function (options: UniversalOptions): Rule {
return (host: Tree, context: SchematicContext) => {
const clientProject = getClientProject(host, options);
if (clientProject.projectType !== 'application') {
throw new SchematicsException(`Universal requires a project type of "application".`);
}
const clientArchitect = getClientArchitect(host, options);
const tsConfigExtends = basename(clientArchitect.build.options.tsConfig);
const rootInSrc = clientProject.root === '';
const tsConfigDirectory = join(normalize(clientProject.root), rootInSrc ? 'src' : '');

if (!options.skipInstall) {
context.addTask(new NodePackageInstallTask());
}

const rootSource = apply(url('./files/root'), [
options.skipServer ? filter(path => !path.startsWith('__serverFileName')) : noop(),
template({
...strings,
...options as object,
stripTsExtension: (s: string) => { return s.replace(/\.ts$/, ''); },
tsConfigExtends,
rootInSrc,
}),
move(tsConfigDirectory),
]);

return chain([
externalSchematic('@schematics/angular', 'universal', options),
mergeWith(rootSource),
addDependenciesAndScripts(options),
]);
};
}
Loading