├── README.md └── recipes └── css.md /README.md: -------------------------------------------------------------------------------- 1 | # Webpack tricks 2 | 3 | Just a small catalog of Webpack tips and tricks I've learned. These tricks work with Webpack v3 unless otherwise specified. 4 | 5 | Table of contents 6 | ----------------- 7 | 8 | * [Progress reporting](#progress-reporting) 9 | * [Minification](#minification) 10 | * [Multiple bundles](#multiple-bundles) 11 | * [Split app and vendor code](#split-app-and-vendor-code) 12 | * [Source maps (Webpack 1)](#source-maps-webpack-1) 13 | * [Source maps (Webpack 2-3)](#source-maps-webpack-2-3) 14 | * [Output CSS files](#output-css-files) 15 | * [Development mode](#development-mode) 16 | * [Investigating bundle sizes](#investigating-bundle-sizes) 17 | * [Smaller React](#smaller-react) 18 | * [Smaller Lodash](#smaller-lodash) 19 | * [Requiring all files in a folder](#requiring-all-files-in-a-folder) 20 | * [Clean up extract-text-webpack-plugin log](#clean-up-extract-text-webpack-plugin-log) 21 | 22 | Progress reporting 23 | ------------------ 24 | 25 | Invoke Webpack with: 26 | 27 | ``` 28 | --progress --colors 29 | ``` 30 | 31 | Minification 32 | ------------ 33 | 34 | Invoke Webpack with `-p` for production builds. In Webpack 2, this also automatically sets `process.env.NODE_ENV === 'production'`. 35 | 36 | ```js 37 | webpack -p 38 | ``` 39 | 40 | Multiple bundles 41 | ---------------- 42 | 43 | Export multiple bundles by setting the output to `[name].js`. This example produces `a.js` and `b.js`. 44 | 45 | ```js 46 | module.exports = { 47 | entry: { 48 | a: './a', 49 | b: './b' 50 | }, 51 | output: { filename: '[name].js' } 52 | } 53 | ``` 54 | 55 | Concerned about duplication? Use the [CommonsChunkPlugin](https://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin) to move the common parts into a new output file. 56 | 57 | ```js 58 | plugins: [ new webpack.optimize.CommonsChunkPlugin('init.js') ] 59 | ``` 60 | 61 | ```html 62 | 63 | 64 | ``` 65 | 66 | Split app and vendor code 67 | ------------------------- 68 | 69 | Use CommonsChunkPlugin to move vendor code into `vendor.js`. 70 | 71 | ```js 72 | var webpack = require('webpack') 73 | 74 | module.exports = { 75 | entry: { 76 | app: './app.js', 77 | vendor: ['jquery', 'underscore', ...] 78 | }, 79 | 80 | output: { 81 | filename: '[name].js' 82 | }, 83 | 84 | plugins: [ 85 | new webpack.optimize.CommonsChunkPlugin('vendor') 86 | ] 87 | } 88 | ``` 89 | 90 | How this works: 91 | 92 | - We make a `vendor` entry point and load it with some libraries 93 | - CommonsChunkPlugin will remove these libraries from `app.js` (because it appears in 2 bundles now) 94 | - CommonsChunkPlugin also moves the Webpack runtime into `vendor.js` 95 | 96 | > Reference: [Code splitting](https://webpack.github.io/docs/code-splitting.html#split-app-and-vendor-code) 97 | 98 | Source maps (Webpack 1) 99 | ----------------------- 100 | 101 | The best source maps option is `cheap-module-eval-source-map`. This shows original source files in Chrome/Firefox dev tools. It's faster than `source-map` and `eval-source-map`. 102 | 103 | ```js 104 | // Webpack 1 only 105 | const DEBUG = process.env.NODE_ENV !== 'production' 106 | 107 | module.exports = { 108 | debug: DEBUG ? true : false, 109 | devtool: DEBUG ? 'cheap-module-eval-source-map' : 'hidden-source-map' 110 | } 111 | ``` 112 | 113 | Your files will now show up in Chrome Devtools as `webpack:///foo.js?a93h`. We want this to be cleaner like `webpack:///path/to/foo.js`. 114 | 115 | ```js 116 | output: { 117 | devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]' 118 | } 119 | ``` 120 | 121 | > Reference: [devtool documentation](https://webpack.github.io/docs/configuration.html#devtool) 122 | 123 | Source maps (Webpack 2-3) 124 | ------------------------- 125 | 126 | The best source maps option is `cheap-module-source-map`. The cheap-module-eval-source-map strategy no longer shows correct traces in Chrome/Firefox. 127 | 128 | ```js 129 | // Webpack 2 only 130 | const DEBUG = process.env.NODE_ENV !== 'production' 131 | 132 | module.exports = { 133 | devtool: DEBUG ? 'cheap-module-source-map' : 'hidden-source-map' 134 | } 135 | ``` 136 | 137 | If you're using [extract-text-webpack-plugin](https://www.npmjs.com/package/extract-text-webpack-plugin), use `'source-map'` instead. CSS sourcemaps won't work otherwise. 138 | 139 | ```js 140 | // Only if you're using extract-text-webpack-plugin 141 | module.exports = { 142 | devtool: DEBUG ? 'source-map' : 'hidden-source-map' 143 | } 144 | ``` 145 | 146 | Your files will now show up in Chrome Devtools as `webpack:///foo.js?a93h`. We want this to be cleaner like `webpack:///path/to/foo.js`. 147 | 148 | ```js 149 | output: { 150 | devtoolModuleFilenameTemplate: 'webpack:///[absolute-resource-path]' 151 | } 152 | ``` 153 | 154 | > Reference: [devtool documentation](https://webpack.js.org/configuration/devtool/#devtool) 155 | 156 | Output CSS files 157 | ---------------- 158 | 159 | This is complicated, and the guide [can be found here](recipes/css.md). 160 | 161 | Development mode 162 | ---------------- 163 | 164 | Want to have certain options only appear in development mode? 165 | 166 | ```js 167 | const DEBUG = process.env.NODE_ENV !== 'production' 168 | 169 | // Webpack 1 170 | module.exports = { 171 | debug: DEBUG ? true : false, 172 | devtool: DEBUG ? 'cheap-module-eval-source-map' : 'hidden-source-map' 173 | } 174 | 175 | // Webpack 2 176 | module.exports = { 177 | devtool: DEBUG ? 'cheap-module-source-map' : 'hidden-source-map' 178 | } 179 | ``` 180 | 181 | __Webpack 1:__ Be sure to invoke Webpack as `env NODE_ENV=production webpack -p` when building your production assets. 182 | 183 | __Webpack 2:__ Invoke Webpack as `webpack -p` when building your production assets. `NODE_ENV` is automatically set by Webpack. 184 | 185 | Investigating bundle sizes 186 | -------------------------- 187 | 188 | Want to see what dependencies are the largest? You can use webpack-bundle-size-analyzer. 189 | 190 | ```js 191 | $ yarn global add webpack-bundle-size-analyzer 192 | 193 | $ ./node_modules/.bin/webpack --json | webpack-bundle-size-analyzer 194 | jquery: 260.93 KB (37.1%) 195 | moment: 137.34 KB (19.5%) 196 | parsleyjs: 87.88 KB (12.5%) 197 | bootstrap-sass: 68.07 KB (9.68%) 198 | ... 199 | ``` 200 | 201 | If you're generating source maps (you should), you can also use source-map-explorer, which also works outside of Webpack. 202 | 203 | ```js 204 | $ yarn global add source-map-explorer 205 | 206 | $ source-map-explorer bundle.min.js bundle.min.js.map 207 | ``` 208 | 209 | > Reference: [webpack-bundle-size-analyzer](https://github.com/robertknight/webpack-bundle-size-analyzer), [source-map-explorer](https://www.npmjs.com/package/source-map-explorer) 210 | 211 | Smaller React 212 | ------------- 213 | 214 | React will build dev tools by default. You don't need this in production. Use the EnvironmentPlugin to make these dev tools disappear. This saves you around 30kb. 215 | 216 | ```js 217 | plugins: [ 218 | new webpack.EnvironmentPlugin({ 219 | NODE_ENV: 'development' 220 | }) 221 | ] 222 | ``` 223 | 224 | __Webpack 1:__ Be sure to invoke Webpack as `env NODE_ENV=production webpack -p` when building your production assets. 225 | 226 | __Webpack 2:__ Invoke Webpack as `webpack -p` when building your production assets. `NODE_ENV` is automatically set by Webpack. 227 | 228 | > Reference: [EnvironmentPlugin documentation](https://webpack.js.org/plugins/environment-plugin/) 229 | 230 | Smaller Lodash 231 | -------------- 232 | 233 | [Lodash](https://lodash.com/) is very useful but usually we only need a small part of its full functionality. [lodash-webpack-plugin](https://github.com/lodash/lodash-webpack-plugin) can help you shrink the lodash build by replacing [feature sets](https://github.com/lodash/lodash-webpack-plugin#feature-sets) of modules with [noop](https://lodash.com/docs#noop), [identity](https://lodash.com/docs#identity), or simpler alternatives. 234 | 235 | ```js 236 | const LodashModuleReplacementPlugin = require('lodash-webpack-plugin'); 237 | 238 | const config = { 239 | plugins: [ 240 | new LodashModuleReplacementPlugin({ 241 | path: true, 242 | flattening: true 243 | }) 244 | ] 245 | }; 246 | ``` 247 | 248 | This may save you >10kb depending on how much you use lodash. 249 | 250 | Requiring all files in a folder 251 | ------------------------------- 252 | 253 | Ever wanted to do this? 254 | 255 | ```js 256 | require('./behaviors/*') /* Doesn't work! */ 257 | ``` 258 | 259 | Use require.context. 260 | 261 | ```js 262 | // http://stackoverflow.com/a/30652110/873870 263 | function requireAll (r) { r.keys().forEach(r) } 264 | 265 | requireAll(require.context('./behaviors/', true, /\.js$/)) 266 | ``` 267 | 268 | > Reference: [require.context](http://webpack.github.io/docs/context.html#require-context) 269 | 270 | Clean up extract-text-webpack-plugin log 271 | ---------------------------------------- 272 | 273 | If you're seeing this in your debug log when using [extract-text-webpack-plugin](https://www.npmjs.com/package/extract-text-webpack-plugin): 274 | 275 | ``` 276 | Child extract-text-webpack-plugin: 277 | + 2 hidden modules 278 | Child extract-text-webpack-plugin: 279 | + 2 hidden modules 280 | Child extract-text-webpack-plugin: 281 | + 2 hidden modules 282 | ``` 283 | 284 | Turn it off using `stats: { children: false }`. 285 | 286 | ```js 287 | /* webpack.config.js */ 288 | stats: { 289 | children: false, 290 | }, 291 | ``` 292 | 293 | > Reference: [extract-text-webpack-plugin#35](https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/35) 294 | -------------------------------------------------------------------------------- /recipes/css.md: -------------------------------------------------------------------------------- 1 | Output CSS files 2 | ================ 3 | 4 | > __NOTE:__ This guide only applies to Webpack 2.x. 5 | 6 | Webpack supports CSS via [css-loader](https://www.npmjs.com/package/css-loader) and [style-loader](https://www.npmjs.com/package/style-loader). You can then use `require('./style.css')` with some minimal configuration. I personally don't find loading CSS via JS an ideal setup, so here's a complicated way to output `.css` files with source maps in development. 7 | 8 | > __NOTE:__ This is complicated and hard to follow along. Check [rstacruz/webpack-starter-kit](https://github.com/rstacruz/webpack-starter-kit) for an example. 9 | 10 | ## What you'll need 11 | 12 | You'll need [ExtractTextWebpackPlugin](https://www.npmjs.com/package/extract-text-webpack-plugin). Let's set up a `DEBUG` constant as described in [§ Development mode](../README.md#development-mode) so we can set up source maps only for development. 13 | 14 | ```js 15 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 16 | const DEBUG = process.env.NODE_ENV !== 'production' 17 | ``` 18 | 19 | ## Entry point 20 | 21 | First define an entry point for your CSS. This will generate `assets/css/app.css` based on `web/css/app.css`. 22 | 23 | ```js 24 | entry: { 25 | 'assets/css/app': './web/css/app.css' 26 | }, 27 | ``` 28 | 29 | ## Module rule 30 | 31 | Add a module rule to parse `*.css` files via ExtractTextPlugin, `postcss-loader` and `css-loader`. The `DEBUG` magic here will only enable source maps in development mode. 32 | 33 | ```js 34 | module: { 35 | rules: [ 36 | { 37 | test: /\.css$/, 38 | use: ExtractTextPlugin.extract({ 39 | fallback: 'style-loader', 40 | use: [ 41 | `css-loader?-url${DEBUG ? '&sourceMap&importLoaders=1' : ''}`, 42 | `postcss-loader${DEBUG ? '?sourceMap=inline' : ''}` 43 | ] 44 | }) 45 | } 46 | ] 47 | } 48 | ``` 49 | 50 | ## Extract to .css 51 | 52 | The entry point actually generates `assets/css/app.js` (JavaScript), but we want it as `app.css`. By using ExtractTextPlugin, you can move the CSS parts into a CSS file. 53 | 54 | ```js 55 | plugins: [ 56 | new ExtractTextPlugin({ 57 | filename: '[name].css', 58 | allChunks: true // preserve source maps 59 | }), 60 | ] 61 | ``` 62 | 63 | ## Source maps 64 | 65 | You need to use `devtool: 'source-map'` for CSS source maps to work. 66 | 67 | ```js 68 | devtool: DEBUG ? 'source-map' : 'hidden-source-map', 69 | ``` 70 | 71 | ## PostCSS configuration 72 | 73 | You most likely want to use PostCSS. Even if you use Less or Sass, PostCSS will let you do autoprefixing and other magic. You'll need a `postcss.config.js` configuration. In this example, I'm adding [postcss-cssnext](https://www.npmjs.com/package/postcss-cssnext), which adds automatic vendor prefixes and transpiling for support for future CSS features. 74 | 75 | ```js 76 | // postcss.config.js 77 | const DEBUG = process.env.NODE_ENV !== 'production' 78 | 79 | module.exports = { 80 | plugins: [ 81 | require('postcss-import')(), 82 | require('postcss-cssnext')(), 83 | ] 84 | } 85 | ``` 86 | --------------------------------------------------------------------------------