README.md 14 KB

Webpack

webpack 是一个为现代的 JavaScript 应用程序进行模块化打包的工具

  • webpack 是一个打包工具
  • webpack 可以将打包打包成最终的静态资源,来部署到静态服务器
  • webpack 默认支持各种模块化开发, ESModule、CommonJS、AMD等

Webpack 的运行依赖 Node 环境,需要先安装 Node.js

Webpack 的安装从 4 版本之后,需要安装两个: Webpack(核心功能) 和 Webpack-cli (脚手架)

执行 webpack 命令会执行 node_modules 下的 bin 目录下的 webpackwebpack 再执行时是依赖 webpack-cli 的,webpack-cli 在执行时,会利用 webpack 进行编译和打包过程

也就是说,webpack 是必须的。如果不使用 webpack 命令,而是自己写命令的话是可以不用 webpack-cli

使用命令行 npm install webpack webpack-cli -g 直接进行全局安装即可

使用命令行 npm install webpack webpack-cli -D 直接进行局部安装即可

第一次使用

正如当前 src 目录中的 01 项目所示,项目结构简单,一个 html 和三个 js 文件

│  index.html
│
└─src
    │  index.js
    │
    └─util
            add.js
            data.js

index.html 根据引入 js 的方式不同,会出现不同的情况

引入代码 出现错误 错误原因
<script src="./src/index.js"></script> Cannot use import statement outside a module (at index.js:1:1) 这是因为浏览器不支持 import 语句,需要修改引入代码为 <script src="./src/index.js" type="module"></script>
<script src="./src/index.js" type="module"></script> 01/src/util/add net::ERR_ABORTED 404 这是因为浏览器不会自动查找指定文件夹下同名的 js 文件,也就是说找不到 ./util/add 这个文件,需要修改为 ./util/add.js, 对 ./util/data 找不到也是同理

通过给 script 标签添加 type 属性和修改模块引入路径,成功让代码运行起来了

为了解决上面的问题,直接使用 webpack 命令来打包项目试试

直接使用 webpack 打包的时候,会检索当前目录下的 src/index.js 文件,并在同级目录下生成 dist/main.js

<script src="./dist/main.js" ></script>

成功运行,因为 main.js 是一个单独的文件,没有引入其他模块,所以不需要指定 type="module",也不需要修改 import 模块的路径

注意: 直接运行 webpack 会搜索当前目录下的 src/index.js 所以要注意执行命令时所在的文件路径,如果没有会报错 Module not found: Error: Can't resolve './src'

一般会使用 npm init -y 来创建 package.json, 进而通过局部安装的方式来安装所需的模块

可以通过 npx webpack 来运行局部安装的 webpack

npx 会执行当前项目的 node_modules/bin 中的模块

可以通过向 package.jsonscript 中添加命令的方式来运行 webpack,使用 npm run build 来执行 build 命令

"scripts": {
"build": "webpack"
},

package.json 在执行命令的时候会优先查找当前目录中的 node_modules/bin

配置选项

入口文件

正如前面所讲,直接使用 webpack 命令会以 src/index.js 为入口(entry),查找依赖图结构

不是所有的项目的入口都是 src/index.js,打包之后的路径也不都是 dist 文件夹,所以 webpack 提供指定入口文件和输出路径的 option

npx webpack --entry ./src/main.js --output-path ./build

以 02 文件夹中的项目为例运行上述命令,会以 src/main.js 为入口,并将输出的 main.js 打包到 build 文件夹中

官方文档 中也有详细说明

当然,如果全部使用命令进行 flag 配置,会非常麻烦,尤其是配置项目多了之后,会导致配置项难以查找,所以一般都是创建一个 webpack 的配置文件 webpack.config.js

webpack 是通过 commonjs 的方式来读取 webpack.config.js,所以使用 module.export = {} 方式来导出配置

毕竟最终 webpack 也是依赖 node 来运行的,所以会使用 node 的模块导入机制

官网中也有对 config 的解释说明

const path = require("path")

module.exports = {
    entry: "./src/main.js",
    output: {
        filename: "./bundle.js",
        path: path.resolve(__dirname, "./build")
    }
}

