自定义vite插件处理uniapp小程序图片

2022-09-30  本文已影响0人  相遇一头猪

背景

最近,新来的小组长要整活,要把项目的小程序重构。之前小程序是用 uniapp 实现的,打包工具是webpack,现在要改成用 uniapp + vite。

图片地址转换

以下小程序特指字节小程序

为什么需要转化

  1. 小程序的限制。小程序对包体积是有限制的:

如果不配置 url-loader 和 file-loader (webpack的2个loader) ,默认会把本地图片转成内联base64,包体积很快就会到达上限,导致无法在开发者工具上扫码预览,无法上传到小程序后台。

  1. 过多的内联图片导致包体积过大,会影响首次加载速度。

如何转换

目前在项目内,所有涉及到本地图片的都是写在css中,例如:

.empty-img {
  width: 100px;
  height: 86px;
  background: url('@/static/no-content.png') no-repeat 0% 0%/100% 100%;
}

所有本地图片都是统一存在 static 目录下,方面构建时上传到 CDN。

webpack

通过在 vue.config.js(uniapp小程序webpack配置文件) 配置 url-loaderfile-loader ,这样在编译时,可以把小于阈值的图片转成base64内联到代码,超过阈值的图片则交由 file-loader 处理,把图片地址改成本地或者CDN地址。

image.png

vite

在vite,看了vite配置的,vite也有类似 url-loader 的配置:build.assetsInlineLimit

image.png

vite也提供了静态资源路径处理的属性 —— base:

image.png

实际上,在小程序中尝试把base设置成CDN的地址,编译出来的路径还是相对路径,跟预期结果不一致,无法满足需求。

uniapp是通过vite插件来转化图片

如果是使用 vite,uniapp 默认把 小于40k的 图片转成 base64。按照 uniapp 官网文档介绍,uniapp 小程序是不支持开发者在 vite 里配置 build.assetsInlineLimit 的:

image.png

oh,那不是无法实现 url-loader 的功能?

先来看下 uniapp 使用 vite 后,css中图片路径编译后的结果:

编译前:

image.png
编译后:
image.png

编译后,基本上图片会被转成base64(超过40k的图片会被转成相对路径),类似这样:

background-image:url('@/static/no-content.png') 
/** 转化后 
    background-image:url(../../static/no-content.png) 
**/

40K这个限制是在哪里看到的?

官方文档里没写,我猜测 uniapp 针对 vite 做了一些配置。于是写了个 vite 插件查看所有配置,发现 assetsInlineLimit 是40k,也就是说大多数图片 (项目中图片一般先经过压缩,超过40K的较少) 会被转成base64内联到代码内。

编译流程图

image.png

vite:css 执行过程

uniapp 实现的一个 vite插件,主要作用是处理css中引用的图片地址:

image.png
经过 vite:css 处理后,css中的图片地址有2种情况:

同时也有一个保存了 contentHash 到 fileName 的映射,举个🌰:

// cs
.test {
    background-image: url('@/static/index/no-content.png');
}

// 经常 vite:css 转化后
.test {
    background-image: url("__VITE_ASSET__8a6b759d__");
}

// 映射
{ '8a6b759d' => 'static/index/no-content.png' }

实际上,最终编译成的小程序.ttss文件是这样的:

.test {
  background-image: url("../../static/index/no-content.png");
}

这一步从 VITE_ASSET__contentHash 转成相对路径是在 vite:css-post 实现的。

实现自定义插件

由于直接在 vite.config.js 配置 build.assetsInlineLimt 无效,可以通过自定义插件强制改变 build.assetsInlineLimt 的大小,例如改成2k:

import { Plugin } from "vite";

export function rewriteAssetsInlineLimit(opt: {
  size: number
}): Plugin {
  const { size } = opt || { size: 2048 };
  return {
    name: "rewrite-build-assetsInlineLimit",
    enforce: 'post',
    config: (config) => {
      if (config.build) {
        config.build.assetsInlineLimit = size;
      }
    }
  };
}

使用该插件后,现在编译后的结果:


image.png

小于 2k 转成 base64,而超过2k的变成了不带hash的相对路径。我们需要的不是相对路径,而是一个http(s)资源的地址,可以由开发者直接通过配置传入。

按照上文所讲,最终生成的相对路径其实是直接根据 contenthash 得到文件路径,它的定义如下:

export function getAssetFilename(
  hash: string,
  config: ResolvedConfig
): string | undefined {
  return assetHashToFilenameMap.get(config)?.get(hash)
}

那么就可以从设置映射的地方入手打补丁,先把不带hash 改成 带hash,同时生成新的带hash的资源目录。

改造前编译结果

image.png

改造后编译结果

image.png

改造前目录

image.png

改造后目录

image.png

接下来,还需要一个插件来把相对地址替换成 http(s)资源的地址(就是把assets目录上传到CDN后的地址)。

这里用到 transform 这个钩子来替换地址。

有几个关键点:

到这里基本已经完成了,目前本地编译结果如下:


image.png

总结

目前已经把uniapp小程序基于webpack打包迁移到 uniapp小程序基于 vite 打包,打包工具需要的能力基本完成了,后续如果遇到坑点会继续发出来。

上一篇 下一篇

猜你喜欢

热点阅读