diff --git a/docs/documentation/build.md b/docs/documentation/build.md index ddd16cfec8e5..a78428af1deb 100644 --- a/docs/documentation/build.md +++ b/docs/documentation/build.md @@ -366,3 +366,13 @@ Note: service worker support is experimental and subject to change. Use file name for lazy loaded chunks.

+ +
+ bundle-dependencies +

+ --bundle-dependencies +

+

+ In a server build, state whether `all` or `none` dependencies should be bundles in the output. +

+
diff --git a/packages/@angular/cli/commands/build.ts b/packages/@angular/cli/commands/build.ts index 55eda26e789d..b4ab4e7aeca1 100644 --- a/packages/@angular/cli/commands/build.ts +++ b/packages/@angular/cli/commands/build.ts @@ -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.' } ]; diff --git a/packages/@angular/cli/models/build-options.ts b/packages/@angular/cli/models/build-options.ts index 28cce0341396..6ed4dcafb87a 100644 --- a/packages/@angular/cli/models/build-options.ts +++ b/packages/@angular/cli/models/build-options.ts @@ -17,6 +17,7 @@ export interface BuildOptions { locale?: string; missingTranslation?: string; extractCss?: boolean; + bundleDependencies?: 'none' | 'all'; watch?: boolean; outputHashing?: string; poll?: number; diff --git a/packages/@angular/cli/models/webpack-config.ts b/packages/@angular/cli/models/webpack-config.ts index a63654213d94..a1e8e438b1d5 100644 --- a/packages/@angular/cli/models/webpack-config.ts +++ b/packages/@angular/cli/models/webpack-config.ts @@ -1,3 +1,4 @@ +import { readTsconfig } from '../utilities/read-tsconfig'; const webpackMerge = require('webpack-merge'); import { CliConfig } from './config'; import { BuildOptions } from './build-options'; @@ -17,6 +18,7 @@ export interface WebpackConfigOptions { projectRoot: string; buildOptions: T; appConfig: any; + tsConfig: any; } export class NgCliWebpackConfig { @@ -33,7 +35,10 @@ export class NgCliWebpackConfig { 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() { diff --git a/packages/@angular/cli/models/webpack-configs/browser.ts b/packages/@angular/cli/models/webpack-configs/browser.ts index bea9cfb97407..579b34991512 100644 --- a/packages/@angular/cli/models/webpack-configs/browser.ts +++ b/packages/@angular/cli/models/webpack-configs/browser.ts @@ -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'); @@ -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 }, @@ -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 + } }; } diff --git a/packages/@angular/cli/models/webpack-configs/common.ts b/packages/@angular/cli/models/webpack-configs/common.ts index 732427a97deb..3acc15d01de3 100644 --- a/packages/@angular/cli/models/webpack-configs/common.ts +++ b/packages/@angular/cli/models/webpack-configs/common.ts @@ -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'); @@ -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: { @@ -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) }; } diff --git a/packages/@angular/cli/models/webpack-configs/server.ts b/packages/@angular/cli/models/webpack-configs/server.ts index c3e5fb465587..48a40281ffe1 100644 --- a/packages/@angular/cli/models/webpack-configs/server.ts +++ b/packages/@angular/cli/models/webpack-configs/server.ts @@ -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(); @@ -33,6 +47,8 @@ export function getServerConfig(_wco: WebpackConfigOptions) { callback(); } } - ] - }; + ]; + } + + return config; } diff --git a/tests/e2e/tests/build/platform-server.ts b/tests/e2e/tests/build/platform-server.ts index e040242fd760..210d0a41832e 100644 --- a/tests/e2e/tests/build/platform-server.ts +++ b/tests/e2e/tests/build/platform-server.ts @@ -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. @@ -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' }; @@ -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('

Here are some links to help you start:

'))); + new RegExp('

Here are some links to help you start:

'))) + .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: '' + \}).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')); }