Skip to content

01 基础篇,了解 webpack 的相关概念,能够配置 webpack ​

本篇文章中 webpack 的版本为 5.73.0,cli 版本为 4.10.0。

第一步,安装 webpack 与 webpack-cli。

shell
npm i webpack@5.73.0 webpack-cli@4.10.0

第二步,创建 src 文件夹,新建 index.js 作为入口文件,并填充如下内容:

js
const hello = () => {
  console.log('hello, webpack5')
}

hello();

第三步,我们直接在终端中输入 npx webpack 看看会发生什么?

可以发现目录中多出了一个 dist 文件夹,这个文件夹就是 webpack 打包输出后的内容。

TIP

从 webpack v4.0.0 开始,webpack 可以不用再引入一个配置文件来打包项目,webpack 会内置一些默认配置供开发者直接使用。

现在我们已跑通了最简单的 webpack 练习环境,接下来我们将结合示例理解 webpack 中 entry,output,loader,plugin,mode 这五大概念。

entry 入口 ​

入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图 (dependency graph)的开始。

我们现在大多数开发的是单页面应用,这里我们以单入口为例,修改目录结构,新增与 src 同级的 webpack.config.js 文件与 add.js 文件。

├─ src
│  ├─ add.js
│  └─ index.js
└─ webpack.config.js
└─ package-lock.json
└─ package.json

webpack.config.js 文件配置如下,entry 配置的是相对路径。

js
module.exports = {
  entry: './src/index.js'
}

add.js 内容如下:

js
const add = (a,b) => {
  return a + b
}

export default add

index.js 引入这个 add 函数并输出。

js
import add from './add.js'

const hello = () => {
  console.log('hello webpack')
}

hello()
console.log(add(2,3))

接下来打开终端输入 npx webpack 回车看看它打包后的文件长什么样。

dist 目录下只有单独的一个 main.js,部分内容如下:

output 输出 ​

可以通过配置 output 选项,告知 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个 entry 起点,但只能指定一个 output 配置。

我们更改 webpack.config.js 的配置如下:

js
const path = require('path')

const resolvePath = _path => path.resolve(__dirname, _path)

module.exports = {
  entry: './src/index.js',

  output: {
    path: resolvePath('./dist'),
    filename: 'scripts/[name].js'
  }
}

讲解一下 output 中这 2 个属性:

  • path:文件输出的路径,必须是个绝对路径
  • filename:文件输出的名称。可以在名称前指定一个输出路径,让文件输出在这个路径下。同时也可以使用占位符 [name] 指定不同的文件名称

终端输入 npx webpack 打包,我们查看打包后的程序。

新的 main.js 被打包到我们在 filname 中指定的 scripts 文件夹下。

这里有个新的问题待我们解决,之前被打包的 main.js 没有被清除,我们希望每次打包都能生成最新的文件,并清除上一次的打包结果。此时在 output 中配置 clean: true 即可解决问题,更改 output 配置如下:

js
output: {
  path: resolvePath('./dist'),
  clean: true,
  filename: 'scripts/[name].js'
}

再次打包,之前的文件已被清除。

TIP

webpack 5 可以方便地在 output 中配置 clean: true 以清空上一次打包的内容。但在 webpack 4 及一下的版本中,需要借助插件 clean-webpack-plugin 来实现该清除功能。

loader ​

webpack 天生只知道怎么处理 JavaScript 和 JSON 文件,对于其它类型文件,webpack 不知道怎么处理,如果没有对应 loader,就会报错。

loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。

那什么是 webpack 不能直接处理的资源呢?我们尝试打包一个 css 资源,调整目录结构如下:

├─ src
│  ├─ css
│  │  └─ index.css
│  └─ add.js
│  └─ index.js
└─ webpack.config.js
└─ package-lock.json
└─ package.json

index.css 中添加如下样式:

css
body {
  background-color: red;
}

index.js 引入 index.css 后,进行打包,结果控制台报错:

js
import add from './add.js'
import './css/index.css'

const hello = () => {
  console.log('hello webpack')
}

hello()
console.log(add(2,3))

这就是 webpack 不知道该怎么处理的资源。

此时就需要我们引入 loader,告诉 webpack 该怎么处理这些资源。

接下来我将围绕样式资源,图片资源,ESNext 等跟大家讲解 loader 的具体使用方法。

样式 loader ​

告诉 webpack 怎么加载 css 文件的 loader:css-loader,style-loader。

