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

一、前言

上一篇讲解了 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
  |- /images
   |- 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: {
      maxSize: 8 * 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: {
      maxSize: 2 * 1024 // 2kb (低于2kb都会压缩)
    }
  },
},
复制代码

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

三、样式文件的处理

1. style-loader & css-loader

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

  • style-loader:将 <style> 标签插入到 DOM 中。

  • css-loader:解析通过 @import、url()、import/require() 这些方式引入的样式文件。

安装 loaders:

npm install --save-dev css-loader style-loader
复制代码

webpack.config.js

module.exports = {
  module: {
    rules: [
      // ...
      {
        test: /\.css$/i,
        use: ["style-loader", "css-loader"],
      },
    ],
  },
};
复制代码

注意两个 loader 的位置,要反过来写。

index.js

// ...
const reset = require("./assets/styles/reset.css");
console.log("css: ", reset);

import "./assets/styles/reset.css";

const dom = document.getElementById("root");
dom.innerHTML += '<p class="iconfont icon-mianxingshezhi">This a text.</p>';
复制代码

reset.css

@font-face {
  font-family: "iconfont";
  /* Project id 1947684 */
  src: url("../fonts/iconfont.woff2?t=1627306378388") format("woff2"), url("../fonts/iconfont.woff?t=1627306378388") format("woff"),
    url("../fonts/iconfont.ttf?t=1627306378388") format("truetype");
}

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

.icon-mianxingshezhi:before {
  content: "\e6ad";
}

/* ... */
复制代码

解释:reset.css 中引入了字体图标(来自 iconfont),index.js 中通过import的方式引入了这个 css 文件,并使用了这个字体图标。

其中,

import './assets/styles/reset.css'

onst reset = require('./assets/styles/reset.css');

这两段语句背后的 CSS 代码就是通过 css-loader 去解析的。

控制台展示如下:

图片

上图中,在 CSS 样式被 css-loader 解析完成后, <style> 标签通过 style-loader 插入到 DOM 中。

2. sass-loader

  • sass-loader:加载一个 Sass/SCSS 文件,并编译成 CSS。

安装:

npm install sass-loader sass webpack --save-dev
复制代码

webpack.config.js

module.exports = {
  module: {
    rules: [
      // ...
      {
        test: /\.s[ac]ss$/i,
        use: [
          // Creates `style` nodes from JS strings
          "style-loader",
          // Translates CSS into CommonJS
          "css-loader",
          // Compiles Sass to CSS
          "sass-loader",
        ],
      },
    ],
  },
};
复制代码

index.js

const global = require("./assets/styles/global.scss");
console.log("scss: ", global);

import "./assets/styles/global.scss";

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

// insert an image
const img = new Image();
img.src = dog1;
// img.width = 200;
img.classList.add("avatar");
root.append(img);
复制代码

global.scss

// 自定义变量
$color: #ff4200;
$fs: 14px;
$ls: 1.2;

// 自定义mixin
@mixin size($w, $h: $w) {
  width: $w;
  height: $h;
}

body {
  font-size: $fs;
  background-color: #eaeaea;
  .avatar {
    @include size(150px);
    transform: translateX(50px);
  }
}
复制代码

控制台展示如下:

图片

上图第二个 <style> 标签内的样式代码就是 global.scss 中转译成 css,并通过 style-loader 插入到 DOM 中的。

3. postcss-loader

PostCSS is a tool for transforming styles with JS plugins. These plugins can lint your CSS, support variables and mixins, transpile future CSS syntax, inline images, and more.

postcss 仓库:github.com/postcss/pos…[7]

autoprefixer:github.com/postcss/aut…[8]

postcss-preset-env:github.com/csstools/po…[9]

a) 安装

安装:

npm install --save-dev postcss-loader postcss
复制代码

webpack.config.js

module.exports = {
  module: {
    rules: [
      // ...
      {
        test: /\.s?css$/i,
        use: ["style-loader", "css-loader", "sass-loader", "postcss-loader"],
      },
    ],
  },
};
复制代码

注意,postcss-loader 要放在最后。

b) 配置

Autoprefixer

PostCSS[10] plugin to parse CSS and add vendor prefixes to CSS rules using values from Can I Use[11].

PostCSS Preset Env

PostCSS Preset Env[12] lets you convert modern CSS into something most browsers can understand, determining the polyfills you need based on your targeted browsers or runtime environments.

PostCSS SCSS Syntax

A SCSS[13] parser for PostCSS[14].

This module does not compile SCSS. It simply parses mixins as custom at-rules & variables as properties, so that PostCSS plugins can then transform SCSS source code alongside CSS.

插件需要安装一下:

npm install postcss-preset-env autoprefixer postcss-scss --save-dev
复制代码

在根目录下,新建:postcss.config.js