将上述代码写入 webpack.config.js 文件中,效果等同于 --entry ./src/main.js --output-path ./build

为什么 output.path 需要指定为 path.resolve(__dirname, "./build")

因为 webpack.config.jsoutput.path 需要指定为绝对路径,这个是 webpack 要求的,所以可以用到 node 内置的 path 模块,来获取当前文件(webpack.config.js) 的绝对路径

然后直接执行 npx webpack,自动读取当前项目目录中的 webpack.config.js 文件来进行操作,从而会生成 build/bundle.js 文件

如果当前目录中不存在 webpack.config.js 文件,而是 wp.config.js,这个时候需要指定配置文件的名称

npx webpack --config ./wp.config.js

依赖图

如果有一个 js 文件名为 test.js,这个文件没有被其他任何模块 import,那么这个文件就不会打包到 webpack 的最终产物中去

webpack 在处理应用程序时,会根据命令或配置文件找到入口文件(比如 main.js),从入口开始,会生成一个依赖关系图,这个依赖关系图会包含应用程序所需的所有模块,然后遍历图结构,打包一个个模块(根据文件的不同使用不同的 loader 来解析)

webpack 提供 tree-shaking 消除无用代码

也就是说,有一个 test.js 文件

console.log(`hello world`);

export function foo()
{
    console.log(`test.js foo`);
}

main.js

如果使用如下代码,则不会引入 test.js

console.log(`main.js`);

如果使用如下代码,则会引入 test.js 但开启 tree-shaking 之后会消除 foo 函数,因为 foo 并没有被使用

import "./test"

console.log(`main.js`)

如果使用如下代码,则会将 foo 函数打包到最终文件中,因为 foo 被使用了

import * as test from "./test"

test.foo();
console.log(`main.js`)

loader

前面提到过,不同类型文件需要不同的 loader 进行处理,比如 js、html、css 等

如果不对 webpack 做任何处理,运行 03 项目

官方案例 https://webpack.docschina.org/guides/asset-management/#loading-css

component.js 中通过 import ../css/index.css 会出现以下错误

ERROR in ./src/css/index.css 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> .content {
|     color: red;
| }
 @ ./src/util/component.js 1:0-25
 @ ./src/index.js 1:0-25

根据 You may need an appropriate loader 你需要一个合适的 loader 来处理这个 css 文件

loader 可以用于对模块的源代码进行转换,这里将 index.css 看作是一个模块,通过 import ../css/index.css 来加载这个模块,但是 webpack 并不知道如何对这个模块进行加载,所以报错

为了让 webpack 知道如何加载,需要指定对应的 loader 来完成加载功能

直接使用 npm 局部安装 css-loaderstyle-loader 即可安装所需 loader

npm install css-loader style-loder -D

安装了 loader 只是表示你有这工具,但是如何使用这个工具需要通过配置来告诉 webpack

官网对 loader 的配置也有说明

  • 内联方式 指明 loader
import "style-loader!css-loader!../css/index.css"
  • 配置方式
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    {
                        loader: 'css-loader',
                    },
                ],
            },
        ],
    },

module 属性可以配置 rules 规则数组

rules 是一个包含多个 rule 的数组

{
    test: /\.css$/,
    use: [
        {
            loader: 'css-loader',
        },
    ],
},

test 用于正则匹配,对匹配上的资源,使用该规则中配置的 loader

/\.css$/ 由于 . 在正则中是特殊符号,需要用 \ 转译;$ 表示匹配末尾

官网中对 loader 也有详细说明

写法一 写法二 写法三
use: [ { loader: "css-loader" } ] loader: "css-loader" use: [ "css-loader" ]

上述三种写法等价

通过上述修改, 终于能够让 webpack 命令正常运行, 得到最后的 bundle.js 文件

但是实际运行的时候, .content 样式并没有正确显示在页面中, 这是因为 css-loader 只是负责解析 css 文件,并不负责插入 css, 所以还需要使用 style-loader 插入 style

常见的样式引入有三种方法

  1. 行内样式, 即直接写在标签中
  2. 页内样式, 即在 html 文件中通过 style 标签进行设置
  3. 外部样式, 即通过外部的 .css 文件进入引入
{
    test: /\.css$/,
    use: [
        { loader: 'style-loader' },
        { loader: 'css-loader' },
    ],
},