css-loader 的作用可以简单理解为将匹配到的 css 文件的样式内容以字符串的形式拼接在一起,然后将其作为 js 模块的导出内容。

经过 css-loader 的转译,我们已经得到了完整的 css 样式代码,style-loader 的作用就是将结果以 style 标签的方式插入 DOM 树中。

这也就是为什么处理 css 文件要有两个 loader,css-loader 将 css 处理为 js 模块后,将内容交给 style-loader,style-loader 再把 css 插入到 style 标签中。

shell
npm i css-loader style-loader

下载完成后,配置 webpack.config.js 使用 loader:

js
module.exports = {
  // ...

  module: {
    rules:[{
      test: /\.css$/,
      // 使用 use,达到链式调用的目的
      use:[
        'style-loader',
        'css-loader'
      ]
    }]
  }
}

这里我们新增了 module 配置项,它用来配置不同 loader 的使用规则。

module.rules 允许你在 webpack 配置中指定多个 loader。

这里的配置意思是,当 webpack 每遇到一个 css 文件时,先用 css-loader 处理,紧接着使用 style-loader 处理。处理完后再去匹配下一个文件。注意 loader 的执行顺序是从下到上,从右到左。先执行 css-loader 处理 css,再执行 style-loader 将 css 通过 style 标签插入页面。

执行 npx webpack,编译成功后,我们在 dist 根目录下新建 index.html,并引入打包后的 main.js 查看结果:

可以发现打包后的 js 和样式全部生效了。

样式通过 style 标签被插入页面。到这里基础的样式 loader 使用就已经成功。但实际开发中我们多以 less,scss 开发为主,处理这些样式文件需要使用对应的 less-loader 和 sass-loader。

less-loader 处理 less 文件 ​

我们调整 src/css 文件结构,新增一个 less 文件,如下所示:

├─ css
│  ├─ index.css
│  └─ rectangle.less

less 文件样式如下:

less
@blue: #4285f4;

.webpack-rectangle{
  width: 100px;
  height: 60px;
  background-color: @blue;
}

src/index.js 引入 less 文件,进行打包。

js
import add from './add.js'
import './css/index.css'
import './css/rectangle.less'

// ...

结果发现报错:

报错信息:不支持 less 文件的打包,需要一个 loader 处理该类文件。

与 css 报错的信息基本一致,这时请出我们的 less-loader。

安装 less,less-loader。

shell
npm i less less-loader

配置 webpack.config.js:

js
// ...
module.exports = {
  // ...
  module: {
    rules:[{
      test: /\.css$/,
      use:[
        'style-loader',
        'css-loader'
      ]
    }, {
      // 匹配 less 文件
      test: /\.less$/,
      // loader 的使用顺序 less-loader,css-loader,style-loader
      use:[
        'style-loader',
        'css-loader',
        'less-loader'
      ]
    }]
  },
  mode:'development'
}

这里新增 mode:'development',防止 webpack 报一些提示信息的错误。

然后在 rules 数组中新增处理 less 文件的 loader 对象。

这里的处理顺序如下所示:

  1. less-loader 将 less 文件转义成 css 文件
  2. 在 1 的基础上,css-loader 将 css 文件处理成 commonJs 模块
  3. 在 2 的基础上,style-loader 将 css 通过 style 标签插入页面中

最后,在 dist 根目录下再次新建一个 index.html 引入 main.js 查看刚刚写的 less 是否生效,这里为了观察方便我们把原来的红色背景改成灰色背景。

html
<body>
  <div class="webpack-rectangle"></div>
  <script src="./scripts/main.js"></script>
</body>

ok,生效!大功告成。接下来我们处理 sass,scss 文件。

sass-loader 处理 sass 文件 ​

调整 src/css 文件结构,新增一个 scss 文件,如下所示:

├─ css
│  ├─ index.css
│  └─ circle.scss
│  └─ rectangle.less

scss 文件样式如下:

scss
$baseCls:"webpack-circle";

.#{$baseCls} {
  width: 100px;
  height: 100px;
  border: 1px solid #4285f4;
  border-radius: 50%;
}

src/index.js 引入 scss 文件,进行打包。

js
import add from './add.js'
import './css/index.css'
import './css/rectangle.less'
import './css/circle.scss'

// ...

下面与处理 less-loader 的方法大同小异。

安装 sass,sass-loader:

shell
npm i sass sass-loader

配置 webpack.config.js 的 rules:

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

