Webpack optimized development experience

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 / / # sourceMappingURL=/path/to/file.js.map at the end of the converted code

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: it can only locate files without generating source map files. It is fast to build and cannot know row and column information
  • Eval source map: you can locate files and row and column information
  • Eval heap 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", () => {
    // This function is automatically executed when editor.js is updated
    // 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", () => {
    // Execute after better.png is updated
    // 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 and use webpack merge to merge the same configurations

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

DefinePlugin allows 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 replace quotation marks with something like 'production', or use JSON.stringify('production ') directly.

Tree Shaking

Describes removing unreferenced code from a JavaScript context. It is enabled by default in production mode.

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

Using the "sideEffects" attribute of package.json as a tag, the compiler is prompted to indicate which files in the project are "pure (pure ES2015 module)", so that unused parts in 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 plugin uses terser to compress JavaScript
  • Optimize CSS assets webpack plugin: optimize and compress CSS using cssnano
  • Mini CSS extract plugin: this plugin 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",
  },

Keywords: Webpack

Added by Mastodont on Tue, 30 Nov 2021 09:57:58 +0200