woojean的博客 模仿、熟练、超越

《webpack官方文档》读书笔记

2017-04-21

Concepts

Introduction

webpack is a module bundler for modern JavaScript applications.

Four Core Concepts: entry, output, loaders, and plugins.

Entry

webpack creates a graph of all of your application’s dependencies. The starting point of this graph is known as an entry point. The entry point tells webpack where to start and follows the graph of dependencies to know what to bundle.

Output

tell webpack the name of our bundle and where we want it to be emitted to.

Loaders

webpack treats every file (.css, .html, .scss, .jpg, etc.) as a module.Loaders in webpack transform these files into modules(as they are added to your dependency graph). They have two purposes:

  • Identify what files should be transformed by a certain loader. (test property)
  • Transform that file so that it can be added to your dependency graph (and eventually your bundle). (use property)

Plugins

plugins are most commonly used performing actions and custom functionality on “compilations” or “chunks” of your bundled modules.

example:webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin'); //installed via npm
const webpack = require('webpack'); //to access built-in plugins
const path = require('path');

const config = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js'
  },
  module: {
    rules: [
      {test: /\.(js|jsx)$/, use: 'babel-loader'}
    ]
  },
  plugins: [
    new webpack.optimize.UglifyJsPlugin(),
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
};

module.exports = config;

Entry Points

there are multiple ways to define the entry property in your webpack configuration.

Single Entry (Shorthand) Syntax

const config = {
  entry: './path/to/my/entry/file.js'
};

Passing an array of file paths to the entry property creates what is known as a “multi-main entry”. This is useful when you would like to inject multiple dependent files together and graph their dependencies into one “chunk”.

Object Syntax

const config = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  }
};

this is the most scalable way of defining entry/entries in your application.

Scenarios

  • 1.Separate App and Vendor Entries
    const config = {
    entry: {
      app: './src/app.js',
      vendors: './src/vendors.js'
    }
    };
    

    this tells webpack to create dependency graphs starting at both app.js and vendors.js. These graphs are completely separate and independent of each other.

  • 2.Multi Page Application
    const config = {
    entry: {
      pageOne: './src/pageOne/index.js',
      pageTwo: './src/pageTwo/index.js',
      pageThree: './src/pageThree/index.js'
    }
    };
    

    telling webpack that we would like 3 separate dependency graphs. Use CommonsChunkPlugin to create bundles of shared application code between each page.

Output

tell webpack how to write the compiled files to disk.while there can be multiple entry points, only one output configuration is specified.

Usage

The minimum requirements for the output property is to set its value to an object including two things :filename,path.

const config = {
  output: {
    filename: 'bundle.js',
    path: '/home/proj/public/assets'
  }
};