解析规则与 less-loader 相似,在此不多赘述。

最后,执行 npx webpack,dist 根目录下创建 index.html 引入 main.js 查看样式。

html
<body>
  <div class="webpack-rectangle"></div>
   <div class="webpack-circle"></div>
  <script src="./scripts/main.js"></script>
</body>

TIP

为了看得清楚,把 body 标签的背景设置为了白色。

大功告成,到这里我们的 webpack 就可以正常解析 less 和 sass 了。

如果我们希望样式被抽离成单独的 css 文件以 link 标签的形式引入页面,需要借助插件实现。下一章节 plugin 中我们跟大家进行详细讲解。

资源 loader ​

webpack4 中处理图片使用 file-loader 和 url-loader,webpack5 中这 2 个 loader 已被内置到 webpack 里,我们只需激活配置即可。

在开始工作之前我们把 src 做个备份,然后继续开发。

调整 src 结构:

├─ src
│  ├─ assets
│  │  └─ changeface.gif
│  │  └─ daxiang.jpg
│  │  └─ putao.png
│  ├─ css
│  │  └─ index.scss
│  └─ add.js
│  └─ index.js

配置 webpack.config.js 的 rules,里面的字段含义我们稍后讨论。

js
rules:[
  // ...
  {
    test: /\.(jpe?g|png|gif|webp|svg)$/,
    type: 'asset',
    generator: {
      filename: 'assets/img/[hash:10][ext]'
    }
  }
]

然后修改 index.scss。

scss
$baseCls:"webpack-img";

.#{$baseCls} {
  display: flex;
  justify-content: space-between;
  div{
    width: 33%;
    height: 230px;
    &.box1{
      background: url(../assets/putao.png) no-repeat center /contain;
    }
    &.box2{
      background: url(../assets/changeface.gif) no-repeat center /contain;
    }
    &.box3{
      background: url(../assets/daxiang.jpg) no-repeat center /contain;
    }
  }
}

最后执行 npx webpack,dist 根目录下创建 index.html 引入 main.js 查看样式。

html
<body>
  <div class="webpack-img">
    <div class="box1"></div>
    <div class="box2"></div>
    <div class="box3"></div>
  </div>
  <script src="./scripts/main.js"></script>
</body>

查看结果:

成功运行,我们看看 dist 目录生成的文件:

新生成了 assets 文件夹,里面包含 hash 名称的图片资源,再看看刚刚的 loader 中有这样的配置:

js
generator: {
    filename: 'assets/img/[hash:10][ext]'
}

这下应该明白了,filename 可以指定图片资源的输出路径,其他字段释义如下:

  • [hash:10]:取 hash 的前 10 位为文件名称
  • [ext]:文件扩展名,之前是什么值,打包后仍然是什么值

可以看出,打包前和打包后,文件扩展名是未发生修改的。到这里图片资源的处理就完成了。

babel-loader ​

将 ES6+ 语法编写的代码转义为向下兼容的 js 代码,使低版本浏览器能正常运行程序。

在使用 babel-loader 前,我们备份一下 src,调整目录结构如下:

├─ src
│  └─ index.js
└─ webpack.config.js
└─ package.json

在 index.js 中编写一段 ES6 代码:

js
class GirlFriend {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  say () {
    console.log(`我叫${this.name},我今年${this.age}岁。很高兴认识你`)
  }
}

const girl = new GirlFriend('Alice',22)

girl.say()

使用 webpack 打包查看结果:

可以发现,ES6 的代码被原封不动地打包了,这样的打包结果,对低版本浏览器并不友好。所以我们需要引入 babel-loader 解决这个问题,下面我们开始配置 babel-loader。

安装依赖。

shell
npm i babel-loader @babel/core @babel/preset-env
  • @babel/core:babel 核心库
  • @babel/preset-env:智能预设

配置 webpack.config.js 的 rules:

js
rules: [
    // ...
    
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]

当然我们也可以在项目根目录中新增 .babelrc 或 babel.config.js 等文件,设置一些 babel 的使用规则。

然后,执行 webpack 查看打包结果:

可以看到我们的 ES6 代码已被转义为 ES5 代码。

到这里常见 loader 的配置就学习完毕了,接下来我们进入 plugin 的学习。

plugin 插件 ​

plugins 选项用于以各种方式自定义 webpack 构建过程。它监听 webpack 的打包过程,执行对应的生命周期回调,拓展 webpack 的功能。

下面我们来了解最常见的 2 个 plugin。

