「Webpack5 专题(二)」静态资源的处理

共 15734字,需浏览 32分钟

 ·

2021-11-10 08:48

一、前言

上一篇讲解了 webpack5 的基础配置。这一篇文章将会介绍如何利用 webpack 中的 Loaders 对静态资源做处理,Loaders 是 webpack 的核心概念之一。

静态资源主要包括以下几方面的内容:

  • 样式文件(CSS)
  • 图片(Images)
  • 字体(Fonts)
  • 数据文件(Data)
  • ...

文件结构:

webpack
|- /dist
 |- index.html
|- /
src
 |- /assets
  |- /
fonts
   |- font-a.otf
   |- font-b.ttf
   |- font-c.woff
   |- font-d.woff2
   |- font-e.svg
  |- /icons
   |- icon-a.png
   |- incon-b.svg
  |- /im
ages
   |- img-a.png
   |- img-b.jpg
   |- img-gif.jpg
  |- /js
   |- js-a.js
  |- /
others
   |- o-a.txt
   |- o-b.xml
  |- /styles
   |- global.scss
   |- reset.css
 |- /
components
  |- content.js
  |- header.js
  |- sidebar.js
|- index.js
|- package.json
|- webpack.config.js
复制代码

assets 文件夹中存放了若干静态文件(例如:字体、图标、图片、文本、样式以及 JS 文件),中间根据文件的不同,又分了几个文件夹。这里为了方便起见,文件名均已作简化处理。

index.js

// js 模块
const Header = require("./components/header");
const Sidebar = require("./components/sidebar");
const Content = require("./components/content");
// 图片模块 (这 5 个都是小图)
const icon1 = require("./assets/icons/mac-os.png");
const icon2 = require("./assets/icons/apple-tv.png");
const icon3 = require("./assets/icons/apple-contacts.png");
const iconSvg1 = require("./assets/icons/arrow-up.svg");
const iconSvg2 = require("./assets/icons/arrow-down.svg");
// 图片模块 (这 3 个都是大图)
const dog1 = require("./assets/images/animal-dog-1.png");
const avatar1 = require("./assets/images/avatar-1.jpg");
const cat1 = require("./assets/images/express-cat-1.gif");
// 数据模块
const fileTxt = require("./assets/data/notes.txt");
const fileXml = require("./assets/data/hello.xml");
// 字体模块
const font = require("./assets/fonts/Calibre-Regular.otf");
const iconFont1 = require("./assets/fonts/iconfont.ttf");
const iconFont2 = require("./assets/fonts/iconfont.woff");
const iconFont3 = require("./assets/fonts/iconfont.woff2");
// 样式模块
// const reset = require('./assets/styles/reset.css');
// const global = require('./assets/styles/global.scss');

const dom = document.getElementById("root");

// header
new Header(dom);
// side-bar
new Sidebar(dom);
// content
new Content(dom);
复制代码

引入了各种文件模块并放入变量中,每一个模块在打包以后都有值,可以正常使用。

二、Loaders — 加载器

webpack enables use of loaders[2] to preprocess files. This allows you to bundle any static resource way beyond JavaScript. You can easily write your own loaders using Node.js.

简单来说,loaders 可以帮我们预处理任何静态文件并打包

例如:图片文件,样式文件、html 文件,甚至是一些数据文件:txt 文件、xml 文件……

那么,如何配置呢?

三、Asset Modules — 静态资源模块

根据 Webpack5 的文档,它简化了之前版本对于文件方面的配置,提供了 Asset Modules[3] (静态资源模块),替代了之前常用的 raw-loader、url-loader、file-loader

也就是说不用再安装这几个 loader 了,直接通过一行type: 'asset' 搞定,书写更加方便!(注意 module 中 type 的设置!)

// webpack 配置文件
const path = require("path");