Options

  • output.chunkFilename : The filename of non-entry chunks as a relative path inside the output.path directory.(非入口文件的命名规则,在按需加载(异步)模块的时候,这样的文件是没有被列在entry中的,如使用CommonJS的方式异步加载模块)
  • output.crossOriginLoading : enables cross-origin loading of chunks.
  • output.devtoolLineToLine : Enable line-to-line mapped mode for all/specified modules.
  • output.filename : Specifies the name of each output file on disk.filename is used solely for naming the individual files.(可以指定文件的hash值,如filename: '[name].js',,可选的项包括:[name][hash][chunkhash]
  • output.hotUpdateChunkFilename : The filename of the Hot Update Chunks. They are inside the output.path directory.Default: "[id].[hash].hot-update.js".
  • output.hotUpdateFunction : The JSONP function used by webpack for async loading of hot update chunks.Default: "webpackHotUpdate".
  • output.hotUpdateMainFilename : The filename of the Hot Update Main File. Default: "[hash].hot-update.json".
  • output.jsonpFunction : The JSONP function used by webpack for async loading of chunks. Default: "webpackJsonp".(A shorter function may reduce the file size a bit. Use a different identifier when having multiple webpack instances on a single page.)
  • output.library : If set, export the bundle as library. output.library is the name.
  • output.libraryTarget : format to export the library : var,this,commonjs,commonjs2,amd,umd.
  • output.path : The output directory as an absolute path (required). ```js // config.js output: { path: “/home/proj/public/assets”, publicPath: “/assets/” }

// index.html

* `output.sourceMapFilename` : The filename of the SourceMaps for the JavaScript files.Default: `"[file].map"`.


## Loaders
Loaders are transformations that are applied on the source code of a module. 
the following specifications define the identical loader usage:
```js
{test: /\.css$/, loader: 'css-loader'}
// or equivalently
{test: /\.css$/, use: 'css-loader'}
// or equivalently
{test: /\.css$/, use: {
  loader: 'css-loader',
  options: {}
}}

Configuration

There are three ways to use loaders in your application:

  • via configuration
    module: {
      rules: [
        {
          test: /\.css$/,
          use: [
            { loader: 'style-loader'},
            {
              loader: 'css-loader',
              options: {
                modules: true
              }
            }
          ]
        }
      ]
    }
    
  • explicit in the require statement
    // Separate loaders from the resource with !
    require('style-loader!css-loader?modules!./styles.css');
    

    It’s possible to overwrite any loaders in the configuration by prefixing the entire rule with ! Options can be passed with a query parameter, just like on the web (?key=value&foo=bar). It’s also possible to use a JSON object (?{“key”:”value”,”foo”:”bar”}).

  • via CLI
    webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
    

Loader Features

  • Loaders can be chained. They are applied in a pipeline to the resource. A chain of loaders are compiled chronologically. The first loader in a chain of loaders returns a value to the next. At the end loader, webpack expects JavaScript to be returned.
  • Loaders can be synchronous or asynchronous.
  • Loaders run in Node.js and can do everything that’s possible there.
  • Loaders accept query parameters. This can be used to pass configuration to the loader.
  • Loaders can also be configured with an options object.
  • Normal modules can export a loader in addition to the normal main via package.json with the loader field.
  • Plugins can give loaders more features.
  • Loaders can emit additional arbitrary files.

Resolving Loaders

Loaders follow the standard module resolution. In most cases it will be loaders from the module path (node_modules). The loader name convention and precedence search order is defined by resolveLoader.moduleTemplates within the webpack configuration API.

Plugins

Plugins are the backbone of webpack. webpack itself is built on the same plugin system that you use in your webpack configuration! They also serve the purpose of doing anything else that a loader cannot do.

Anatomy

A webpack plugin is a JavaScript object that has an apply property. This apply property is called by the webpack compiler, giving access to the entire compilation lifecycle.

// ConsoleLogOnBuildWebpackPlugin.js
function ConsoleLogOnBuildWebpackPlugin() {

};

ConsoleLogOnBuildWebpackPlugin.prototype.apply = function(compiler) {
  compiler.plugin('run', function(compiler, callback) {
    console.log("The webpack build process is starting!!!");

    callback();
  });
};

Usage

Since plugins can take arguments/options, you must pass a new instance to the plugins property in your webpack configuration.

plugins: [
  new webpack.optimize.UglifyJsPlugin(),
  new HtmlWebpackPlugin({template: './src/index.html'})
]

Configuration

webpack’s configuration file is a JavaScript file that exports an object(it’s a standard Node.js CommonJS module,).

You should NOT use the following things. Technically you could use them, but it’s not recommended:

  • Access CLI arguments, when using the webpack CLI (instead write your own CLI, or use –env)
  • Export non-deterministic values (calling webpack twice should result in the same output files)
  • Write long configurations (instead split the configuration into multiple files)

The Simplest Configuration,Multiple Targets,Using TypeScript,Using JSX.略。

Modules

In modular programming, developers break programs up into discrete chunks of functionality called a module.

What is a webpack Module

In contrast to Node.js modules, webpack modules can express their dependencies in a variety of ways:

  • An ES2015 import statement
  • A CommonJS require() statement
  • An AMD define and require statement
  • An @import statement inside of a css/sass/less file.
  • An image url in a stylesheet (url(…)) or html (<img src=…>) file.

Supported Module Types

略。

Module Resolution

A resolver is a library which helps in locating a module by its absolute path.

webpack can resolve three kinds of file paths:

  • Absolute paths
    import "/home/me/file";
    import "C:\\Users\\me\\file";
    
  • Relative paths
    import "../src/file1";
    import "./file2";
    
  • Module paths
    import "module";
    import "module/lib/file";
    

    Modules are searched for inside all directories specified in resolve.modules. You can replace the original module path by an alternate path by creating an alias for it using resolve.alias configuration option.Once the path is resolved based on the above rule, the resolver checks to see if the path points to a file or a directory.

If the path points to a file:

  • If the path has a file extension, then the file is bundled straightaway.
  • Otherwise, the file extension is resolved using the resolve.extensions option, which tells the resolver which extensions (eg - .js, .jsx) are acceptable for resolution.

If the path points to a folder: then the following steps are taken to find the right file with the right extension:

  • If the folder contains a package.json file, then fields specified in resolve.mainFields configuration option are looked up in order, and the first such field in package.json determines the file path.
  • If there is no package.json or if the main fields do not return a valid path, file names specified in the resolve.mainFiles configuration option are looked for in order, to see if a matching filename exists in the imported/required directory .
  • The file extension is then resolved in a similar way using the resolve.extensions option.

Resolving Loaders

This follows the same rules as those specified for file resolution. But the resolveLoader configuration option can be used to have separate resolution rules for loaders.

Caching

Every filesystem access is cached, so that multiple parallel or serial requests to the same file occur faster. In watch mode, only modified files are evicted from the cache. If watch mode is off, then the cache gets purged before every compilation.

Dependency Graph

Any time one file depends on another, webpack treats this as a dependency.(This allows webpack to take non-code assets, such as images or web fonts, and also provide them as dependencies).

When webpack processes your application, it starts from a list of modules defined on the command line or in its config file. Starting from these entry points, webpack recursively builds a dependency graph that includes every module your application needs, then packages all of those modules into a small number of bundles - often, just one - to be loaded by the browser.

Targets

webpack offers multiple deployment targets that you can set in your webpack configuration. Each target has a variety of deployment/environment specific additions, support to fit its needs.

module.exports = {
  target: 'node'
};

In the example above, using node webpack will compile for usage in a Node.js-like environment (uses Node.js require to load chunks and not touch any built in modules like fs or path).

Hot Module Replacement

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.

How It Works

From The App View

  • The app code asks the HMR runtime to check for updates.
  • The HMR runtime downloads the updates (asynchronously) and tells the app code that an update is available.
  • The app code then asks the HMR runtime to apply the updates.
  • The HMR runtime applies the update (synchronously).

From The Compiler (webpack) View In addition to the normal assets, the compiler needs to emit an “update” to allow updating from previous version to the new version. The “update” consists of two parts:

  • The update manifest (JSON)
  • One or more update chunks (JavaScript) The manifest contains the new compilation hash and a list of all update chunks.Each update chunk contains code for all updated modules in the respective chunk (or a flag indicating that the module was removed).The compiler makes sure that module IDs and chunk IDs are consistent between these builds. It typically stores these IDs in memory (for example, when using webpack-dev-server), but it’s also possible to store them in a JSON file.

From The Module View HMR is an opt-in feature that only affects modules containing HMR code. One example would be patching styling through the style-loader. In order for patching to work, style-loader implements the HMR interface; when it receives an update through HMR, it replaces the old styles with the new ones. a single handler can handle an update to a complete module tree. If a single module in this tree is updated, the complete module tree is reloaded (only reloaded, not transferred).

From The HMR Runtime View 略。

What It Can Be Used For

webpack-dev-server supports a hot mode in which it tries to update with HMR before trying to reload the whole page.

Guides

Getting Started

webpack simplifies your workflow by quickly constructing a dependency graph of your application and bundling them in the right order.

略。

Installation

If you are using npm scripts in your project, npm will try to look for webpack installation in your local modules for which this installation technique is useful.

"scripts": {
    "start": "webpack --config mywebpack.config.js"
}

This is standard and recommended practice.

Note that a global webpack installation is not a recommended practice. This locks you down to a specific version of webpack and might fail in projects that use a different version.

Migrating from v1 to v2

略。

Code Splitting

split your code into various bundles which you can then load on demand.

Resource splitting for caching and parallel loads

  • Vendor code splitting : CommonsChunkPlugin.
  • CSS splitting : ExtractTextWebpackPlugin.

On demand code-splitting

While resource splitting of the previous kind requires the user to specify the split points upfront in the configuration, one can also create dynamic split points in the application code. using import() or require.ensure().

Code Splitting - CSS

Importing CSS

// Import the CSS file like a JavaScript module
import 'bootstrap/dist/css/bootstrap.css';

Using css-loader

module.exports = {
    module: {
        rules: [{
            test: /\.css$/,
            use: 'css-loader'
        }]
    }
}

As a result, the CSS is bundled along with JavaScript.This has the disadvantage that you will not be able to utilize the browser’s ability to load CSS asynchronously and parallel. Instead, your page will have to wait until your whole JavaScript bundle is loaded, to style itself.

Using ExtractTextWebpackPlugin

you can generate a new bundle specifically for all the CSS modules and add them as a separate tag in the index.html:

var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
    module: {
         rules: [{
             test: /\.css$/,
             use: ExtractTextPlugin.extract({
                 use: 'css-loader'
             })
         }]
     },
     plugins: [
         new ExtractTextPlugin('styles.css'),
     ]
}

