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

背景

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

图片地址转换

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

为什么需要转化

  1. 小程序的限制。小程序对包体积是有限制的:
  • 未配置分包的情况下,小程序包源码大小不超过 4M
  • 分包情况
    • 子包 < 2M
    • 主包 < 4M
    • 总包 < 16M

如果不配置 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种情况:

  • base64
  • VITE_ASSET__contentHash

同时也有一个保存了 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 这个钩子来替换地址。

有几个关键点:

  • 区分是本地编译还是生产环境编译,分别对应本地服务地址和CDN地址 (例如通过注入process.env变量判断);

  • 基于性能考虑,dev环境 图片不需要生成hash值,也不需要生成新图片目录,只需要把图片地址替换成本地地址,同时开启一个静态服务,可以通过http访问到图片即可;

  • build环境 图片需要生成hash值,需要生成新图片目录,方便上传CDN;

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


image.png

效果对比

vite 使用自定义插件前后小程序编译后的体积:

image.png

此时,图片被打包 ,到小程序内,整个小程序体积 4.4MB

图片地址改成CDN地址,通过网络请求访问,上传小程序前删除图片目录,整个小程序体积832KB


image.png

使用自定义插件后小程序的体积下降了超过80%

总结

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容