From 9dee17d44b124dad92d0eb9a132e9dc64519ddbb Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Wed, 18 Jan 2017 16:42:44 +0000 Subject: [PATCH 1/7] refactor(build): consolidate build options --- packages/angular-cli/commands/build.run.ts | 38 +--- packages/angular-cli/commands/build.ts | 86 ++++---- .../commands/github-pages-deploy.run.ts | 4 +- packages/angular-cli/commands/serve.run.ts | 24 +-- packages/angular-cli/commands/serve.ts | 40 +--- packages/angular-cli/models/index.ts | 4 - .../models/webpack-build-development.ts | 3 - .../models/webpack-build-production.ts | 31 --- packages/angular-cli/models/webpack-config.ts | 184 +++++++++++------- .../common.ts} | 44 ++--- .../models/webpack-configs/development.ts | 5 + .../models/webpack-configs/index.ts | 6 + .../models/webpack-configs/production.ts | 30 +++ .../styles.ts} | 33 ++-- .../test.js} | 4 +- .../typescript.ts} | 14 +- .../utils.ts} | 0 packages/angular-cli/plugins/karma.js | 4 +- .../tasks/{build-webpack.ts => build.ts} | 33 +--- .../tasks/{serve-webpack.ts => serve.ts} | 38 ++-- tests/e2e/tests/build/deploy-url.ts | 28 ++- tests/e2e/tests/build/output-dir.ts | 2 +- tests/e2e/tests/build/prod-build.ts | 3 + tests/e2e/tests/build/scripts-array.ts | 2 +- tests/e2e/tests/build/styles/css.ts | 2 +- tests/e2e/tests/build/styles/extract-css.ts | 90 +++++++-- tests/e2e/tests/build/styles/include-paths.ts | 4 +- tests/e2e/tests/build/styles/less.ts | 2 +- tests/e2e/tests/build/styles/postcss.ts | 2 +- tests/e2e/tests/build/styles/scss.ts | 2 +- tests/e2e/tests/build/styles/styles-array.ts | 16 +- tests/e2e/tests/build/styles/stylus.ts | 2 +- tests/e2e/tests/third-party/bootstrap.ts | 2 +- 33 files changed, 384 insertions(+), 398 deletions(-) delete mode 100644 packages/angular-cli/models/index.ts delete mode 100644 packages/angular-cli/models/webpack-build-development.ts delete mode 100644 packages/angular-cli/models/webpack-build-production.ts rename packages/angular-cli/models/{webpack-build-common.ts => webpack-configs/common.ts} (80%) create mode 100644 packages/angular-cli/models/webpack-configs/development.ts create mode 100644 packages/angular-cli/models/webpack-configs/index.ts create mode 100644 packages/angular-cli/models/webpack-configs/production.ts rename packages/angular-cli/models/{webpack-build-styles.ts => webpack-configs/styles.ts} (81%) rename packages/angular-cli/models/{webpack-build-test.js => webpack-configs/test.js} (96%) rename packages/angular-cli/models/{webpack-build-typescript.ts => webpack-configs/typescript.ts} (76%) rename packages/angular-cli/models/{webpack-build-utils.ts => webpack-configs/utils.ts} (100%) rename packages/angular-cli/tasks/{build-webpack.ts => build.ts} (53%) rename packages/angular-cli/tasks/{serve-webpack.ts => serve.ts} (86%) diff --git a/packages/angular-cli/commands/build.run.ts b/packages/angular-cli/commands/build.run.ts index 0c211b7bbc09..668a4261e143 100644 --- a/packages/angular-cli/commands/build.run.ts +++ b/packages/angular-cli/commands/build.run.ts @@ -1,46 +1,16 @@ import { Version } from '../upgrade/version'; -import WebpackBuild from '../tasks/build-webpack'; -import { BuildOptions } from './build'; - -export default function buildRun(commandOptions: BuildOptions) { - if (commandOptions.environment === '') { - if (commandOptions.target === 'development') { - commandOptions.environment = 'dev'; - } - if (commandOptions.target === 'production') { - commandOptions.environment = 'prod'; - } - } - - if (!commandOptions.outputHashing) { - if (commandOptions.target === 'development') { - commandOptions.outputHashing = 'none'; - } - if (commandOptions.target === 'production') { - commandOptions.outputHashing = 'all'; - } - } - - if (typeof commandOptions.sourcemap === 'undefined') { - if (commandOptions.target == 'development') { - commandOptions.sourcemap = true; - } - if (commandOptions.target == 'production') { - commandOptions.sourcemap = false; - } - } +import Build from '../tasks/build'; +import { BuildTaskOptions } from './build'; +export default function buildRun(commandOptions: BuildTaskOptions) { const project = this.project; // Check angular version. Version.assertAngularVersionIs2_3_1OrHigher(project.root); - const buildTask = new WebpackBuild({ + const buildTask = new Build({ cliProject: project, ui: this.ui, - outputPath: commandOptions.outputPath, - target: commandOptions.target, - environment: commandOptions.environment, }); return buildTask.run(commandOptions); diff --git a/packages/angular-cli/commands/build.ts b/packages/angular-cli/commands/build.ts index 880ff26262b1..f6769aade08f 100644 --- a/packages/angular-cli/commands/build.ts +++ b/packages/angular-cli/commands/build.ts @@ -1,24 +1,39 @@ +import { BuildOptions } from '../models/webpack-config'; + const Command = require('../ember-cli/lib/models/command'); -export interface BuildOptions { - target?: string; - environment?: string; - outputPath?: string; +// defaults for BuildOptions +export const BaseBuildCommandOptions: any = [ + { + name: 'target', + type: String, + default: 'development', + aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] + }, + { name: 'environment', type: String, aliases: ['e'] }, + { name: 'output-path', type: 'Path', aliases: ['op'] }, + { name: 'aot', type: Boolean, default: false }, + { name: 'sourcemap', type: Boolean, aliases: ['sm'] }, + { name: 'vendor-chunk', type: Boolean, default: true, aliases: ['vc'] }, + { name: 'base-href', type: String, default: '/', aliases: ['bh'] }, + { name: 'deploy-url', type: String, aliases: ['d'] }, + { name: 'verbose', type: Boolean, default: false, aliases: ['v'] }, + { name: 'progress', type: Boolean, default: true, aliases: ['pr'] }, + { name: 'i18n-file', type: String }, + { name: 'i18n-format', type: String }, + { name: 'locale', type: String }, + { name: 'extract-css', type: Boolean, aliases: ['ec']}, + { + name: 'output-hashing', + type: String, + values: ['none', 'all', 'media', 'bundles'], + description: 'define the output filename cache-busting hashing mode', + aliases: ['oh'] + }, +]; + +export interface BuildTaskOptions extends BuildOptions { watch?: boolean; - watcher?: string; - supressSizes: boolean; - baseHref?: string; - aot?: boolean; - sourcemap?: boolean; - vendorChunk?: boolean; - verbose?: boolean; - progress?: boolean; - i18nFile?: string; - i18nFormat?: string; - locale?: string; - deployUrl?: string; - outputHashing?: string; - extractCss?: boolean | null; } const BuildCommand = Command.extend({ @@ -26,38 +41,11 @@ const BuildCommand = Command.extend({ description: 'Builds your app and places it into the output path (dist/ by default).', aliases: ['b'], - availableOptions: [ - { - name: 'target', - type: String, - default: 'development', - aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] - }, - { name: 'environment', type: String, default: '', aliases: ['e'] }, - { name: 'output-path', type: 'Path', default: null, aliases: ['o'] }, - { name: 'watch', type: Boolean, default: false, aliases: ['w'] }, - { name: 'watcher', type: String }, - { name: 'suppress-sizes', type: Boolean, default: false }, - { name: 'base-href', type: String, default: null, aliases: ['bh'] }, - { name: 'aot', type: Boolean, default: false }, - { name: 'sourcemap', type: Boolean, aliases: ['sm'] }, - { name: 'vendor-chunk', type: Boolean, default: true }, - { name: 'verbose', type: Boolean, default: false }, - { name: 'progress', type: Boolean, default: true }, - { name: 'i18n-file', type: String, default: null }, - { name: 'i18n-format', type: String, default: null }, - { name: 'locale', type: String, default: null }, - { name: 'deploy-url', type: String, default: null, aliases: ['d'] }, - { - name: 'output-hashing', - type: String, - values: ['none', 'all', 'media', 'bundles'], - description: 'define the output filename cache-busting hashing mode' - }, - { name: 'extract-css', type: Boolean, default: true } - ], + availableOptions: BaseBuildCommandOptions.concat([ + { name: 'watch', type: Boolean, default: false, aliases: ['w'] } + ]), - run: function (commandOptions: BuildOptions) { + run: function (commandOptions: BuildTaskOptions) { return require('./build.run').default.call(this, commandOptions); } }); diff --git a/packages/angular-cli/commands/github-pages-deploy.run.ts b/packages/angular-cli/commands/github-pages-deploy.run.ts index f0343c1b4edd..1e8f1211a027 100644 --- a/packages/angular-cli/commands/github-pages-deploy.run.ts +++ b/packages/angular-cli/commands/github-pages-deploy.run.ts @@ -6,7 +6,7 @@ import * as chalk from 'chalk'; import * as fs from 'fs'; import * as fse from 'fs-extra'; import * as path from 'path'; -import WebpackBuild from '../tasks/build-webpack'; +import Build from '../tasks/build'; import CreateGithubRepo from '../tasks/create-github-repo'; import { CliConfig } from '../models/config'; import { GithubPagesDeployOptions } from './github-pages-deploy'; @@ -44,7 +44,7 @@ export default function githubPagesDeployRun(options: GithubPagesDeployOptions, // declared here so that tests can stub exec const execPromise = <(cmd: string, options?: any) => Promise>denodeify(exec); - const buildTask = new WebpackBuild({ + const buildTask = new Build({ ui: this.ui, cliProject: this.project, target: options.target, diff --git a/packages/angular-cli/commands/serve.run.ts b/packages/angular-cli/commands/serve.run.ts index b97e427608cf..3b3992426a07 100644 --- a/packages/angular-cli/commands/serve.run.ts +++ b/packages/angular-cli/commands/serve.run.ts @@ -2,7 +2,7 @@ import * as denodeify from 'denodeify'; const assign = require('lodash/assign'); const SilentError = require('silent-error'); const PortFinder = require('portfinder'); -import ServeWebpackTask from '../tasks/serve-webpack'; +import ServeTask from '../tasks/serve'; import { Version } from '../upgrade/version'; import { ServeTaskOptions } from './serve'; @@ -11,20 +11,6 @@ PortFinder.basePort = 49152; const getPort = denodeify(PortFinder.getPort); export default function serveRun(commandOptions: ServeTaskOptions) { - if (commandOptions.environment === '') { - if (commandOptions.target === 'development') { - commandOptions.environment = 'dev'; - } - if (commandOptions.target === 'production') { - commandOptions.environment = 'prod'; - } - } - - // default to extractCss to true on prod target - if (typeof commandOptions.extractCss === 'undefined') { - commandOptions.extractCss = commandOptions.target === 'production'; - } - // Check angular version. Version.assertAngularVersionIs2_3_1OrHigher(this.project.root); commandOptions.liveReloadHost = commandOptions.liveReloadHost || commandOptions.host; @@ -32,16 +18,12 @@ export default function serveRun(commandOptions: ServeTaskOptions) { return checkExpressPort(commandOptions) .then(() => autoFindLiveReloadPort(commandOptions)) .then((opts: ServeTaskOptions) => { - commandOptions = assign({}, opts, { - baseURL: this.project.config(commandOptions.target).baseURL || '/' - }); - - const serve = new ServeWebpackTask({ + const serve = new ServeTask({ ui: this.ui, project: this.project, }); - return serve.run(commandOptions); + return serve.run(opts); }); } diff --git a/packages/angular-cli/commands/serve.ts b/packages/angular-cli/commands/serve.ts index a1af11e528e6..0822b41ce1df 100644 --- a/packages/angular-cli/commands/serve.ts +++ b/packages/angular-cli/commands/serve.ts @@ -1,3 +1,6 @@ +import { BuildOptions } from '../models/webpack-config'; +import { BaseBuildCommandOptions } from './build'; + const PortFinder = require('portfinder'); const Command = require('../ember-cli/lib/models/command'); @@ -5,32 +8,20 @@ PortFinder.basePort = 49152; const defaultPort = process.env.PORT || 4200; -export interface ServeTaskOptions { +export interface ServeTaskOptions extends BuildOptions { port?: number; host?: string; proxyConfig?: string; - watcher?: string; liveReload?: boolean; liveReloadHost?: string; liveReloadPort?: number; liveReloadBaseUrl?: string; liveReloadLiveCss?: boolean; - target?: string; - environment?: string; ssl?: boolean; sslKey?: string; sslCert?: string; - aot?: boolean; - sourcemap?: boolean; - verbose?: boolean; - progress?: boolean; open?: boolean; - vendorChunk?: boolean; hmr?: boolean; - i18nFile?: string; - i18nFormat?: string; - locale?: string; - extractCss?: boolean | null; } const ServeCommand = Command.extend({ @@ -38,7 +29,7 @@ const ServeCommand = Command.extend({ description: 'Builds and serves your app, rebuilding on file changes.', aliases: ['server', 's'], - availableOptions: [ + availableOptions: BaseBuildCommandOptions.concat([ { name: 'port', type: Number, default: defaultPort, aliases: ['p'] }, { name: 'host', @@ -48,7 +39,6 @@ const ServeCommand = Command.extend({ description: 'Listens only on localhost by default' }, { name: 'proxy-config', type: 'Path', aliases: ['pc'] }, - { name: 'watcher', type: String, default: 'events', aliases: ['w'] }, { name: 'live-reload', type: Boolean, default: true, aliases: ['lr'] }, { name: 'live-reload-host', @@ -74,21 +64,9 @@ const ServeCommand = Command.extend({ default: true, description: 'Whether to live reload CSS (default true)' }, - { - name: 'target', - type: String, - default: 'development', - aliases: ['t', { 'dev': 'development' }, { 'prod': 'production' }] - }, - { name: 'environment', type: String, default: '', aliases: ['e'] }, { name: 'ssl', type: Boolean, default: false }, { name: 'ssl-key', type: String, default: 'ssl/server.key' }, { name: 'ssl-cert', type: String, default: 'ssl/server.crt' }, - { name: 'aot', type: Boolean, default: false }, - { name: 'sourcemap', type: Boolean, default: true, aliases: ['sm'] }, - { name: 'vendor-chunk', type: Boolean, default: true }, - { name: 'verbose', type: Boolean, default: false }, - { name: 'progress', type: Boolean, default: true }, { name: 'open', type: Boolean, @@ -101,12 +79,8 @@ const ServeCommand = Command.extend({ type: Boolean, default: false, description: 'Enable hot module replacement', - }, - { name: 'i18n-file', type: String, default: null }, - { name: 'i18n-format', type: String, default: null }, - { name: 'locale', type: String, default: null }, - { name: 'extract-css', type: Boolean, default: null } - ], + } + ]), run: function(commandOptions: ServeTaskOptions) { return require('./serve.run').default.call(this, commandOptions); diff --git a/packages/angular-cli/models/index.ts b/packages/angular-cli/models/index.ts deleted file mode 100644 index 0912f182b898..000000000000 --- a/packages/angular-cli/models/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './webpack-build-common'; -export * from './webpack-build-production'; -export * from './webpack-build-development'; -export * from './webpack-build-utils'; diff --git a/packages/angular-cli/models/webpack-build-development.ts b/packages/angular-cli/models/webpack-build-development.ts deleted file mode 100644 index 19b6f1281e30..000000000000 --- a/packages/angular-cli/models/webpack-build-development.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const getWebpackDevConfigPartial = function(projectRoot: string, appConfig: any) { - return { }; -}; diff --git a/packages/angular-cli/models/webpack-build-production.ts b/packages/angular-cli/models/webpack-build-production.ts deleted file mode 100644 index 21c4f74dc14a..000000000000 --- a/packages/angular-cli/models/webpack-build-production.ts +++ /dev/null @@ -1,31 +0,0 @@ -import * as path from 'path'; -import * as webpack from 'webpack'; -import {CompressionPlugin} from '../lib/webpack/compression-plugin'; - - -export const getWebpackProdConfigPartial = function(projectRoot: string, - appConfig: any, - sourcemap: boolean, - verbose: any) { - const appRoot = path.resolve(projectRoot, appConfig.root); - - return { - plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('production') - }), - new webpack.LoaderOptionsPlugin({ minimize: true }), - new webpack.optimize.UglifyJsPlugin({ - mangle: { screw_ie8 : true }, - compress: { screw_ie8: true, warnings: verbose }, - sourceMap: sourcemap - }), - new CompressionPlugin({ - asset: '[path].gz[query]', - algorithm: 'gzip', - test: /\.js$|\.html$|\.css$/, - threshold: 10240 - }) - ] - }; -}; diff --git a/packages/angular-cli/models/webpack-config.ts b/packages/angular-cli/models/webpack-config.ts index f46231955904..71f864b36d89 100644 --- a/packages/angular-cli/models/webpack-config.ts +++ b/packages/angular-cli/models/webpack-config.ts @@ -1,91 +1,131 @@ -import { - getWebpackAotConfigPartial, - getWebpackNonAotConfigPartial -} from './webpack-build-typescript'; const webpackMerge = require('webpack-merge'); import { CliConfig } from './config'; -import { getWebpackCommonConfig } from './webpack-build-common'; -import { getWebpackDevConfigPartial } from './webpack-build-development'; -import { getWebpackProdConfigPartial } from './webpack-build-production'; -import { getWebpackStylesConfig } from './webpack-build-styles'; +import { + getCommonConfig, + getDevConfig, + getProdConfig, + getStylesConfig, + getNonAotConfig, + getAotConfig +} from './webpack-configs'; +const path = require('path'); + +export interface BuildOptions { + target?: string; + environment?: string; + outputPath?: string; + aot?: boolean; + sourcemap?: boolean; + vendorChunk?: boolean; + baseHref?: string; + deployUrl?: string; + verbose?: boolean; + progress?: boolean; + i18nFile?: string; + i18nFormat?: string; + locale?: string; + extractCss?: boolean; + outputHashing?: string; +} + +export interface WebpackConfigOptions { + projectRoot: string; + buildOptions: BuildOptions; + appConfig: any; +} export class NgCliWebpackConfig { - // TODO: When webpack2 types are finished lets replace all these any types - // so this is more maintainable in the future for devs public config: any; + constructor(buildOptions: BuildOptions) { - constructor( - public ngCliProject: any, - public target: string, - public environment: string, - outputDir?: string, - baseHref?: string, - i18nFile?: string, - i18nFormat?: string, - locale?: string, - isAoT = false, - sourcemap = true, - vendorChunk = false, - verbose = false, - progress = true, - deployUrl?: string, - outputHashing?: string, - extractCss = true, - ) { - const appConfig = CliConfig.fromProject().config.apps[0]; - const projectRoot = this.ngCliProject.root; - - appConfig.scripts = appConfig.scripts || []; - appConfig.styles = appConfig.styles || []; - appConfig.outDir = outputDir || appConfig.outDir; - appConfig.deployUrl = deployUrl || appConfig.deployUrl; - - let baseConfig = getWebpackCommonConfig( - projectRoot, - environment, - appConfig, - baseHref, - sourcemap, - vendorChunk, - verbose, - progress, - outputHashing, - ); - let targetConfigPartial = this.getTargetConfig(projectRoot, appConfig, sourcemap, verbose); - - let config = webpackMerge(baseConfig, targetConfigPartial); + this.validateBuildOptions(buildOptions); - if (appConfig.main || appConfig.polyfills) { - const typescriptConfigPartial = isAoT - ? getWebpackAotConfigPartial(projectRoot, appConfig, i18nFile, i18nFormat, locale) - : getWebpackNonAotConfigPartial(projectRoot, appConfig); + const configPath = CliConfig.configFilePath(); + const projectRoot = path.dirname(configPath); + let appConfig = CliConfig.fromProject().config.apps[0]; - config = webpackMerge(config, typescriptConfigPartial); - } + appConfig = this.addAppConfigDefaults(appConfig); + buildOptions = this.addTargetDefaults(buildOptions); + buildOptions = this.mergeConfigs(buildOptions, appConfig); + + const wco: WebpackConfigOptions = { projectRoot, buildOptions, appConfig }; - const stylesConfig = getWebpackStylesConfig( - projectRoot, - appConfig, - target, - sourcemap, - outputHashing, - extractCss - ); + let webpackConfigs = [ + getCommonConfig(wco), + getStylesConfig(wco), + this.getTargetConfig(wco) + ]; - config = webpackMerge(config, stylesConfig); + if (appConfig.main || appConfig.polyfills) { + const typescriptConfigPartial = buildOptions.aot + ? getAotConfig(wco) + : getNonAotConfig(wco); + webpackConfigs.push(typescriptConfigPartial); + } - this.config = config; + // add style config + this.config = webpackMerge(webpackConfigs); } - getTargetConfig(projectRoot: string, appConfig: any, sourcemap: boolean, verbose: boolean): any { - switch (this.target) { + getTargetConfig(webpackConfigOptions: WebpackConfigOptions): any { + switch (webpackConfigOptions.buildOptions.target) { case 'development': - return getWebpackDevConfigPartial(projectRoot, appConfig); + return getDevConfig(webpackConfigOptions); case 'production': - return getWebpackProdConfigPartial(projectRoot, appConfig, sourcemap, verbose); - default: - throw new Error("Invalid build target. Only 'development' and 'production' are available."); + return getProdConfig(webpackConfigOptions); } } + + // Validate build options + private validateBuildOptions(buildOptions: BuildOptions) { + if (buildOptions.target !== 'development' && buildOptions.target !== 'production') { + throw new Error("Invalid build target. Only 'development' and 'production' are available."); + } + } + + // Fill in defaults for build targets + private addTargetDefaults(buildOptions: BuildOptions) { + const targetDefaults: any = { + development: { + environment: 'dev', + outputHashing: 'none', + sourcemap: true, + extractCss: false + }, + production: { + environment: 'prod', + outputHashing: 'all', + sourcemap: false, + extractCss: true, + aot: true + } + }; + + return Object.assign({}, targetDefaults[buildOptions.target], buildOptions); + } + + // Fill in defaults from angular-cli.json + private mergeConfigs(buildOptions: BuildOptions, appConfig: any) { + const mergeableOptions = { + outputPath: appConfig.outDir, + deployUrl: appConfig.deployUrl + }; + + return Object.assign({}, mergeableOptions, buildOptions); + } + + private addAppConfigDefaults(appConfig: any) { + const appConfigDefaults: any = { + scripts: [], + styles: [] + }; + + // can't use Object.assign here because appConfig has a lot of getters/setters + for (let key of Object.keys(appConfigDefaults)) { + appConfig[key] = appConfig[key] || appConfigDefaults[key]; + } + + return appConfig; + } } diff --git a/packages/angular-cli/models/webpack-build-common.ts b/packages/angular-cli/models/webpack-configs/common.ts similarity index 80% rename from packages/angular-cli/models/webpack-build-common.ts rename to packages/angular-cli/models/webpack-configs/common.ts index 3efc9dc2aabd..f04558041db6 100644 --- a/packages/angular-cli/models/webpack-build-common.ts +++ b/packages/angular-cli/models/webpack-configs/common.ts @@ -1,9 +1,10 @@ import * as webpack from 'webpack'; import * as path from 'path'; -import { GlobCopyWebpackPlugin } from '../plugins/glob-copy-webpack-plugin'; -import { packageChunkSort } from '../utilities/package-chunk-sort'; +import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin'; +import { packageChunkSort } from '../../utilities/package-chunk-sort'; import { BaseHrefWebpackPlugin } from '@angular-cli/base-href-webpack'; -import { extraEntryParser, lazyChunksFilter, getOutputHashFormat } from './webpack-build-utils'; +import { extraEntryParser, lazyChunksFilter, getOutputHashFormat } from './utils'; +import { WebpackConfigOptions } from '../webpack-config'; const autoprefixer = require('autoprefixer'); const ProgressPlugin = require('webpack/lib/ProgressPlugin'); @@ -23,17 +24,8 @@ const SilentError = require('silent-error'); * require('file-loader') */ -export function getWebpackCommonConfig( - projectRoot: string, - environment: string, - appConfig: any, - baseHref: string, - sourcemap: boolean, - vendorChunk: boolean, - verbose: boolean, - progress: boolean, - outputHashing: string -) { +export function getCommonConfig(wco: WebpackConfigOptions) { + const { projectRoot, buildOptions, appConfig } = wco; const appRoot = path.resolve(projectRoot, appConfig.root); const nodeModules = path.resolve(projectRoot, 'node_modules'); @@ -57,7 +49,7 @@ export function getWebpackCommonConfig( } // determine hashing format - const hashFormat = getOutputHashFormat(outputHashing); + const hashFormat = getOutputHashFormat(buildOptions.outputHashing); // process global scripts if (appConfig.scripts.length > 0) { @@ -71,7 +63,7 @@ export function getWebpackCommonConfig( }); } - if (vendorChunk) { + if (buildOptions.vendorChunk) { extraPlugins.push(new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', chunks: ['main'], @@ -84,8 +76,8 @@ export function getWebpackCommonConfig( if (!('source' in appConfig.environments)) { throw new SilentError(`Environment configuration does not contain "source" entry.`); } - if (!(environment in appConfig.environments)) { - throw new SilentError(`Environment "${environment}" does not exist.`); + if (!(buildOptions.environment in appConfig.environments)) { + throw new SilentError(`Environment "${buildOptions.environment}" does not exist.`); } extraPlugins.push(new webpack.NormalModuleReplacementPlugin( @@ -94,7 +86,7 @@ export function getWebpackCommonConfig( // See https://webpack.github.io/docs/list-of-plugins.html#normalmodulereplacementplugin new RegExp(path.resolve(appRoot, appConfig.environments['source']) .replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&')), - path.resolve(appRoot, appConfig.environments[environment]) + path.resolve(appRoot, appConfig.environments[buildOptions.environment]) )); } @@ -106,10 +98,12 @@ export function getWebpackCommonConfig( })); } - if (progress) { extraPlugins.push(new ProgressPlugin({ profile: verbose, colors: true })); } + if (buildOptions.progress) { + extraPlugins.push(new ProgressPlugin({ profile: buildOptions.verbose, colors: true })); + } return { - devtool: sourcemap ? 'source-map' : false, + devtool: buildOptions.sourcemap ? 'source-map' : false, resolve: { extensions: ['.ts', '.js'], modules: [nodeModules], @@ -120,8 +114,8 @@ export function getWebpackCommonConfig( context: projectRoot, entry: entryPoints, output: { - path: path.resolve(projectRoot, appConfig.outDir), - publicPath: appConfig.deployUrl, + path: path.resolve(projectRoot, buildOptions.outputPath), + publicPath: buildOptions.deployUrl, filename: `[name]${hashFormat.chunk}.bundle.js`, sourceMapFilename: `[name]${hashFormat.chunk}.bundle.map`, chunkFilename: `[id]${hashFormat.chunk}.chunk.js` @@ -141,13 +135,13 @@ export function getWebpackCommonConfig( plugins: [ new HtmlWebpackPlugin({ template: path.resolve(appRoot, appConfig.index), - filename: path.resolve(appConfig.outDir, appConfig.index), + filename: path.resolve(buildOptions.outputPath, appConfig.index), chunksSortMode: packageChunkSort(appConfig), excludeChunks: lazyChunks, xhtml: true }), new BaseHrefWebpackPlugin({ - baseHref: baseHref + baseHref: buildOptions.baseHref }), new webpack.optimize.CommonsChunkPlugin({ minChunks: Infinity, diff --git a/packages/angular-cli/models/webpack-configs/development.ts b/packages/angular-cli/models/webpack-configs/development.ts new file mode 100644 index 000000000000..2f4e580bf762 --- /dev/null +++ b/packages/angular-cli/models/webpack-configs/development.ts @@ -0,0 +1,5 @@ +import { WebpackConfigOptions } from '../webpack-config'; + +export const getDevConfig = function (wco: WebpackConfigOptions) { + return {}; +}; diff --git a/packages/angular-cli/models/webpack-configs/index.ts b/packages/angular-cli/models/webpack-configs/index.ts new file mode 100644 index 000000000000..dc3c96bac237 --- /dev/null +++ b/packages/angular-cli/models/webpack-configs/index.ts @@ -0,0 +1,6 @@ +export * from './common'; +export * from './development'; +export * from './production'; +export * from './styles'; +export * from './typescript'; +export * from './utils'; diff --git a/packages/angular-cli/models/webpack-configs/production.ts b/packages/angular-cli/models/webpack-configs/production.ts new file mode 100644 index 000000000000..0c5a8542c122 --- /dev/null +++ b/packages/angular-cli/models/webpack-configs/production.ts @@ -0,0 +1,30 @@ +import * as path from 'path'; +import * as webpack from 'webpack'; +import { CompressionPlugin } from '../../lib/webpack/compression-plugin'; +import { WebpackConfigOptions } from '../webpack-config'; + + +export const getProdConfig = function (wco: WebpackConfigOptions) { + const { projectRoot, buildOptions, appConfig } = wco; + const appRoot = path.resolve(projectRoot, appConfig.root); + + return { + plugins: [ + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production') + }), + new webpack.LoaderOptionsPlugin({ minimize: true }), + new webpack.optimize.UglifyJsPlugin({ + mangle: { screw_ie8: true }, + compress: { screw_ie8: true, warnings: buildOptions.verbose }, + sourceMap: buildOptions.sourcemap + }), + new CompressionPlugin({ + asset: '[path].gz[query]', + algorithm: 'gzip', + test: /\.js$|\.html$|\.css$/, + threshold: 10240 + }) + ] + }; +}; diff --git a/packages/angular-cli/models/webpack-build-styles.ts b/packages/angular-cli/models/webpack-configs/styles.ts similarity index 81% rename from packages/angular-cli/models/webpack-build-styles.ts rename to packages/angular-cli/models/webpack-configs/styles.ts index a41ea51464dd..ff7c32c57ce8 100644 --- a/packages/angular-cli/models/webpack-build-styles.ts +++ b/packages/angular-cli/models/webpack-configs/styles.ts @@ -2,8 +2,9 @@ import * as webpack from 'webpack'; import * as path from 'path'; import { SuppressExtractedTextChunksWebpackPlugin -} from '../plugins/suppress-entry-chunks-webpack-plugin'; -import { extraEntryParser, getOutputHashFormat } from './webpack-build-utils'; +} from '../../plugins/suppress-entry-chunks-webpack-plugin'; +import { extraEntryParser, getOutputHashFormat } from './utils'; +import { WebpackConfigOptions } from '../webpack-config'; const postcssDiscardComments = require('postcss-discard-comments'); const autoprefixer = require('autoprefixer'); @@ -25,14 +26,8 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin'); * require('sass-loader') */ -export function getWebpackStylesConfig( - projectRoot: string, - appConfig: any, - target: string, - sourcemap: boolean, - outputHashing: string, - extractCss: boolean, -) { +export function getStylesConfig(wco: WebpackConfigOptions) { + const { projectRoot, buildOptions, appConfig } = wco; const appRoot = path.resolve(projectRoot, appConfig.root); const entryPoints: { [key: string]: string[] } = {}; @@ -40,10 +35,12 @@ export function getWebpackStylesConfig( const extraPlugins: any[] = []; // discard comments in production - const extraPostCssPlugins = target === 'production' ? [postcssDiscardComments] : []; + const extraPostCssPlugins = buildOptions.target === 'production' + ? [postcssDiscardComments] + : []; // determine hashing format - const hashFormat = getOutputHashFormat(outputHashing); + const hashFormat = getOutputHashFormat(buildOptions.outputHashing); // use includePaths from appConfig const includePaths: string [] = []; @@ -77,7 +74,7 @@ export function getWebpackStylesConfig( // stylus-loader doesn't support webpack.LoaderOptionsPlugin properly, // so we need to add options in it's query { test: /\.styl$/, loaders: [`stylus-loader?${JSON.stringify({ - sourceMap: sourcemap, + sourceMap: buildOptions.sourcemap, paths: includePaths })}`] } ]; @@ -103,7 +100,7 @@ export function getWebpackStylesConfig( } // supress empty .js files in css only entry points - if (extractCss) { + if (buildOptions.extractCss) { extraPlugins.push(new SuppressExtractedTextChunksWebpackPlugin()); } @@ -114,15 +111,15 @@ export function getWebpackStylesConfig( // extract global css from js files into own css file new ExtractTextPlugin({ filename: `[name]${hashFormat.extract}.bundle.css`, - disable: !extractCss + disable: !buildOptions.extractCss }), new webpack.LoaderOptionsPlugin({ options: { postcss: [autoprefixer()].concat(extraPostCssPlugins), - cssLoader: { sourceMap: sourcemap }, - sassLoader: { sourceMap: sourcemap, includePaths }, + cssLoader: { sourceMap: buildOptions.sourcemap }, + sassLoader: { sourceMap: buildOptions.sourcemap, includePaths }, // less-loader doesn't support paths - lessLoader: { sourceMap: sourcemap }, + lessLoader: { sourceMap: buildOptions.sourcemap }, // stylus-loader doesn't support LoaderOptionsPlugin properly, options in query instead // context needed as a workaround https://github.com/jtangelder/sass-loader/issues/285 context: projectRoot, diff --git a/packages/angular-cli/models/webpack-build-test.js b/packages/angular-cli/models/webpack-configs/test.js similarity index 96% rename from packages/angular-cli/models/webpack-build-test.js rename to packages/angular-cli/models/webpack-configs/test.js index a1b1dbe98ecd..9905f11a695d 100644 --- a/packages/angular-cli/models/webpack-build-test.js +++ b/packages/angular-cli/models/webpack-configs/test.js @@ -26,7 +26,7 @@ const ProgressPlugin = require('webpack/lib/ProgressPlugin'); */ -const getWebpackTestConfig = function (projectRoot, environment, appConfig, testConfig) { +const getTestConfig = function (projectRoot, environment, appConfig, testConfig) { const appRoot = path.resolve(projectRoot, appConfig.root); const extraRules = []; @@ -151,4 +151,4 @@ const getWebpackTestConfig = function (projectRoot, environment, appConfig, test }; } -module.exports.getWebpackTestConfig = getWebpackTestConfig; +module.exports.getTestConfig = getTestConfig; diff --git a/packages/angular-cli/models/webpack-build-typescript.ts b/packages/angular-cli/models/webpack-configs/typescript.ts similarity index 76% rename from packages/angular-cli/models/webpack-build-typescript.ts rename to packages/angular-cli/models/webpack-configs/typescript.ts index 0e7ba7807aa6..cba5a918b719 100644 --- a/packages/angular-cli/models/webpack-build-typescript.ts +++ b/packages/angular-cli/models/webpack-configs/typescript.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import {AotPlugin} from '@ngtools/webpack'; +import { WebpackConfigOptions } from '../webpack-config'; const g: any = global; @@ -8,7 +9,8 @@ const webpackLoader: string = g['angularCliIsLocal'] : '@ngtools/webpack'; -export const getWebpackNonAotConfigPartial = function(projectRoot: string, appConfig: any) { +export const getNonAotConfig = function(wco: WebpackConfigOptions) { + const { projectRoot, appConfig } = wco; let exclude = [ '**/*.spec.ts' ]; if (appConfig.test) { exclude.push(path.join(projectRoot, appConfig.root, appConfig.test)); }; return { @@ -32,8 +34,8 @@ export const getWebpackNonAotConfigPartial = function(projectRoot: string, appCo }; }; -export const getWebpackAotConfigPartial = function(projectRoot: string, appConfig: any, - i18nFile: string, i18nFormat: string, locale: string) { +export const getAotConfig = function(wco: WebpackConfigOptions) { + const { projectRoot, buildOptions, appConfig } = wco; let exclude = [ '**/*.spec.ts' ]; if (appConfig.test) { exclude.push(path.join(projectRoot, appConfig.root, appConfig.test)); }; return { @@ -50,9 +52,9 @@ export const getWebpackAotConfigPartial = function(projectRoot: string, appConfi new AotPlugin({ tsConfigPath: path.resolve(projectRoot, appConfig.root, appConfig.tsconfig), mainPath: path.join(projectRoot, appConfig.root, appConfig.main), - i18nFile: i18nFile, - i18nFormat: i18nFormat, - locale: locale, + i18nFile: buildOptions.i18nFile, + i18nFormat: buildOptions.i18nFormat, + locale: buildOptions.locale, exclude: exclude }) ] diff --git a/packages/angular-cli/models/webpack-build-utils.ts b/packages/angular-cli/models/webpack-configs/utils.ts similarity index 100% rename from packages/angular-cli/models/webpack-build-utils.ts rename to packages/angular-cli/models/webpack-configs/utils.ts diff --git a/packages/angular-cli/plugins/karma.js b/packages/angular-cli/plugins/karma.js index a53c2e732181..465416d3114e 100644 --- a/packages/angular-cli/plugins/karma.js +++ b/packages/angular-cli/plugins/karma.js @@ -1,7 +1,7 @@ const path = require('path'); const fs = require('fs'); -const getWebpackTestConfig = require('../models/webpack-build-test').getWebpackTestConfig; +const getTestConfig = require('../models/webpack-configs/test').getTestConfig; const CliConfig = require('../models/config').CliConfig; const init = (config) => { @@ -42,7 +42,7 @@ const init = (config) => { } // add webpack config - const webpackConfig = getWebpackTestConfig(config.basePath, environment, appConfig, testConfig); + const webpackConfig = getTestConfig(config.basePath, environment, appConfig, testConfig); const webpackMiddlewareConfig = { noInfo: true, // Hide webpack output because its noisy. stats: { // Also prevent chunk and module display output, cleaner look. Only emit errors. diff --git a/packages/angular-cli/tasks/build-webpack.ts b/packages/angular-cli/tasks/build.ts similarity index 53% rename from packages/angular-cli/tasks/build-webpack.ts rename to packages/angular-cli/tasks/build.ts index b086ae2e48a6..699f448509f9 100644 --- a/packages/angular-cli/tasks/build-webpack.ts +++ b/packages/angular-cli/tasks/build.ts @@ -2,41 +2,22 @@ import * as rimraf from 'rimraf'; import * as path from 'path'; const Task = require('../ember-cli/lib/models/task'); import * as webpack from 'webpack'; -import { BuildOptions } from '../commands/build'; +import { BuildTaskOptions } from '../commands/build'; import { NgCliWebpackConfig } from '../models/webpack-config'; -import { getWebpackStatsConfig } from '../models/'; +import { getWebpackStatsConfig } from '../models/webpack-configs/utils'; import { CliConfig } from '../models/config'; export default Task.extend({ - run: function (runTaskOptions: BuildOptions) { + run: function (runTaskOptions: BuildTaskOptions) { const project = this.cliProject; - const outputDir = runTaskOptions.outputPath || CliConfig.fromProject().config.apps[0].outDir; - const deployUrl = runTaskOptions.deployUrl || - CliConfig.fromProject().config.apps[0].deployUrl; - rimraf.sync(path.resolve(project.root, outputDir)); - const config = new NgCliWebpackConfig( - project, - runTaskOptions.target, - runTaskOptions.environment, - outputDir, - runTaskOptions.baseHref, - runTaskOptions.i18nFile, - runTaskOptions.i18nFormat, - runTaskOptions.locale, - runTaskOptions.aot, - runTaskOptions.sourcemap, - runTaskOptions.vendorChunk, - runTaskOptions.verbose, - runTaskOptions.progress, - deployUrl, - runTaskOptions.outputHashing, - runTaskOptions.extractCss, - ).config; + const outputPath = runTaskOptions.outputPath || CliConfig.fromProject().config.apps[0].outDir; + rimraf.sync(path.resolve(project.root, outputPath)); - const webpackCompiler = webpack(config); + const webpackConfig = new NgCliWebpackConfig(runTaskOptions).config; + const webpackCompiler = webpack(webpackConfig); const statsConfig = getWebpackStatsConfig(runTaskOptions.verbose); return new Promise((resolve, reject) => { diff --git a/packages/angular-cli/tasks/serve-webpack.ts b/packages/angular-cli/tasks/serve.ts similarity index 86% rename from packages/angular-cli/tasks/serve-webpack.ts rename to packages/angular-cli/tasks/serve.ts index 57b714c6b887..a1e862a398e2 100644 --- a/packages/angular-cli/tasks/serve-webpack.ts +++ b/packages/angular-cli/tasks/serve.ts @@ -5,7 +5,7 @@ const SilentError = require('silent-error'); const Task = require('../ember-cli/lib/models/task'); import * as webpack from 'webpack'; const WebpackDevServer = require('webpack-dev-server'); -import { getWebpackStatsConfig } from '../models/'; +import { getWebpackStatsConfig } from '../models/webpack-configs/utils'; import { NgCliWebpackConfig } from '../models/webpack-config'; import { ServeTaskOptions } from '../commands/serve'; import { CliConfig } from '../models/config'; @@ -22,24 +22,14 @@ export default Task.extend({ const projectConfig = CliConfig.fromProject().config; const appConfig = projectConfig.apps[0]; - let config = new NgCliWebpackConfig( - this.project, - serveTaskOptions.target, - serveTaskOptions.environment, - undefined, - undefined, - serveTaskOptions.i18nFile, - serveTaskOptions.i18nFormat, - serveTaskOptions.locale, - serveTaskOptions.aot, - serveTaskOptions.sourcemap, - serveTaskOptions.vendorChunk, - serveTaskOptions.verbose, - serveTaskOptions.progress, - undefined, - undefined, - serveTaskOptions.extractCss - ).config; + const serveDefaults = { + // default deployUrl to '' on serve to prevent the default from angular-cli.json + deployUrl: '' + }; + + serveTaskOptions = Object.assign({}, serveDefaults, serveTaskOptions); + + let webpackConfig = new NgCliWebpackConfig(serveTaskOptions).config; // This allows for live reload of page when changes are made to repo. // https://webpack.github.io/docs/webpack-dev-server.html#inline-mode @@ -57,8 +47,8 @@ export default Task.extend({ ui.writeLine(` See ${chalk.blue(webpackHmrLink)}`); ui.writeLine(' for information on working with HMR for Webpack.'); entryPoints.push('webpack/hot/dev-server'); - config.plugins.push(new webpack.HotModuleReplacementPlugin()); - config.plugins.push(new webpack.NamedModulesPlugin()); + webpackConfig.plugins.push(new webpack.HotModuleReplacementPlugin()); + webpackConfig.plugins.push(new webpack.NamedModulesPlugin()); if (serveTaskOptions.extractCss) { ui.writeLine(oneLine` ${chalk.yellow('NOTICE')} (HMR) does not allow for CSS hot reload when used @@ -66,9 +56,9 @@ export default Task.extend({ `); } } - if (!config.entry.main) { config.entry.main = []; } - config.entry.main.unshift(...entryPoints); - webpackCompiler = webpack(config); + if (!webpackConfig.entry.main) { webpackConfig.entry.main = []; } + webpackConfig.entry.main.unshift(...entryPoints); + webpackCompiler = webpack(webpackConfig); const statsConfig = getWebpackStatsConfig(serveTaskOptions.verbose); diff --git a/tests/e2e/tests/build/deploy-url.ts b/tests/e2e/tests/build/deploy-url.ts index 61c94d949a6f..7fcff1ff397d 100644 --- a/tests/e2e/tests/build/deploy-url.ts +++ b/tests/e2e/tests/build/deploy-url.ts @@ -1,15 +1,31 @@ -import {ng} from '../../utils/process'; -import {expectFileToMatch} from '../../utils/fs'; -import {updateJsonFile} from '../../utils/project'; +import { ng } from '../../utils/process'; +import { expectFileToMatch, writeMultipleFiles } from '../../utils/fs'; +import { updateJsonFile } from '../../utils/project'; +import { stripIndents } from 'common-tags'; -export default function() { - return ng('build', '-d', 'deployUrl/') +export default function () { + return Promise.resolve() + .then(() => writeMultipleFiles({ + 'src/styles.css': 'div { background: url("./assets/more.svg"); }', + 'src/assets/more.svg': stripIndents` + + + + `})) + .then(() => ng('build', '--deploy-url=deployUrl/', '--extract-css')) .then(() => expectFileToMatch('dist/index.html', 'deployUrl/main.bundle.js')) + // verify --deploy-url isn't applied to extracted css urls + .then(() => expectFileToMatch('dist/styles.bundle.css', 'url\(more.svg\)')) + // verify option also works in config .then(() => updateJsonFile('angular-cli.json', configJson => { const app = configJson['apps'][0]; app['deployUrl'] = 'config-deployUrl/'; })) .then(() => ng('build')) - .then(() => expectFileToMatch('dist/index.html', 'config-deployUrl/main.bundle.js')); + .then(() => expectFileToMatch('dist/index.html', 'config-deployUrl/main.bundle.js')) + // verify --deploy-url is applied to non-extracted css urls + .then(() => ng('build', '--deploy-url=deployUrl/', '--extract-css=false')) + .then(() => expectFileToMatch('dist/styles.bundle.js', + '__webpack_require__.p \+ \"more.svg\"')); } diff --git a/tests/e2e/tests/build/output-dir.ts b/tests/e2e/tests/build/output-dir.ts index 0e34c8450fb9..7e56e1d5f2f5 100644 --- a/tests/e2e/tests/build/output-dir.ts +++ b/tests/e2e/tests/build/output-dir.ts @@ -6,7 +6,7 @@ import {updateJsonFile} from '../../utils/project'; export default function() { - return ng('build', '-o', './build-output') + return ng('build', '-op', './build-output') .then(() => expectFileToExist('./build-output/index.html')) .then(() => expectFileToExist('./build-output/main.bundle.js')) .then(() => expectToFail(expectGitToBeClean)) diff --git a/tests/e2e/tests/build/prod-build.ts b/tests/e2e/tests/build/prod-build.ts index 8b2dd6fb0a66..f56addd7b7c5 100644 --- a/tests/e2e/tests/build/prod-build.ts +++ b/tests/e2e/tests/build/prod-build.ts @@ -12,6 +12,9 @@ export default function() { // Check for cache busting hash script src .then(() => expectFileToMatch('dist/index.html', /main\.[0-9a-f]{20}\.bundle\.js/)) .then(() => expectFileToMatch('dist/index.html', /styles\.[0-9a-f]{20}\.bundle\.css/)) + // Defaults to AoT + .then(() => expectFileToMatch('dist/main.bundle.js', + /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//)) // Check that the process didn't change local files. .then(() => expectGitToBeClean()); diff --git a/tests/e2e/tests/build/scripts-array.ts b/tests/e2e/tests/build/scripts-array.ts index 57cabfa08a2d..5b2e844848ce 100644 --- a/tests/e2e/tests/build/scripts-array.ts +++ b/tests/e2e/tests/build/scripts-array.ts @@ -30,7 +30,7 @@ export default function () { ]; app['styles'] = [{ input: 'common-entry-style.css', output: 'common-entry' }]; })) - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) // files were created successfully .then(() => expectFileToMatch('dist/scripts.bundle.js', 'string-script')) .then(() => expectFileToMatch('dist/scripts.bundle.js', 'input-script')) diff --git a/tests/e2e/tests/build/styles/css.ts b/tests/e2e/tests/build/styles/css.ts index f1abac5b37f0..169cb4e23078 100644 --- a/tests/e2e/tests/build/styles/css.ts +++ b/tests/e2e/tests/build/styles/css.ts @@ -21,7 +21,7 @@ export default function () { } } `}) - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) .then(() => expectFileToMatch('dist/styles.bundle.css', /body\s*{\s*background-color: blue;\s*}/)) .then(() => expectFileToMatch('dist/styles.bundle.css', diff --git a/tests/e2e/tests/build/styles/extract-css.ts b/tests/e2e/tests/build/styles/extract-css.ts index 92c614999f22..53fac5a795e1 100644 --- a/tests/e2e/tests/build/styles/extract-css.ts +++ b/tests/e2e/tests/build/styles/extract-css.ts @@ -1,21 +1,79 @@ -import { writeMultipleFiles, expectFileToMatch } from '../../../utils/fs'; +import { + writeMultipleFiles, + expectFileToExist, + expectFileToMatch +} from '../../../utils/fs'; import { ng } from '../../../utils/process'; -import { stripIndents } from 'common-tags'; +import { updateJsonFile } from '../../../utils/project'; +import { expectToFail } from '../../../utils/utils'; +import { oneLineTrim } from 'common-tags'; export default function () { - return writeMultipleFiles({ - 'src/styles.css': stripIndents` - div { background: url("./assets/more.svg"); } - `, - 'src/assets/more.svg': stripIndents` - - - - `}) + return Promise.resolve() + .then(() => writeMultipleFiles({ + 'src/string-style.css': '.string-style { color: red }', + 'src/input-style.css': '.input-style { color: red }', + 'src/lazy-style.css': '.lazy-style { color: red }', + 'src/pre-rename-style.css': '.pre-rename-style { color: red }', + 'src/pre-rename-lazy-style.css': '.pre-rename-lazy-style { color: red }', + 'src/common-entry-style.css': '.common-entry-style { color: red }', + 'src/common-entry-script.js': 'console.log(\'common-entry-script\');' + })) + .then(() => updateJsonFile('angular-cli.json', configJson => { + const app = configJson['apps'][0]; + app['styles'] = [ + 'string-style.css', + { input: 'input-style.css' }, + { input: 'lazy-style.css', lazy: true }, + { input: 'pre-rename-style.css', output: 'renamed-style' }, + { input: 'pre-rename-lazy-style.css', output: 'renamed-lazy-style', lazy: true }, + { input: 'common-entry-style.css', output: 'common-entry' } + ]; + app['scripts'] = [{ input: 'common-entry-script.js', output: 'common-entry' }]; + })) .then(() => ng('build', '--extract-css')) - .then(() => expectFileToMatch('dist/styles.bundle.css', - /div\s*{\s*background:\s*url\(more.svg\);\s*}/)) - .then(() => ng('build', '--extract-css', '--deploy-url=client/')) - .then(() => expectFileToMatch('dist/styles.bundle.css', - /div\s*{\s*background:\s*url\(more.svg\);\s*}/)); + // files were created successfully + .then(() => expectFileToMatch('dist/styles.bundle.css', '.string-style')) + .then(() => expectFileToMatch('dist/styles.bundle.css', '.input-style')) + .then(() => expectFileToMatch('dist/lazy-style.bundle.css', '.lazy-style')) + .then(() => expectFileToMatch('dist/renamed-style.bundle.css', '.pre-rename-style')) + .then(() => expectFileToMatch('dist/renamed-lazy-style.bundle.css', '.pre-rename-lazy-style')) + .then(() => expectFileToMatch('dist/common-entry.bundle.css', '.common-entry-style')) + .then(() => expectFileToMatch('dist/common-entry.bundle.js', 'common-entry-script')) + // there are no js entry points for css only bundles + .then(() => expectToFail(() => expectFileToExist('dist/style.bundle.js'))) + .then(() => expectToFail(() => expectFileToExist('dist/lazy-style.bundle.js'))) + .then(() => expectToFail(() => expectFileToExist('dist/renamed-style.bundle.js'))) + .then(() => expectToFail(() => expectFileToExist('dist/renamed-lazy-style.bundle.js'))) + // index.html lists the right bundles + .then(() => expectFileToMatch('dist/index.html', oneLineTrim` + + + + `)) + .then(() => expectFileToMatch('dist/index.html', oneLineTrim` + + + + + `)) + // also check when css isn't extracted + .then(() => ng('build', '--no-extract-css')) + // files were created successfully + .then(() => expectFileToMatch('dist/styles.bundle.js', '.string-style')) + .then(() => expectFileToMatch('dist/styles.bundle.js', '.input-style')) + .then(() => expectFileToMatch('dist/lazy-style.bundle.js', '.lazy-style')) + .then(() => expectFileToMatch('dist/renamed-style.bundle.js', '.pre-rename-style')) + .then(() => expectFileToMatch('dist/renamed-lazy-style.bundle.js', '.pre-rename-lazy-style')) + .then(() => expectFileToMatch('dist/common-entry.bundle.js', '.common-entry-style')) + .then(() => expectFileToMatch('dist/common-entry.bundle.js', 'common-entry-script')) + // index.html lists the right bundles + .then(() => expectFileToMatch('dist/index.html', oneLineTrim` + + + + + + + `)); } diff --git a/tests/e2e/tests/build/styles/include-paths.ts b/tests/e2e/tests/build/styles/include-paths.ts index 5515ad09308d..4f4913e2f328 100644 --- a/tests/e2e/tests/build/styles/include-paths.ts +++ b/tests/e2e/tests/build/styles/include-paths.ts @@ -47,12 +47,12 @@ export default function () { }; })) // files were created successfully - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) .then(() => expectFileToMatch('dist/styles.bundle.css', /h1\s*{\s*color: red;\s*}/)) .then(() => expectFileToMatch('dist/main.bundle.js', /h2.*{.*color: red;.*}/)) .then(() => expectFileToMatch('dist/styles.bundle.css', /h3\s*{\s*color: #008000;\s*}/)) .then(() => expectFileToMatch('dist/main.bundle.js', /h4.*{.*color: #008000;.*}/)) - .then(() => ng('build', '--aot')) + .then(() => ng('build', '--extract-css', '--aot')) .then(() => expectFileToMatch('dist/styles.bundle.css', /h1\s*{\s*color: red;\s*}/)) .then(() => expectFileToMatch('dist/main.bundle.js', /h2.*{.*color: red;.*}/)) .then(() => expectFileToMatch('dist/styles.bundle.css', /h3\s*{\s*color: #008000;\s*}/)) diff --git a/tests/e2e/tests/build/styles/less.ts b/tests/e2e/tests/build/styles/less.ts index edb197b9997a..c3644469d8b6 100644 --- a/tests/e2e/tests/build/styles/less.ts +++ b/tests/e2e/tests/build/styles/less.ts @@ -31,7 +31,7 @@ export default function () { })) .then(() => replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.less')) - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) .then(() => expectFileToMatch('dist/styles.bundle.css', /body\s*{\s*background-color: blue;\s*}/)) .then(() => expectFileToMatch('dist/styles.bundle.css', diff --git a/tests/e2e/tests/build/styles/postcss.ts b/tests/e2e/tests/build/styles/postcss.ts index 809ce7919f71..14636a95c7de 100644 --- a/tests/e2e/tests/build/styles/postcss.ts +++ b/tests/e2e/tests/build/styles/postcss.ts @@ -10,7 +10,7 @@ export default function () { div { flex: 1 } `) // uses autoprefixer plugin for all builds - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) .then(() => expectFileToMatch('dist/styles.bundle.css', stripIndents` /* normal-comment */ /*! important-comment */ diff --git a/tests/e2e/tests/build/styles/scss.ts b/tests/e2e/tests/build/styles/scss.ts index 9ba76e715617..1a1f9dbe6e43 100644 --- a/tests/e2e/tests/build/styles/scss.ts +++ b/tests/e2e/tests/build/styles/scss.ts @@ -31,7 +31,7 @@ export default function () { })) .then(() => replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.scss')) - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) .then(() => expectFileToMatch('dist/styles.bundle.css', /body\s*{\s*background-color: blue;\s*}/)) .then(() => expectFileToMatch('dist/styles.bundle.css', diff --git a/tests/e2e/tests/build/styles/styles-array.ts b/tests/e2e/tests/build/styles/styles-array.ts index 7e568ff59ec0..efa6bf531f3f 100644 --- a/tests/e2e/tests/build/styles/styles-array.ts +++ b/tests/e2e/tests/build/styles/styles-array.ts @@ -1,6 +1,5 @@ import { writeMultipleFiles, - expectFileToExist, expectFileToMatch } from '../../../utils/fs'; import { ng } from '../../../utils/process'; @@ -30,7 +29,7 @@ export default function () { ]; app['scripts'] = [{ input: 'common-entry-script.js', output: 'common-entry' }]; })) - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) // files were created successfully .then(() => expectFileToMatch('dist/styles.bundle.css', '.string-style')) .then(() => expectFileToMatch('dist/styles.bundle.css', '.input-style')) @@ -39,11 +38,6 @@ export default function () { .then(() => expectFileToMatch('dist/renamed-lazy-style.bundle.css', '.pre-rename-lazy-style')) .then(() => expectFileToMatch('dist/common-entry.bundle.css', '.common-entry-style')) .then(() => expectFileToMatch('dist/common-entry.bundle.js', 'common-entry-script')) - // there are no js entry points for css only bundles - .then(() => expectToFail(() => expectFileToExist('dist/style.bundle.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/lazy-style.bundle.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/renamed-style.bundle.js'))) - .then(() => expectToFail(() => expectFileToExist('dist/renamed-lazy-style.bundle.js'))) // index.html lists the right bundles .then(() => expectFileToMatch('dist/index.html', oneLineTrim` @@ -56,11 +50,5 @@ export default function () { - `)) - .then(() => ng('build', '--no-extract-css')) - // js files still exist when not extracting css - .then(() => expectFileToExist('dist/styles.bundle.js')) - .then(() => expectFileToExist('dist/lazy-style.bundle.js')) - .then(() => expectFileToExist('dist/renamed-style.bundle.js')) - .then(() => expectFileToExist('dist/renamed-lazy-style.bundle.js')); + `)); } diff --git a/tests/e2e/tests/build/styles/stylus.ts b/tests/e2e/tests/build/styles/stylus.ts index b318dfa73d24..d971f1dfd71d 100644 --- a/tests/e2e/tests/build/styles/stylus.ts +++ b/tests/e2e/tests/build/styles/stylus.ts @@ -31,7 +31,7 @@ export default function () { })) .then(() => replaceInFile('src/app/app.component.ts', './app.component.css', './app.component.styl')) - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) .then(() => expectFileToMatch('dist/styles.bundle.css', /body\s*{\s*background-color: #00f;\s*}/)) .then(() => expectFileToMatch('dist/styles.bundle.css', diff --git a/tests/e2e/tests/third-party/bootstrap.ts b/tests/e2e/tests/third-party/bootstrap.ts index f99fc436b339..b8a8de5384a8 100644 --- a/tests/e2e/tests/third-party/bootstrap.ts +++ b/tests/e2e/tests/third-party/bootstrap.ts @@ -16,7 +16,7 @@ export default function() { '../node_modules/bootstrap/dist/js/bootstrap.js' ); })) - .then(() => ng('build')) + .then(() => ng('build', '--extract-css')) .then(() => expectFileToMatch('dist/scripts.bundle.js', '* jQuery JavaScript')) .then(() => expectFileToMatch('dist/scripts.bundle.js', '/*! tether ')) .then(() => expectFileToMatch('dist/scripts.bundle.js', '* Bootstrap')) From db656b262ba31dea555e3225f506dbd4a146ca74 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Fri, 20 Jan 2017 22:56:09 +0000 Subject: [PATCH 2/7] correctly match main bundle --- tests/e2e/tests/build/prod-build.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/e2e/tests/build/prod-build.ts b/tests/e2e/tests/build/prod-build.ts index f56addd7b7c5..8866b220edb1 100644 --- a/tests/e2e/tests/build/prod-build.ts +++ b/tests/e2e/tests/build/prod-build.ts @@ -1,4 +1,5 @@ import {join} from 'path'; +import {readdirSync} from 'fs'; import {expectFileToExist, expectFileToMatch} from '../../utils/fs'; import {ng} from '../../utils/process'; import {expectGitToBeClean} from '../../utils/git'; @@ -13,9 +14,10 @@ export default function() { .then(() => expectFileToMatch('dist/index.html', /main\.[0-9a-f]{20}\.bundle\.js/)) .then(() => expectFileToMatch('dist/index.html', /styles\.[0-9a-f]{20}\.bundle\.css/)) // Defaults to AoT - .then(() => expectFileToMatch('dist/main.bundle.js', - /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//)) - + .then(() => { + const main = readdirSync('./dist').find(name => !!name.match(/main.[a-z0-9]+\.bundle\.js/)); + expectFileToMatch(`dist/${main}`, /bootstrapModuleFactory.*\/\* AppModuleNgFactory \*\//); + }) // Check that the process didn't change local files. .then(() => expectGitToBeClean()); } From bb7a44435dc8401b3b22f7629a7bce367de4dd70 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Sun, 22 Jan 2017 20:16:50 +0000 Subject: [PATCH 3/7] fix package sort --- packages/angular-cli/utilities/package-chunk-sort.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/angular-cli/utilities/package-chunk-sort.ts b/packages/angular-cli/utilities/package-chunk-sort.ts index 14c044e87547..d4c5be21be88 100644 --- a/packages/angular-cli/utilities/package-chunk-sort.ts +++ b/packages/angular-cli/utilities/package-chunk-sort.ts @@ -1,4 +1,4 @@ -import { ExtraEntry, extraEntryParser } from '../models/webpack-build-utils'; +import { ExtraEntry, extraEntryParser } from '../models/webpack-configs/utils'; // Sort chunks according to a predefined order: // inline, polyfills, all scripts, all styles, vendor, main From b780224e133e5e05f461ed13e3475dfc31426117 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 24 Jan 2017 12:00:21 +0000 Subject: [PATCH 4/7] fix extract test --- tests/e2e/tests/build/styles/extract-css.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/tests/build/styles/extract-css.ts b/tests/e2e/tests/build/styles/extract-css.ts index 53fac5a795e1..b0d5a80f81c2 100644 --- a/tests/e2e/tests/build/styles/extract-css.ts +++ b/tests/e2e/tests/build/styles/extract-css.ts @@ -47,9 +47,9 @@ export default function () { .then(() => expectToFail(() => expectFileToExist('dist/renamed-lazy-style.bundle.js'))) // index.html lists the right bundles .then(() => expectFileToMatch('dist/index.html', oneLineTrim` - - + + `)) .then(() => expectFileToMatch('dist/index.html', oneLineTrim` From 6401b2c26438a9c0f95446bcda93dd5f125b08de Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 24 Jan 2017 15:48:28 +0000 Subject: [PATCH 5/7] change output path flag in docs --- docs/documentation/build.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/build.md b/docs/documentation/build.md index d2b5fefc142a..7f532b1d5622 100644 --- a/docs/documentation/build.md +++ b/docs/documentation/build.md @@ -73,7 +73,7 @@ or `ng serve --prod` will also make use of uglifying and tree-shaking functional `--dev` flag to set build target and environment to development -`--output-path` (`-o`) path where output will be placed +`--output-path` (`-po`) path where output will be placed `--output-hashing` define the output filename cache-busting hashing mode From abddb86823296b8d81d6da8cb4711e9ec6307e28 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 24 Jan 2017 16:07:12 +0000 Subject: [PATCH 6/7] fix script order again arghhhh --- tests/e2e/tests/build/styles/extract-css.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/e2e/tests/build/styles/extract-css.ts b/tests/e2e/tests/build/styles/extract-css.ts index b0d5a80f81c2..ff08c2aef1de 100644 --- a/tests/e2e/tests/build/styles/extract-css.ts +++ b/tests/e2e/tests/build/styles/extract-css.ts @@ -53,8 +53,9 @@ export default function () { `)) .then(() => expectFileToMatch('dist/index.html', oneLineTrim` - + + `)) // also check when css isn't extracted @@ -70,10 +71,11 @@ export default function () { // index.html lists the right bundles .then(() => expectFileToMatch('dist/index.html', oneLineTrim` - + + + - `)); } From 62f8f98ca2b7a147a1e2baadb82deb528be10241 Mon Sep 17 00:00:00 2001 From: Filipe Silva Date: Tue, 24 Jan 2017 17:16:09 +0000 Subject: [PATCH 7/7] correct build and serve options --- docs/documentation/build.md | 18 +++++++++++----- docs/documentation/serve.md | 42 +++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/docs/documentation/build.md b/docs/documentation/build.md index 7f532b1d5622..d4d63bbd757b 100644 --- a/docs/documentation/build.md +++ b/docs/documentation/build.md @@ -65,6 +65,8 @@ All builds make use of bundling, and using the `--prod` flag in `ng build --pro or `ng serve --prod` will also make use of uglifying and tree-shaking functionality. ## Options +`--watch` (`-w`) flag to run builds when files change + `--target` (`-t`) define the build target `--environment` (`-e`) defines the build environment @@ -75,14 +77,20 @@ or `ng serve --prod` will also make use of uglifying and tree-shaking functional `--output-path` (`-po`) path where output will be placed -`--output-hashing` define the output filename cache-busting hashing mode +`--aot` flag whether to build using Ahead of Time compilation -`--watch` (`-w`) flag to run builds when files change +`--sourcemap` (`-sm`) output sourcemaps -`--surpress-sizes` flag to suppress sizes from build output +`--vendor-chunk` (`-vb`) use a separate bundle containing only vendor libraries `--base-href` (`-bh`) base url for the application being built -`--aot` flag whether to build using Ahead of Time compilation +`--deploy-url` (`-d`) url where files will be deployed + +`--verbose` (`-v`) adds more details to output logging + +`--progress` (`-pr`) log progress to the console while building -`--extract-css` extract css from global styles onto css files instead of js ones +`--extract-css` (`-ec`) extract css from global styles onto css files instead of js ones + +`--output-hashing` define the output filename cache-busting hashing mode diff --git a/docs/documentation/serve.md b/docs/documentation/serve.md index b69ee2c714dc..d1b836c0c186 100644 --- a/docs/documentation/serve.md +++ b/docs/documentation/serve.md @@ -8,11 +8,9 @@ ## Options `--port` (`-p`) port to serve the application on -`--host` (`-H`) +`--host` (`-H`) host where to listen -`--proxy-config` (`-pc`) - -`--watcher` (`-w`) provide a new watcher +`--proxy-config` (`-pc`) proxy configuration file `--live-reload` (`-lr`) flag to turn off live reloading @@ -24,18 +22,40 @@ `--live-reload-live-css` flag to live reload CSS -`--target` (`-t`, `-dev`, `-prod`) target environment - -`--environment` (`-e`) build environment - `--ssl` flag to turn on SSL `--ssl-key` path to the SSL key `--ssl-cert` path to the SSL cert -`--aot` flag to turn on Ahead of Time compilation - `--open` (`-o`) opens the app in the default browser -`--extract-css` extract css from global styles onto css files instead of js ones +`--hmr` use hot module reload + +`--target` (`-t`) define the build target + +`--environment` (`-e`) defines the build environment + +`--prod` flag to set build target and environment to production + +`--dev` flag to set build target and environment to development + +`--output-path` (`-po`) path where output will be placed + +`--aot` flag whether to build using Ahead of Time compilation + +`--sourcemap` (`-sm`) output sourcemaps + +`--vendor-chunk` (`-vb`) use a separate bundle containing only vendor libraries + +`--base-href` (`-bh`) base url for the application being built + +`--deploy-url` (`-d`) url where files will be deployed + +`--verbose` (`-v`) adds more details to output logging + +`--progress` (`-pr`) log progress to the console while building + +`--extract-css` (`-ec`) extract css from global styles onto css files instead of js ones + +`--output-hashing` define the output filename cache-busting hashing mode