Code Splitting - Libraries

略。

Code Splitting - Async

split your bundle into chunks which can be downloaded asynchronously at a later time.

Dynamic import: import()

The ES2015 Loader spec defines import() as method to load ES2015 modules dynamically on runtime.import() takes the module name as argument and returns a Promise: import(name) -> Promise. webpack treats import() as a split-point and puts the requested module in a separate chunk.

fully dynamic statements, such as import(foo), will fail because webpack requires at least some file location information. This is because foo could potentially be any path to any file in your system or project. The import() must contain at least some information about where the module is located, so bundling can be limited to a specific directory or set of files. For example, import(./locale/${language}.json) will cause every .json file in the ./locale directory to be bundled into the split-point.

Chunk names

Since webpack 2.4.0, chunk names for dynamic imports can be specified using a “magic comment”.

import(/* webpackChunkName: "my-chunk-name" */ 'module');

Usage with Babel

If you want to use import with Babel, you’ll need to install/add the syntax-dynamic-import plugin .

npm install --save-dev babel-core babel-loader babel-plugin-syntax-dynamic-import babel-preset-es2015

webpack.config.js

module.exports = {
  entry: './index-es2015.js',
  output: {
    filename: 'dist.js',
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /(node_modules)/,
      use: [{
        loader: 'babel-loader',
        options: {
          presets: [['es2015', {modules: false}]],
          plugins: ['syntax-dynamic-import']
        }
      }]
    }]
  }
};

Usage with Babel and async/await

npm install --save-dev babel-plugin-transform-async-to-generator babel-plugin-transform-regenerator babel-plugin-transform-runtime

index-es2017.js

async function determineDate() {
  const moment = await import('moment');
  return moment().format('LLLL');
}

determineDate().then(str => console.log(str));

webpack.config.js

module.exports = {
  entry: './index-es2017.js',
  output: {
    filename: 'dist.js',
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /(node_modules)/,
      use: [{
        loader: 'babel-loader',
        options: {
          presets: [['es2015', {modules: false}]],
          plugins: [
            'syntax-dynamic-import',
            'transform-async-to-generator',
            'transform-regenerator',
            'transform-runtime'
          ]
        }
      }]
    }]
  }
};

require.ensure()

require.ensure() is specific to webpack and superseded by import(). 略。

Building for Production

Running:

webpack -p

or:

webpack --optimize-minimize --define process.env.NODE_ENV="'production'"

This performs the following steps:

  • Minification(using UglifyJsPlugin)
  • Runs the LoaderOptionsPlugin
  • Sets the Node environment variable

Specifying –optimize-minimize on the command line, the following plugin configuration is added:

plugins:[
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: options.devtool && (options.devtool.indexOf("sourcemap") >= 0 || options.devtool.indexOf("source-map") >= 0)
    })
]

We encourage you to have Source Maps enabled in production. They are useful for debugging and to run benchmark tests. webpack can generate inline Source Maps included in the bundles or separated files.

Running webpack -p (or –define process.env.NODE_ENV=”‘production’”) invokes the DefinePlugin in the following way:

plugins:[
  new webpack.DefinePlugin({
    'process.env.NODE_ENV': JSON.stringify('production')
  })
]

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.

The manual way: Configuring webpack for multiple environments

the easiest way is to write separate js files for each environment. Have the following snippet in your webpack.config.js:

function buildConfig(env) {
  return require('./config/' + env + '.js')(env)
}

module.exports = buildConfig;

And from our package.json, where we build our application using webpack, the command goes like this:

"build:dev": "webpack --env=dev --progress --profile --colors",
"build:dist": "webpack --env=prod --progress --profile --colors",

we passed the environment variable to our webpack.config.js file. The environment variable is then passed to buildConfig method which simply loads the right js file for the build.

An advanced approach would be to have a base configuration file, put in all common functionalities, and then have environment specific files and simply use ‘webpack-merge’ to merge them. This would help to avoid code repetitions.

For example, you could have all your base configurations like resolving your js, ts, png, jpeg, json and so on.. in a common base file : base.js

module.exports = function() {
    return {
        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$/,
                use: [
                    'awesome-typescript-loader',
                    'angular2-template-loader'
                ],
                exclude: [/\.(spec|e2e)\.ts$/]
            }, {
                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 base config with an environment specific configuration file using ‘webpack-merge’.

prod.js

const webpackMerge = require('webpack-merge');

const commonConfig = require('./base.js');

module.exports = function() {
    return webpackMerge(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
            })
        ]
    })
}

Caching

To enable long-term caching of static resources produced by webpack:

  • Use [chunkhash] to add a content-dependent cache-buster to each file.
  • Extract the webpack manifest into a separate file.
  • Ensure that the entry point chunk containing the bootstrapping code doesn’t change hash over time for the same set of dependencies.

The problem

Each webpack build generates a unique hash which can be used to compose a filename, by including output placeholders.But the problem here is, builds after any file update will update all filenames and clients will have to re-download all application code.

Generating unique hashes for each file

webpack allows you to generate hashes depending on file contents, by replacing the placeholder [hash] with [chunkhash].

Don’t use [chunkhash] in development since this will increase compilation time. Separate development and production configs and use [name].js for development and [name].[chunkhash].js in production.

Get filenames from webpack compilation stats

In order to reference a correct file(with right hash) in the HTML, we’ll need information about our build. This can be extracted from webpack compilation stats by using this plugin

plugins: [
  function() {
    this.plugin("done", function(stats) {
      require("fs").writeFileSync(
        path.join(__dirname, "build", "stats.json"),
        JSON.stringify(stats.toJson()));
    });
  }
]

Alternatively, just use one of these plugins to export JSON files:

  • webpack-manifest-plugin
  • assets-webpack-plugin

Deterministic hashes

To minimize the size of generated files, webpack uses identifiers instead of module names. During compilation, identifiers are generated, mapped to chunk filenames and then put into a JavaScript object called chunk manifest. To generate identifiers that are preserved over builds, webpack supplies the NamedModulesPlugin (recommended for development) and HashedModuleIdsPlugin (recommended for production).The chunk manifest (along with bootstrapping/runtime code) is then placed into the entry chunk and it is crucial for webpack-packaged code to work.

The problem with this, is the same as before: Whenever we change any part of the code it will, even if the rest of its contents wasn’t altered, update our entry chunk to include the new manifest. we should use ChunkManifestWebpackPlugin, which will extract the manifest to a separate JSON file. This replaces the chunk manifest with a variable in the webpack runtime. But we can do even better; we can extract the runtime into a separate entry by using CommonsChunkPlugin.

the final webpack.config.js:

var path = require("path");
var webpack = require("webpack");
var ChunkManifestPlugin = require("chunk-manifest-webpack-plugin");
var WebpackChunkHash = require("webpack-chunk-hash");

module.exports = {
  entry: {
    vendor: "./src/vendor.js", // vendor reference file(s)
    main: "./src/index.js" // application code
  },
  output: {
    path: path.join(__dirname, "build"),
    filename: "[name].[chunkhash].js",
    chunkFilename: "[name].[chunkhash].js"
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: ["vendor", "manifest"], // vendor libs + extracted manifest
      minChunks: Infinity,
    }),
    new webpack.HashedModuleIdsPlugin(),
    new WebpackChunkHash(),
    new ChunkManifestPlugin({
      filename: "chunk-manifest.json",
      manifestVariable: "webpackManifest"
    })
  ]
};

The manifestVariable option is the name of the global variable where webpack will look for the manifest JSON. This should be defined before we require our bundle in HTML:

<script>
//<![CDATA[
window.webpackManifest = {"0":"main.5f020f80c23aa50ebedf.js","1":"vendor.81adc64d405c8b218485.js"}
//]]>
</script>

Development

webpack-dev-server and webpack-dev-middleware use in-memory compilation, meaning that the bundle will not be saved to disk. This makes compiling faster and results in less mess on your file system. 开发工具介绍,略。

Development - Vagrant

略。

Dependency Management

略。

Shimming

different ways to help webpack understand these broken modules. 略。

Authoring Libraries

If you are the author of a JavaScript library and are looking to streamline your bundle strategy then this document will help you. 略。

Improving Build Performance

空。

Comparison with other bundlers

略。

Handling Compatibility

空。

Using environment variables

The standard approach in Node.js modules can be applied: Set an environment variable when running webpack and refer to the variables using Node’s process.env. The variable NODE_ENV is commonly used as de-facto standard.

module.exports = {
  plugins: [
    new webpack.optimize.UglifyJsPlugin({
       compress: process.env.NODE_ENV === 'production'
    })
  ]
};

Hot Module Replacement - React

HMR is particularly useful in applications using a single state tree since components are “dumb” and will reflect the latest application state, even after their source is changed and they are replaced.

Project Config

npm install --save-dev webpack webpack-dev-server
npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react
npm install --save-dev style-loader css-loader
npm install --save react react-dom react-hot-loader@next

.babelrc:

{
  "presets": [
    ["es2015", {"modules": false}],
    // webpack understands the native import syntax, and uses it for tree shaking

    "react"
    // Transpile React components to JavaScript
  ],
  "plugins": [
    "react-hot-loader/babel"
    // Enables React code to work with HMR.
  ]
}

Setting Babel’s module plugin to false helps fix many issues.

webpack.config.js:

const { resolve } = require('path');
const webpack = require('webpack');

module.exports = {
  context: resolve(__dirname, 'src'),

  entry: [
    'react-hot-loader/patch',
    // activate HMR for React

    'webpack-dev-server/client?http://localhost:8080',
    // bundle the client for webpack-dev-server
    // and connect to the provided endpoint

    'webpack/hot/only-dev-server',
    // bundle the client for hot reloading
    // only- means to only hot reload for successful updates

    './index.js'
    // the entry point of our app
  ],
  output: {
    filename: 'bundle.js',
    // the output bundle

    path: resolve(__dirname, 'dist'),

    publicPath: '/'
    // necessary for HMR to know where to load the hot update chunks
  },

  devtool: 'inline-source-map',

  devServer: {
    hot: true,
    // enable HMR on the server

    contentBase: resolve(__dirname, 'dist'),
    // match the output path

    publicPath: '/'
    // match the output `publicPath`
  },

  module: {
    rules: [
      {
        test: /\.jsx?$/,
        use: [ 'babel-loader', ],
        exclude: /node_modules/
      },
      {
        test: /\.css$/,
        use: [ 'style-loader', 'css-loader?modules', ],
      },
    ],
  },

  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    // enable HMR globally

    new webpack.NamedModulesPlugin(),
    // prints more readable module names in the browser console on HMR updates
  ],
};
  • Setting set devServer: { hot: true } causes webpack will expose the module.hot API to our code
  • We use the module.hot hook to enable HMR for specific resources (App.js in this example). The most important property here is module.hot.accept, which specifies how to handle changes to specific dependencies.
  • Whenever src/components/App.js or its dependencies are changed module.hot.accept will fire the render method. The render method will even fire when App.css is changed because it is included in App.js.

src/index.js:

import React from 'react';
import ReactDOM from 'react-dom';

import { AppContainer } from 'react-hot-loader';
// AppContainer is a necessary wrapper component for HMR

import App from './components/App';

const render = (Component) => {
  ReactDOM.render(
    <AppContainer>
      <Component/>
    </AppContainer>,
    document.getElementById('root')
  );
};

render(App);

// Hot Module Replacement API
if (module.hot) {
  module.hot.accept('./components/App', () => {
    render(App)
  });
}

Lazy Loading - React

略。

Public Path

specify the base path for all the assets on your application.

output: {
    path: "/home/proj/cdn/assets/[hash]",
    publicPath: "http://cdn.example.com/assets/[hash]/"
}

Another possible use case is to set the public path on the fly. webpack exposes a global variable that let’s you do that, it’s called webpack_public_path. So in your application entry point, you can simply do this:

__webpack_public_path__ = process.env.ASSET_PATH;

Integration with task/test runners

webpack is a module bundler, like Browserify or Brunch. It is not a task runner. Make, Grunt, or Gulp are task runners. Task runners handle automation of common development tasks such as linting, building, or testing your project. Compared to bundlers, task runners have a higher level focus.

Often webpack users use npm scripts as their task runner. Even though webpack core focuses on bundling, you can find a variety of extensions that allow you to use it in a task runner kind of way.

Tree Shaking

先略。

webpack & Typescript

To start using webpack with Typescript you need a couple of things:

  • Install the Typescript compiler in your project.
  • Install a Typescript loader (in this case we’re using ts-loader).
  • Create a tsconfig.json file to contain our TypeScript compilation configuration.
  • Create webpack.config.js to contain our webpack configuration.
npm install --save-dev typescript ts-loader

详略。

DOCUMENTATION(Configuration)

Introduction

