diff --git a/docs/documentation/build.md b/docs/documentation/build.md index 09b453aeb8f3..7da7e30e7ae5 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.

+ +
+ compilation-target +

+ --compilation-target (aliases: -ct, -es5, -es2015) default value: ES5 +

+

+ Defines the compilation target (ES5 or ES2015). +

+
diff --git a/package-lock.json b/package-lock.json index 57bff5d23332..604cff1a2f8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -94,6 +94,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/copy-webpack-plugin/-/copy-webpack-plugin-4.0.0.tgz", "integrity": "sha1-v9qPXtOIzwnEJqvlER/Mt/PyXw0=", + "dev": true, "requires": { "@types/minimatch": "3.0.0", "@types/webpack": "3.0.9" @@ -173,7 +174,8 @@ "@types/minimatch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.0.tgz", - "integrity": "sha512-BnRgLPs1oy9gV8b4dAW8jilIa7Kpe3uNucAvv4RkY7Yt6rj9E6Kk4Qa/18LOSinA+XDBPdzx3MqYPEP5mw8rDA==" + "integrity": "sha512-BnRgLPs1oy9gV8b4dAW8jilIa7Kpe3uNucAvv4RkY7Yt6rj9E6Kk4Qa/18LOSinA+XDBPdzx3MqYPEP5mw8rDA==", + "dev": true }, "@types/minimist": { "version": "1.2.0", @@ -193,7 +195,8 @@ "@types/node": { "version": "6.0.87", "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.87.tgz", - "integrity": "sha512-Xo0pYENOBaGtJUhi50KH6gdBNQmZQQxAwBArsJpBd15ncoz+LZD5Ev14vuezcw62CsQ1q6bM++7jA6jfwaAbfQ==" + "integrity": "sha512-Xo0pYENOBaGtJUhi50KH6gdBNQmZQQxAwBArsJpBd15ncoz+LZD5Ev14vuezcw62CsQ1q6bM++7jA6jfwaAbfQ==", + "dev": true }, "@types/request": { "version": "2.0.1", @@ -224,17 +227,20 @@ "@types/source-map": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@types/source-map/-/source-map-0.5.1.tgz", - "integrity": "sha512-/GVAjL1Y8puvZab63n8tsuBiYwZt1bApMdx58/msQ9ID5T05ov+wm/ZV1DvYC/DKKEygpTJViqQvkh5Rhrl4CA==" + "integrity": "sha512-/GVAjL1Y8puvZab63n8tsuBiYwZt1bApMdx58/msQ9ID5T05ov+wm/ZV1DvYC/DKKEygpTJViqQvkh5Rhrl4CA==", + "dev": true }, "@types/tapable": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-0.2.3.tgz", - "integrity": "sha1-CIiw8gzH5Y4cIqGIi06WPu+qgQo=" + "integrity": "sha1-CIiw8gzH5Y4cIqGIi06WPu+qgQo=", + "dev": true }, "@types/uglify-js": { "version": "2.6.29", "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-2.6.29.tgz", "integrity": "sha512-BdFLCZW0GTl31AbqXSak8ss/MqEZ3DN2MH9rkAyGoTuzK7ifGUlX+u0nfbWeTsa7IPcZhtn8BlpYBXSV+vqGhQ==", + "dev": true, "requires": { "@types/source-map": "0.5.1" } @@ -243,6 +249,7 @@ "version": "3.0.9", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-3.0.9.tgz", "integrity": "sha512-xXqusBBKbYb8fA1jtE3iO75uRW1ejqGuH93V+6fhbfNY59ndKjfhftJVxcSaYAMDjmFTRBHy82d+513JKuHD5g==", + "dev": true, "requires": { "@types/node": "6.0.87", "@types/tapable": "0.2.3", @@ -7496,54 +7503,22 @@ "optional": true }, "uglifyjs-webpack-plugin": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", - "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.0.0-beta.1.tgz", + "integrity": "sha1-WVwU2Lhb7dcIq3pugRiU++DmmJA=", "requires": { "source-map": "0.5.6", - "uglify-js": "2.8.29", + "uglify-es": "3.0.28", "webpack-sources": "1.0.1" }, "dependencies": { - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" - }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", - "wordwrap": "0.0.2" - } - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "uglify-es": { + "version": "3.0.28", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.0.28.tgz", + "integrity": "sha512-xw1hJsSp361OO0Sq0XvNyTI2wfQ4eKNljfSYyeYX/dz9lKEDj+DK+A8CzB0NmoCwWX1MnEx9f16HlkKXyG65CQ==", "requires": { - "source-map": "0.5.6", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" - } - }, - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", - "window-size": "0.1.0" + "commander": "2.11.0", + "source-map": "0.5.6" } } } @@ -7868,11 +7843,64 @@ "has-flag": "2.0.0" } }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "requires": { + "source-map": "0.5.6", + "uglify-js": "2.8.29", + "webpack-sources": "1.0.1" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, "yargs": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", diff --git a/package.json b/package.json index 9f8fa594217c..99c0332bef82 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "stylus": "^0.54.5", "stylus-loader": "^3.0.1", "typescript": "~2.4.2", + "uglifyjs-webpack-plugin": "1.0.0-beta.1", "url-loader": "^0.5.7", "webpack": "~3.5.5", "webpack-concat-plugin": "1.4.0", diff --git a/packages/@angular/cli/commands/build.ts b/packages/@angular/cli/commands/build.ts index 96fcb7ceae2c..b249ba9f2821 100644 --- a/packages/@angular/cli/commands/build.ts +++ b/packages/@angular/cli/commands/build.ts @@ -9,7 +9,7 @@ const Command = require('../ember-cli/lib/models/command'); const config = CliConfig.fromProject() || CliConfig.fromGlobal(); const buildConfigDefaults = config.getPaths('defaults.build', [ 'sourcemaps', 'baseHref', 'progress', 'poll', 'deleteOutputPath', 'preserveSymlinks', - 'showCircularDependencies', 'commonChunk', 'namedChunks' + 'showCircularDependencies', 'commonChunk', 'namedChunks', 'compilationTarget' ]); // defaults for BuildOptions @@ -176,6 +176,18 @@ export const baseBuildCommandOptions: any = [ aliases: ['nc'], description: 'Use file name for lazy loaded chunks.', default: buildConfigDefaults['namedChunks'] + }, + { + name: 'compilation-target', + type: String, + default: buildConfigDefaults['compilationTarget'], + aliases: [ + 'ct', + { 'ES5': 'ES5' }, { 'ES2015': 'ES2015' }, + // Support lowercase in the alias. + { 'es5': 'ES5' }, { 'es2015': 'ES2015' }, + ], + description: 'Defines the compilation target (ES5 or ES2015).' } ]; diff --git a/packages/@angular/cli/lib/config/schema.json b/packages/@angular/cli/lib/config/schema.json index 9a890308dbab..6ebbfda4d53d 100644 --- a/packages/@angular/cli/lib/config/schema.json +++ b/packages/@angular/cli/lib/config/schema.json @@ -496,6 +496,11 @@ "namedChunks": { "description": "Use file name for lazy loaded chunks.", "type": "boolean" + }, + "compilationTarget": { + "description": "Defines the compilation target (ES5 or ES2015).", + "type": "string", + "default": "ES5" } } }, diff --git a/packages/@angular/cli/models/build-options.ts b/packages/@angular/cli/models/build-options.ts index 3bc2706a10ce..b57cace5665a 100644 --- a/packages/@angular/cli/models/build-options.ts +++ b/packages/@angular/cli/models/build-options.ts @@ -25,4 +25,5 @@ export interface BuildOptions { showCircularDependencies?: boolean; buildOptimizer?: boolean; namedChunks?: boolean; + compilationTarget?: 'ES5' | 'ES2015'; } diff --git a/packages/@angular/cli/models/webpack-config.ts b/packages/@angular/cli/models/webpack-config.ts index 35ed6d50042c..f4bde668b4ab 100644 --- a/packages/@angular/cli/models/webpack-config.ts +++ b/packages/@angular/cli/models/webpack-config.ts @@ -74,6 +74,13 @@ export class NgCliWebpackConfig { throw new Error("Invalid build target. Only 'development' and 'production' are available."); } + buildOptions.compilationTarget = buildOptions.compilationTarget || 'ES5'; + buildOptions.compilationTarget = + buildOptions.compilationTarget.toUpperCase() as 'ES5' | 'ES2015'; + if (buildOptions.compilationTarget !== 'ES5' && buildOptions.compilationTarget !== 'ES2015') { + throw new Error("Invalid compilation target. Only 'ES5' and 'ES2015' are available."); + } + if (buildOptions.buildOptimizer && !(buildOptions.aot || buildOptions.target === 'production')) { throw new Error('The `--build-optimizer` option cannot be used without `--aot`.'); diff --git a/packages/@angular/cli/models/webpack-configs/common.ts b/packages/@angular/cli/models/webpack-configs/common.ts index 82afcaab7cff..1194a499a640 100644 --- a/packages/@angular/cli/models/webpack-configs/common.ts +++ b/packages/@angular/cli/models/webpack-configs/common.ts @@ -153,6 +153,10 @@ export function getCommonConfig(wco: WebpackConfigOptions) { resolve: { extensions: ['.ts', '.js'], modules: ['node_modules', nodeModules], + mainFields: [ + ...(buildOptions.compilationTarget === 'ES2015' ? ['es2015'] : []), + 'browser', 'module', 'main' + ], symlinks: !buildOptions.preserveSymlinks }, resolveLoader: { diff --git a/packages/@angular/cli/models/webpack-configs/production.ts b/packages/@angular/cli/models/webpack-configs/production.ts index dc81100d2085..9aa27e85b02c 100644 --- a/packages/@angular/cli/models/webpack-configs/production.ts +++ b/packages/@angular/cli/models/webpack-configs/production.ts @@ -9,12 +9,14 @@ import { StaticAssetPlugin } from '../../plugins/static-asset'; import { GlobCopyWebpackPlugin } from '../../plugins/glob-copy-webpack-plugin'; import { WebpackConfigOptions } from '../webpack-config'; +const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); + export const getProdConfig = function (wco: WebpackConfigOptions) { const { projectRoot, buildOptions, appConfig } = wco; let extraPlugins: any[] = []; - let entryPoints: {[key: string]: string[]} = {}; + let entryPoints: { [key: string]: string[] } = {}; if (appConfig.serviceWorker) { const nodeModules = path.resolve(projectRoot, 'node_modules'); @@ -66,7 +68,7 @@ export const getProdConfig = function (wco: WebpackConfigOptions) { extraPlugins.push(new GlobCopyWebpackPlugin({ patterns: [ 'ngsw-manifest.json', - {glob: 'ngsw-manifest.json', input: path.resolve(projectRoot, appConfig.root), output: ''} + { glob: 'ngsw-manifest.json', input: path.resolve(projectRoot, appConfig.root), output: '' } ], globOptions: { cwd: projectRoot, @@ -99,7 +101,7 @@ export const getProdConfig = function (wco: WebpackConfigOptions) { })); } - const uglifyCompressOptions: any = { screw_ie8: true, warnings: buildOptions.verbose }; + const uglifyCompressOptions: any = {}; if (buildOptions.buildOptimizer) { // This plugin must be before webpack.optimize.UglifyJsPlugin. @@ -110,21 +112,43 @@ export const getProdConfig = function (wco: WebpackConfigOptions) { uglifyCompressOptions.passes = 3; } + if (buildOptions.compilationTarget === 'ES2015') { + extraPlugins.push(new UglifyJSPlugin({ + sourceMap: buildOptions.sourcemaps, + uglifyOptions: { + ecma: 6, + warnings: buildOptions.verbose, + ie8: false, + mangle: true, + compress: uglifyCompressOptions, + output: { + ascii_only: true, + comments: false + }, + } + })); + } else { + uglifyCompressOptions.screw_ie8 = true; + uglifyCompressOptions.warnings = buildOptions.verbose; + extraPlugins.push(new webpack.optimize.UglifyJsPlugin({ + mangle: { screw_ie8: true }, + compress: uglifyCompressOptions, + output: { ascii_only: true }, + sourceMap: buildOptions.sourcemaps, + comments: false + })); + + } + return { entry: entryPoints, - plugins: extraPlugins.concat([ + plugins: [ new webpack.EnvironmentPlugin({ 'NODE_ENV': 'production' }), new webpack.HashedModuleIdsPlugin(), new webpack.optimize.ModuleConcatenationPlugin(), - new webpack.optimize.UglifyJsPlugin({ - mangle: { screw_ie8: true }, - compress: uglifyCompressOptions, - output: { ascii_only: true }, - sourceMap: buildOptions.sourcemaps, - comments: false - }) - ]) + ...extraPlugins + ] }; }; diff --git a/packages/@angular/cli/models/webpack-configs/typescript.ts b/packages/@angular/cli/models/webpack-configs/typescript.ts index 348dc6369805..01c7a53de74c 100644 --- a/packages/@angular/cli/models/webpack-configs/typescript.ts +++ b/packages/@angular/cli/models/webpack-configs/typescript.ts @@ -63,6 +63,9 @@ function _createAotPlugin(wco: WebpackConfigOptions, options: any) { }; } + options.compilerOptions = options.compilerOptions || {}; + options.compilerOptions.target = buildOptions.compilationTarget; + return new AotPlugin(Object.assign({}, { mainPath: path.join(projectRoot, appConfig.root, appConfig.main), i18nFile: buildOptions.i18nFile, diff --git a/packages/@angular/cli/package.json b/packages/@angular/cli/package.json index fdac6d70b8aa..8e755aebac95 100644 --- a/packages/@angular/cli/package.json +++ b/packages/@angular/cli/package.json @@ -76,6 +76,7 @@ "stylus": "^0.54.5", "stylus-loader": "^3.0.1", "typescript": ">=2.0.0 <2.6.0", + "uglifyjs-webpack-plugin": "1.0.0-beta.1", "url-loader": "^0.5.7", "webpack": "~3.5.5", "webpack-concat-plugin": "1.4.0", diff --git a/packages/@angular/cli/tasks/eject.ts b/packages/@angular/cli/tasks/eject.ts index 242404503843..a910d961a139 100644 --- a/packages/@angular/cli/tasks/eject.ts +++ b/packages/@angular/cli/tasks/eject.ts @@ -24,6 +24,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const SilentError = require('silent-error'); const CircularDependencyPlugin = require('circular-dependency-plugin'); const ConcatPlugin = require('webpack-concat-plugin'); +const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const Task = require('../ember-cli/lib/models/task'); const ProgressPlugin = require('webpack/lib/ProgressPlugin'); @@ -158,6 +159,10 @@ class JsonWebpackSerializer { return plugin.settings; } + private _uglifyjsPlugin(plugin: any) { + return plugin.options; + } + private _pluginsReplacer(plugins: any[]) { return plugins.map(plugin => { let args = plugin.options || undefined; @@ -229,6 +234,10 @@ class JsonWebpackSerializer { args = this._concatPlugin(plugin); this.variableImports['webpack-concat-plugin'] = 'ConcatPlugin'; break; + case UglifyJSPlugin: + args = this._uglifyjsPlugin(plugin); + this.variableImports['uglifyjs-webpack-plugin'] = 'UglifyJsPlugin'; + break; default: if (plugin.constructor.name == 'AngularServiceWorkerPlugin') { this._addImport('@angular/service-worker/build/webpack', plugin.constructor.name); @@ -546,6 +555,7 @@ export default Task.extend({ 'circular-dependency-plugin', 'webpack-concat-plugin', 'copy-webpack-plugin', + 'uglifyjs-webpack-plugin', ].forEach((packageName: string) => { packageJson['devDependencies'][packageName] = ourPackageJson['dependencies'][packageName]; }); diff --git a/tests/e2e/tests/build/compilation-target.ts b/tests/e2e/tests/build/compilation-target.ts new file mode 100644 index 000000000000..9b6313e4cc36 --- /dev/null +++ b/tests/e2e/tests/build/compilation-target.ts @@ -0,0 +1,18 @@ +import { appendToFile, expectFileToMatch } from '../../utils/fs'; +import { ng } from '../../utils/process'; +import { expectToFail } from '../../utils/utils'; + + +export default function () { + return Promise.resolve() + .then(() => ng('build', '--prod', '--es2015')) + // Check class constructors are present in the vendor output. + .then(() => expectFileToMatch('dist/vendor.bundle.js', /class \w{constructor\(\){/)) + // Force import a known ES6 module and build with prod and es6. + // ES6 modules will cause UglifyJS to fail on a ES5 compilation target. + .then(() => appendToFile('src/main.ts', ` + import * as es6module from '@angular/core/@angular/core'; + console.log(es6module); + `)) + .then(() => expectToFail(() => ng('build', '--prod', '--es5'))); +}