# webpack 5 从零开始构建项目

webpack于两个月前(今年10月)发布,距离上一次大版本升级已经过去两年了,目测本文有效期也可以挺个两年。

本文基于webpack5从零开始构建一个项目,梳理一下整个webpack构建流程,中间可能提及与webpack4的差异,顺便搞清楚自己一直以来对webpack理解模糊的点。

# 初始化项目

以React项目为例,新建项目文件夹并初始化。

// 创建文件夹
mkdir react-app
cd react-app
npm init

一路回车到底,会生成一个package.json文件

新建入口文件app.js和HTML模板index.html

// app.js
import React from 'react';
import ReactDOM from 'react-dom';

const App = () => {
  return <div>webpack5</div>
}

ReactDOM.render(<App />, document.querySelector("#app"));
// index.html
<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <title>webpack 5 从零开始构建项目</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

安装react依赖,以及加载less文件所需的loader

npm i react react-dom style-loader css-loader less less-loader -S

安装开发工具依赖

npm i webpack webpack-cli -D

开发环境需要用到devServer

npm i @webpack-cli/serve webpack-dev-server -D

安装babel系列,转义ES6和React

npm i @babel/core @babel/preset-env @babel/preset-react babel-loader babel-preset-react -D

安装html-webpack-plugin,从模板文件生成HTML

npm i html-webpack-plugin -D

创建.babelrc文件并配置

{
  "presets": [
    "@babel/preset-react"
  ]
}

# 开始配置webpack.config.js

// webpack.config.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./app.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "webpack.bundle.js",
  },
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          {
            loader: "style-loader",
          },
          {
            loader: "css-loader",
          },
          {
            loader: "less-loader",
            options: {
              lessOptions: {
                strictMath: true,
              },
            },
          },
        ],
      },
      {
        test: /\.m?js$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [["@babel/preset-env", { targets: "defaults" }]],
            cacheDirectory: true,
          },
        },
      },
    ],
  },
  plugins: [
    // 根据指定模板文件生成HTML
    new HtmlWebpackPlugin({
      template: "./index.html",
    }),
    // 开启热替换
    new webpack.HotModuleReplacementPlugin(),
  ],
};

然后,在package.json里,配置scripts命令

"dev": "webpack serve --config ./webpack.config.js"

webpack serve会自动启动DevServer服务,不需要在webpack.config.js里做任何配置。

运行项目,本地服务默认端口为8080

npm run dev

如果提示端口占用,配置另一个端口即可

devServer: {
  port: 9000
}

# DllPlugin 单独编译第三方库

npm run dev编译结果提示文件大小超出244kb,可能影响性能,需要将大文件拆分,怎么拆?

项目中,react、react-dom等npm包很少变更,不需要每次都重复编译,因此将这类包单独编译成一个文件。DllPlugin就是用来干这个活的。

DllPlugin与DllReferencePlugin配合使用,DllPlugin创建一个manifest.json文件供DllReferencePlugin提供映射依赖关系使用。

DllPlugin在webpack.dll.config.js中配置,DllReferencePlugin在webpack.config.js中配置。

另外,开启代码压缩,webpack4代码压缩插件是new webpack.optimize.UglifyJsPlugin(),不支持ES6,在压缩某些包的时候会报以下错误:

Unexpected token: punc (()

在webpack5中,内置代码压缩插件 terser-webpack-plugin,支持ES6,所以直接用它就好。

// webpack.dll.config.js
const vendors = ["react", "react-dom"];
const webpack = require("webpack");
const path = require("path");
const TerserPlugin = require("terser-webpack-plugin");
// 注意:output.library和DllPlugin配置的name必须完全一样
module.exports = {
  output: {
    path: path.resolve('./static/js'),
    filename: "[name].dll.js?v=[hash]",
    library: "[name]",
  },
  entry: {
    dll_base: vendors,
  },
  optimization: {
    // 代码压缩
    minimize: true,
    minimizer: [
      new TerserPlugin({
        // 开启多进程并行构建
        parallel: true,
      }),
    ],
  },
  plugins: [
    // 生成映射文件给DllReferencePlugin使用
    new webpack.DllPlugin({
      path: path.resolve(__dirname, "./manifest.base.json"),
      name: "[name]",
      context: __dirname,
    }),
  ],
};

webpack.config.js增加一个插件配置

plugins: [
  // 把只有 dll 的 bundle(们)引用到需要的预编译的依赖
  new webpack.DllReferencePlugin({
    context: __dirname,
    manifest,
    name: "dll_base",
  }),
]

在package.json中增加scripts命令

"dll": "webpack --config ./webpack.dll.config.js",

执行npm run dll会生成dll_base.dll.js,手动在HTML模板里引入

<scsript src="./static/js/dll_base.dll.js"></script>

开发过程中,定位报错的代码,发现是编译之后的,不利于调试,开启source-map:

devtool: "eval-source-map"

devtool有多种可选值 (opens new window),兼顾编译速度与可调试性,选择"eval-source-map"

到目前为止,已经构建了一个最基本的项目的开发环境。

上次更新: 10/23/2021, 10:33:11 PM