From 59d585d00a2e76418bc2d6951c5229fe5711a397 Mon Sep 17 00:00:00 2001 From: Hans Larsen Date: Wed, 4 Oct 2017 16:38:48 -0700 Subject: [PATCH] feat(@angular/cli): add a bundle dependencies flag 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 #7903. --- docs/documentation/build.md | 10 ++++++ packages/@angular/cli/commands/build.ts | 7 ++++ packages/@angular/cli/models/build-options.ts | 1 + .../@angular/cli/models/webpack-config.ts | 7 +++- .../cli/models/webpack-configs/browser.ts | 25 ++++++++++++- .../cli/models/webpack-configs/common.ts | 25 +------------ .../cli/models/webpack-configs/server.ts | 30 ++++++++++++---- tests/e2e/tests/build/platform-server.ts | 35 +++++++++++++++++-- 8 files changed, 105 insertions(+), 35 deletions(-) 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')); }