likes
comments
collection
share

从零开始学 Webpack 4.0(二)

作者站长头像
站长
· 阅读数 79

原文本人写于 2022-04-30 16:42:51

一、前言

这次文章的学习内容主要是围绕着 Webpack 的 Loader 以及 Plugin 的使用。

备注:之前因为我的 npm 版本比较高,好像是 8.0 的,在安装一些依赖出现过报错,于是我通过降级到 6.0 来解决。有小伙伴安装依赖出现问题时可以借鉴一下。

查看 npm 版本号

npm -v

npm 降级到 6.0

npm install npm@6.14.16 -g

降级后把项目原本的 node_modules 目录以及 package-lock.json 文件删除,运行命令重新安装依赖。

重新安装依赖,npm i 等价于 npm install

npm i

安装依赖没问题的小伙伴没必要特意去降级 npm。

项目结构及问题产生

在学习之前先来看一下我们的项目结构,为了方便接下来的演示,我们暂时把 index.html 放到 dist 目录里面去。本次以 demo02 为项目文件夹,也就是根路径,命令都会在这里去执行。

从零开始学 Webpack 4.0(二)

package.json

{
  "name": "demo02",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "scripts": {
    "bundle": "webpack"
  },
  "keywords": [],
  "author": "phao",
  "license": "ISC",
  "devDependencies": {
    "webpack": "^4.26.0",
    "webpack-cli": "^3.1.2"
  }
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>demo02</title>
</head>
<body>
  <div id="app"></div>

  <script src="main.js"></script>
</body>
</html>

index.js

import avatar from './avatar.jpg'

console.log(avatar)

var img = new Image()
img.src = avatar
var app = document.getElementById('app')
app.append(img)

webpack.config.js

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

avatar.jpg

从零开始学 Webpack 4.0(二)

安装依赖 二选一

(1)没有 demo1 项目的 package-lock.json,直接运行 npm i 命令安装依赖以及生成 package-lock.json。

(2)有上次 demo1 项目的 package-lock.json,则复制到本项目使用(记得把里面的 "name" 改为 "demo02"),然后运行命令安装依赖:

npm ci

安装完依赖后,我们来运行一下打包命令。

npm run bundle

这时候会直接出现报错

从零开始学 Webpack 4.0(二)

原因是 Webpack 在打包的时候碰到 jpg 图片文件,它不知道怎么去处理,这时候就需要用到 Loader。

二、什么是 Loader?

  • Loader 就是一个打包方案,它知道对于一个特定的文件该如何进行打包。
  • Webpack 不能识别非 js 为后缀的模块,这时候就需要 Loader 来处理。
  • 就像 Vue 项目里 vue 后缀的文件,就需要通过 Vue Loader 来进行处理。

三、对静态资源 图片文件 的打包

file-loader

安装 file-loader

npm install file-loader@2.0.0 -D

修改 webpack.config.js,配置 module 的 rules。

webpack.config.js

const path = require('path')

module.exports = {
  mode: 'development',
  entry: {
    main: './src/index.js'
  },
  module: {
    // rules 里面可以配置很多条规则
    rules: [{
      // 匹配以 jpg 结尾的文件用一个 loader 去处理打包
      test: /\.jpg$/,
      use: {
        loader: 'file-loader'
      }
    }]
  },
  output: {
    filename: 'main.js',
    path: path.resolve(__dirname, 'dist')
  }
}

运行打包命令

npm run bundle

成功打包,index.html 在浏览器打开能够正常显示图片。

file-loader 做的事情就是在 Webpack 打包 index.js 的时候,碰到里面引入的 avatar.jpg 文件,先把文件复制一份到输出的 dist 目录下,以哈希值为其命名,接着把文件相对于 dist 目录的地址返回给 index.js 里面的变量 avatar。

浏览器控制台打印的 avatar(index.js 里的 console.log(avatar)):

从零开始学 Webpack 4.0(二)

在使用 file-loader 时,不想图片文件名给更改,这时候就需要配置 options,传入一些参数。

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [{
      test: /\.jpg$/,
      use: {
        loader: 'file-loader',
        options: {
          // name 指的是原本的文件名,ext 指的是原本的文件后缀。
          // 这种配置语法叫做 placeholder,也就是占位符,通过查看官网相关文档可以了解其他占位符。
          name: '[name].[ext]'
          // 还可以这样写
          // name: '[name]_[hash].[ext]'
        }
      }
    }]
  },
  ...
}

