Webpack optimized development experience

Time:2021-12-30

Webpack Dev Server

It integrates the functions of automatic compilation and automatic refresh of browser. The webpack dev server does not write to any output files after compilation, but keeps the bundle files in memory and then serves them to the server.

yarn add webpack-dev-server --dev
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  mode: "development",
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.png$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10 * 1024,
          },
        },
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Ideal Webpack Develop Env",
      meta: {
        viewport: "width=device-width",
      },
      template: "./src/index.html",
    }),
    //It is better not to use this plug-in in the development stage and copy static resource files frequently
    // new CopyWebpackPlugin({
    //   patterns: ["public"],
    // }),
  ],
  devServer: {
    //Specify additional static resource file path
    contentBase: ["./public"],
    proxy: {
      "/api": {
        // http://localhost:8080/api/users -> https://api.github.com/api/users
        target: "https://api.github.com",
        // http://localhost:8080/api/users -> https://api.github.com/users
        pathRewrite: {
          "^/api": "",
        },
      },
    },
  },
};
yarn webpack serve --open

Source Map

A source map is an information file that stores location information. That is, each position of the converted code corresponds to the position before conversion.

With it, when an error occurs, the debugger will directly display the original code instead of the converted code. This undoubtedly brings great convenience to developers.

To enable source map, you only need to add at the end of the converted code//# sourceMappingURL=/path/to/file.js.map

Source map in webpack

const path = require("path");

module.exports = {
  mode: "development",
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "dist"),
  },
  devtool: "source-map",
};

Common mode:

  • eval: only files can be located, and source map files are not generated. The construction speed is fast, and row and column information cannot be known
  • eval-source-map: you can locate files and row and column information
  • eval-cheap-source-map: only line information can be located, and the code is transformed by loader
  • eval-cheap-module-source-map: source code of development environment

HMR(Hot Module Replacement)

HMR allows all types of modules to be updated at run time without a full refresh

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  mode: "development",
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "dist"),
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /.png$/,
        use: {
          loader: "url-loader",
          options: {
            limit: 10 * 1024,
          },
        },
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Ideal Webpack Develop Env",
      meta: {
        viewport: "width=device-width",
      },
      template: "./src/index.html",
    }),
  ],
  devServer: {
    hot: true,
    //Hotonly: true // only use HMR, not fallback to live reloading
  },
  devtool: "source-map",
};

Because CSS has uniform rules, you only need to override the replacement style, and style loader has implemented the hot replacement of HMR. JS files are not complex and diverse, and there are no unified rules, so we need to implement them according to the actual situation

import "./main.css";
import createEditor from "./editor";

const editor = createEditor();
document.body.appendChild(editor);

// ================================================================
//HMR manual processing module hot update
//Don't worry about the redundancy of these codes in the production environment, because after packaging through webpack,
//All the code will be removed, which is only used in the development phase
if (module.hot) {
  let hotEditor = editor;
  module.hot.accept("./editor.js", () => {
    //When editor JS update to automatically execute this function
    //Temporary record editor content
    const value = hotEditor.innerHTML;
    //Remove pre update elements
    document.body.removeChild(hotEditor);
    //Create a new editor
    //At this point, createeditor is the updated function
    hotEditor = createEditor();
    //Restore editor content
    hotEditor.innerHTML = value;
    //Append to page
    document.body.appendChild(hotEditor);
  });

  module.hot.accept("./better.png", () => {
    //When better Execute after PNG update
    //Overriding the setting SRC triggers the picture element to reload, thereby locally updating the picture
    img.src = background;
  });

  //The style loader automatically processes the updated styles internally, so there is no need to manually process the style module
}

Configuration in different environments

Create different configurations for different environments, usingwebpack-mergeMerge the same configuration

webpack.common.js

const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/main.js",
  output: {
    filename: "js/bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.(png|jpe?g|gif)$/,
        use: {
          loader: "file-loader",
          options: {
            outputPath: "img",
            name: "[name].[ext]",
          },
        },
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: "Webpack Tutorial",
      template: "./src/index.html",
    }),
  ],
};

webpack.dev.js

const webpack = require("webpack");
const { merge } = require("webpack-merge");
const common = require("./webpack.common");

module.exports = merge(common, {
  mode: "development",
  devtool: "source-map",
  devServer: {
    hot: true,
    contentBase: "public",
  },
  plugins: [new webpack.HotModuleReplacementPlugin()],
});

webpack.prod.js

const { merge } = require("webpack-merge");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const common = require("./webpack.common");

module.exports = merge(common, {
  mode: "production",
  plugins: [new CleanWebpackPlugin(), new CopyWebpackPlugin(["public"])],
});