module.exports = {
  mode"production",
  entry: {
    main"./src/index.js",
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename"bundle.js",
  },
  // *** 模块选项中匹配的文件会通过 loaders 来转换!
  module: {
    rules: [
      // 图片文件
      {
        test/\.(jpe?g|png|gif|svg)$/i,
        type"asset"// 一般会转换为 "asset/resource"
      },
      // 字体文件
      {
        test/\.(otf|eot|woff2?|ttf|svg)$/i,
        type"asset"// 一般会转换为 "asset/inline"
      },
      // 数据文件
      {
        test/\.(txt|xml)$/i,
        type"asset"// 一般会转换成 "asset/source"
      },
    ],
  },
};
复制代码

每次打包后,文件都会根据 output 的配置放进输出文件夹,但是对于压缩处理的文件则不会被打包到文件夹,只会以base64 编码格式或者 URI 编码等格式存在于 bundle.js 的源码中。

  • 源码中的 base64
image-20210725202916049.png
  • 源码中的 URI
image-20210725203031020.png

默认情况下,当文件小于 8 kb 则会采用 base64 编码,那么上面出现的 URI 编码怎么设置的呢?

1. 特殊处理

某些文件需要特殊处理。例如,SVG 文件转换为 URI 编码后,与 base64 相比,体积会更小;

处理 txt、xml 文件 type: 'asset/source' 更好,它会导出资源的源码,这样就能看到全貌,而非 base64 格式的编码字符串。

最小化 SVG 文件需要安装一个第三方库:mini-svg-data-uri[4].

npm i -D mini-svg-data-uri
复制代码

安装之后,重新配置如下:

// webpack 配置文件
const path = require("path");
const miniSVGDataURI = require("mini-svg-data-uri");

module.exports = {
  // ...
  module: {
    rules: [
      // 图片文件
      {
        test/\.(jpe?g|png|gif)$/i,
        type"asset",
      },
      // svg文件
      {
        test/\.svg$/i,
        type"asset/inline",
        generator: {
          dataUrl(content) {
            content = content.toString();
            return miniSVGDataURI(content);
          },
        },
      },
      // 字体文件
      {
        test/\.(otf|eot|woff2?|ttf|svg)$/i,
        type"asset",
      },
      // 数据文件
      {
        test/\.(txt|xml)$/i,
        type"asset/source",
      },
    ],
  },
};
复制代码

至此,打包完成。现在,看看这些文件模块究竟被打包成什么数据了,在 index.js 文件中添加如下代码:

// js 模块
const Header = require("./components/header");
const Sidebar = require("./components/sidebar");
const Content = require("./components/content");
// 图片模块 (这 5 个都是小图)
const icon1 = require("./assets/icons/mac-os.png");
const icon2 = require("./assets/icons/apple-tv.png");
const icon3 = require("./assets/icons/apple-contacts.png");
const iconSvg1 = require("./assets/icons/arrow-up.svg");
const iconSvg2 = require("./assets/icons/arrow-down.svg");
// 图片模块 (这 3 个都是大图)
const dog1 = require("./assets/images/animal-dog-1.png");
const avatar1 = require("./assets/images/avatar-1.jpg");
const cat1 = require("./assets/images/express-cat-1.gif");
// 其他文件模块
const fileTxt = require("./assets/data/notes.txt");
const fileXml = require("./assets/data/hello.xml");
// 字体模块
const font = require("./assets/fonts/Calibre-Regular.otf");
const iconFont1 = require("./assets/fonts/iconfont.ttf");
const iconFont2 = require("./assets/fonts/iconfont.woff");
const iconFont3 = require("./assets/fonts/iconfont.woff2");
// 样式模块
// const reset = require('./assets/styles/reset.css');
// const global = require('./assets/styles/global.scss');

// 在控制台中打印这些模块:
console.log("icon-png: ", icon1);
console.log("icon-svg: ", iconSvg1);
console.log("png: ", dog1);
console.log("jpg: ", avatar1);
console.log("gif: ", cat1);
console.log("txt: ", fileTxt);
console.log("xml: ", fileXml);
console.log("font: ", font);
console.log("iconFont: ", iconFont1);

const dom = document.getElementById("root");

// 插入一张图片
const img = new Image();
img.src = dog1; // 图片模块 dog1 作为 img 变量的 src 属性值
img.width = 200;
root.append(img);

