Skip to content

Commit 93aa109

Browse files
committed
feat(@angular/cli): add ability to build bundle for node
1 parent 2939379 commit 93aa109

5 files changed

Lines changed: 115 additions & 1 deletion

File tree

packages/@angular/cli/lib/config/schema.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@
8383
"type": "string",
8484
"description": "Base url for the application being built."
8585
},
86+
"platform": {
87+
"type": "string",
88+
"default": "browser",
89+
"description": "The runtime platform of the app."
90+
},
8691
"index": {
8792
"type": "string",
8893
"default": "index.html",

packages/@angular/cli/models/webpack-config.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
getDevConfig,
88
getProdConfig,
99
getStylesConfig,
10+
getServerConfig,
1011
getNonAotConfig,
1112
getAotConfig
1213
} from './webpack-configs';
@@ -37,9 +38,12 @@ export class NgCliWebpackConfig {
3738
}
3839

3940
public buildConfig() {
41+
const platformConfig = this.wco.appConfig.platform === 'server' ?
42+
getServerConfig(this.wco) : getBrowserConfig(this.wco);
43+
4044
let webpackConfigs = [
4145
getCommonConfig(this.wco),
42-
getBrowserConfig(this.wco),
46+
platformConfig,
4347
getStylesConfig(this.wco),
4448
this.getTargetConfig(this.wco)
4549
];

packages/@angular/cli/models/webpack-configs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './browser';
22
export * from './common';
33
export * from './development';
44
export * from './production';
5+
export * from './server';
56
export * from './styles';
67
export * from './test';
78
export * from './typescript';
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { WebpackConfigOptions } from '../webpack-config';
2+
3+
/**
4+
* Returns a partial specific to creating a bundle for node
5+
* @param _wco Options which are include the build options and app config
6+
*/
7+
export const getServerConfig = function (_wco: WebpackConfigOptions) {
8+
return {
9+
target: 'node',
10+
output: {
11+
libraryTarget: 'commonjs'
12+
},
13+
externals: [
14+
/^@angular/,
15+
function (_: any, request: any, callback: (error?: any, result?: any) => void) {
16+
// Absolute & Relative paths are not externals
17+
if (request.match(/^\.{0,2}\//)) {
18+
return callback();
19+
}
20+
21+
try {
22+
// Attempt to resolve the module via Node
23+
const e = require.resolve(request);
24+
if (/node_modules/.test(e)) {
25+
// It's a node_module
26+
callback(null, request);
27+
} else {
28+
// It's a system thing (.ie util, fs...)
29+
callback();
30+
}
31+
} catch (e) {
32+
// Node couldn't find it, so it must be user-aliased
33+
callback();
34+
}
35+
}
36+
]
37+
};
38+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { normalize } from 'path';
2+
3+
import { updateJsonFile, updateTsConfig } from '../../utils/project';
4+
import { expectFileToMatch, writeFile, replaceInFile, prependToFile } from '../../utils/fs';
5+
import { ng, exec } from '../../utils/process';
6+
import { getGlobalVariable } from '../../utils/env';
7+
8+
export default function () {
9+
// Skip this in Appveyor tests.
10+
if (getGlobalVariable('argv').appveyor) {
11+
return Promise.resolve();
12+
}
13+
14+
return Promise.resolve()
15+
.then(() => updateJsonFile('.angular-cli.json', configJson => {
16+
const app = configJson['apps'][0];
17+
delete app['polyfills'];
18+
delete app['styles'];
19+
app['platform'] = 'server';
20+
}))
21+
.then(() => updateJsonFile('package.json', packageJson => {
22+
const dependencies = packageJson['dependencies'];
23+
dependencies['@angular/platform-server'] = '^4.0.0';
24+
}))
25+
.then(() => updateTsConfig(tsConfig => {
26+
tsConfig['angularCompilerOptions'] = {
27+
entryModule: 'app/app.module#AppModule'
28+
};
29+
}))
30+
.then(() => writeFile('./src/main.ts', 'export { AppModule } from \'./app/app.module\';'))
31+
.then(() => prependToFile('./src/app/app.module.ts',
32+
'import { ServerModule } from \'@angular/platform-server\';'))
33+
.then(() => replaceInFile('./src/app/app.module.ts', /\[\s*BrowserModule/g,
34+
`[BrowserModule.withServerTransition(\{ appId: 'app' \}), ServerModule`))
35+
.then(() => exec(normalize('npm'), 'install', '--silent', '--no-progress'))
36+
.then(() => ng('build'))
37+
// files were created successfully
38+
.then(() => expectFileToMatch('dist/main.bundle.js',
39+
/__webpack_exports__, "AppModule"/))
40+
.then(() => writeFile('./index.js', `
41+
require('zone.js/dist/zone-node');
42+
require('reflect-metadata');
43+
const fs = require('fs');
44+
const \{ AppModule \} = require('./dist/main.bundle');
45+
const \{ renderModule \} = require('@angular/platform-server');
46+
47+
renderModule(AppModule, \{
48+
url: '/',
49+
document: '<app-root></app-root>'
50+
\}).then(html => \{
51+
fs.writeFileSync('dist/index.html', html);
52+
\});
53+
`))
54+
.then(() => exec(normalize('node'), 'index.js'))
55+
.then(() => expectFileToMatch('dist/index.html',
56+
new RegExp('<h2 _ngcontent-c0="">Here are some links to help you start: </h2>')))
57+
.then(() => ng('build', '--aot'))
58+
// files were created successfully
59+
.then(() => expectFileToMatch('dist/main.bundle.js',
60+
/__webpack_exports__, "AppModuleNgFactory"/))
61+
.then(() => replaceInFile('./index.js', /AppModule/g, 'AppModuleNgFactory'))
62+
.then(() => replaceInFile('./index.js', /renderModule/g, 'renderModuleFactory'))
63+
.then(() => exec(normalize('node'), 'index.js'))
64+
.then(() => expectFileToMatch('dist/index.html',
65+
new RegExp('<h2 _ngcontent-c0="">Here are some links to help you start: </h2>')));
66+
}

0 commit comments

Comments
 (0)