webpack is fed via a configuration object. All the available configuration options are specified below:

Configuration Languages

define you configuration files in any language. 略。

Configuration Types

Exporting a function to use –env

module.exports = function(env) {
  return {
    plugins: [
      new webpack.optimize.UglifyJsPlugin({
         compress: env.production // compress only in production build
      })
    ]
  };
};

Exporting a Promise

module.exports = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({
        entry: './app.js',
        /* ... */
      })
    }, 5000)
  })
}

Exporting multiple configurations

module.exports = [{
  output: {
    filename: './dist-amd.js',
    libraryTarget: 'amd'
  },
  entry: './app.js',
}, {
  output: {
    filename: './dist-commonjs.js',
    libraryTarget: 'commonjs'
  },
  entry: './app.js',
}]

Entry and Context

context

The base directory, an absolute path, for resolving entry points and loaders from configuration.By default the current directory is used.

entry

The point or points to enter the application. A dynamically loaded module is not an entry point.

If a string or array of strings is passed, the chunk is named main. If an object is passed, each key is the name of a chunk, and the value describes the entrypoint for the chunk.

Output

instructing webpack on how and where it should output your bundles, assets and anything else you bundle or load with webpack.

output.chunkFilename

determines the name of on-demand loaded chunk files.

output.crossOriginLoading

Only used when target is web, which uses JSONP for loading on-demand chunks, by adding script tags.

output.hashFunction

output.hashDigest

output.hashDigestLength

output.hashSalt

output.filename

determines the name of each output bundle. The default value is “[name].js”. notes:

  • this option is called filename but you are still allowed to something like “js/[name]/bundle.js” to create a folder structure.
  • this options does not affect output files for on-demand-loaded chunks. For these files the output.chunkFilename option is used.
  • It also doesn’t affect files created by loaders. For these files see loader options.
  • The lengths of [hash] and [chunkhash] can be specified using [hash:16] (defaults to 20). (Alternatively, specify output.hashDigestLength to configure the length globally.)
  • When using the ExtractTextWebpackPlugin, use [contenthash] to obtain a hash of the extracted file (neither [hash] nor [chunkhash] work).

output.hotUpdateChunkFilename

output.hotUpdateFunction

output.hotUpdateMainFilename

output.jsonpFunction

Only used when target is web, which uses JSONP for loading on-demand chunks. This needs to be changed if multiple webpack runtimes (from different compilation) are used on the same webpage.

output.library

output.libraryTarget

output.path

The output directory as an absolute path.

output.pathinfo

Tell webpack to include comments in bundles with information about the contained modules. This option defaults to false and should not be used in production, but it’s very useful in development when reading the generated code.

output.publicPath

specifies the public URL of the output directory when referenced in a browser. A relative URL is resolved relative to the HTML page (or tag). Server-relative URLs, protocol-relative URLs or absolute URLs are also possible and sometimes required, i. e. when hosting assets on a CDN. The value of the option is prefixed to every URL created by the runtime or loaders. the value of this option **ends with / **in most cases.The default value is an empty string “”.

The webpack-dev-server also takes a hint from publicPath, using it to determine where to serve the output files from.

output.sourceMapFilename

only used when devtool uses a SourceMap option which writes an output file.

output.sourcePrefix

Change the prefix for each line in the output bundles.

output.strictModuleExceptionHandling

Tell webpack to remove a module from the module instance cache (require.cache) if it throws an exception when it is required.It defaults to false for performance reasons.

output.umdNamedDefine

Module

determine how the different types of modules within a project will be treated.

module.noParse

Prevent webpack from parsing any files matching the given regular expression(s). Ignored files should not have calls to import, require, define or any other importing mechanism. This can boost build performance when ignoring large libraries.

noParse: /jquery|lodash/

module.rules

An array of Rules which are matched to requests when modules are created. These rules can modify how the module is created. They can apply loaders to the module, or modify the parser.

Rule

A Rule can be separated into three parts — Conditions, Results and nested Rules.

Rule conditions

There are two input values for the conditions:resource and issuer.When we import “./style.css” from app.js, the resource is /path/to/style.css and the issuer is /path/to/app.js. In a Rule the properties test, include, exclude and resource are matched with the resource and the property issuer is matched with the issuer. When using multiple conditions, all conditions must match.

Rule results

Rule results are used only when the Rule condition matches.There are two output values of a Rule:

  • Applied loaders: An array of loaders applied to the resource.
  • Parser options: An options object which should be used to create the parser for this module.

Nested rules

Nested rules can be specified under the properties rules and oneOf. These rules are evaluated when the Rule condition matches.

Rule.enforce

Specifies the category of the loader(“pre” | “post”). No value means normal loader.There is also an additional category “inlined loader” which are loaders applied inline of the import/require.

  • All loaders are sorted in the order post, inline, normal, pre and used in this order.
  • All normal loaders can be omitted (overridden) by prefixing ! in the request.
  • All normal and pre loaders can be omitted (overridden) by prefixing -! in the request.
  • All normal, post and pre loaders can be omitted (overridden) by prefixing !! in the request.
  • Inline loaders and ! prefixes should not be used as they are non-standard. They may be use by loader generated code.

Rule.exclude

is a shortcut to Rule.resource.exclude.

Rule.include

is a shortcut to Rule.resource.include.

Rule.issuer

