1. Configurations in different environments

There are two ways:

  1. The configuration file is exported based on the environment

    const path = require('path')
    const { CleanWebpackPlugin } = require('clean-webpack-plugin')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const CopyWebpackPlugin = require('copy-webpack-plugin')
    const webpack = require('webpack')
    
    const config = {
      mode: 'none'.entry: './src/main.js'.output: {
        filename: 'bundle.js'.path: path.join(__dirname, 'dist')},devtool: 'cheap-eval-module-source-map'.devServer: {
        contentBase: '/public/'.proxy: {
          '/api': {
            target: 'https://api.github.com'.pathRewrite: { '^/api': ' ' },
            changeOrigin: true}}},module: {
        rules: [{test: /.css$/,
            use: ['style-loader'.'css-loader']]}},plugins: [
        // Used to generate index.html
        new HtmlWebpackPlugin({
          title: 'Webpack Tutorials'.meta: {
            viewport: 'width=device-width'
          },
          template: './template/index.html'
        }),
        new webpack.HotModuleReplacementPlugin()
      ]
    };
    // env Environment name parameter argv passed through the CLI All parameters passed through the CLI procedure
    module.exports = (env, argv) = > {
      if (env === 'production') {
        config.mode = 'production';
        config.devtool = false;
        config.plugins = [
          ...config.plugins,
          new CleanWebpackPlugin(),
          new CopyWebpackPlugin(['public'])]}return config;
    }
    Copy the code
  2. Each environment corresponds to a configuration file

    // webpack.common.js // Basic public configuration
    // webpack.prod.js // Production environment configuration
    // webpack.dev.js // Development environment configuration
    
    // Take webpack.prod.js as an example
    const common = require('./webpack.common');
    const merge = require('webpack-merge');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    const CopyWebpackPlugin = require('copy-webpack-plugin');
    // Use merge
    // If you use object. assign, you will overwrite all attributes of the same name, which is fine for value types, but not for reference types such as plugins
    module.exports = merge(common, {
      mode: 'production'.plugins: [
        new CleanWebpackPlugin(),
        new CopyWebpackPlugin(['public'])]})Copy the code

2. DefinePlugin

DefinePlugin allows you to create a global constant that can be configured at compile time. This can be very useful when the development mode and the release mode are built to allow different behaviors. If logging is not performed in a development build but in a release build, global constants can be used to determine whether logging is performed. This is where the DefinePlugin comes in, setting it so you can forget the rules for developing and publishing builds.

Inject global members into code, enabled by default during development builds or release builds, and inject a process.env.node_env member into code that many third-party modules use to determine the current operating environment.

// webpack.config.js
const webpack = require('webpack');
module.exports = {
  mode: 'none'.entry: './src/main.js'.output: {
    filename: 'bundle.js'
  },
  plugins: [
    new webpack.DefinePlugin({ // The value of the member should be a snippet of code that complies with THE JS syntax
      API_BASE_URL: JSON.stringify('https://api.example.com')]}})Copy the code

3. Tree Shaking

Official website Description:

Tree shaking is a term used to describe removing dead-code from a JavaScript context. It relies on the statically structured features of ES2015 module syntax, such as import and export. This term and concept was actually popularized by the ES2015 module packaging tool rollup.

The official webPack 2 version has built-in support for ES2015 modules (also known as Harmony Modules) and unused module detection. The new official version of WebPack 4 extends this detection capability by using the package.json “sideEffects” attribute as a marker to provide compiler with hints as to which files in the project are “pure “. This allows you to safely delete unused portions of the file.

ES6 Module dependencies are built at compile time, not run time. Based on this feature, Webpack provides Tree Shaking. It helps us detect modules in the packaging process that are not referenced in the project. This part of the code will never be executed, hence it is called “dead code”. Webpack marks this code and removes it from the final bundle when the resource is compressed.

Tree Shaking works only for ES6 Modules. Sometimes we’ll see that the entire library is loaded even though we reference only one interface in a library, and the bundle size does not decrease because of the tree shaking. This is probably due to the fact that the library is exported in CommonJS, which is still used by most NPM packages for better compatibility. There are some NPM packages that provide both ES6 Module and CommonJS exports. If possible, we should use ES6 Module modules for tree Shaking efficiency.

Tree Shaking in Webpack is used to mark unused exported members as unused and de-export them in modules that re-export them.

