webpack 加载资源文件

2024-11-12
webpack
84

背景

最近用 webpack5 + vue 开发项目时,发现css 中通过 url 引入的文件不能显示。看配置啥的都没写错,几番查询下来,最终搞明白来龙去脉。

当时的配置如下

webpack.config.js

{
 module: {
   rules: [
      {
        test: /\.less$/,
        use: ['style-loader', 'css-loader', 'less-loader'],
      },
      {
        test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|eot|ttf|otf)$/,
        loader: 'file-loader',
      }
   ]
 }
}

vue 文件


在 vue template 里引用的图片可以显示,在less文件中使用 url 就不能显示。

原理

webpack 中的模块

Webpack 是一个基于模块而进行打包的工具,有哪些东西被其视为模块呢?具体如下:

  1. 基于 ES6 import 引入的

  2. 基于 CommonJS require() 引入的

  3. 基于 AMD define/require 引入的

Webpack 原生只能识别 .js|.mjs|.json 的模块,其它未知文件类型模块则需要通过 loaders 进行转化后处理。比如:

  • .css 样式模块通过的 css-loader 转化为可识别的 js 代码。简而言之就是导出文件内容,类似 export default "...css文件内容..."

  • .ts TypeScript 模块通过 ts-loader 转化为 js 代码

  • .jpg|png|gif 图片模块通过 file-loader 转化为 js 代码

Webpack 5 新增 Asset Modules

从 v5 开始,webpack 开始原生支持一些资源文件模块,比如图片、字体、图标 svg 等。

module.exports = {
    output: {
       assetModuleFilename: 'images/[name][ext]', // v5
    },
    module: {
        rules: [{
          test: /\.png$/,
          // v4
          use: {
            loader: 'file-loader',
            options: {
              outputPath: 'images/',
              name: '[name].[ext]',
            },
          },
          // v5
          type: 'asset/resource',
          generator: {
            filename: 'static/[name][ext]',
          },
        }]
    },
};

webpack 4 通过 file-loader来将图片资源转换为js资源,并且修改文件路径

webpack 5 则通过 type: 'asset/resource'告诉webpack,这类图片通过自己内置的 asset 模块来解析。

什么是**asset**模块

在 webpack5 之前,可能需要使用 raw-loader、file-loader、url-loader 来加载资源。

  1. raw-loader:将文件作为字符串导入

  2. file-loader:处理文件的路径并输出文件到输出目录

  3. url-loader:有条件将文件转化为 base64 URL,如果文件大于 limit 值,通常交给 file-loader 处理。

在 webpack5+,以上方法已经过时了,webpack5 自己使用了**asset**来代替以上 loader。

**asset**类型有四种。

  1. asset/resource: 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。

  2. asset/inline: 导出一个资源的 data URI。之前通过使用 url-loader 实现。

  3. asset/source: 导出资源的源代码。之前通过使用 raw-loader 实现。

  4. asset: 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。

总结一下就是,webpack 将原本资源加载的几个 loader 全部自己实现了,纯粹的抢活干。

为什么和 file-loader 功能冲突呢

从上面的介绍可以发现,如果要使用 **asset**模块,需要给文件类型设置 type: 'asset/resource',webpack 才识别为内置的 asset 类型。上面的配置里并没有这个配置。

在查阅官方文档时发现的问题的关键

webpack 不只识别 type: 'asset/resource' 类型

当使用 new URL('./path/to/asset', import.meta.url),webpack 也会创建资源模块。

image.png

官方文档地址: https://webpack.docschina.org/guides/asset-modules/#url-assets

同时,使用 css-loader 之后,url 引入的文件会被转译成 new URL()

// css
.main{
  backgroud: url('@/assets/images/a.png')
}

// css-loader 转译之后
var ___CSS_LOADER_URL_IMPORT_0___ = new URL("@/assets/images/a.png", import.meta.url);

.main {
  background: url(${___CSS_LOADER_URL_REPLACEMENT_0___});
}

正好,两个文件处理模块在 new URL 这里同时生效了,导致 file-loader 先处理一波,asset 模块再处理时文件已经不在了,最终有地址没图片内容。

怎么解决

既然两者的冲突无法避免,那就直接 asset 好啦

{
  test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|eot|ttf|otf)$/,
  type: 'asset/resource',
}

如果需要使用 url-loader 的功能可以配置为

{
  test: /\.(jpe?g|png|svg|gif)/i,
  type: 'asset',
  generator: {
    filename: 'img/[hash][ext][query]' // 局部指定输出位置
  },
  parser: {
    dataUrlCondition: {
      maxSize: 8 * 1024 // 限制于 8kb
    }
  }
},

将低于8k的文件以base64的格式打包到js里

其他支持的模块类型

除了asset模块,webpack5 还增加了一下类型模块

type 描述
javascript/auto 默认。包括 esm/cjs/amd 写法的文件模块
javascript/esm .mjs 结尾的文件模块
javascript/dynamic 不确定性的 js 模块,比如 externals 里配置的
json .json 文件模块
~~webassembly/*~~ 实验性
asset 资源文件模块。对应 url-loader 里的 size limit
asset/source 同上。对应 raw-loader
asset/inline 同上。对应 url-loader
asset/resource 同上。对应 file-loader

参考文档

https://juejin.cn/post/6970333716040122381

https://webpack.docschina.org/guides/asset-modules