Skip to content

Commit

Permalink
feat(@angular/cli): add a bundle dependencies flag
Browse files Browse the repository at this point in the history
This flag allows people who know what theyre doing to bundle the
server build together with all dependencies. It should only be
used when the whole rendering process is part of the main.ts
or one of its dependencies.

Fixes angular#7903.
  • Loading branch information
hansl committed Oct 5, 2017
1 parent b6dfa8d commit 59d585d
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 35 deletions.
10 changes: 10 additions & 0 deletions docs/documentation/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,13 @@ Note: service worker support is experimental and subject to change.
Use file name for lazy loaded chunks.
</p>
</details>

<details>
<summary>bundle-dependencies</summary>
<p>
<code>--bundle-dependencies</code>
</p>
<p>
In a server build, state whether `all` or `none` dependencies should be bundles in the output.
</p>
</details>
7 changes: 7 additions & 0 deletions packages/@angular/cli/commands/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,13 @@ export const baseBuildCommandOptions: any = [
default: false,
aliases: ['sri'],
description: 'Enables the use of subresource integrity validation.'
},
{
name: 'bundle-dependencies',
type: ['none', 'all'],
default: 'none',
description: 'Available on server platform only. Which external dependencies to bundle into '
+ 'the module. By default, all of node_modules will be kept as requires.'
}
];

Expand Down
1 change: 1 addition & 0 deletions packages/@angular/cli/models/build-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface BuildOptions {
locale?: string;
missingTranslation?: string;
extractCss?: boolean;
bundleDependencies?: 'none' | 'all';
watch?: boolean;
outputHashing?: string;
poll?: number;
Expand Down
7 changes: 6 additions & 1 deletion packages/@angular/cli/models/webpack-config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { readTsconfig } from '../utilities/read-tsconfig';
const webpackMerge = require('webpack-merge');
import { CliConfig } from './config';
import { BuildOptions } from './build-options';
Expand All @@ -17,6 +18,7 @@ export interface WebpackConfigOptions<T extends BuildOptions = BuildOptions> {
projectRoot: string;
buildOptions: T;
appConfig: any;
tsConfig: any;
}

export class NgCliWebpackConfig<T extends BuildOptions = BuildOptions> {
Expand All @@ -33,7 +35,10 @@ export class NgCliWebpackConfig<T extends BuildOptions = BuildOptions> {
buildOptions = this.addTargetDefaults(buildOptions);
buildOptions = this.mergeConfigs(buildOptions, appConfig, projectRoot);

this.wco = { projectRoot, buildOptions, appConfig };
const tsconfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
const tsConfig = readTsconfig(tsconfigPath);

this.wco = { projectRoot, buildOptions, appConfig, tsConfig };
}

public buildConfig() {
Expand Down
25 changes: 24 additions & 1 deletion packages/@angular/cli/models/webpack-configs/browser.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as fs from 'fs';
import * as webpack from 'webpack';
import * as path from 'path';
import * as ts from 'typescript';
const HtmlWebpackPlugin = require('html-webpack-plugin');
const SubresourceIntegrityPlugin = require('webpack-subresource-integrity');

Expand Down Expand Up @@ -67,7 +68,16 @@ export function getBrowserConfig(wco: WebpackConfigOptions) {
}));
}

const supportES2015 = wco.tsConfig.options.target !== ts.ScriptTarget.ES3
&& wco.tsConfig.options.target !== ts.ScriptTarget.ES5;

return {
resolve: {
mainFields: [
...(supportES2015 ? ['es2015'] : []),
'browser', 'module', 'main'
]
},
output: {
crossOriginLoading: buildOptions.subresourceIntegrity ? 'anonymous' : false
},
Expand All @@ -91,6 +101,19 @@ export function getBrowserConfig(wco: WebpackConfigOptions) {
minChunks: Infinity,
name: 'inline'
})
].concat(extraPlugins)
].concat(extraPlugins),
node: {
fs: 'empty',
// `global` should be kept true, removing it resulted in a
// massive size increase with Build Optimizer on AIO.
global: true,
crypto: 'empty',
tls: 'empty',
net: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
};
}
25 changes: 1 addition & 24 deletions packages/@angular/cli/models/webpack-configs/common.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import * as webpack from 'webpack';
import * as path from 'path';
import * as CopyWebpackPlugin from 'copy-webpack-plugin';
import * as ts from 'typescript';
import { NamedLazyChunksWebpackPlugin } from '../../plugins/named-lazy-chunks-webpack-plugin';
import { InsertConcatAssetsWebpackPlugin } from '../../plugins/insert-concat-assets-webpack-plugin';
import { extraEntryParser, getOutputHashFormat, AssetPattern } from './utils';
import { isDirectory } from '../../utilities/is-directory';
import { WebpackConfigOptions } from '../webpack-config';
import { readTsconfig } from '../../utilities/read-tsconfig';

const ConcatPlugin = require('webpack-concat-plugin');
const ProgressPlugin = require('webpack/lib/ProgressPlugin');
Expand Down Expand Up @@ -160,19 +158,11 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
}

