likes
comments
collection
share

一个代码报错的溯源过程

作者站长头像
站长
· 阅读数 4

前言

一个代码报错的溯源过程 网络上大家都调侃程序员不看warning,只看error,确实程序和人有一个能跑就行,一个小小的warning,还想参加蟠桃大会... 跑题了 最近在使用一个新的前端框架,发现某些情况下会出现一些意料之外的错误,整个排查过程有些波折,所以想记录一下~

问题的出现

wxt 一个快速开发浏览器插件的框架,其借助了vite构建能力,实现插件开发过程的热更新,对插件开发体验挺好的,支持任意UI描述框架。于是在尝试过程中,我使用了Vue来构建界面,但在content-script的开发过程中,偶然发现更新了script的内容之后,出现了两个问题:

  1. 浏览器热更新失效了,并且console打印了vue的warn。

一个代码报错的溯源过程

  1. vite进程中断了,终端控制台报错:
 ERROR  "default" is not exported by "entrypoints/content/App.vue?vue&type=script&setup=true&lang.ts", imported by "entrypoints/content/App.vue".

问题的排查

查了一下框架github仓库的issue,有人提过content-script的UI无法渲染问题,但无人给出解决方案。那么有两个选择:自己排查 or 放弃。牛马人,当然要试一试排查。

这看似是两个不相干的问题,一个是丢失template、另外一个是没有默认导出属性;但Vue单文件都是由compiler-sfc解析器解析成js的,我们可以暂时先把它看成是一个问题:compiler-sfc解析异常。

排查目标确定

既然假设compiler-sfc解析异常,那就先从compiler-sfc解析产物入手,看它解析之后的js长啥样。

一个代码报错的溯源过程

我改了handleClick方法的代码,App.vue文件解析之后,setup方法返回了__returned__变量,Hmm...render函数呢?不见了!难怪vue提示没有模板或者渲染函数。

既然产物有问题,那就对比一下正常情况下的产物应该是什么样的。

一个代码报错的溯源过程 显然正常情况下单文件解析器是有返回渲染函数的,哪是哪里出问题了?

对比两者,可以看出异常产物有__returned__变量,那就直接进compiler-sfc看它是怎么处理的。

一个代码报错的溯源过程

不是内联模板的情况下,就插入这段代码;没了,线索中断了吗?下一步?

换个方向思考,谁调用了compiler-sfc?在vite解析vue单文件中,使用了@vitejs/plugin-vue插件来解析。好!问题换成:难道@vitejs/plugin-vue在调用compiler-sfc的时候设置了这个inlineTemplate属性?

排查的深入

一个代码报错的溯源过程

一个代码报错的溯源过程 在查看@vitejs/plugin-vue源码之后,发现是在生产模式下才有可能把它设置成true,没了,线索又断了?

并没有,它引入了一个新方向,热更新(hot updated),并且我知道产物文件是打包生成的,那肯定涉及生产模式。好的,那么问题换成:生产模式与开发模式下,@vitejs/plugin-vue插件做了啥?

错误根源的发现

@vitejs/plugin-vue是在handleHotUpdategenScriptCode调用resolveScript的,显然一个是热更新用的,一个是打包编译用的。而resolveScript代码里面有两个前置return的判断条件:

  1. 没有script
  2. 存在缓存

resolveScript打了个日志,再跑一下复现流程,发现修改App.vue文件之后,执行了两次resolveScript第一次由热更新触发,第二次由build构建触发。

(忘记讲了,wxt框架针对插件的content-script脚本,是使用build构建之后,再插入到页面中,并且wxt启动的时候,是background.js content-script.js popup 三种插件模式都使用)

第二次执行resolveScript的时候,没调用compiler-sfccompileScript

一个代码报错的溯源过程

查看代码上下文之后,发现热更新的时候,会设置缓存(热更新缓存用于只替换修改的内容,不会全量替换)

一个代码报错的溯源过程

问题就出在了缓存这里,build编译代码的时候,拿的是热更新的代码,导致APP.vue解析成js丢失了一些数据。 那为什么vite的buildserver会共用@vitejs/plugin-vue的上下文呢?难道vite设计如此?进vite官网看看!

一个代码报错的溯源过程

原来,vite的createServerbuild共用同个vite进程,那@vitejs/plugin-vue也只会在vite.config.ts中执行那一次,所以@vitejs/plugin-vue的cached也就被api.createServerapi.build 共用了。

所以,要解决此问题,只能从根源上解决,那就是将wxt的build模式,由node子进程去执行!

意外的解决方案

在最初看到vue单文件解析器的inlineTemplate属性之后,我在wxt框架源码中搜索inline/template/inlineTemplate,发现它默认会给vitebuild.sourceMap属性设置成内联inline。程序员的下意识思路就是,试试改成其他的会怎么样?

然后我将其改为false,开发插件要个嘚sourceMap啊...

结果,它就正常工作了!!! 到现在我还没找到sourceMap和解析器inlineTemplate的关联...

后续

给它issue回复了这个临时解决方案~ wxt-issue#538

写在最后

debug源于好奇心,愿好奇心不减,代码永存,脑机永生。

一个代码报错的溯源过程