希望文件生成到某个文件夹下,例如图片打包的时候去到 dist 目录下的 images 文件夹,需要配置 outputPath。

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [{
      test: /\.jpg$/,
      use: {
        loader: 'file-loader',
        options: {
          name: '[name].[ext]',
          // 没有 images 文件夹的话会自己生成
          outputPath: 'images/'
        }
      }
    }]
  },
  ...
}

file-loader 不仅能处理 jpg、png、gif 这样的图片文件,也支持其他格式文件的处理。

url-loader

url-loader 也能实现对图片文件的打包,区别在于它会把图片转换为 base64 的字符串直接放到打包后输出的 js 文件(本项目中 dist 目录下的 main.js)内。这种做法的好处是少了一次 http 请求,坏处就是当图片比较大的时候会导致 js 文件变大、加载变慢,影响到页面的运行效果。

这时候需要配置 url-loader 的 limit 参数,一两 kb 的小图片就直接转换为 base64 放入 js 文件,来减少 http 请求,大图片则通过 file-loader 去进行处理。

因此,使用 url-loader 还需要安装 file-loader,因为它们之间存在依赖关系。

安装 url-loader

npm install url-loader@1.1.2 -D

修改 webpack.config.js,使用 url-loader。

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [{
      test: /\.(jpg|png|gif)$/,
      use: {
        loader: 'url-loader',
        options: {
          name: '[name].[ext]',
          outputPath: 'images/',
          // 当图片超过 2048 字节(2kb)就会像 file-loader 一样处理
          limit: 2048
        }
      }
    }]
  },
  ...
}

运行打包命令,打包完成,index.html 在浏览器打开能够正常显示图片。

四、对静态资源 样式文件 的打包

现在来给图片加点样式,在 src 目录下新建 index.css 以及 avatar.css 文件,然后在 index.js 里给页面显示的图片加上 class。

index.css

@import url('./avatar.css');

body{
  background: #ccc;
}

avatar.css

.avatar{
  width: 150px;
  height: 150px;
}

index.js

import avatar from './avatar.jpg'
import './index.css'

var img = new Image()
img.src = avatar
// 新增 class
img.classList.add('avatar')
var app = document.getElementById('app')
app.append(img)

这时候运行打包命令,肯定会报错,因为 Webpack 不知道怎么处理这些 css 文件。

style-loader 、 css-loader

使用 style-loader 以及 css-loader 来处理 css 文件的打包。

安装

npm install style-loader@0.23.1 css-loader@1.0.1 -D

修改 webpack.config.js,新增 module 的 rules。

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      // 新增对 css 文件的打包处理
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  ...
}

运行打包命令,打包成功,index.html 在浏览器打开能够正常显示样式。

从零开始学 Webpack 4.0(二)

查看浏览器控制台,页面上挂载了 style 标签。挂载的 style 标签个数与使用的 css 文件个数有关。

从零开始学 Webpack 4.0(二)

css-loader 可以分析几个 css 文件之间的关系,去进行处理。

style-loader 在得到 css-loader 处理完的内容后,会把这段内容挂载到页面的 head 部分。

所以在处理 css 文件的打包上,这两个 Loader 要配合使用。

sass-loader

在 src 目录下新增 index.scss 以及 avatar.scss 文件。index.js 去除原有 index.css 的引入,改为引入 index.scss。

index.scss

@import url('./avatar.scss');

body{
  background: #ccc;
}

avatar.scss

body{
  .avatar{
    width: 150px;
    height: 150px;
  }
}

index.js

import avatar from './avatar.jpg'
import './index.scss'

var img = new Image()
img.src = avatar
img.classList.add('avatar')
var app = document.getElementById('app')
app.append(img)

安装 sass-loader 及其依赖的 sass

npm install sass-loader@8.0.2 sass@1.26.5 -D

使用 sass-loader

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      // 新增对 scss 文件的打包处理
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              // 再执行一遍 sass-loader
              importLoaders: 1
            }
          },
          'sass-loader'
        ]
      }
    ]
  },
  ...
}

