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', diff --git a/src/content/guides/production.md b/src/content/guides/production.md index 7451ca89c6c9..008a4359f583 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 @@ -15,271 +15,220 @@ 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`](https://github.com/survivejs/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 installing `webpack-merge` and 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: - -```js -// webpack.config.js -const webpack = require('webpack'); +``` bash +npm install --save-dev webpack-merge +``` -module.exports = { - /*...*/ - plugins:[ - new webpack.optimize.UglifyJsPlugin({ - sourceMap: options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0) - }) - ] -}; +__project__ + +``` 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. +__webpack.common.js__ +``` 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') ++ } ++ }; +``` -### Source Maps +__webpack.dev.js__ -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. +``` diff ++ const merge = require('webpack-merge'); ++ const common = require('./webpack.common.js'); ++ ++ module.exports = merge(common, { ++ devtool: 'inline-source-map', ++ devServer: { ++ contentBase: './dist' ++ } ++ }); +``` -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). +__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, { ++ plugins: [ ++ new UglifyJSPlugin() ++ ] ++ }); +``` -### Node Environment Variable +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. -Running `webpack -p` (or `--define process.env.NODE_ENV="'production'"`) invokes the [`DefinePlugin`](/plugins/define-plugin) in the following way: +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. -```js -// webpack.config.js -const webpack = require('webpack'); -module.exports = { - /*...*/ - plugins:[ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify('production') - }) - ] -}; -``` +## NPM Scripts -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`. +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: -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). +__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" + } + } +``` -## The Manual Way +Feel free to run those scripts and see how the output changes as we continue adding to our _production_ configuration. -When we do have multiple configurations in mind for different environments, the easiest approach is to write separate webpack configurations for each environment. +## Minification -### Simple Approach +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: -The simplest way to do this is just to define two fully independent configuration files, like so: +- [`BabelMinifyWebpackPlugin`](https://github.com/webpack-contrib/babel-minify-webpack-plugin) +- [`ClosureCompilerPlugin`](https://github.com/roman01la/webpack-closure-compiler) -__webpack.dev.js__ +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. -```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 - } -} -``` -__webpack.prod.js__ +## Source Mapping -```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 - }) - ] -} -``` +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_: -Then, by tweaking the `scripts` in your `package.json`, like so: +__webpack.prod.js__ -__package.json__ +``` diff + const merge = require('webpack-merge'); + const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); + const common = require('./webpack.common.js'); -```js -"scripts": { - ... - "build:dev": "webpack --env=dev --progress --profile --colors", - "build:dist": "webpack --env=prod --progress --profile --colors" -} + module.exports = merge(common, { ++ devtool: 'cheap-module-source-map', + plugins: [ + new UglifyJSPlugin() + ] + }) ``` -you can now toggle between the two configurations by turning our base configuration into a function and accepting the `env` parameter (set via `--env`): -__webpack.config.js__ +## Specify the Environment -```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. +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__ -### Advanced Approach +``` diff + const webpack = require('webpack'); + 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') ++ } ++ }) + ] + }) +``` -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. +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. -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. +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: -We'll start by adding our base configuration: +__src/index.js__ -__webpack.common.js__ +``` diff + import { cube } from './math.js'; ++ ++ if (process.env.NODE_ENV !== 'production') { ++ console.log('Looks like we are in development mode!'); ++ } -```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 - } - } - } - ] - }, + function component() { + var element = document.createElement('pre'); - plugins: [ - new ForkCheckerPlugin(), + element.innerHTML = [ + 'Hello webpack!', + '5 cubed is equal to ' + cube(5) + ].join('\n\n'); - new webpack.optimize.CommonsChunkPlugin({ - name: ['polyfills', 'vendor'].reverse() - }), + return element; + } - new HtmlWebpackPlugin({ - template: 'src/index.html', - chunksSortMode: 'dependency' - }) - ] -} + document.body.appendChild(component()); ``` -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: +## CLI Alternatives -- 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`. +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. -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. +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. diff --git a/src/content/guides/tree-shaking.md b/src/content/guides/tree-shaking.md index dfedcbc4a4e0..eee52ba1c41e 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 @@ -14,75 +14,135 @@ 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; + return x * x; } -// This function gets included export function cube(x) { - return x * x * 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 and remove `lodash` for simplicity: -```javascript -import {cube} from './maths.js'; -console.log(cube(5)); // 125 +__src/index.js__ + +``` diff +- import _ from 'lodash'; ++ import { cube } from './math.js'; + + function component() { +- var element = document.createElement('div'); ++ var element = document.createElement('pre'); + +- // Lodash, now imported by this script +- element.innerHTML = _.join(['Hello', 'webpack'], ' '); ++ element.innerHTML = [ ++ 'Hello webpack!', ++ '5 cubed is equal to ' + cube(5) ++ ].join('\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 bundle: + +__dist/bundle.js (around lines 90 - 100)__ -```javascript -/* ... webpackBootstrap ... */ -/******/ ([ -/* 0 */ +``` js +/* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "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; } -// This function gets included function cube(x) { return x * x * x; } +``` -/***/ }), -/* 1 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +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. -"use strict"; -Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); -/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__maths_js__ = __webpack_require__(0); -console.log(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__maths_js__["a" /* cube */])(5)); // 125 +## Minify the Output -/***/ }) -``` +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... + +Let's start by installing it: -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: +``` bash +npm i --save-dev uglifyjs-webpack-plugin +``` -```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))} +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() ++ ] +}; ``` -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. +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. + +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 + +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).