webpack 是目前使用比较流行的一个前端模块打包器, 前端的任何资源都被当成一个模块来处理, 如图片, CSS 文件等等. 在基于 webpack 构建的前端项目中, 一般都会配置有关 CSS 文件处理的规则, 这其中也包括 CSS 文件中图片资源的处理, 那么 webpack 到底是怎么处理它的呢? 笔者之前也遇到过类似图片路劲的问题, 为此还写过一篇博文 webpack 生成的 CSS 文件 background-image url 图片无法加载. 今天就来说说 webpack 是怎么处理 CSS 文件中的图片路径的, 首先上一个具体的例子.
一个具体问题
最近使用 https://github.com/umijs/umi 搭建前端的一个项目, 在使用过程可能遇到一个 umi 的 bug, 为此还提出了一个 issue 项目配置 CSS module 影响到 CSS-loader 对第三方库 CSS 文件中图片 url 的处理 https://github.com/umijs/umi/issues/3950 . 顺便在简述下:
在项目中通过设置 cssLoaderOptions.modules 为 true 来开启 CSS module
项目中引入了第三方库 kindeditor 的 CSS 文件.
import 'kindeditor/themes/default/default.css'
该 default.CSS 文件中有通过 url 引入图片资源, 并且图片资源非相对路径写法, 例如其中一处的写法:
- .ke-toolbar-icon-url {
- background-image: url(background.PNG);
- }
然后通过 NPM start 开启本地服务进行预览时, 编译报错, 如下图:
奇怪, 明明对应的图片资源是存在的, webpack 编译时为啥找不到呢? 苦苦寻思了一番没有找到答案, 于是一头扎进 webpack 和 CSS-loader 源码的海洋中开启 "寻宝" 之旅.
不卖关子了, 导致上述的直接原因:
CSS-loader 没有对 CSS 文件的 url 方法进行处理(转化为相对路径)
这样导致 webpack 在整合经过 loader 处理后的 default.CSS 模块时, 因为模块用到 require(background.PNG)来引用图片资源, 此时就用到 Node.JS 的模块加载机制, 其具体可以查看本博客另一篇文章谈谈 NPM 依赖管理, 也可以查看 Node.JS 官网 module 章节 https://nodejs.org/api/modules.html .Node.JS 在解析 background.PNG 图片路径时, 会将其解析为第三方模块, 这样会从 node_modules 中查找, 通过在 webpack 中打印错误日志, 可以从其中看出一些端倪, 如下图, missing 字段表示查找过的路径均没有找到对应的资源文件.
umi 内部其实使用 CSS-loader-1(fork mailto:CSS-loader@1.x 而来)来处理 CSS 文件的, 导致 CSS-loader 没有对 CSS 文件图片路径进行处理的底层原因:
项目开启 CSS module 后, 不该影响到 node_modules 中 CSS 文件的 CSS module 的情况而实际上产生了影响; 导致没有对第三方库中的 CSS 文件中图片路径进行处理
webpack 是怎么转化 CSS 中的图片路径的?
如果单纯为了解决上面问题就可以到此为止, 但是处于好奇, 毕竟被坑了几次, 想知道 webpack 是怎么处理 CSS 文件中的图片路径的. 例如我们在项目中这样写过 CSS:
- .xxa {
- background: url(background.PNG)
- }
或者这样:
- .xxb {
- background: url(~alias/background.PNG)
- }
在项目中, 不论我们用 Less,stylus 还是 Sass 等 CSS 预处理库编写 CSS, 其最终是通过对应的 loader 如 Less-loader 将编写的样式转换变换为 CSS, 然后通过 CSS-loader 来处理 CSS 中有关路径的转换, 其作用拿其官网的介绍来说:
The CSS-loader interprets @import and url() like import/require() and will resolve them.
最常见处理 CSS 样式的项目, 一般经过以下几个 loader 从右到左顺序执行, 拿 Less 编写的样式来说, 以内联 loader 的展示形式来说明:
!!CSS-loader!post-loader!Less-loader!./xxx/xx.Less
当然 CSS-loader 处理后还要经过 style-loader 或者 mini-CSS-extract-plugin 提供的 loader 处理, 但是这不在本次谈论范围.
下面通过一幅图来看看经过 webpack 解析模块到 CSS 产出这一过程, webpack 帮我们做了什么.
具体就来简单分析整个流程, 可能分析有不正确的地方, 还请大家批评指正
NormalModuleFactory 解析并创建模块
首先从入口文件 (entry 配置的文件) 开始构建, 使用 NormalModuleFactory 来解析并创建模块
NormalModuleFactory 使用 enhance-resolve 来解析依赖的模块绝对地址, 如果模块地址解析错误就会如文章开头的问题抛出错误, 解析正确则会创建依赖模块. 如上图中的./index.CSS, 模块地址解析成功后, webpack 为 index.CSS 创建的模块属性如下图:
创建的一个模块, 一般包括模块的 type,context,request,userRequest,rawRequest,resource,dependencies 和 loaders 等模块相关信息.
模块创建后, 会用其依赖处理的 loader 来编译模块内容, 模块依赖的 loader 存放在模块的 loader 属性数组中; 对于 CSS 文件最后是用 CSS-loader 来处理.
CSS-loader 编译 CSS 文件
CSS-loader 官网说的会对 CSS 文件的 url/@import 进行处理, 但是具体实现细节并没有详细阐述. 下面来简单说说对 CSS-loader 的主要功能:
转换 CSS 中的 url 和 @import 为 require/import;
例如 url 中的地址 (绝对地址除外) 会被解析为相对地址, 防止 webpack 在解析模块地址时出错; 这其中包括 webpack alias 别名组成的地址和 node_moduels 库中地址. 顺便说下:
CSS-loader 内部是通过 postcss 生成 CSS 的 ast 并遍历找出其 url 方法来完成转换的.
按 comonjs 模块的形式生成 CSS 文件模块内容
CSS 文件最终转换后的 commonjs 模块形式, 模块的后缀还是. CSS, 其内容如下图所示:
CSS-loader 还处理 CSS module, 也是通过遍历 CSS 的 ast 来完成转换
这样通过 CSS-loader 完成了 CSS 文件中图片 url 路径的转换, 有助于 webpack 寻找图片资源的具体位置.
url-loader 处理图片资源
其实, 在 CSS-loader 处理完 CSS 模块过程中, 会再次通过 NormalModuleFactory 来解析并创建其内部的图片模块, webpack 模块对应的属性如下图:
生成图片对应的 commonjs 模块内容可能为 base64 的内容, 如下图:
也可能为图片资源产出的引用地址, 如下图:
这主要取决于 url-loader 在处理图片资源时是否指定 limit 配置项值, 该值会跟图片内容大小进行比较. 在 limit 值小于图片内容大小时, 则使用 file-loader 来实现图片提出到 webpack 编译产出的对应位置下.
上面图片模块内容为图片产出地址, 正是 file-loader 处理的结果, 其实现以下几项功能:
图片内容会抽离到 webapck 的编译产出位置.
按照 loader 的 name 配置和 webpack 的 publicPath 配置项生成最终的 url.
因为图片的内容被抽离掉, 那么 webpack 生成的图片模块内容应该为该图片的引用地址. 这涉及到两部分
根据 file-loader 的 name 配置项生成相对地址部分.
如下 file-loader 配置项:
- {
- test: /\.(PNG|jpe?g|gif)$/i,
- loader: 'file-loader',
- options: {
- name: 'static/[name].[hash:8].[ext]',
- },
- }
然后根据 loader-utils 的 interpolateName 方法解析对应的 url. 例如上面 CSS 文件的图片地址转换结果:
./background.PNG 会转换为: static/background.a9153e95.PNG
根据 webpackConfig.output.publishPath 生成图片的引用地址.
最终生成的地址为:
__webpack_public_path__ + "static/background.a9153e95.png";
经过上面步骤的处理, 我们看到产出的最终 CSS 文件的效果如下图:
顺便说一下, 如果产出的 CSS 文件经过 mini-CSS-extract-plugin 提供的 loader 进行统一抽离, 那么它也可能会影响 CSS 文件中图片的引用路径, 尤其该 loader 配置了 publicPath 内容, 如下面 loader 的配置:
- {
- loader: MiniCssExtractPlugin.loader,
- options: {
- publicPath: 'public/path/to/'
- }
- }
那么 CSS 文件中图片的路径最终结果如下图:
这就是 webapck 通过各种 loader 处理 CSS 中图片路劲的过程, 通过这一过程我们可能只是大概对这一过程有一个大概的认知, 如果要深入理解还是需要花时间研究.
参考文献
- CSS-loaderr
- https://github.com/webpack-contrib/url-loader
- https://github.com/webpack-contrib/file-loader
来源: https://www.cnblogs.com/wonyun/p/12261101.html