// Read the tsconfig to determine if we should prefer ES2015 modules.
const tsconfigPath = path.resolve(projectRoot, appConfig.root, appConfig.tsconfig);
const tsConfig = readTsconfig(tsconfigPath);
const supportES2015 = tsConfig.options.target !== ts.ScriptTarget.ES3
&& tsConfig.options.target !== ts.ScriptTarget.ES5;

return {
resolve: {
extensions: ['.ts', '.js'],
modules: ['node_modules', nodeModules],
mainFields: [
...(supportES2015 ? ['es2015'] : []),
'browser', 'module', 'main'
],
symlinks: !buildOptions.preserveSymlinks
},
resolveLoader: {
Expand Down Expand Up @@ -201,19 +191,6 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
},
plugins: [
new webpack.NoEmitOnErrorsPlugin()
].concat(extraPlugins),
node: {
fs: 'empty',
// `global` should be kept true, removing it resulted in a
// massive size increase with Build Optimizer on AIO.
global: true,
crypto: 'empty',
tls: 'empty',
net: 'empty',
process: true,
module: false,
clearImmediate: false,
setImmediate: false
}
].concat(extraPlugins)
};
}
30 changes: 23 additions & 7 deletions packages/@angular/cli/models/webpack-configs/server.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { WebpackConfigOptions } from '../webpack-config';
import * as ts from 'typescript';

/**
* Returns a partial specific to creating a bundle for node
* @param _wco Options which are include the build options and app config
* @param wco Options which are include the build options and app config
*/
export function getServerConfig(_wco: WebpackConfigOptions) {
return {
export function getServerConfig(wco: WebpackConfigOptions) {
const supportES2015 = wco.tsConfig.options.target !== ts.ScriptTarget.ES3
&& wco.tsConfig.options.target !== ts.ScriptTarget.ES5;

const config: any = {
resolve: {
mainFields: [
...(supportES2015 ? ['es2015'] : []),
'main', 'module',
],
},
target: 'node',
output: {
libraryTarget: 'commonjs'
},
externals: [
node: false,
};

if (wco.buildOptions.bundleDependencies == 'none') {
config.externals = [
/^@angular/,
function (_: any, request: any, callback: (error?: any, result?: any) => void) {
(_: any, request: any, callback: (error?: any, result?: any) => void) => {
// Absolute & Relative paths are not externals
if (request.match(/^\.{0,2}\//)) {
return callback();
Expand All @@ -33,6 +47,8 @@ export function getServerConfig(_wco: WebpackConfigOptions) {
callback();
}
}
]
};
];
}

return config;
}
35 changes: 33 additions & 2 deletions tests/e2e/tests/build/platform-server.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { normalize } from 'path';

import { updateJsonFile, updateTsConfig } from '../../utils/project';
import { expectFileToMatch, writeFile, replaceInFile, prependToFile } from '../../utils/fs';
import {
expectFileToMatch,
writeFile,
replaceInFile,
prependToFile,
appendToFile,
} from '../../utils/fs';
import { ng, silentNpm, exec } from '../../utils/process';
import { getGlobalVariable } from '../../utils/env';
import { expectToFail } from '../../utils/utils';

export default function () {
// Skip this in Appveyor tests.
Expand Down Expand Up @@ -34,6 +41,7 @@ export default function () {
dependencies['@angular/platform-server'] = platformServerVersion;
}))
.then(() => updateTsConfig(tsConfig => {
tsConfig.compilerOptions.types = ['node'];
tsConfig['angularCompilerOptions'] = {
entryModule: 'app/app.module#AppModule'
};
Expand Down Expand Up @@ -73,5 +81,28 @@ export default function () {
.then(() => replaceInFile('./index.js', /renderModule/g, 'renderModuleFactory'))
.then(() => exec(normalize('node'), 'index.js'))
.then(() => expectFileToMatch('dist/index.html',
new RegExp('<h2 _ngcontent-c0="">Here are some links to help you start: </h2>')));
new RegExp('<h2 _ngcontent-c0="">Here are some links to help you start: </h2>')))
.then(() => expectFileToMatch('./dist/main.bundle.js',
/require\(["']@angular\/[^"']*["']\)/))

// Check externals.
.then(() => prependToFile('./src/app/app.module.ts', `
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
`)
.then(() => appendToFile('./src/app/app.module.ts', `
import * as fs from 'fs';
import { renderModule } from '@angular/platform-server';
renderModule(AppModule, \{
url: '/',
document: '<app-root></app-root>'
\}).then(html => \{
fs.writeFileSync('dist/index.html', html);
\});
`)))
.then(() => ng('build', '--bundle-dependencies=all'))
.then(() => expectToFail(() => expectFileToMatch('./dist/main.bundle.js',
/require\(["']@angular\/[^"']*["']\)/)))
.then(() => exec(normalize('node'), 'dist/main.bundle.js'));
}

0 comments on commit 59d585d

Please sign in to comment.