使用 HtmlWebpackPlugin 打包 html 页面 ​

之前我们打包 js,css 后,总要在 dist 根目录下新建一个 html,引入打包的 js 并进行一些设置,才能在页面中看到打包后的效果。这种方式非常不方便,因此我们通过 HtmlWebpackPlugin 这一插件,让 webpack 帮我们自动打包出一个 index.html。

这里我们提一个需求,我们希望 webpack 能打包一个 html 页面,这个页面生成时带有这样的内容 <h2>Hello CengFan!</h2>,并引入打包后的 js 资源。

需求有了接下来我们一步步实现。

步骤 1,src 文件夹下新建一个 html,内容如下:

html
<body>
  <h2>Hello Cengfan!</h2>
</body>

步骤 2,安装依赖。

shell
npm i html-webpack-plugin

步骤 3,配置 webpack.config.js。

js
const path = require('path')
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin')

const resolvePath = _path => path.resolve(__dirname, _path)

module.exports = {
  // ...

  plugins: [
    new HtmlWebpackPlugin({
      template: resolvePath('./src/index.html'),
    })
  ],

  mode: 'development'
}

template 指定以哪个 html 为入口文件,值是一个绝对路径。

plugin 都要遵循先引用,然后使用 new 关键字进行实例化调用的原则。

步骤 4,执行 webpack 查看打包结果。

符合预期,js 也正常运行。

使用 MiniCssExtractPlugin 打包独立的 css 文件 ​

还记得之前在样式 loader 一节中我们遇到的问题吗,css 无法被单独打包成文件只能通过 style 标签被插入页面。到这里这个问题就能被解决了。

我们恢复之前备份的资源 loader 文件夹,把它设置成 src,并添加 index.html。下面我们正式开始。

步骤 1,安装插件。

shell
npm i mini-css-extract-plugin

步骤 2,配置 webpack.config.js。

js
// 引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

module.exports = {
  // ...

  plugins: [
    // ...
    new MiniCssExtractPlugin({
      filename: 'css/[name].css',
    }),
  ],

  mode: 'development'
}

这里的 filename 与 output 中的类似,不多赘述。

js
module.exports = {
  //...

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

  //...
}

由于样式是用 scss 写的,所以我们还需更改与之匹配的 loader。如果不更改对应的 loader,最后样式还是会通过 style 标签被插入页面。

index.scss:

scss
$baseCls:"webpack-img";

.#{$baseCls} {
  display: flex;
  justify-content: space-between;
  div{
    width: 33%;
    height: 230px;
    &.box1{
      background: red;
    }
    &.box2{
      background: green;
    }
    &.box3{
      background: blue;
    }
  }
}

步骤 3,配置 html 的内容。

html
<body>
  <div class="webpack-img">
    <div class="box1"></div>
    <div class="box2"></div>
    <div class="box3"></div>
  </div>
  <h2>Hello Cengfan</h2>
</body>

步骤 4,执行 webpack 查看打包结果。

可以看出样式文件已被单独打包。至此常用 plugin 介绍完毕。

mode 模式 ​

mode 主要分为开发/生产两种环境,不同环境打包出来的代码不同。

  • development:开发环境
  • production:生产环境

首先看看开发环境下打包的 js 代码:

再看看生产环境下打包的 js 代码:

生产环境下我们还可以配合其他插件,压缩生产环境的代码。

其他配置 devServer ​

之前我们每次对项目进行修改,都要重新执行 webpack 打包,然后手动开启服务检查打包结果,这是非常不方便的。我们希望 webpack 能自动化处理这些事情,这时就需要使用 devServer 解决这些问题。

步骤 1,安装 webpack-dev-server。

shell
npm i webpack-dev-server

步骤 2,配置 webpack.config.js。

js
//...
module.exports = {
  //...
  devServer: {
    host: 'localhost',
    port: 8080,
    open: true,
    hot: true,
  },

  mode: 'development'
}

步骤 3,终端输入命令 npx webpack serve 查看结果。

此时页面就以 8080 端口被启动起来了。

devServer 启动的开发服务器,存在于内存中,不会往硬盘写入文件,没有输出内容。所以即使我们删除 dist 目录,页面还是会照常运行。

最后 ​

至此 webpack 常用的配置已列举完毕,如果你也跟着一路看下来相信基本的配置应该不成问题了。受限于篇幅,更全的配置还是推荐大家去官网查看。

Released under the MIT License.