运行打包命令,打包成功,index.html 在浏览器打开能够正常显示样式。

(如果遇到打包失败,大概率是因为我们 Webpack 版本太低导致 sass-loader 相关内容没有安装完整,但我们观察到 package.json 的 "devDependencies" 里面确实有安装的记录,我们需要把 node_modules 目录以及 package-lock.json 文件删除,运行命令 npm i 重新安装依赖即可正常打包。)

在 Webpack 的配置里,Loader 是有先后执行顺序的,从下到上,从右到左。

首先是 sass-loader 对 scss 文件进行翻译,转换成 css 代码,给到 css-loader 去进行处理,最后通过 style-loader 挂载到页面的 style 标签上。

当 Webpack 在打包 index.scss 时会使用 sass-loader 进行处理,接着就会到 css-loader 去进行处理,但在 css-loader 的处理中发现刚刚传过来的代码中有一个 scss 文件的引入,这时候我们通过 options 里 importLoaders: 1 的配置,让这个引入的 scss 文件再回到前面的 scss-loader 去进行处理。

postcss-loader

当我们在运用一些 css3 的特性时,往往需要加上浏览器厂商前缀做兼容,这时候就可以用 postcss-loader 结合其 autoprefixer 这个插件来替我们自动去进行添加。

安装 postcss-loader 及其插件 autoprefixer

npm install postcss-loader@3.0.0 autoprefixer@9.3.1 -D

按照官方文档的指示,在项目根路径下去新建 postcss.config.js 去做 postcss-loader 的配置。

postcss.config.js

module.exports = {
  plugins: [
    require('autoprefixer')
  ]
}

使用 postcss-loader

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      // 对 scss 文件的打包处理新增 postcss-loader 去进行处理
      {
        test: /\.scss$/,
        // sass-loader postcss-loader css-loader style-loader 官方推荐的调用顺序
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              // 再执行一遍 sass-loader 以及 postcss-loader
              importLoaders: 2
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
    ]
  },
  ...
}

去 avatar.scss 里新增一些 css3 的特性进行尝试

avatar.scss

body{
  .avatar{
    width: 150px;
    height: 150px;
    transform: translate(100px, 50px);
  }
}

运行打包命令,打包成功,index.html 在浏览器运行,查看控制台发现并没有自动添加浏览器厂商前缀。

从零开始学 Webpack 4.0(二)

这是因为 Webpack 认为你是在新版本浏览器中使用这些特性,也就没有必要去添加这些前缀。我们可以通过配置 browserlist 来说明本项目需要适配的浏览器版本,因为适配范围内包含一些老版本的浏览器,这时候去进行打包就能够添加上前缀。

browserlist 的常用配置方法有两种,一种是 package.json 中添加配置,一种是新增 .browserslistrc 文件来进行配置。各配置项命中的是本项目需要兼容的浏览器版本,各不同配置项结果取的是并集。

项目根路径下新增 .browserslistrc

.browserslistrc

# Browsers that we support 
# 这里写注释
 
last 2 version
> 1%
maintained node versions

运行打包命令,打包成功,index.html 在浏览器运行,查看控制台发现这次添加上浏览器厂商前缀了。

从零开始学 Webpack 4.0(二)

CSS 模块化

当我们在 index.js 往页面上增加多个元素,这时候在 index.js 引入的 css 样式文件是全局的,会使页面上几个元素都受到该样式文件的影响,为了避免这种样式的影响,需要用到 css 的模块化,它能够使模块之间的样式不会有任何的耦合和冲突。

在 css-loader 里开启配置项

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 2,
              // 开启 css 模块化打包
              modules: true
            }
          },
          'postcss-loader',
          'sass-loader'
        ]
      }
    ]
  },
  ...
}

修改 index.js

index.js

import avatar from './avatar.jpg'
import style from './avatar.scss'

var img = new Image()
img.src = avatar
img.classList.add(style.avatar)
var app = document.getElementById('app')
app.append(img)

运行打包命令,打包成功,index.html 在浏览器打开能够正常显示样式。

字体文件的打包

在 src 目录下创建文件夹 fonts,把在 iconfont 下载的字体文件放入。