Execute package command

yarn webpack --config webpack.dev.js

DefinePlugin

DefinePluginAllows you to replace variables in your code with other values or expressions at compile time. This is very useful when different operations need to be carried out according to the development mode and production mode. For example, replace the domain name

new webpack.DefinePlugin({
      //Value requires a snippet of code
      API_BASE_URL: JSON.stringify("https://api.example.com"),
    }),

Note that since this plug-in will directly replace the text, the provided value must contain an actual quotation mark in the string itself. In general, you can use similar'"production"'Replace quotation marks like this, or use them directlyJSON.stringify('production')

Tree Shaking

Describes removing unreferenced code from a JavaScript context.productionMode is enabled by default.

const path = require("path");
const webpack = require("webpack");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  mode: "none",
  entry: "./src/main.js",
  output: {
    filename: "bundle.js",
    path: path.join(__dirname, "dist"),
  },
  optimization: {
    //The module exports only the used members
    usedExports: true,
    //Compressed output results
    minimize: true,
  },
};

sideEffects

adoptpackage.jsonof"sideEffects"Attribute is used as a tag to provide a prompt to the compiler to indicate which files in the project are “pure (pure es2015 module)”, so that unused parts of the file can be safely deleted.

{
  "sideEffects": false
}

If your code does have some side effects, you can provide an array instead:

{
  "sideEffects": ["./src/some-side-effectful-file.js"]
}

“Side effect” is defined as code that performs special behavior during import, rather than exposing only one or more exports. For example, Polyfill affects the global scope and usually does not provide export.

Code subcontracting

Multi entry packaging

It is applicable to multi page packaging. One page corresponds to one packaging entry to extract the public part

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",
  },
  optimization: {
    splitChunks: {
      //Automatically extract all common modules to a separate bundle
      chunks: "all",
    },
  },
  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"],
    }),
  ],
};

Dynamic import

Dynamically imported modules will be automatically subcontracted to realize on-demand loading

Magic note: define the name of the subcontracting bundle

// import posts from './posts/posts'
// import album from './album/album'

const render = () => {
  const hash = window.location.hash || "#posts";

  const mainElement = document.querySelector(".main");

  mainElement.innerHTML = "";

  if (hash === "#posts") {
    // mainElement.appendChild(posts())
    import(/* webpackChunkName: 'components' */ "./posts/posts").then(
      ({ default: posts }) => {
        mainElement.appendChild(posts());
      }
    );
  } else if (hash === "#album") {
    // mainElement.appendChild(album())
    import(/* webpackChunkName: 'components' */ "./album/album").then(
      ({ default: album }) => {
        mainElement.appendChild(album());
      }
    );
  }
};

render();

window.addEventListener("hashchange", render);

CSS file compression subcontracting

yarn add terser-webpack-plugin optimize-css-assets-webpack-plugin mini-css-extract-plugin --dev
  • terser-webpack-plugin: this plug-in uses terser to compress JavaScript
  • optimize-css-assets-webpack-plugin: optimize and compress CSS using cssnano
  • mini-css-extract-plugin: this plug-in will extract CSS into a separate file, create a CSS file for each JS file containing CSS, and support on-demand loading of CSS and sourcemaps
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: [
      //Declaring an array will make webpack think that we need custom compression, so we need to declare the compression of JS files ourselves
      new TerserWebpackPlugin(),
      new OptimizeCssAssetsWebpackPlugin(),
    ],
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          //'style loader ', // inject styles through style tags
          MiniCssExtractPlugin.loader,
          "css-loader",
        ],
      },
    ],
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: "Dynamic import",
      template: "./src/index.html",
      filename: "index.html",
    }),
    new MiniCssExtractPlugin(),
  ],
};

Hash file name

File name dependent hash cache.

At the project level, any change to the project will change

  output: {
    filename: "[name]-[hash].js",
  },

At the chunk level, changes to the same chunk will reference changes to the same chunk

  output: {
    filename: "[name]-[chunkhash].js",
  },

At the file level, different files have different hash values, and the hash length is 8 bits

  output: {
    filename: "[name]-[contenthash:8].js",
  },

Recommended Today

JS generate guid method

JS generate guid method https://blog.csdn.net/Alive_tree/article/details/87942348 Globally unique identification(GUID) is an algorithm generatedBinaryCount Reg128 bitsNumber ofidentifier , GUID is mainly used in networks or systems with multiple nodes and computers. Ideally, any computational geometry computer cluster will not generate two identical guids, and the total number of guids is2^128In theory, it is difficult to make two […]