# webpack 基础

环境:webpack 5, node.js 16+

# 1. 前言

# 1.1. 为什么需要打包工具?

开发时,我们会使用框架(React、Vue)、ES6(语法、API)、CSS 预处理器(Less/Sass/Stylus)等等 进行开发。

这样的代码要想在浏览器运行,必须将其编译成浏览器能识别的 JS、CSS 文件。

这些重复繁琐的工作交给构建工具正好合适。

除此之外,打包工具还能压缩代码、做兼容性处理、提升代码性能等。

# 1.2. 有哪些打包工具?

  • Grunt
  • Gulp
  • Parcel
  • webpack
  • Rollup
  • Vite
  • ...

目前市面上最流行的是 webpack,所以我们主要以 webpack 来介绍使用打包工具

# 2. 基本使用

webpack 是一个静态资源打包工具。

它会以一个或多个文件作为打包的入口,将我们整个项目所有涉及的文件编译组合成一个或多个文件。

webpack 输出的文件叫做 bundle,可以在浏览器端运行。

# 2.1. 功能介绍

webpack 本身功能是有限的:

  • 开发模式:只能处理 ES Module
  • 生产模式:处理 ES Module,压缩 JS 代码

# 2.2. 开始使用

# 2.2.1. 资源目录

webpack_code          # 项目根目录(所有指令必须在这个目录运行)
    └── src           # 项目源码目录
    │   ├── js        # js文件目录
    │   │   ├── subtract.js
    │   │   └── sum.js
    │   └── main.js   # 项目主文件
    └── public
        ├── index.html

# 2.2.2. 创建文件

// subtract.js:
export default function (num1, num2) {
  return num1 - num2;
}


// sum.js:
export default (...nums) => {
  return nums.reduce(
    (prevRes, curr) => (prevRes + curr), 0
  );
};


// main.js:
import sum from "./js/sum";
import subtract from "./js/subtract";

console.log( sum(1, 2, 3) );
console.log( subtract(10, 9) );

index.html:

<h1>hello webpack</h1>
<script src="../dist/main.js"></script>

# 2.2.3. 下载依赖

  1. 打开终端,切换到项目根目录

  2. 创建 package.json 文件

    npm init -y
    
    • package.jsonname 字段不能叫做 webpack, 否则会报错
  3. 安装依赖

    npm i webpack webpack-cli -D
    
    • 安装包及版本:
    "webpack": "^5.79.0",
    "webpack-cli": "^5.0.1"
    

# 2.2.4. 启用 webpack

## 开发模式:
npx webpack ./src/main.js --mode=development

## 生产模式:
npx webpack ./src/main.js --mode=production

说明:

  • npx webpack: 是用来运行本地安装 webpack 包的。
  • ./src/main.js: 指定 webpack 从 main.js 文件开始打包。
  • --mode=development:指定模式(环境)。

默认 webpack 会将文件打包输出到 dist 目录下

# 2.3. 小结

webpack 本身功能比较少,只能处理 js 资源,一旦遇到 css 等其他资源就会报错。

我们学习 webpack,就是主要学习如何处理其他资源。

# 3. 基本配置

# 3.1. 五大核心概念

核心 说明
entry(入口) 指示 webpack 从哪个文件开始打包
output(输出) 指示 webpack 打包完的文件输出到哪里去,如何命名等
loader(加载器) webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,webpack 才能解析
plugins(插件) 扩展 webpack 的功能
mode(模式) 开发模式,development ;生产模式,production

# 3.2. webpack 配置文件

默认位置: 项目根目录

默认名称: webpack.config.js

简单配置:

const path = require("path");

module.exports = {
  // 入口:相对路径和绝对路径都行
  entry: "./src/main.js",

  // 输出
  output: {
    // path: 文件输出目录,必须是绝对路径
    path: path.resolve(__dirname, "dist"),

    // filename: 输出文件名
    filename: "main.js",
  },

  // 加载器
  module: {
    rules: [],
  },

  // 插件
  plugins: [],

  // 模式
  mode: "development", // 开发模式
};

