From 3fde1434055951c24faca335603db78503113176 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Fri, 11 Aug 2017 00:38:54 -0400 Subject: [PATCH 1/9] docs(guides): rewrite/re-outline the tree-shaking page This continues our work #1258 and resolves most of the issues mentioned in #1331. It is a WIP though, we still need to populate the `TODO` code blocks. --- src/content/guides/tree-shaking.md | 137 ++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 43 deletions(-) diff --git a/src/content/guides/tree-shaking.md b/src/content/guides/tree-shaking.md index dfedcbc4a4e0..c5e742aa900c 100644 --- a/src/content/guides/tree-shaking.md +++ b/src/content/guides/tree-shaking.md @@ -14,75 +14,126 @@ related: url: https://medium.com/modus-create-front-end-development/webpack-2-tree-shaking-configuration-9f1de90f3233#.15tuaw71x --- -_Tree shaking_ is a term commonly used in the JavaScript context for dead-code elimination, or more precisely, live-code import. It relies on ES2015 module [import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)/[export](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) for the [static structure](http://exploringjs.com/es6/ch_modules.html#static-module-structure) of its module system. The name and concept have been popularized by the ES2015 module bundler [rollup](https://github.com/rollup/rollup). +_Tree shaking_ is a term commonly used in the JavaScript context for dead-code elimination. It relies on the [static structure](http://exploringjs.com/es6/ch_modules.html#static-module-structure) of ES2015 module syntax, i.e. [`import`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) and [`export`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export). The name and concept have been popularized by the ES2015 module bundler [rollup](https://github.com/rollup/rollup). -webpack 2 comes with a built-in support for ES2015 modules (alias *harmony modules*) as well as unused module export detection. +The webpack 2 release came with built-in support for ES2015 modules (alias _harmony modules_) as well as unused module export detection. +T> The remainder of this guide will stem from [Getting Started](/guides/getting-started). If you haven't read through that guide already, please do so now. -## Example -Consider a **maths.js** library file exporting two functions, `square` and `cube`: +## Add a Utility -```javascript -// This function isn't used anywhere +Let's add a new utility file to our project, `src/math.js`, that exports two functions: + +__project__ + +``` diff +webpack-demo +|- package.json +|- webpack.config.js +|- /dist + |- bundle.js + |- index.html +|- /src + |- index.js + |- math.js +|- /node_modules +``` + +__src/math.js__ + +``` javascript export function square(x) { return x * x; } -// This function gets included export function cube(x) { return x * x * x; } ``` -In our **main.js** we are selectively importing `cube`: +With that in place, let's update our entry script to utilize this one of these new methods: + +``` diff + import _ from 'lodash'; ++ import { cube } from './math.js'; -```javascript -import {cube} from './maths.js'; -console.log(cube(5)); // 125 + function component() { + var element = document.createElement('div'); + + // Lodash, now imported by this script +- element.innerHTML = _.join(['Hello', 'webpack'], ' '); ++ element.innerHTML = _.join([ + 'Hello webpack!', + '5 cubed is equal to ' + cube(5) + ], '\n\n'); + + return element; + } + + document.body.appendChild(component()); ``` -Running `node_modules/.bin/webpack main.js dist.js` and inspecting `dist.js` reveals that `square` is not being exported (see the "unused harmony export square" comment): +Note that we __did not `import` the `square` method__ from the `src/math.js` module. That function is what's known as "dead code", meaning an unused `export` that should be dropped. Now let's run our npm script, `npm run build`, and inspect the output: -```javascript -/* ... webpackBootstrap ... */ -/******/ ([ -/* 0 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +__dist/bundle.js__ -"use strict"; -/* unused harmony export square */ -/* harmony export (immutable) */ __webpack_exports__["a"] = cube; -// This function isn't used anywhere -function square(x) { - return x * x; -} +``` js +// TODO: Display contents with `unused harmony export square`... +``` -// This function gets included -function cube(x) { - return x * x * x; -} +Note the `unused harmony export square` comment above. If you look at the code below it, you'll notice that `square` is not being exported, however, it is still included in the bundle. We'll fix that in the next section. + + +## Minify the Output -/***/ }), -/* 1 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +So we've cued up our "dead code" to be dropped by using the `import` and `export` syntax, but we still need to drop it from the bundle. To do that, we'll add a minifier that supports dead code removal -- the [`UglifyJSPlugin`](/plugins/uglifyjs-webpack-plugin) -- to our configuration... -"use strict"; -Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__maths_js__ = __webpack_require__(0); +Let's start by installing it: -console.log(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__maths_js__["a" /* cube */])(5)); // 125 +``` bash +npm i --save-dev uglifyjs-webpack-plugin +``` -/***/ }) +And then adding it into our config: + +__webpack.config.js__ + +``` diff +const path = require('path'); +const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); + +module.exports = { + entry: './src/index.js', + output: { + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist') +- } ++ }, ++ plugins: [ ++ new UglifyJSPlugin() ++ ] +}; ``` -When running a [production build](/guides/production), `node_modules/.bin/webpack --optimize-minimize main.js dist.min.js`, only the minimized version of `cube` but not `square` remains in the build: +T> Note that the `--optimize-minimize` flag can be used to insert the `UglifyJsPlugin` as well. + +With that squared away, we can run another `npm run build` and see if anything has changed: + +__dist/bundle.js__ -```javascript -/* ... */ -function(e,t,n){"use strict";function r(e){return e*e*e}t.a=r} -/* ... */ -function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0);console.log(n.i(r.a)(5))} +``` js +// TODO: Display contents with `square removed... ``` -T> Note that the `--optimize-minimize` flag enables tree shaking by including the `UglifyJsPlugin` behind the scenes. Alternatively, the `UglifyJsPlugin` can be included manually in the `plugins` section of your configuration file. The plugin, combined with webpack's resolving of `import` and `export` statements, is what makes tree shaking possible. See the [production build](/guides/production) guide for more information. +Notice anything missing? The `square` function has been dropped and our output bundle is now a few bytes smaller! While that may not seem like much in this contrived example, tree shaking can yield a significant decrease in bundle size when working on larger applications with complex dependency trees. + + +## Conclusion + +So, what we've learned is that in order to take advantage of _tree shaking_, you must... + +- Use ES2015 module syntax (i.e. `import` and `export`). +- Include a minifier that supports dead code removal (e.g. the `UglifyJSPlugin`). + +If you are interested in more ways to optimize your output, please jump to the next guide for details on building for [production](/guides/production). From 588861cb9c12fbf312d078fe470f4748d5871bc0 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 12 Aug 2017 00:06:57 -0400 Subject: [PATCH 2/9] docs(guides): start outlining the production guide I've copied the older content to separate files locally so I could start outlining the structure of the updated guide. I will slowly integrate various parts back in as this gets more fleshed out. --- src/content/guides/production.md | 295 +++++++------------------------ 1 file changed, 68 insertions(+), 227 deletions(-) diff --git a/src/content/guides/production.md b/src/content/guides/production.md index 7451ca89c6c9..e4246dcef349 100644 --- a/src/content/guides/production.md +++ b/src/content/guides/production.md @@ -15,271 +15,112 @@ contributors: - xgqfrms --- -The following article describes the best practices and tools to use when using webpack to build a production version of a site or application. +In this guide we'll dive into some of the best practices and utilities for building a production site or application. +T> This walkthrough stems from [Tree Shaking](/guides/tree-shaking) and [Development](/guides/development). Please ensure you are familiar with the concepts/setup introduced in those guides before continuing on. -## The Automatic Way -Running `webpack -p` (or equivalently `webpack --optimize-minimize --define process.env.NODE_ENV="'production'"`). This performs the following steps: +## Setup -- Minification using `UglifyJsPlugin` -- Runs the `LoaderOptionsPlugin` (see its [documentation](/plugins/loader-options-plugin)) -- Sets the NodeJS environment variable triggering certain packages to compile differently +The goals of _development_ and _production_ builds differ greatly. In _development_, we want strong source mapping and a localhost server with live reloading or hot module replacement. In _production_, our goals shift to a focus on minified bundles, lighter weight source maps, and optimized assets to improve load time. With this logical separation at hand, we typically recommend writing __separate webpack configurations__ for each environment. +While we will separate the _production_ and _development_ specific bits out, note that we'll still maintain a "common" configuration to keep things DRY. In order to merge these configurations together, we'll use a utility called [`webpack-merge`](). With the "common" configuration in place, we won't have to duplicate code within the environment-specific configurations. -### Minification +Let's start by splitting out the bits we've already work on in previous guides: -webpack comes with `UglifyJsPlugin`, which runs [UglifyJS](http://lisperator.net/uglifyjs/) in order to minimize the output. The plugin supports all of the [UglifyJS options](https://github.com/mishoo/UglifyJS2#usage). Specifying `--optimize-minimize` on the command line, the following plugin configuration is added: +__project__ -```js -// webpack.config.js -const webpack = require('webpack'); - -module.exports = { - /*...*/ - plugins:[ - new webpack.optimize.UglifyJsPlugin({ - sourceMap: options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0) - }) - ] -}; +``` diff + webpack-demo + |- package.json +- |- webpack.config.js ++ |- webpack.common.js ++ |- webpack.dev.js ++ |- webpack.prod.js + |- /dist + |- /src + |- index.js + |- math.js + |- /node_modules ``` -Thus, depending on the [devtool options](/configuration/devtool), Source Maps are generated. - - -### Source Maps - -We encourage you to have source maps enabled in production, as they are useful for debugging as well as running benchmark tests. webpack can generate inline source maps within bundles or as separate files. - -In your configuration, use the `devtool` object to set the Source Map type. We currently support seven types of source maps. You can find more information about them in our [configuration](/configuration/devtool) documentation page (`cheap-module-source-map` is one of the simpler options, using a single mapping per line). - - -### Node Environment Variable - -Running `webpack -p` (or `--define process.env.NODE_ENV="'production'"`) invokes the [`DefinePlugin`](/plugins/define-plugin) in the following way: - -```js -// webpack.config.js -const webpack = require('webpack'); +__webpack.common.js__ -module.exports = { - /*...*/ - plugins:[ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('production') - }) - ] -}; +``` diff ++ const path = require('path'); ++ const CleanWebpackPlugin = require('clean-webpack-plugin'); ++ const HtmlWebpackPlugin = require('html-webpack-plugin'); ++ ++ module.exports = { ++ entry: { ++ app: './src/index.js' ++ }, ++ plugins: [ ++ new CleanWebpackPlugin(['dist']), ++ new HtmlWebpackPlugin({ ++ title: 'Production' ++ }) ++ ], ++ output: { ++ filename: '[name].bundle.js', ++ path: path.resolve(__dirname, 'dist') ++ } ++ }; ``` -The `DefinePlugin` performs search-and-replace operations on the original source code. Any occurrence of `process.env.NODE_ENV` in the imported code is replaced by `"production"`. Thus, checks like `if (process.env.NODE_ENV !== 'production') console.log('...')` are evaluated to `if (false) console.log('...')` and finally minified away using `UglifyJS`. - -T> Technically, `NODE_ENV` is a system environment variable that Node.js exposes into running scripts. It is used by convention to determine development-vs-production behavior by server tools, build scripts, and client-side libraries. Contrary to expectations, `process.env.NODE_ENV` is not set to `"production"` __within__ the build script `webpack.config.js`, see [#2537](https://github.com/webpack/webpack/issues/2537). Thus, conditionals like `process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'` do not work as expected. See how to use [environment variables](/guides/environment-variables). - - -## The Manual Way +__webpack.dev.js__ -When we do have multiple configurations in mind for different environments, the easiest approach is to write separate webpack configurations for each environment. +``` diff ++ module.exports = { ++ devtool: 'inline-source-map', ++ devServer: { ++ contentBase: './dist' ++ } ++ } +``` +__webpack.prod.js__ -### Simple Approach +``` diff ++ const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); ++ ++ module.exports = { ++ plugins: [ ++ new UglifyJSPlugin() ++ ] ++ } +``` -The simplest way to do this is just to define two fully independent configuration files, like so: +In `webpack.common.js`, we now have our `entry` and `output` setup configured and we've included any plugins that are required for both environments. In `webpack.dev.js`, we've added the recommended `devtool` for that environment (strong source mapping), as well as our simple `devServer` configuration. Finally, in `webpack.prod.js`, we included the `UglifyJSPlugin` which was first introduced by the [tree shaking]() guide. -__webpack.dev.js__ -```js -module.exports = { - devtool: 'cheap-module-source-map', - - output: { - path: path.join(__dirname, '/../dist/assets'), - filename: '[name].bundle.js', - publicPath: publicPath, - sourceMapFilename: '[name].map' - }, - - devServer: { - port: 7777, - host: 'localhost', - historyApiFallback: true, - noInfo: false, - stats: 'minimal', - publicPath: publicPath - } -} -``` +## Merging Configurations -__webpack.prod.js__ +... -```js -module.exports = { - output: { - path: path.join(__dirname, '/../dist/assets'), - filename: '[name].bundle.js', - publicPath: publicPath, - sourceMapFilename: '[name].map' - }, - - plugins: [ - new webpack.LoaderOptionsPlugin({ - minimize: true, - debug: false - }), - new webpack.optimize.UglifyJsPlugin({ - beautify: false, - mangle: { - screw_ie8: true, - keep_fnames: true - }, - compress: { - screw_ie8: true - }, - comments: false - }) - ] -} -``` -Then, by tweaking the `scripts` in your `package.json`, like so: +## Minification -__package.json__ +... -```js -"scripts": { - ... - "build:dev": "webpack --env=dev --progress --profile --colors", - "build:dist": "webpack --env=prod --progress --profile --colors" -} -``` -you can now toggle between the two configurations by turning our base configuration into a function and accepting the `env` parameter (set via `--env`): +## Production Source Mapping -__webpack.config.js__ +... -```js -module.exports = function(env) { - return require(`./webpack.${env}.js`) -} -``` -See the CLI's [common options section](/api/cli#common-options) for more details on how to use the `env` flag. -### Advanced Approach -A more complex approach would be to have a base configuration file, containing the configuration common to both environments, and then merge that with environment specific configurations. This would yield the full configuration for each environment and prevent repetition for the common bits. -The tool used to perform this "merge" is simply called [webpack-merge](https://github.com/survivejs/webpack-merge) and provides a variety of merging options, though we are only going to use the simplest version of it below. -We'll start by adding our base configuration: -__webpack.common.js__ -```js -module.exports = { - entry: { - 'polyfills': './src/polyfills.ts', - 'vendor': './src/vendor.ts', - 'main': './src/main.ts' - }, - - output: { - path: path.join(__dirname, '/../dist/assets'), - filename: '[name].bundle.js', - publicPath: publicPath, - sourceMapFilename: '[name].map' - }, - - resolve: { - extensions: ['.ts', '.js', '.json'], - modules: [path.join(__dirname, 'src'), 'node_modules'] - }, - - module: { - rules: [ - { - test: /\.ts$/, - exclude: [/\.(spec|e2e)\.ts$/], - use: [ - 'awesome-typescript-loader', - 'angular2-template-loader' - ] - }, - { - test: /\.css$/, - use: ['to-string-loader', 'css-loader'] - }, - { - test: /\.(jpg|png|gif)$/, - use: 'file-loader' - }, - { - test: /\.(woff|woff2|eot|ttf|svg)$/, - use: { - loader: 'url-loader', - options: { - limit: 100000 - } - } - } - ] - }, - - plugins: [ - new ForkCheckerPlugin(), - - new webpack.optimize.CommonsChunkPlugin({ - name: ['polyfills', 'vendor'].reverse() - }), - - new HtmlWebpackPlugin({ - template: 'src/index.html', - chunksSortMode: 'dependency' - }) - ] -} -``` -And then merge this common configuration with an environment specific configuration file using `webpack-merge`. Let's look at an example where we merge our production file: -__webpack.prod.js__ -```js -const Merge = require('webpack-merge'); -const CommonConfig = require('./webpack.common.js'); - -module.exports = Merge(CommonConfig, { - plugins: [ - new webpack.LoaderOptionsPlugin({ - minimize: true, - debug: false - }), - new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': JSON.stringify('production') - } - }), - new webpack.optimize.UglifyJsPlugin({ - beautify: false, - mangle: { - screw_ie8: true, - keep_fnames: true - }, - compress: { - screw_ie8: true - }, - comments: false - }) - ] -}) -``` -You will notice three major updates to our 'webpack.prod.js' file: -- Use `webpack-merge` to combine it with the 'webpack.common.js'. -- We moved the `output` property to `webpack.common.js` as it is common to all environments. -- We defined `'process.env.NODE_ENV'` as `'production'` using the `DefinePlugin` only in `webpack.prod.js`. +## CLI Alternatives -The example above only demonstrates a few typical configuration options used in each (or both) environments. Now that you know how to split up configurations, the choice of what options go where is up to you. +... From c6d88a17717dbf8efba1f5e33e5c7b1140f41a41 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 12 Aug 2017 00:08:42 -0400 Subject: [PATCH 3/9] docs(guides): remove unintentional hmr plugin from development Adding this is never even mentioned, I think it was just copied over by accident. It's covered in detail in the hot-module-replacement guide which comes immediately after this one. cc @TheDutchCoder --- src/content/guides/development.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/content/guides/development.md b/src/content/guides/development.md index 65571863825e..7d14c3f59687 100644 --- a/src/content/guides/development.md +++ b/src/content/guides/development.md @@ -267,8 +267,7 @@ __webpack.config.js__ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Output Management' - }), - new webpack.HotModuleReplacementPlugin() + }) ], output: { filename: '[name].bundle.js', From 5744b80e62aca42b991df9eab1c838c18389b4af Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sun, 13 Aug 2017 13:47:27 -0400 Subject: [PATCH 4/9] docs(guides): resort and add minor update --- src/content/guides/production.md | 2 +- src/content/guides/tree-shaking.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/content/guides/production.md b/src/content/guides/production.md index e4246dcef349..d759b0baea1b 100644 --- a/src/content/guides/production.md +++ b/src/content/guides/production.md @@ -1,6 +1,6 @@ --- title: Production -sort: 7 +sort: 8 contributors: - henriquea - rajagopal4890 diff --git a/src/content/guides/tree-shaking.md b/src/content/guides/tree-shaking.md index c5e742aa900c..62d6324dab83 100644 --- a/src/content/guides/tree-shaking.md +++ b/src/content/guides/tree-shaking.md @@ -1,6 +1,6 @@ --- title: Tree Shaking -sort: 8 +sort: 7 contributors: - simon04 - zacanger @@ -54,6 +54,8 @@ export function cube(x) { With that in place, let's update our entry script to utilize this one of these new methods: +__src/index.js__ + ``` diff import _ from 'lodash'; + import { cube } from './math.js'; From 56b7660d42f7efd83f1c4b47501227e62efa9852 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sun, 13 Aug 2017 13:48:09 -0400 Subject: [PATCH 5/9] docs(guides): continue work on the production rewrite --- src/content/guides/production.md | 133 ++++++++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 11 deletions(-) diff --git a/src/content/guides/production.md b/src/content/guides/production.md index d759b0baea1b..069436e342e3 100644 --- a/src/content/guides/production.md +++ b/src/content/guides/production.md @@ -26,7 +26,11 @@ The goals of _development_ and _production_ builds differ greatly. In _developme While we will separate the _production_ and _development_ specific bits out, note that we'll still maintain a "common" configuration to keep things DRY. In order to merge these configurations together, we'll use a utility called [`webpack-merge`](). With the "common" configuration in place, we won't have to duplicate code within the environment-specific configurations. -Let's start by splitting out the bits we've already work on in previous guides: +Let's start by installing `webpack-merge` and splitting out the bits we've already work on in previous guides: + +``` bash +npm install --save-dev webpack-merge +``` __project__ @@ -71,56 +75,163 @@ __webpack.common.js__ __webpack.dev.js__ ``` diff -+ module.exports = { ++ const merge = require('webpack-merge'); ++ const common = require('./webpack.common.js'); ++ ++ module.exports = merge(common, { + devtool: 'inline-source-map', + devServer: { + contentBase: './dist' + } -+ } ++ }); ``` __webpack.prod.js__ ``` diff ++ const merge = require('webpack-merge'); + const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); ++ const common = require('./webpack.common.js'); + -+ module.exports = { ++ module.exports = merge(common, { + plugins: [ + new UglifyJSPlugin() + ] -+ } ++ }) ``` In `webpack.common.js`, we now have our `entry` and `output` setup configured and we've included any plugins that are required for both environments. In `webpack.dev.js`, we've added the recommended `devtool` for that environment (strong source mapping), as well as our simple `devServer` configuration. Finally, in `webpack.prod.js`, we included the `UglifyJSPlugin` which was first introduced by the [tree shaking]() guide. +Note the use of `merge()` in the environment-specific configurations to easily include our common configuration in `dev` and `prod`. The `webpack-merge` tool offers a variety of advanced features for merging but for our use case we won't need any of that. + -## Merging Configurations +## NPM Scripts -... +Now let's repoint our `scripts` to the new configurations. We'll use the _development_ one for our `webpack-dev-server`, `npm start`, script and the _production_ one for our `npm run build` script: + +__package.json__ + +``` diff + { + "name": "development", + "version": "1.0.0", + "description": "", + "main": "webpack.config.js", + "scripts": { +- "start": "webpack-dev-server --open", ++ "start": "webpack-dev-server --open --config webpack.dev.js", +- "build": "webpack" ++ "build": "webpack --config webpack.prod.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "css-loader": "^0.28.4", + "csv-loader": "^2.1.1", + "express": "^4.15.3", + "file-loader": "^0.11.2", + "html-webpack-plugin": "^2.29.0", + "style-loader": "^0.18.2", + "webpack": "^3.0.0", + "webpack-dev-middleware": "^1.12.0", + "xml-loader": "^1.2.1" + } + } +``` + +Feel free to run those scripts and see how the output changes as we continue adding to our _production_ configuration. ## Minification -... +Note that while the [`UglifyJSPlugin`]() is a great place to start for minification, there are other options out there. Here's a few more popular ones: +- [`BabiliWebpackPlugin`]() +- [`ClosureCompiler`]() -## Production Source Mapping +If you decide to try another, just make sure your new choice also drops dead code as described in the [tree shaking](/guides/tree-shaking) guide. -... +## Source Mapping +We encourage you to have source maps enabled in production, as they are useful for debugging as well as running benchmark tests. That said, you should choose one with a fairly quick build speed that's recommended for production use (see [`devtool`](/configuration/devtool)). For this guide, we'll use the `cheap-module-source-map` option in _production_ as opposed to the `inline-source-map` we used in _development_: +__webpack.prod.js__ +``` diff + const merge = require('webpack-merge'); + const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); + const common = require('./webpack.common.js'); + + module.exports = merge(common, { ++ devtool: 'cheap-module-source-map', + plugins: [ + new UglifyJSPlugin() + ] + }) +``` +## Specify the Environment +Many libraries will key off the `process.env.NODE_ENV` variable to determine what should be included in the library. For example, when not in _production_ some libraries may add additional logging and testing to make debugging easier. However, with `process.env.NODE_ENV === 'production'` they might drop or add significant portions of code to optimize how things run for your actual users. We can use webpack's built in [`DefinePlugin`]() to define this variable for all our dependencies: +__webpack.prod.js__ + +``` diff + const merge = require('webpack-merge'); + const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); + const common = require('./webpack.common.js'); + + module.exports = merge(common, { + devtool: 'cheap-module-source-map', + plugins: [ +- new UglifyJSPlugin() ++ new UglifyJSPlugin(), ++ new webpack.DefinePlugin({ ++ 'process.env': { ++ 'NODE_ENV': JSON.stringify('production') ++ } ++ }) + ] + }) +``` +T> Technically, `NODE_ENV` is a system environment variable that Node.js exposes into running scripts. It is used by convention to determine dev-vs-prod behavior by server tools, build scripts, and client-side libraries. Contrary to expectations, `process.env.NODE_ENV` is not set to `"production"` __within__ the build script `webpack.config.js`, see [#2537](https://github.com/webpack/webpack/issues/2537). Thus, conditionals like `process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'` within webpack configurations do not work as expected. +If you're using a library like [React](), you should actually see a significant drop in bundle size after adding this plugin. Also note that any of our local `/src` code can key off of this as well, so the following check would be valid: +__src/index.js__ +``` diff + import _ from 'lodash'; + import { cube } from './math.js'; ++ ++ if (process.env.NODE_ENV !== 'production') { ++ console.log('Looks like we are in development mode!'); ++ } + + function component() { + var element = document.createElement('div'); + + // Lodash, now imported by this script + element.innerHTML = _.join([ + 'Hello webpack!', + '5 cubed is equal to ' + cube(5) + ], '\n\n'); + + return element; + } + + document.body.appendChild(component()); +``` ## CLI Alternatives -... +Some of what has been described above is also achievable via the command line. For example, the `--optimize-minize` flag will include the `UglifyJSPlugin` behind the scenes. The `--define process.env.NODE_ENV="'production'"` will do the same for the `DefinePlugin` instance described above. And, `webpack -p` will automatically include invoke both those flags and thus the plugins to be included. + +While these short hand methods are nice, we usually recommend just using the configuration as it's better to understand exactly what is being done for you in both cases. The configuration also gives you more control on fine tuning other options within both plugins. + +?> TODO: Consider mentioning the `LoaderOptionsPlugin`... is this still relevant? From b5314773ff56421e5015896d8faef36b800d1a6a Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sun, 13 Aug 2017 14:35:59 -0400 Subject: [PATCH 6/9] docs(guides): fill in missing links in production --- src/content/guides/production.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/content/guides/production.md b/src/content/guides/production.md index 069436e342e3..6de4b7635000 100644 --- a/src/content/guides/production.md +++ b/src/content/guides/production.md @@ -24,7 +24,7 @@ T> This walkthrough stems from [Tree Shaking](/guides/tree-shaking) and [Develop The goals of _development_ and _production_ builds differ greatly. In _development_, we want strong source mapping and a localhost server with live reloading or hot module replacement. In _production_, our goals shift to a focus on minified bundles, lighter weight source maps, and optimized assets to improve load time. With this logical separation at hand, we typically recommend writing __separate webpack configurations__ for each environment. -While we will separate the _production_ and _development_ specific bits out, note that we'll still maintain a "common" configuration to keep things DRY. In order to merge these configurations together, we'll use a utility called [`webpack-merge`](). With the "common" configuration in place, we won't have to duplicate code within the environment-specific configurations. +While we will separate the _production_ and _development_ specific bits out, note that we'll still maintain a "common" configuration to keep things DRY. In order to merge these configurations together, we'll use a utility called [`webpack-merge`](https://github.com/survivejs/webpack-merge). With the "common" configuration in place, we won't have to duplicate code within the environment-specific configurations. Let's start by installing `webpack-merge` and splitting out the bits we've already work on in previous guides: @@ -100,7 +100,7 @@ __webpack.prod.js__ + }) ``` -In `webpack.common.js`, we now have our `entry` and `output` setup configured and we've included any plugins that are required for both environments. In `webpack.dev.js`, we've added the recommended `devtool` for that environment (strong source mapping), as well as our simple `devServer` configuration. Finally, in `webpack.prod.js`, we included the `UglifyJSPlugin` which was first introduced by the [tree shaking]() guide. +In `webpack.common.js`, we now have our `entry` and `output` setup configured and we've included any plugins that are required for both environments. In `webpack.dev.js`, we've added the recommended `devtool` for that environment (strong source mapping), as well as our simple `devServer` configuration. Finally, in `webpack.prod.js`, we included the `UglifyJSPlugin` which was first introduced by the [tree shaking](/guides/tree-shaking) guide. Note the use of `merge()` in the environment-specific configurations to easily include our common configuration in `dev` and `prod`. The `webpack-merge` tool offers a variety of advanced features for merging but for our use case we won't need any of that. @@ -145,10 +145,10 @@ Feel free to run those scripts and see how the output changes as we continue add ## Minification -Note that while the [`UglifyJSPlugin`]() is a great place to start for minification, there are other options out there. Here's a few more popular ones: +Note that while the [`UglifyJSPlugin`](/plugins/uglifyjs-webpack-plugin) is a great place to start for minification, there are other options out there. Here's a few more popular ones: -- [`BabiliWebpackPlugin`]() -- [`ClosureCompiler`]() +- [`BabelMinifyWebpackPlugin`](https://github.com/webpack-contrib/babel-minify-webpack-plugin) +- [`ClosureCompilerPlugin`](https://github.com/roman01la/webpack-closure-compiler) If you decide to try another, just make sure your new choice also drops dead code as described in the [tree shaking](/guides/tree-shaking) guide. @@ -175,7 +175,7 @@ __webpack.prod.js__ ## Specify the Environment -Many libraries will key off the `process.env.NODE_ENV` variable to determine what should be included in the library. For example, when not in _production_ some libraries may add additional logging and testing to make debugging easier. However, with `process.env.NODE_ENV === 'production'` they might drop or add significant portions of code to optimize how things run for your actual users. We can use webpack's built in [`DefinePlugin`]() to define this variable for all our dependencies: +Many libraries will key off the `process.env.NODE_ENV` variable to determine what should be included in the library. For example, when not in _production_ some libraries may add additional logging and testing to make debugging easier. However, with `process.env.NODE_ENV === 'production'` they might drop or add significant portions of code to optimize how things run for your actual users. We can use webpack's built in [`DefinePlugin`](/plugins/define-plugin) to define this variable for all our dependencies: __webpack.prod.js__ @@ -200,7 +200,7 @@ __webpack.prod.js__ T> Technically, `NODE_ENV` is a system environment variable that Node.js exposes into running scripts. It is used by convention to determine dev-vs-prod behavior by server tools, build scripts, and client-side libraries. Contrary to expectations, `process.env.NODE_ENV` is not set to `"production"` __within__ the build script `webpack.config.js`, see [#2537](https://github.com/webpack/webpack/issues/2537). Thus, conditionals like `process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js'` within webpack configurations do not work as expected. -If you're using a library like [React](), you should actually see a significant drop in bundle size after adding this plugin. Also note that any of our local `/src` code can key off of this as well, so the following check would be valid: +If you're using a library like [`react`](https://facebook.github.io/react/), you should actually see a significant drop in bundle size after adding this plugin. Also note that any of our local `/src` code can key off of this as well, so the following check would be valid: __src/index.js__ @@ -227,11 +227,11 @@ __src/index.js__ document.body.appendChild(component()); ``` +?> TODO: Consider mentioning the `LoaderOptionsPlugin`... is this still relevant? + ## CLI Alternatives Some of what has been described above is also achievable via the command line. For example, the `--optimize-minize` flag will include the `UglifyJSPlugin` behind the scenes. The `--define process.env.NODE_ENV="'production'"` will do the same for the `DefinePlugin` instance described above. And, `webpack -p` will automatically include invoke both those flags and thus the plugins to be included. While these short hand methods are nice, we usually recommend just using the configuration as it's better to understand exactly what is being done for you in both cases. The configuration also gives you more control on fine tuning other options within both plugins. - -?> TODO: Consider mentioning the `LoaderOptionsPlugin`... is this still relevant? From 81672d00029cd4a88aeeec94b20921c38e53386e Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Tue, 22 Aug 2017 21:48:50 -0400 Subject: [PATCH 7/9] docs(guides): minor fix in tree-shaking --- src/content/guides/tree-shaking.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/guides/tree-shaking.md b/src/content/guides/tree-shaking.md index 62d6324dab83..dc885d70798a 100644 --- a/src/content/guides/tree-shaking.md +++ b/src/content/guides/tree-shaking.md @@ -66,9 +66,9 @@ __src/index.js__ // Lodash, now imported by this script - element.innerHTML = _.join(['Hello', 'webpack'], ' '); + element.innerHTML = _.join([ - 'Hello webpack!', - '5 cubed is equal to ' + cube(5) - ], '\n\n'); ++ 'Hello webpack!', ++ '5 cubed is equal to ' + cube(5) ++ ], '\n\n'); return element; } From 39e288c48dd032f4f9f1a42e241c8b0e58c4da35 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 26 Aug 2017 11:08:36 -0400 Subject: [PATCH 8/9] docs(guides): make final corrections to production/tree-shaking --- src/content/guides/production.md | 13 ++++------ src/content/guides/tree-shaking.md | 39 ++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/content/guides/production.md b/src/content/guides/production.md index 6de4b7635000..008a4359f583 100644 --- a/src/content/guides/production.md +++ b/src/content/guides/production.md @@ -97,7 +97,7 @@ __webpack.prod.js__ + plugins: [ + new UglifyJSPlugin() + ] -+ }) ++ }); ``` In `webpack.common.js`, we now have our `entry` and `output` setup configured and we've included any plugins that are required for both environments. In `webpack.dev.js`, we've added the recommended `devtool` for that environment (strong source mapping), as well as our simple `devServer` configuration. Finally, in `webpack.prod.js`, we included the `UglifyJSPlugin` which was first introduced by the [tree shaking](/guides/tree-shaking) guide. @@ -180,6 +180,7 @@ Many libraries will key off the `process.env.NODE_ENV` variable to determine wha __webpack.prod.js__ ``` diff + const webpack = require('webpack'); const merge = require('webpack-merge'); const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); const common = require('./webpack.common.js'); @@ -205,7 +206,6 @@ If you're using a library like [`react`](https://facebook.github.io/react/), you __src/index.js__ ``` diff - import _ from 'lodash'; import { cube } from './math.js'; + + if (process.env.NODE_ENV !== 'production') { @@ -213,13 +213,12 @@ __src/index.js__ + } function component() { - var element = document.createElement('div'); + var element = document.createElement('pre'); - // Lodash, now imported by this script - element.innerHTML = _.join([ + element.innerHTML = [ 'Hello webpack!', '5 cubed is equal to ' + cube(5) - ], '\n\n'); + ].join('\n\n'); return element; } @@ -227,8 +226,6 @@ __src/index.js__ document.body.appendChild(component()); ``` -?> TODO: Consider mentioning the `LoaderOptionsPlugin`... is this still relevant? - ## CLI Alternatives diff --git a/src/content/guides/tree-shaking.md b/src/content/guides/tree-shaking.md index dc885d70798a..5c80d61ad694 100644 --- a/src/content/guides/tree-shaking.md +++ b/src/content/guides/tree-shaking.md @@ -44,31 +44,32 @@ __src/math.js__ ``` javascript export function square(x) { - return x * x; + return x * x; } export function cube(x) { - return x * x * x; + return x * x * x; } ``` -With that in place, let's update our entry script to utilize this one of these new methods: +With that in place, let's update our entry script to utilize this one of these new methods and remove `lodash` for simplicity: __src/index.js__ ``` diff - import _ from 'lodash'; +- import _ from 'lodash'; + import { cube } from './math.js'; function component() { - var element = document.createElement('div'); +- var element = document.createElement('div'); ++ var element = document.createElement('pre'); - // Lodash, now imported by this script +- // Lodash, now imported by this script - element.innerHTML = _.join(['Hello', 'webpack'], ' '); -+ element.innerHTML = _.join([ ++ element.innerHTML = [ + 'Hello webpack!', + '5 cubed is equal to ' + cube(5) -+ ], '\n\n'); ++ ].join('\n\n'); return element; } @@ -76,12 +77,24 @@ __src/index.js__ document.body.appendChild(component()); ``` -Note that we __did not `import` the `square` method__ from the `src/math.js` module. That function is what's known as "dead code", meaning an unused `export` that should be dropped. Now let's run our npm script, `npm run build`, and inspect the output: +Note that we __did not `import` the `square` method__ from the `src/math.js` module. That function is what's known as "dead code", meaning an unused `export` that should be dropped. Now let's run our npm script, `npm run build`, and inspect the output bundle: -__dist/bundle.js__ +__dist/bundle.js (around lines 90 - 100)__ ``` js -// TODO: Display contents with `unused harmony export square`... +/* 1 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +/* unused harmony export square */ +/* harmony export (immutable) */ __webpack_exports__["a"] = cube; +function square(x) { + return x * x; +} + +function cube(x) { + return x * x * x; +} ``` Note the `unused harmony export square` comment above. If you look at the code below it, you'll notice that `square` is not being exported, however, it is still included in the bundle. We'll fix that in the next section. @@ -125,10 +138,10 @@ With that squared away, we can run another `npm run build` and see if anything h __dist/bundle.js__ ``` js -// TODO: Display contents with `square removed... +!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var t={};n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Ob ject.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=0)}([function(e,n,t){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=t(1);document.body.appendChild(function(){var e=documen t.createElement("div");return e.innerHTML=["Hello webpack!","5 cubed is equal to "+Object(r.a)(5)].join("\n\n"),e}())},function(e,n,t){"use strict";function r(e){return e*e*e}n.a=r}]); ``` -Notice anything missing? The `square` function has been dropped and our output bundle is now a few bytes smaller! While that may not seem like much in this contrived example, tree shaking can yield a significant decrease in bundle size when working on larger applications with complex dependency trees. +Notice anything missing? Most notably the whole bundle is minified and mangled. However, if you look carefully, you won't see the `square` function included but will see a mangled version of the `cube` function (`function r(e){return e*e*e}n.a=r`). With minification and tree shaking our bundle is now a few bytes smaller! While that may not seem like much in this contrived example, tree shaking can yield a significant decrease in bundle size when working on larger applications with complex dependency trees. ## Conclusion From ed1bc157eb840c190feb9edd7335e77fbbe585d8 Mon Sep 17 00:00:00 2001 From: Greg Venech Date: Sat, 26 Aug 2017 12:42:05 -0400 Subject: [PATCH 9/9] docs(guides): fix line length issue in tree-shaking --- src/content/guides/tree-shaking.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/content/guides/tree-shaking.md b/src/content/guides/tree-shaking.md index 5c80d61ad694..eee52ba1c41e 100644 --- a/src/content/guides/tree-shaking.md +++ b/src/content/guides/tree-shaking.md @@ -133,15 +133,9 @@ module.exports = { T> Note that the `--optimize-minimize` flag can be used to insert the `UglifyJsPlugin` as well. -With that squared away, we can run another `npm run build` and see if anything has changed: +With that squared away, we can run another `npm run build` and see if anything has changed. -__dist/bundle.js__ - -``` js -!function(e){function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}var t={};n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Ob ject.defineProperty(e,t,{configurable:!1,enumerable:!0,get:r})},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},n.p="",n(n.s=0)}([function(e,n,t){"use strict";Object.defineProperty(n,"__esModule",{value:!0});var r=t(1);document.body.appendChild(function(){var e=documen t.createElement("div");return e.innerHTML=["Hello webpack!","5 cubed is equal to "+Object(r.a)(5)].join("\n\n"),e}())},function(e,n,t){"use strict";function r(e){return e*e*e}n.a=r}]); -``` - -Notice anything missing? Most notably the whole bundle is minified and mangled. However, if you look carefully, you won't see the `square` function included but will see a mangled version of the `cube` function (`function r(e){return e*e*e}n.a=r`). With minification and tree shaking our bundle is now a few bytes smaller! While that may not seem like much in this contrived example, tree shaking can yield a significant decrease in bundle size when working on larger applications with complex dependency trees. +Notice anything different about `dist/bundle.js`? Clearly the whole bundle is now minified and mangled, but, if you look carefully, you won't see the `square` function included but will see a mangled version of the `cube` function (`function r(e){return e*e*e}n.a=r`). With minification and tree shaking our bundle is now a few bytes smaller! While that may not seem like much in this contrived example, tree shaking can yield a significant decrease in bundle size when working on larger applications with complex dependency trees. ## Conclusion