webpack5 下 background url 图片打包失败

一、省流

webpack5 下,css-loaderfile/url-loader 对于 css 文件(无论是 import 一份 css 文件还是 vue 里面的 style 块)里的 background-image: url() 资源文件处理有冲突。表现在:

打包出来的 url 使用的是 css-loader 自己产出的图片。但是这张图片生成有问题,导致图片无法显示。

解决方法看最后面。

二、溯源

根据《react 基础工程(react-redux & react-router)》里的配置,里面配置了 file/url-loader 去处理资源文件。

{
  test: /\.css$/,
  use: [
    isDev ? 'vue-style-loader' : MiniCssExtractPlugin.loader,
    'css-loader',
  ],
},
{
  test: /\.(png|jpg|gif)$/i,
  use: [
    {
      loader: 'file-loader',
      options: {
        esModule: false,
        limit: 8192,
        name: 'media/[name].[hash].[ext]',
      },
    },
  ],
}

对于普通使用并没有问题。但是后面使用了 background-image: url() 发现图片显示不出来。去产物文件夹(我这里是 dist 文件夹)一看,发现在根路径多了几张图片,但是打不开,显示图片格式损坏。而配置 name: 'media/[name].[hash].[ext]' 里指定的 media 文件夹也有正常的图片。但是一看处理后的 css 使用的却是那一堆在根路径的损坏的图片。

也就是:background-image: url() 没有使用 file/url-loader 打包出来的图片,而是使用了别的 loader 产出的图片,而且这堆图片还生成失败了。

file/url-loader 去掉后,生成的图片正常了。也就是和这两个冲突了。接着看看问题在哪。插一句:我是在一个旧项目做了升级的,之前用的低版本也就是 webpack4 以及其他一系列低版本的配套是可以的,升级了后才出现问题。

一开始我怀疑是 mini-css-extract-plugin,怀疑是抽取 css 文件的时候,顺便生成了图片。后面我把这个去掉,只留下 css-loader 发现也会产出图片。这我着实没想到,以为只是单纯处理 css,遇到资源文件还是交给 file/url-loader。但是 webpack4 是好的,或者 webpack5 没有 file/url-loader 也是好的。

三、查找

那就缩小了排查范围,就着这几个关键字进行了搜索,终于发现问题:webpack5 对于资源文件处理进行了更改,具体可看官网《资源模块》介绍。也就是 webpack5 不需要再对资源文件指定相应的 loader 去处理了。而是加上 type 声明,比如 type: asset/resource。而如果我们还按照老的配置,可能会对资源重复处理,从而导致生成了一堆没法用的损坏图。

确实是 css-loaderfile/url-loader 有冲突,决定性证据也懒得去看源码了,这 webpack 4 升 5 真的改变好大。还记得刚出那会一大堆插件不兼容,不升级都没法用。

四、解决

既然知道问题了,那么就有以下解决方案:

  1. 继续使用 file/url-loader,但是要加上 type: 'javascript/auto' 声明。看官网例子:
module.exports = {
  module: {
    rules: [{
        test: /\.(png|jpg|gif)$/i,
        use: [{
          loader: 'url-loader',
          options: { limit: 8192 },
        }],
        type: 'javascript/auto', // 加上这一句
      },
    ]},
};

结果:css-loader 不会再在根路径产出图片,打包后的 css url 使用的是 file/url-loader 生成的图片。

  1. 继续使用 file/url-loader,指定不要参与 url 的处理:
module.exports = {
  module: {
    rules: [{
        test: /\.(png|jpg|gif)$/i,
        use: [{
          loader: 'url-loader',
          options: { limit: 8192 },
        }],
        dependency: { not: ['url'] }, // 加上这一句
      },
    ]},
};

结果:css-loader 和 file/url-loader 依然在各自地点生成图片,css url 使用的依然是 css-loader 在根路径产出的图片。各论各的,毫无疑问容易造成冗余,以及文件位置不统一。

  1. 完全改为 webpack5 的写法,不用再指定 loader 去处理:
module.exports = {
  module: {
    rules: [{
        test: /\.(png|jpg|gif|svg)$/i,
        type: 'asset/resource',
        generator: {
          filename: 'media/[name].[hash].[ext]',
        },
      },
    ]},
};

结果:不会再在根路径产出图片,打包后的 css url 使用的是统一位置生成的资源文件。

  1. 或许还有其他,够用了…

五、推荐

方法二明显不太合理,淘汰。简单修改可以用方法一。不嫌麻烦还是推荐改成方案三。

再吐槽一句,这算不算 webpack 的背刺?