运行指令:

npx webpack

# 4. 处理样式资源

css、less、sass、scss、styl

说明:webpack 不能识别样式文件,需要借助 loader 来解析

参考:webpack 官方 loader 文档 (opens new window)

安装包及版本:

"css-loader": "^6.7.3",
"less-loader": "^11.1.0",
"sass": "^1.62.0",
"sass-loader": "^13.2.2",
"style-loader": "^3.3.2",
"stylus-loader": "^7.1.0",

# 4.1. css

安装:

# 把 CSS 插入到 DOM 中。
npm i -D style-loader

# css-loader: 将 css 文件编译成 webpack 能识别的模块(cjs)
npm i -D css-loader 

配置:

module.rules = [
  {
    test: /\.css$/, // 匹配:文件名以 .css 结尾的
    use: ["style-loader", "css-loader"], // loader 执行顺序是从右到左
  },
  // ...
]

参考:css-loader —— webpack5 (opens new window)

# 4.2. less

安装:

# 将 less 文件编译成 css 文件
npm i -D less-loader

配置:

module.rules = [
  {
    test: /\.less$/,
    use: ["style-loader", "css-loader", "less-loader"],
  }
  // ...
];

参考:less-loader —— webpack5 (opens new window)

# 4.3. sass、scss

安装:

# sass-loader 依赖 sass
npm i -D sass

# 将 sass 文件编译成 css 文件
npm i -D sass-loader

配置:

module.rules = [
  {
    test: /\.s[ac]ss$/,
    use: ["style-loader", "css-loader", "sass-loader"],
  },
  // ...
]

参考:sass-loader —— webpack5 (opens new window)

# 4.4. styl

安装:

# 将 styl 文件编译成 css 文件
npm i -D stylus-loader

配置:

module.rules = [
  {
    test: /\.styl$/,
    use: ["style-loader", "css-loader", "stylus-loader"],
  }
  // ...
]

参考:stylus-loader —— webpack5 (opens new window)

# 5. 处理图片资源

webpack 4 处理资源,需要以下 loader:

  • raw-loader: to import a file as a string
  • url-loader: to inline a file into the bundle as a data URI
  • file-loader: to emit a file into the output directory

webpack 5 已内置上面的 loader,只需要配置即可:

  • asset/resource: emits a separate file and exports the URL. Previously achievable by using file-loader.
  • asset/inline: exports a data URI of the asset. Previously achievable by using url-loader.
  • asset/source: exports the source code of the asset. Previously achievable by using raw-loader.
  • asset: automatically chooses between exporting a data URI and emitting a separate file. Previously achievable by using url-loader with asset size limit.

配置:

module.rules = [
  {
    test: /\.(png|jpe?g|gif|webp|svg)$/,
    type: "asset",
    parser: {
      dataUrlCondition: {
        /*
          小于 10kb 的图片会转成 base64
          优点:减少 HTTP 请求。
          缺点:体积会增大一点点
        */ 
        maxSize: 10 * 1024, 
      }
    }
  },
  // ...
],

备注:

  • 就算不配置,webpack 默认会使用 asset/resource 进行处理

参考:asset-modules —— webpack5 (opens new window)

# 6. 修改输出资源的名称和路径

打包后,dist 目录的结构:

├── dist
    └── static
         ├── imgs
         │    └── 7003350e.png
         └── js
              └── main.js

配置:

module.exports = {
  entry: "./src/main.js",
  output: {
    path: path.resolve(__dirname, "dist"),
    /*
      将 js 文件输出到 static/js 目录中
    */
    filename: "static/js/main.js", 
  },
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif|webp)$/,
        type: "asset",
        parser: { dataUrlCondition: { maxSize: 10 * 1024 } },
        generator: {
          /* 
            将图片文件输出到 static/imgs 目录中

            [hash:8]: hash 值取前 8 位
            [ext]:  使用之前的文件扩展名
            [query]: 添加之前的query参数
          */
          filename: "static/imgs/[hash:8][ext][query]",
        },
      },
    ],
  },
};