A Condition matched with the issuer.

Rule.loader

is a shortcut to Rule.use: [ { loader } ].

Rule.loaders

is an alias to Rule.use. (It exists for compatibility reasons. Use Rule.use instead.)

Rule.oneOf

An array of Rules from which only the first matching Rule is used when the Rule matches.

Rule.options / Rule.query

are shortcuts to Rule.use: [ { options } ]. (Rule.query only exists for compatibility reasons. Use Rule.options instead.)

Rule.parser

An object with parser options. All applied parser options are merged.

Rule.resource

A Condition matched with the resource.

Rule.resourceQuery

A Condition matched with the resource query. The condition matches against a string that starts with a question mark (“?exampleQuery”).

Rule.rules

An array of Rules that is also used when the Rule matches.

Rule.test

is a shortcut to Rule.resource.test.

Rule.use

A list of UseEntries which are applied to modules. Each entry specifies a loader to be used. assing a string (i.e. use: [ "style-loader" ]) is a shortcut to the loader property (i.e. use: [ { loader: "style-loader "} ]). Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured).

Condition

Conditions can be one of these:

  • A string: To match the input must start with the provided string. I. e. an absolute directory path, or absolute path to the file.
  • A RegExp: It’s tested with the input.
  • A function: It’s called with the input and must return a truthy value to match.
  • An array of Conditions: At least one of the Condition must match.
  • A object: All properties must match. Each property has a defined behavior.

ie:

  • { test: Condition }: The Condition must match. The convention is the provide a RegExp or array of RegExps here, but it’s not enforced.
  • { include: Condition }: The Condition must match. The convention is the provide a string or array of strings here, but it’s not enforced.
  • { exclude: Condition }: The Condition must NOT match. The convention is the provide a string or array of strings here, but it’s not enforced.
  • { and: [Condition] }: All Conditions must match.
  • { or: [Condition] }: Any Condition must match.
  • { not: Condition }: The Condition must NOT match.

UseEntry

It must have a loader property being a string. It is resolved relative to the configuration context with the loader resolving options (resolveLoader).It can have a options property being a string or object. This value is passed to the loader, which should interpret it as loader options.

{
  loader: "css-loader",
  options: {
    modules: true
  }
}

Module Contexts

(Deprecated)

Resolve

change how modules are resolved.

resolve

Configure how modules are resolved. For example, when calling import “lodash” in ES2015, the resolve options can change where webpack goes to look for “lodash”.

resolve.alias

alias: {
  Utilities: path.resolve(__dirname, 'src/utilities/'),
  Templates: path.resolve(__dirname, 'src/templates/')
}

// Now, instead of using relative paths when importing like so:
import Utility from '../../utilities/utility';

// you can use the alias:
import Utility from 'Utilities/utility';

A trailing $ can also be added to the given object’s keys to signify an exact match:

alias: {
  xyz$: path.resolve(__dirname, 'path/to/file.js')
}

import Test1 from 'xyz'; // Success, file.js is resolved and imported
import Test2 from 'xyz/file.js'; // Error, /path/to/file.js/file.js is invalid

resolve.aliasFields

Specify a field to be parsed.

resolve.descriptionFiles

The JSON files to use for descriptions.

descriptionFiles: ["package.json"]

resolve.enforceExtension

If true, it will not allow extension-less files. So by default require(‘./foo’) works if ./foo has a .js extension, but with this enabled only require(‘./foo.js’) will work.

resolve.enforceModuleExtension

Whether to require to use an extension for modules .

resolve.extensions

Automatically resolve certain extensions. which is what enables users to leave off the extension when importing:

import File from '../path/to/file'

Using this will override the default array, meaning that webpack will no longer try to resolve modules using the default extensions. For modules that are imported with their extension, e.g. import SomeFile from “./somefile.ext”, to be properly resolved, a string containing “*” must be included in the array.

resolve.mainFields

this option will determine which fields in it’s package.json are checked.

resolve.mainFiles

The filename to be used while resolving directories. Default:

mainFiles: ["index"]

resolve.modules

Tell webpack what directories should be searched when resolving modules. A relative path will be scanned similarly to how Node scans for node_modules, by looking through the current directory as well as it’s ancestors (i.e. ./node_modules, ../node_modules, and on). With an absolute path, it will only search in the given directory.

defaults to:

modules: ["node_modules"]

resolve.unsafeCache

Enable aggressive, but unsafe, caching of modules.

resolveLoader

used only to resolve webpack’s loader packages.

{
  modules: ["node_modules"],
  extensions: [".js", ".json"],
  mainFields: ["loader", "main"]
}

resolveLoader.moduleExtensions

The extensions which are tried when resolving a module (e.g. loaders).

resolve.plugins

A list of additional resolve plugins which should be applied.

Whether to resolve symlinks to their symlinked location.

resolve.cachePredicate

A function which decides whether a request should be cached or not.

Plugins

customize the webpack build process in a variety of ways. webpack comes with a variety built-in plugins available under webpack.[plugin-name].

DevServer

webpack-dev-server.

devServer

This set of options is picked up by webpack-dev-server and can be used to change its behavior in various ways.

devServer: {
  contentBase: path.join(__dirname, "dist"),
  compress: true,
  port: 9000
}

devServer.clientLogLevel

Possible values are none, error, warning or info (default).

devServer.compress