src 目录下新增 icon.scss,把 iconfont 下载的 css 文件内容放入,并把里面对字体文件的引用路径 url 通过相对路径的形式指向刚刚创建的 fonts 文件夹内,base64 的 url 不用修改。

icon.scss

@font-face {
  font-family: "iconfont"; /* Project id 3281407 */
  src: url('./fonts/iconfont.eot?t=1648373455950'); /* IE9 */
  src: url('./fonts/iconfont.eot?t=1648373455950#iefix') format('embedded-opentype'), /* IE6-IE8 */
       url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAMQAAsAAAAABvAAAALBAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACCcAqBbIFjATYCJAMICwYABCAFhGcHLxseBsgekiSCEA8OAJ4rQMASD8+v/Tp35iEiSbXTiGaJCOlnTKJYq6SOr1hI6/+7pn9AnHYakBTCQXJXoDQjV3CtZj0WCoAkoJxRe+0JwNZ6Pv/3jltAAV8QaK1Na+MDy7d+0MaY1omok4eewfLKr2bf5GcEyk1oFjzTPTgC8QWO6SFbKKc5yDOjoJI4iNQ+SaLJUqEYXZrEPYSRXQQ/4i7+fPwwFZFkinJOzuJ2lwinn3+6I0LOAoL80dlyNfS0DEXIMQIkYrU/Ni9EtLFDlNtCddHKJPj8MxAIDeLQWqH+Oie9EnRS3jPxfcGjlmQgefb+OWBz0nUUNZIYF3UY73g8lc7OnioSrl4uIuz8/LmQFh1B6LrbkToSTAmxnqzGO46DgjZGXc4M4gHL4RzGUZxpHDV9w3lkOXQcW0+qLsZ3lcYIZeCKMCrTe96ndvN9+YWPj9vpxO///t5JI3t7T/m+IqG30bz2cF9/926a5Pf9/Mzm+7YiFGZ2Zlo6LJGdkRbFZf5O406Tv9EPrQD/t/K3fOIn/8b7r7XmExovrUjgeet3Ifb830qAOUDpXdiCXyqfAYnJ+MlSG1+nMnPC7lJS7gb5D46r3s9UVcOtp0KphhdwlOjIkSvVRUusIRSpMIVipaZRbljX6goNjEJEKkMfO0GotY1MtQfkat3QEusVRZr9oVitf5Rbj7jtKvSF0EExx7CI+FWkEgx64ilZTHUIS4tajjaFxzA1q2gQlRWXVpM9WI/pHDPMS1I5YwQRatChbnka1moNyEgNaiywYpkxY31JCWl7SbFg0EEHCuMwmAjhrUJUBAZ6JCSz8vcNwSSLtDgUpI3FKDMVpY6UKVYK0HpkeqjhQXYxWyIpxzAEQlCS0EG69SBMS8sAMbYPUsMETDF5QsSoXolLIqiseHuF7te2QDlHnYVrzRU9t7UIAAAAAA==') format('woff2'),
       url('./fonts/iconfont.woff?t=1648373455950') format('woff'),
       url('./fonts/iconfont.ttf?t=1648373455950') format('truetype'),
       url('./fonts/iconfont.svg?t=1648373455950#iconfont') format('svg');
}