# 7. 自动清空上次打包资源

配置:

module.exports = {
  output: {
    /*
      Clean the output directory before emit. 
      在生成文件之前清空 output.path 目录
    */
    clean: true, 
  }
}

参考:output.clean (opens new window)

# 8. 处理字体图标资源

配置:

module.rules = [
  {
    test: /\.(ttf|woff2?)$/,
    type: "asset/resource", // 原封不动的输出
    generator: {
      filename: "static/media/[hash:8][ext][query]",
    },
  },
  // ...
]

# 9. 处理其他资源

不需要对内容进行特殊处理的资源,原封不动输出即可,如:

  • 音频:mp3, wma
  • 视频:mp4, avi
  • 文档:Excel, Word, PDF, ...

与 “处理字体图标资源” 的方式一样:

module.rules = [
  {
    test: /\.(ttf|woff2?|mp4|mp3|avi)$/,
    type: "asset/resource",
    generator: {
      filename: "static/media/[hash:8][ext][query]",
    },
  },
  // ...
]

备注:

  • 媒体资源一般存放在服务器,不需要前端处理

# 10. 处理 JS 资源

webpack 只能处理 JS 的模块化语法(ESM、CommonJS),我们需要:

  • babel: 将高版本 ES 语法 转为 低版本 ES 语法
  • eslint: 语法风格检查

# 10.1. eslint

# 10.1.1. 配置文件

有两种方式:

  1. 创建额外的配置文件

    • .eslintrc
    • .eslintrc.json
    • ...
    • .eslintrc.js
  2. 在 package.json 的 eslintConfig 属性中进行配置

    {
      "name": "my-package",
      "version": "1.0.0",
      "eslintConfig": {
        "extends": [],
        "env": {},
        "parserOptions": {},
        "rules": {},
      }
    }
    

# 10.1.2. 具体配置

.eslintrc.js:

module.exports = {
  // 继承其他规则
  extends: [
    'eslint:recommended',
  ],
  
  /*
    An environment provides predefined global variables.

    https://eslint.org/docs/latest/use/configure/language-options
  */
  env: {
    browser: true, // browser global variables
    node: true,    // Node.js global variables and Node.js scoping.
    es2022: true,  //  adds all ECMAScript 2022 globals and automatically sets the ecmaVersion parser option to 13.
    jquery: false, // jQuery global variables
  },

  /*
    Specifying Parser Options

    https://eslint.org/docs/latest/use/configure/language-options#specifying-parser-options
  */
  parserOptions: {
    ecmaVersion: 'latest', // use the most recently supported version 
    sourceType: 'module',  // your code is in ECMAScript modules
  },

  /*
    Configure Rules

      "off" or 0 - turn the rule off
      "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code)
      "error" or 2 - turn the rule on as an error (exit code is 1 when triggered)

    https://eslint.org/docs/latest/use/configure/rules
  */
  rules: {
    "eqeqeq": "warn",
  },

};

# 10.1.3. 忽略文件

说明:

  • 在 webpack 配置中已经指定了 eslint 的校验目录为 src 目录
  • 忽略文件主要是给 IDE 用的,避免其对 dist 目录的文件进行校验

.eslintignore :

dist/

参考:eslintignore的忽略配置 (opens new window)

# 10.1.4. 在 webpack 中使用

说明:(启用 eslint)

  • webpack 4 中使用的是 loader
  • webpack 5 中使用的是 plugin

安装:

npm i -D eslint-webpack-plugin eslint

## "eslint-webpack-plugin": "^4.0.1",
## "eslint": "^8.38.0",

配置:

const ESLintWebpackPlugin = require("eslint-webpack-plugin");