Enable gzip compression for everything served.

devServer.contentBase

Tell the server where to serve content from. This is only necessary if you want to serve static files. devServer.publicPath will be used to determine where the bundles should be served from, and takes precedence. It is also possible to serve from multiple directories.

devServer.filename

This option lets you reduce the compilations in lazy mode. By default in lazy mode, every request results in a new compilation. With filename, it’s possible to only compile when a certain file is requested.If output.filename is set to bundle.js and filename is used like this:

lazy: true,
filename: "bundle.js"

It will now only compile the bundle when /bundle.js is requested. filename has no effect when used without lazy mode.

devServer.headers

Adds headers to all requests.

devServer.historyApiFallback

When using the HTML5 History API, the index.html page will likely have to be served in place of any 404 responses. Enable this by passing:

historyApiFallback: true

By passing an object this behavior can be controlled further using options like rewrites:

historyApiFallback: {
  rewrites: [
    { from: /^\/$/, to: '/views/landing.html' },
    { from: /^\/subpage/, to: '/views/subpage.html' },
    { from: /./, to: '/views/404.html' }
  ]
}

devServer.host - CLI only

Specify a host to use. By default this is localhost.

devServer.hot

Enable webpack’s Hot Module Replacement feature.

devServer.hotOnly

Enables Hot Module Replacement without page refresh as fallback in case of build failures.

devServer.https

https: {
  key: fs.readFileSync("/path/to/server.key"),
  cert: fs.readFileSync("/path/to/server.crt"),
  ca: fs.readFileSync("/path/to/ca.pem"),
}

devServer.inline

Toggle between the dev-server’s two different modes. By default the application will be served with inline mode enabled. This means that a script will be inserted in your bundle to take care of live reloading, and build messages will appear in the browser console.

It is also possible to use iframe mode, which uses an <iframe> under a notification bar with messages about the build. To switch to iframe mode:

inline: false

Inline mode is recommended when using Hot Module Replacement.

devServer.lazy

When lazy is enabled, the dev-server will only compile the bundle when it gets requested. watchOptions will have no effect when used with lazy mode.

devServer.noInfo

Errors and warnings will still be shown.

devServer.overlay

Shows a full-screen overlay in the browser when there are compiler errors or warnings.

devServer.port

Specify a port number to listen for requests on.

devServer.proxy

proxy: {
  "/api": "http://localhost:3000"
}

devServer.progress

Output running progress to console.

devServer.public

devServer.publicPath

devServer.quiet

nothing except the initial startup information will be written to the console.

devServer.setup

Here you can access the Express app object and add your own custom middleware to it. For example, to define custom handlers for some paths:

setup(app){
  app.get('/some/path', function(req, res) {
    res.json({ custom: 'response' });
  });
}

devServer.staticOptions

configure advanced options for serving static files from contentBase.

devServer.stats

precisely control what bundle information gets displayed.

devServer.watchContentBase

Tell the server to watch the files served by the devServer.contentBase option. File changes will trigger a full page reload.

devServer.watchOptions

Control options related to watching the files.

webpack uses the file system to get notified of file changes. In some cases this does not work. For example, when using Network File System (NFS). Vagrant also has a lot of problems with this. In these cases, use polling:

watchOptions: {
  poll: true  // If this is too heavy on the file system, you can change this to an integer to set the interval in milliseconds.
}

Devtool

controls if and how Source Maps are generated. 略。

Target

webpack can compile for multiple environments or targets. To understand what a target is in detail, read the concepts. 略。

Watch and WatchOptions

watch

Watch mode is turned off by default.In webpack-dev-server and webpack-dev-middleware watch mode is enabled by default.

watchOptions

A set of options used to customize watch mode:

watchOptions: {
  aggregateTimeout: 300,
  ignored: /node_modules/
  poll: 1000
}

Externals

provides a way of excluding dependencies from the output bundles.

Prevent bundling of certain imported packages and instead retrieve these external dependencies at runtime.

For example, to include jQuery from a CDN instead of bundling it:

// index.html
...
<script src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous"></script>
...

// webpack.config.js
externals: {
  jquery: 'jQuery'
}

the code shown below will still work:

import $ from 'jquery';
$('.my-element').animate(...);

externals accepts various syntax and interprets them in different manners:

  • string jQuery in the externals indicates that your bundle will need jQuery variable in the global form.

  • array
    externals: {
    subtract: ['./math', 'subtract']
    }
    
  • object
  • function
  • regex

Node

configure whether to polyfill or mock certain Node.js globals and modules. This allows code originally written for the Node.js environment to run in other environments like the browser. This feature is provided by webpack’s internal NodeStuffPlugin.

node.console

node.process

node.global

node.__filename

node.__dirname

node.Buffer

node.setImmediate

略。

Performance

allows you to control how webpack notifies you of assets and entrypoints that exceed a specific file limit.

For example if you have an asset that is over 250kb, webpack will emit a warning notifiying you of this.

performance.hints

performance.maxEntrypointSize

performance.maxAssetSize

performance.assetFilter

Stats

precisely control what bundle information gets displayed. This can be a nice middle ground if you don’t want to use quiet or noInfo because you want some bundle information, but not all of it. For webpack-dev-server, this property needs to be in the devServer object.

Other Options

This page is still a work in progress. 略。


公众号
文章目录