故事要从我们公司的新官网说起,新官网是叫外包做的,前后端没有分离,对,你没听错,都到了 2023 年的今天,新项目依然是前后端混在一起,堆成一座屎山,然后通过模板渲染展示
故事要从我们公司的新官网说起,新官网是叫外包做的,前后端没有分离,对,你没听错,都到了 2023 年的今天,新项目依然是前后端混在一起,堆成一座屎山,然后通过模板渲染展示网页。 当然,对于非研发的,技术栈咋样都不重要,又不是不能用~ 各位看官,听到上面的情况,是不是隐隐约约感觉到会有啥惊喜(惊吓) 哈,你来翻译翻译,什么叫惊喜?好,那我来翻译翻译,惊喜(bushi)就是自从新官网上线后,PV(页面访问量)下降了 50%。是的,你没看错,原因就是打开官网巨卡,一般需要 7~8s。 就单单请求个 html,就需要耗费 7s+的时间。 运营那边被老板亲切问候后,就跑过来找我们研发帮忙看问题,把情况说的特严重,唉,最终还不是得我们帮忙处理烂摊子 那没办法,我们就开始分析一通,啪的一下,很快呀,就找到了加载贼慢的原因:
那第二点自然是需要前端去搞了,图片太多,导致 http 请求太多,那好办,把小图片转 base64 不就好。 嗯,思路很简单,如果是前后端分离的项目,我们一般无脑配置 webpack url-loader 的体积限制就好,或者配置 webpack5 的 asset,即在导出一个 data URI 和发送一个单独的文件之间自动选择:
很简单对吧,但当你想快速 cv 以上配置的时候,发现,前端代码都是混在后端代码里面,一堆 html 文件,html 里面又混杂着一堆的 Thymeleaf 语法(Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP,JSP??都啥年代了) 越看越不对劲,正所谓理想很丰满,现实很骨感,唉,只能长叹一声。 但没办法,领导可不管用啥方式,只要有个方法把 html 里面图片小于某个指定值,如 10k,那就转 base64,让这些小图片不走 http 请求。 思路那么,正常的路走不通(当然也有可能有其他更快捷的方式,只是比较赶,暂时想不到更好、更简单的方式实现),那就另辟蹊径。 全体流程如下: 1. mvn clean // marven 清空输出目录 2. mvn compile // marven编译 3. 将所有 html 的的小图片都转 base64 4. mvn package // marven 打包 以上主要重点关注第3点,既然要将所有 html 的的小图片都转 base64,那么自然而然可以通过写个 node 脚本来实现,大概可以分为以下几个步骤:
上面的步骤应该比较清晰的,不过,等一下,最近 chatgpt 不是很火吗,让它来写不就好~ 实现过程递归读取指定目录下的所有 html 文件路径 htmlPaths首先判断入口目录的下的内容,是文件的话就判断是否以.html 结尾,是的话则放入 htmlFilePaths,是目录的话则递归遍历,那我们来问神奇的 chatgpt: 嗯,很不错,第一次几乎完美,顺利拿到所有的 html 路径 获取每个 html 里面的图片 src自然而然想到用正则匹配来做,所以我马上问: 一看结果就不是我想要的,当然也是我描述的不清晰,导致 chatgpt 以为要获取 html 文档的图片 src 获取 html 字符串的所有图片 src应该问要获取 html 字符串的,所以我接着问:
运行后,得到的结果: 不错不错,再接再厉。 忽略注释的代码但 html 代码可能 img 被注释了,如 <!-- <img src='xxx.jpg'> -->,那么我们实际上没必要去转换,故我们让个让其忽略注释的: 那我们来试试,将其中注释代码中插入 img,看看是否会解析:
可以看到被注释掉的 image3 不会被匹配到 本身是 base64,则忽略继续继续,但图片 src 可能一开始就是 base64 了,就没必要转了,而 base64 是 data:image 开头的,所以我们再让它加下条件:
我们运行看下结果 成功跳过了 base64 的,但是,好像没有了忽略注释代码的条件,啊这。。 所以我就让他忽略注释和 base64 的,但好像一直丢三落四,按下葫芦浮起瓢,大家可以看看 上面的注释的被匹配到了 注释又又又被匹配到了,算了,我直接问如果忽略 base64,然后再组合忽略注释的就好,组合后的代码如下:
忽略 Thymeleaf 语法还有个问题,img 的 src 可能是通过服务端渲染导入的,那么我们要忽略掉,大致语法为 <img th:src="${t.imgUrl1}" />,也就是以 th:开头的
上面的@{可以忽略,实际上也是属于 Thymeleaf 语法,屏蔽 th:src 即可 结合起来,封装成一个函数
运行下,看下结果: src 转 base64自此,我们拿到所有 html 里面的 src 了,那么判断下是否小于指定 size,是的话转 base64 获取文件大小可通过 fs 的 statSync 来拿到对应文件的 size,如下
如果满足条件则将图片转 base64,而图片本身是 Buffer,所以要转一下:
通过加上前缀data:image/${extname.slice(1)};base64,,其中 extname 是文件后缀名,通过path.extname拿到:
每得到一个 base64,则替换原先的 src:
最后将新的 html 替换旧的 htmlhtml 下满足条件的 src 全部替换好后,就可以将新的 html 替换老的了,实际上也就是重写回去:
性能优化但我们发现整个过程中有两处可以优化代码:
针对第一点,我们可以通过声明一个 Set,存放大于指定尺寸的 src:
针对第二点,我们可以通过声明一个 Map,key 为 src,value 为 base64:
每次判断到对应的 src 有值,则直接拿之前的 base64,不再转化:
总的代码
总结因为太多的小图片导致 http 请求阻塞,所以要把满足条件的小图片转为 base64, 而前端的 html 混在在后端代码里面,且里面混杂着 Thymeleaf 模板语法,想通过 webpack 打包的方式看起来好像不行(至少目前不知道咋办),所以退而求其之自己写个脚本来处理。 大致思路就是:
|
2021-06-04
2019-01-10
2019-02-17
2021-09-12
2021-09-30