// header
new Header(dom);
// side-bar
new Sidebar(dom);
// content
new Content(dom);
复制代码

浏览器页面展示如下:

可以看到 SVG 文件 (icon-svg) 是被处理成 URI 编码的!

2. 配置静态文件名

默认情况下,打包以后的文件是散在 dist 文件夹中的,难以区分和维护。

现在,需要将他们分门别类地放进对应的文件夹中,就需要对文件名做统一的管理。

webpack.config.js

const path = require("path");
const miniSVGDataURI = require("mini-svg-data-uri");

module.exports = {
  mode"production",
  entry: {
    main"./src/index.js",
  },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename"bundle.js",
    // 静态文件打包后的路径及文件名(默认是走全局的,如果有独立的设置就按照自己独立的设置来。)
    assetModuleFilename"assets/[name]_[hash][ext]",
  },
  module: {
    rules: [
      // 图片文件
      {
        test/\.(jpe?g|png|gif)$/i,
        type"asset",
        generator: {
          filename"images/[name]_[hash][ext]"// 独立的配置
        },
      },
      // svg 文件
      {
        test/\.svg$/i,
        type"asset",
        generator: {
          dataUrl(content) {
            content = content.toString();
            return miniSVGDataURI(content);
          },
        },
      },
      // 字体文件
      {
        test/\.(otf|eot|woff2?|ttf|svg)$/i,
        type"asset",
        generator: {
          filename"fonts/[name]_[hash][ext]",
        },
      },
      // 数据文件
      {
        test/\.(txt|xml)$/i,
        type"asset/source"// exports the source code of the asset
      },
    ],
  },
};
复制代码

第 23 - 25 行,用于单独配置静态文件名;

generator: {
  filename'images/[name]_[hash][ext]' // 单独配置打包的路径及文件名
},
复制代码

第 12 行,assetModuleFilename: 'assets/[name][ext]', 用于设置全局的静态文件路径及文件名。如果文件模块没有单独进行配置,就按照这个来设置文件名。

其中,[name] 表示原来的文件名,[hash] 表示散列值,[ext] 表示文件后缀。这些都属于占位符(placeholders),在 webpack4 中有提到:v4.webpack.js.org/loaders/fil…[5]

3. asset 类型

当 type 设置为'asset',就会按照以下的策略去打包文件:

  • 如果一个模块大小超过 8 kb(这个值是默认的),就使用 asset/resource,被打包进输出文件夹中。(类似于 file-loader)
  • 否则,就使用 asset/inline,内联到打包文件中。(类似于 url-loader)

区别在于:前者会被单独放进输出文件夹中,后者被处理成 base64 编码字符串内敛进打包出的 JS 文件中。

后者的好处在于减少一次 http 请求,但是过长的字符串也会加重 js 的体积导致加载变慢,因此需要根据实际情况来确定到底采用哪一种方式去处理文件。

注意,当被作为后者处理时,是可以设置编码方式的,例如上面提到的特殊处理。

手动通过 `Rule.parser.dataUrlCondition.maxSize`[6] 去设置两者的界限:

{
  test/\.(jpe?g|png|gif)$/i,
  type'asset',
  generator: {
    filename'images/[name]_[hash][ext]',
  },
  parser: {
    dataUrlCondition: {
      maxSize8 * 1024 // 8kb (低于8kb都会压缩成 base64)
    }
  },
},
{
  test/\.svg$/i,
  type'asset',
  generator: {
    filename'icons/[name]_[hash][ext]',
    dataUrl(content) {
      content = content.toString();
      return miniSVGDataURI(content); // 通过插件提供的编码算法处理文件
    }
  },
  parser: {
    dataUrlCondition: {
      maxSize2 * 1024 // 2kb (低于2kb都会压缩)
    }
  },
},
复制代码

另外,除了asset/resourceasset/inline,还有一个上文提到的,用于处理 txt、xml 文件的asset/source,它相当于 webpack4 的 raw-loader。

三、样式文件的处理

1. style-loader & css-loader

style-loader 和 css-loader 是相辅相成的。

  • style-loader:将