module.exports = {
  syntax: "postcss-scss",
  plugins: [require("autoprefixer"), "postcss-preset-env"],
};
复制代码

webpack.config.js 不需要修改。

四、补充:css-loader 的 options

不同的 loader 都会有各自的 options,css-loaders 有两个实用的 options。

  • importLoaders:The option importLoaders allows you to configure how many loaders before css-loader should be applied to @imported resources and CSS modules/ICSS imports. 简单来说,就是允许你在执行 css-loader 前,让某些资源要经过前面的 loaders 去处理的个数。

  • modules:Allows to enable/disable CSS Modules or ICSS and setup configuration. 允许你开启 CSS Module

webpack.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.sc?ss$/i,
        use: [
          "style-loader",
          {
            loader: "css-loader",
            options: {
              importLoaders: 2,
              // 0 => no loaders (default);
              // 1 => postcss-loader;
              // 2 => postcss-loader, sass-loader
              modules: true, // 默认是 false ***
            },
          },
          "sass-loader",
          "postcss-loader",
        ],
      },
    ],
  },
};
复制代码

注意,上面 importLoaders 设置为 2,表示匹配到样式文件时,都会去执行一开始的两个 loader,也就是 'sass-loader' 以及 'postcss-loader'.

至于 modules 设置为 true,则保证了样式模块的独立性,不会被互相覆盖。具体看下面的例子:

a) 不设置 modules 选项

src/assets/styles/global.scss

// 自定义变量
$color: #ff4200;
$fs: 14px;
$ls: 1.2;

// 自定义mixin
@mixin size($w, $h: $w) {
  width: $w;
  height: $h;
}

body {
  font-size: $fs;
  background-color: #eaeaea;
  .avatar {
    @include size(150px);
    transform: translateX(50px);
  }
}
复制代码

src/assets/js/createImg.js

import avatar from "../images/avatar-2.jpg";
import dog1 from "../images/animal-dog-1.png";

function createImg(root) {
  const img = new Image();
  img.src = dog1;
  img.classList.add("avatar"); // 添加 avatar 类名
  root.append(img);
}

export default createImg;
复制代码

src/index.js

import "./assets/styles/reset.css";
import "./assets/styles/global.scss";
import dog1 from "./assets/images/animal-dog-1.png";
import createImg from "./assets/js/createImg.js";

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

// 第一张图的创建
createImg(dom);

// 第二张图的创建
// insert an image
const img = new Image();
img.src = dog1;
img.classList.add("avatar"); // // 添加 avatar 类名
root.append(img);
复制代码

在这种情况下,import './assets/styles/global.scss'这一句对全局的文件都有效果,因此它的后一句 import createImg from './assets/js/createImg.js'; 就会受到影响。影响的结果就是,createImg.js 文件中的第 7 行代码的样式添加,添加的就是 global.scss 中的第 15 行的选择器 'avatar' 的样式。

打包后,结果如图所示:

图片

image-20210731205046599.png

b) 将 modules 设置为 true

设置完后,代码不变的情况下,打包完成后,查看结果如图所示:

图片

前后两张秋田犬均变成了超大的图,原因是样式没有作用到图片上。

当应用 CSS Module[15] 时,要通过以下的方式添加样式:

(注意:这里仅对 index.js 中的代码作修改,使之恢复,而 createImg.js 模块中的代码不变,从而形成对比。)

src/index.js

import "./assets/styles/global.scss";
import style from "./assets/styles/global.scss"; // 自定义变量名称,作为 css module 导入
import dog1 from "./assets/images/animal-dog-1.png";
import createImg from "./assets/js/createImg.js";

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

// 第一张图的创建
createImg(dom);

// 第二张图的创建
// insert an image
const img = new Image();
img.src = dog1;
// css module 的使用:通过自定义变量名称 style 加 . 的形式访问到 avatar 类名
img.classList.add(style.avatar); 
root.append(img);
复制代码

打包后,结果如图所示:

图片

通过对比,就可以发现,通过模块方式设置的秋田犬的样式是有的,而第一张则没有。

这种利用 CSS Module 的方式来添加样式的方法,解决了全局覆盖同名样式(一般是类名和 id)的尴尬情况。

阅读参考:

  • github.com/css-modules…[16]

  • github.com/css-modules…[17]

  • webpack.js.org/loaders/css…[18]

  • webpack.js.org/loaders/css…[19]

小结

以上,是本篇的所有内容。在 Webpack5 中,对于静态资源的处理,我们只要简单地设置 type 就能处理文件,非常方便。但是有些特殊情况还是需要去单独处理的,例如资源输出的路径及文件名的设置、URI 编码格式的设置、转 base64 的文件大小限制的设置。样式文件中,为了防止全局的样式污染,可以开启 CSS Module 来避免。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值