module.exports = {
  // ...
  plugins: [
    new ESLintWebpackPlugin({
      // A string indicating the root of your files.
      context: path.resolve(__dirname, "src"),
      // Will cause the module build to fail if there are any errors, default true
      failOnError: false,

      // Specify the files and/or directories to exclude. Must be relative to options.context.
      exclude: 'node_modules', // Default: 'node_modules'
    }),
  ],
}

参考:eslint-webpack-plugin (opens new window)

# 10.2. babel

# 10.2.1. 配置文件

有两种方式:

  1. 创建额外的配置文件
  • .babelrc
  • .babelrc.js
  • .babelrc.json
  • babel.config
  • babel.config.json
  • babel.config.js
  1. 在 package.json 的 babel 属性中进行配置

    {
      "name": "my-package",
      "version": "1.0.0",
      "babel": {
        "presets": [ ... ],
        "plugins": [ ... ],
      }
    }
    

# 10.2.2. 具体配置

babel.config.js:

module.exports = {
  /*
    预设:一组 babel 插件,扩展 babel 功能,类比 eslint 的 extends 属性

    Babel presets can act as sharable set of Babel plugins and/or config options.

    Official Presets:
      @babel/preset-env for compiling ES2015+ syntax
      @babel/preset-typescript for TypeScript
      @babel/preset-react for React
      @babel/preset-flow for Flow
  */
  presets: [ '@babel/preset-env' ],
  plugins: []
};

# 10.2.3. 在 webpack 中使用

安装:

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

## "babel-loader": "^9.1.2",
## "@babel/core": "^7.21.4",
## "@babel/preset-env": "^7.21.4",

配置:

module.rules = [
  {
    test: /\.js$/,
    exclude: /node_modules/,
    loader: "babel-loader",
  },
  // ...
]

参考:babel-loader (opens new window)

# 11. 处理 Html 资源

作用:

  • 根据模板生成 HTML 文件
  • 自动注入 bundle(打包后的文件)

安装:

npm i -D html-webpack-plugin

## "html-webpack-plugin": "^5.5.1",

配置:

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

module.exports = {
  // ...
  plugins: [
    // ...
    new HtmlWebpackPlugin({
      /*
        指定模板位置
      */
      template: path.resolve(__dirname, "public/index.html"),
    }),
  ]
};

参考:html-webpack-plugin (opens new window)

# 12. 开发服务器

说明:

  • 启用开发服务器后,所有代码都会在内存中编译打包,不会输出到 dist 目录
  • 源代码一旦被修改,就会重新编译打包

安装:

npm i -D webpack-dev-server

## "webpack-dev-server": "^4.13.3"

配置:

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

module.exports = {
  // ...
  devServer: {
    host: "localhost",
    port: "3000",
    open: true, // 自动打开浏览器
  },
};

运行:

npx webpack serve

参考:dev-server (opens new window)

# 13. 区分模式

# 13.1. 介绍

开发模式:

  • 启用 dev-server,不需要把编译结果输出到 dist

生产模式:

  • 优化代码运行性能

所以,我们需要准备两套配置文件

# 13.2. 目录

${root}/
  config/
    webpack.dev.js
    webpack.prod.js

备注:

  • 移动 webpack 配置文件后,相对路径不用改,绝对路径需要修改

# 13.3. webpack.dev.js

module.exports = {
  mode: "development",
  devServer: {
    host: "localhost",
    port: "3000", 
    open: true, 
  },
  // ...
}

# 13.4. webpack.prod.js

module.exports = {
  mode: "production",
  // ...
}

# 13.5. 运行指令

package.json:

{
  "scripts": {
    "start": "npm run dev",
    "dev": "webpack serve --config ./config/webpack.dev.js",
    "build": "webpack --config ./config/webpack.prod.js"
  }
}

备注:

  • 使用 npm script,.bin 目录也会加入环境变量,因此 npx 也可以省略掉

# 14. css 优化

在生产环境下,对 css 的处理进行优化

# 14.1. 提取成单独文件