注意: webpack 加载 loader 的顺序是数组序号从大到小执行的

也就是根据上述配置, webpack 会先执行 css-loader 再执行 style-loader. 这个顺序就是正确的, 如果反向的话会执行 style-loader 再执行 css-loader, 但是没有 css-loader 解析 style-loader 怎么插入呢?

通过观察生成的 bundle.js 可以看到 webpack 是如何处理 css 的. 它通过创建一个 style 的方式将内容通过页内样式插入到 html

e.exports = function(e) {
    var t = document.createElement("style");
    return e.setAttributes(t, e.attributes), e.insert(t, e.options), t
}

可能会用 lesssassstylus 的预处理器来编写 css 样式,效率更高,那么如何支持呢?

less 为例,想要将 less 转为 css,需要使用 less 这个库。想要解析 less 需要使用使用 less-loader

less-loader 的执行需要 less, 所以两个库都要安装

所以需要执行以下代码

npm install less less-loader -D

那么根据流水线操作,针对 .less 结尾的文件,需要首先使用 less-loader 将其转为 css,然后使用 css-loader 解析,最后使用 style-loader 插入到界面中

所以最终得到 lessrule 内容如下

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

记得 rule 不同的等效写法吗

浏览器兼容性

针对不同的浏览器支持的特性,比如 css 特性、js 语法等,会导致各种兼容性问题

那么某个功能如果存在兼容性问题,是否需要针对这个功能对不同的浏览器做特殊处理呢?很多情况都是根据浏览器的市场占有率来决定的

caniuse 是一个判断某些功能能否使用的网站,其在 usage-table 界面中提供了市场占有率

很多项目存在 .browserslistrc 文件,内容可能如下

> 1%
last 2 versions
not dead

这里的 > 1% 就是指市场占有率大于 1%,根据当前运行的浏览器的版本,查找 caniuse 网站中的占有率,进而判断是否需要支持

通过 Browserslist 工具来共享兼容性配置给其他工具(babelautoprefixer等)使用

Browserslist 是一个在不同的前端工具之间,共项目标浏览器Node.js版本的配置

上述例子有一个叫 not dead 的配置,译为没有死亡的。 Browserslistdead 的定义是 24 个月内没有官方支持或更新的浏览器

上述例子有一个叫 last 2 versions 的配置,表示每个浏览器的最后 2 个版本。比如 last 2 Chrome versions 就是 Chrome 浏览器最近的两个版本

除此之外,还有针对 node 的版本规则、针对指定平台浏览器的规则、支持特定功能浏览器的规则 等

首先通过 npm 安装 browserslist

npm install browserslist -D

然后就可以使用 browserslist 查询支持的浏览器了

cmd: npx browserslist ">1%, last 2 versions, not dead"

and_chr 132
and_ff 132
and_qq 14.9
and_uc 15.5
android 132
chrome 132
chrome 131
chrome 109
edge 132
edge 131
firefox 134
firefox 133
ios_saf 18.3
ios_saf 18.2
ios_saf 18.1
ios_saf 17.6-17.7
kaios 3.0-3.1
kaios 2.5
op_mini all
op_mob 80
opera 114
opera 113
safari 18.3
safari 18.2
samsung 27
samsung 26

在命令中 , 等价于 or 也就是只要满足其中一个条件即可

cmd: npx browserslist ">1% and last 2 versions and not dead"
and_chr 132
chrome 132
chrome 131
edge 132
edge 131
firefox 134
ios_saf 18.2
op_mob 80
samsung 27

在命令中 and 表示所有条件必须全部满足才行

那么如何在项目中进行配置呢?

  • 通过在 package.json 中新增 browserslist 属性进行配置,其他工具会根据该配置自动适配
{
  "name": "01",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    // ..
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    // ..
  },
  "browserslist": [
    ">1%",
    "last 2 version",
    "not dead",
  ]
}
  • 通过新增 .browserslistrc 文件,进行配置

    >1%"
    last 2 version"
    not dead"