.iconfont {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-pic:before {
  content: "\e6f9";
}

新增对字体文件的打包处理

webpack.config.js

...
module.exports = {
  ...
  module: {
    rules: [
      ...
      {
        test: /\.(ttf|woff|woff2|svg|eot)$/,
        use: {
          loader: 'file-loader',
          options: {
            name: '[name].[ext]',
            // 新版本 file-loader 会默认开启一个叫 esModule 的配置项,
            // 页面无法正常显示图标时可以把它关了。
            // esModule: false
          }
        }
      }
    ]
  },
  ...
}

修改 index.js,让页面去展示图标。

index.js

import style from './icon.scss'

var app = document.getElementById('app')
app.classList.add(style.iconfont, style['icon-pic'])

运行打包命令,打包成功,index.html 在浏览器打开能够正常显示图标。

从零开始学 Webpack 4.0(二)

五、什么是 Plugin?

Plugin,插件,可以在 Webpack 运行到某个时刻的时候,帮你做一些事情,比如打包前执行什么操作,打包后执行什么操作,有些像生命周期函数。

六、生成 dist 目录下 index.html

HtmlWebpackPlugin

在本次项目中我们把 index.html 放到 dist 目录下,这种做法肯定是不恰当的。dist 目录下存放的是 Webpack 打包后输出的文件,src 目录下存放的才是项目的源文件,但把 index.html 放到 src 目录,会因为一些路径没有正确配置的原因,导致 index.html 无法正确引入打包后的文件。这时候我们就需要通过官方推荐的 HtmlWebpackPlugin 这个插件来解决该问题。

HtmlWebpackPlugin 会在打包结束后,自动生成一个 html 文件,并把打包生成的相关文件自动引入到这个 html 文件中,最后把该 html 文件输出到 dist 目录下。

这个 html 文件可以采用我们提供的模板去生成,在 src 目录下创建 index.html 作为模板。

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

安装 html-webpack-plugin

npm install html-webpack-plugin@3.2.0 -D

使用 html-webpack-plugin

webpack.config.js

...
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
  ...
  module: {
    ...
  },
  plugins: [
    new HtmlWebpackPlugin({
      // 指定 src 目录下的 index.html 作为模板文件
      template: 'src/index.html',
      // 指定生成 html 的 title
      title: 'demo02自定义title'
    })
  ],
  output: {
    ...
  }
}

删除 dist 目录下 index.html,运行打包命令,打包成功,dist 目录下生成 index.html,在浏览器打开能够正常显示。

七、去除 dist 目录下冗余文件

CleanWebpackPlugin

当我们不去删除 dist 目录下的文件,经过上面一系列的练习会发现 dist 目录下存在不少冗余文件,但每次打包前都需要手动删除一次比较麻烦,这时候我们可以去使用一个第三方插件 CleanWebpackPlugin,它可以在 Webpack 打包前对 dist 目录进行删除。

安装 clean-webpack-plugin

npm install clean-webpack-plugin@1.0.0 -D

使用 clean-webpack-plugin

webpack.config.js

...
const CleanWebpackPlugin = require('clean-webpack-plugin')

module.exports = {
  ...
  module: {
    ...
  },
  plugins: [
    new HtmlWebpackPlugin({
      ...
    }),
    // 表示的是打包之前会使用这个插件去删除 dist 目录
    new CleanWebpackPlugin(['dist'])
  ],
  output: {
    ...
  }
}

运行打包命令,观察项目目录,首先 dist 目录给删除,然后 Webpack 进行打包,创建 dist 目录输出文件。

八、官方文档

学习完以上内容,或者对一些配置项有疑惑的,可以前往 Webpack 4.0 官网 阅读相关中文文档。

建议阅读的内容为:

  • (1)文档 > LOADER,页面左侧的 file-loader、url-loader、style-loader、css-loader、postcss-loader、sass-loader。
  • (2)文档 > 指南,页面左侧的 管理资源。
  • (3)文档 > PLUGIN,页面左侧的 HtmlWebpackPlugin。

HtmlWebpackPlugin 和 CleanWebpackPlugin 毕竟本文中用的版本都比较旧,对具体配置项感兴趣的小伙伴建议学习完本系列文章后,上新版本实践的时候再去了解。

英语比较好的小伙伴可以直接阅读 Webpack 4.0 官网 的英文文档。

九、总结

通过本篇文章的学习,我们对 Webpack 这个模块打包工具有了更深的认识,知道了什么是 Loader,什么是 Plugin,以及怎样去使用它们。

那么这么多的 Loader 以及 Plugin,应当怎样去学习呢?

当我们需要实现某个功能的时候,先去网上搜索 Webpack 相应的配置,这些配置会告诉我们需要用到哪些 Loader 或 Plugin,这时候再去查阅相关的文档学习即可。

本文最后的代码我会上传到 码云(gitee.com/phao97)上,项目文件夹为 demo02。

如果觉得本篇文章对你有帮助,不妨点个赞或者给相关的 Git 仓库一个 Star,你的鼓励是我持续更新的动力!

转载自:https://juejin.cn/post/7215151665930338364
评论
请登录