module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'
  },
  optimization: {
    usedExports: true.// The module only exports used members (Webpack will identify code it thinks is not used and mark it in the initial packaging step)
    concatenateModules: true.// As far as possible, merge all modules into a single function to increase runtime efficiency and reduce code volume, a feature added in V3
    minimize: true // Tell WebPack to use TerserPlugin to compress bundles, identify unused exports, and eliminate useless code}}Copy the code

There is another problem with Tree Shaking: if you use babel-Loader, it will fail. The prerequisite for Tree Shaking is to use ES Modules to organize code. That is, the code that is packaged by WebPack must be modular using ESM, and webPack will send different files to different Loaders according to the configuration. In order to convert ECMAScript features in the code, babel-loader is mostly used to process JS. Babel may convert ES Modules to CommonJS when converting code. The code Tree Shaking handles when optimizing is not modular with ESM implementations.

We verify this with the following code and configuration:

// util.js
export const Button = () = > {
  return document.createElement('button')
  console.log('dead-code') // Test whether invalid code can be removed
}
export const Link = () = > {
  return document.createElement('a')}export const Heading = level= > {
  return document.createElement('h' + level)
}

// index.js
import { Button } from './util'
document.body.appendChild(Button())
Copy the code

Take a look at the following configuration:

module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'
  },
  module: {
    rules: [{test: /.js$/,
        use: {
          loader: 'babel-loader'.options: {
            presets: ['@babel/preset-env']}}}]},optimization: {
    // The module exports only used members
    usedExports: true}};Copy the code

Perform WebPack packing at the terminal:

UsedExports is in effect. If compressed code is enabled, unused exported code can still be removed, so Tree Shaking is not broken.

So why is that?

This is because the ES Modules conversion plug-in has been automatically turned off in the new version of babel-Loader.

Babel-loader

Webpack V2 has supported ESM and dynamic imports since its inception.

Let’s look at @babel/preset-env

ShouldTransformESM: false disables ESM transformation, so Webpack is packed with ESM code and Tree Shaking is working.

Next, we force the plugin on to test the effect:

module: {
  rules: [{test: /.js$/,
      use: {
        loader: 'babel-loader'.options: {
          presets: [['@babel/preset-env', { modules: 'commonjs' }] // Change it here}}}]}Copy the code

Then, pack and look at bundle.js:

UsedExports: true will not work, even if Tree Shaking is enabled.

The latest version of babel-loader does not cause Tree Shaking to fail. If you are not sure about the version, the easiest way is to set module to false (modules: False), which ensures that PRESET -env does not turn on the ESM transformation plug-in, and thus ensures that Tree Shaking works.

module: {
  rules: [{test: /.js$/,
      use: {
        loader: 'babel-loader'.options: {
          presets: [['@babel/preset-env', { modules: false }] // Close the ESM conversion plug-in}}}]}Copy the code

4. sideEffects

Allowing us to configure ways to flag code for side effects gives Tree Shaking more room for compression

Side effect: Code that performs special behavior when importing, rather than just exposing one or more exports. For example, polyfill, which affects the global scope, usually does not provide export. (Generally used to flag NPM packages for side effects)

Sample code is as follows:

// src/components/button.js
export default() = > {return document.createElement('button')
  console.log('dead-code')}// src/components/head.js
export default level => {
  return document.createElement('h' + level)
}

// src/components/link.js
export default() = > {return document.createElement('a')}// src/components/index.js
export { default as Button } from './button'
export { default as Link } from './link'
export { default as Heading } from './head'

// src/index.js
import { Button } from './components'
document.body.appendChild(Button())
Copy the code
// webpack.config.js
module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'
  },
  module: {},
  optimization: {}}Copy the code

Perform webpack packaging and then look at dist/bundle.js and you’ll see that all the component modules are packaged into bundle.js.

Next, open sideEffects:

// webpack.config.js
module.exports = {
  mode: 'none'.entry: './src/index.js'.output: {
    filename: 'bundle.js'
  },
  module: {},
  optimization: {
    sideEffects: true // Check for sideEffects in package.json of the module where the current code is located}}Copy the code

Add sideEffects properties to package.json:

// package.json
{
  // ...
  "sideEffects": false // indicate that all code in the project affected by package.json has no side effects
}
Copy the code

At this point, when you run webpack packaging and look at dist/bundle.js, you’ll see that unused modules are not packaged.

To further modify the example:

Add SRC /extend.js SRC /global.css:

// src/extend.js
// Add an extension method to the prototype for Number
Number.prototype.pad = function (size) {
  // Convert numbers to strings => '8'
  let result = this + ' '
  // Add a 0 before a number => '008'
  while (result.length < size) {
    result = '0' + result
  }
  return result
}

// src/global.css
body {
  background-color: #fff;
}
Copy the code

Modify the SRC/index. Js:

// src/index.js
import { Button } from './components'

// The style file belongs to the side effects module
import './global.css'

// Side effects module
import './extend'

console.log((3).pad(4))

document.body.appendChild(Button())
Copy the code

At this point, all code is still marked with no side effects:

// package.json
{
  // ...
  "sideEffects": false // indicate that all code in the project affected by package.json has no side effects
}
Copy the code

Perform webpack packing and then look at dist/bundle.js and you’ll see that the exten.js global.css introduced is not packaged into bundle.js.

If you want to package extension code and global styles into bundle.js, the solution is configured in sideEffects:

// package.json
{
  / /... Other key: value
  "sideEffects": [
    "./src/extend.js"."*.css"]}Copy the code

Do the webpack here and then look at dist/bundle.js and you’ll see that the exten.js global.css modules with side effects are packaged into bundle.js.

5. Code segmentation

5.1 Multi-entry Packaging

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  mode: 'none'.entry: {
    index: './src/index.js'.album: './src/album.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  module: {
    rules: [{test: /\.css$/,
        use: [
          'style-loader'.'css-loader']]}},plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Multi Entry'.template: './src/index.html'.filename: 'index.html'.chunks: ['index']}),new HtmlWebpackPlugin({
      title: 'Multi Entry'.template: './src/album.html'.filename: 'album.html'.chunks: ['album']]}})Copy the code

There are likely to be public modules in different entry points, and the same module may appear in different packaging results according to the above configuration of multiple entry points, so public modules should be extracted:

module.exports = {
  // Other configurations
  optimization: {
    splitChunks: {
      // Automatically extract all public modules into separate bundles
      chunks: 'all'}}}Copy the code

5.2 Dynamic Import

All dynamically imported modules are automatically extracted into a separate bundle, enabling subcontracting, which is more flexible than multi-entry.

// After adding the webpackChunkName annotation, the dynamically imported module's packaged name is the name provided in the annotation
import(/* webpackChunkName: 'chunkName' */'Module path').then(({ default: module }) = > {
  // Execute logic after dynamic import
})
Copy the code

6. MiniCssExtractPlugin

A plug-in that extracts CSS from the packaging result (extracts CSS into a separate file, enabling on-demand loading of CSS modules)

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  mode: 'none'.entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  module: {
    rules: [{test: /\.css$/,
        use: [
          // 'style-loader', // to inject styles into HTML via the style tag
          MiniCssExtractPlugin.loader, // Use this loader instead of style-loader
          'css-loader']]}},plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Dynamic import'.template: './src/index.html'.filename: 'index.html'
    }),
    new MiniCssExtractPlugin()
  ]
}

Copy the code

7. OptimizeCssAssetsWebpackPlugin

Compress the output CSS file

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')

module.exports = {
  mode: 'none'.entry: {
    main: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js'
  },
  optimization: {
    minimizer: [ // WebPack thinks we need to customize the compressor plug-in to use when configuring the array
      new TerserWebpackPlugin(), // If this parameter is not added, the packed JS file will not be compressed
      new OptimizeCssAssetsWebpackPlugin()
    ]
  },
  module: {
    rules: [{test: /\.css$/,
        use: [
          // 'style-loader', // inject the style through the style tag
          MiniCssExtractPlugin.loader,
          'css-loader']]}},plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Dynamic import'.template: './src/index.html'.filename: 'index.html'
    }),
    new MiniCssExtractPlugin()
  ]
}
Copy the code

8. Output the file name Hash

To resolve the problem caused by static resource cache setting, you are advised to use Hash for the file name output in production mode

Webpack’s output.filename and the filename attribute of most plug-ins support placeholders to hash file names

The following three types are supported:

  • Filename: ‘[name].[hash].bundle.js’ Specifies the hash of the entire project. Any change in the project will change the packaged hash

  • Filename: ‘[name].[chunkhash].bundle.js’ Chunk-level hash. All chunkhash packets on the same route are the same

  • Filename: ‘[name].[contenthash].bundle.js’ File level hash, which is generated based on the content of the output file, that is, different files will have different hash

    Contenthash is the best way to solve the cache problem because it pinpoints the file level.

    [name].[contenthash:8].bundle.js

This article is part of the “Gold Nuggets For Free!” Activity, click to viewEvent details

Reference: Webpack Chinese