问题:

  • 使用 style-loader,所有的样式都在 JS 里
  • 要等到 JS 下载完,执行 JS 的时候才会将 CSS 插入页面中
  • 这会造成闪屏的问题(先出现 HTML 结构,再设置样式)

解决:

  • <head> 使用 <link> 提前引入所有样式

安装:

npm i -D mini-css-extract-plugin

## "mini-css-extract-plugin": "^2.7.5",

配置:

const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.rules = [
  // ...
  { 
    test: /\.css$/, 
    use: [MiniCssExtractPlugin.loader, "css-loader"] 
  },
  {
    test: /\.less$/,
    use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
  },
]

plugins = [
  // ...
  new MiniCssExtractPlugin({
    // 指定输出文件名
    filename: "static/css/main.css",
  }),
]

参考:mini-css-extract-plugin (opens new window)

# 14.2. 兼容性处理

说明:

  • 解决样式兼容问题
  • 添加浏览器厂商前缀

安装:

npm i -D postcss-loader postcss postcss-preset-env

## "postcss": "^8.4.23",
## "postcss-loader": "^7.2.4",
## "postcss-preset-env": "^8.3.2",

配置:

module.rules = [
  // ...
  { 
    test: /\.css$/, 
    use: [
      MiniCssExtractPlugin.loader, 
      "css-loader",
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: { plugins: [ "postcss-preset-env", ] },
        },
      },
    ] 
  },
  {
    test: /\.less$/,
    use: [
      MiniCssExtractPlugin.loader, 
      "css-loader", 
      {
        loader: "postcss-loader",
        options: {
          postcssOptions: { plugins: [ "postcss-preset-env", ] },
        },
      },
      "less-loader"
    ],
  },
]

package.json 中设置兼容哪些浏览器:

{
  "browserslist": [
    "last 2 version", // 最近的两个版本
    "> 1%",           // 覆盖 99% 的浏览器
    "not dead"        // “死掉”的浏览器和版本就不考虑了
  ]
}

优化代码:(抽取重复代码)

const getStyleLoaders = (preProcessor) => {
  return [
    MiniCssExtractPlugin.loader,
    "css-loader",
    {
      loader: "postcss-loader",
      options: {
        postcssOptions: {
          plugins: [
            "postcss-preset-env",
          ],
        },
      },
    },
    preProcessor,
  ].filter(Boolean); // Boolean(undefined) === false
};

module.rules = [
  // ...
  { test: /\.css$/, use: getStyleLoaders() },
  { test: /\.less$/, use: getStyleLoaders('less-loader') },
  { test: /\.s[ac]ss$/, use: getStyleLoaders('sass-loader') },
  { test: /\.styl$/, use: getStyleLoaders('stylus-loader') },
]

参考:

# 14.3. 压缩

安装:

npm i -D css-minimizer-webpack-plugin

## "css-minimizer-webpack-plugin": "^5.0.0",

配置:

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

plugins = [
  // ...
  new CssMinimizerPlugin(),
]

// 也可以配置在 optimization 中,建议配置 plugins 中
optimization.minimizer = [
  // ...
  new CssMinimizerPlugin(),
]

参考:css-minimizer-webpack-plugin (opens new window)

# 15. HTML、JS 压缩

默认生产模式已经开启了:HTML 压缩和 JS 压缩

不需要额外进行配置

# 16. 总结

  1. 两种开发模式

    • 开发模式:代码能编译自动化运行
    • 生产模式:代码编译优化输出
  2. webpack 基本功能

    • 开发模式:可以编译 ES Module 语法
    • 生产模式:可以编译 ES Module 语法,压缩 js 代码
  3. webpack 配置文件

    • 五个核心概念
      • entry
      • output
      • loader
      • plugins
      • mode
    • devServer 配置
  4. webpack 脚本指令用法

    • webpack 直接打包输出
    • webpack serve 启动开发服务器,内存编译打包没有输出
本章目录