前言
网络上大家都调侃程序员不看warning,只看error,确实程序和人有一个能跑就行,一个小小的warning,还想参加蟠桃大会... 跑题了 最近在使用一个新的前端框架,发现某些情况下会出现一些意料之外的错误,整个排查过程有些波折,所以想记录一下~
问题的出现
wxt 一个快速开发浏览器插件的框架,其借助了vite构建能力,实现插件开发过程的热更新,对插件开发体验挺好的,支持任意UI描述框架。于是在尝试过程中,我使用了Vue来构建界面,但在content-script
的开发过程中,偶然发现更新了script的内容之后,出现了两个问题:
- 浏览器热更新失效了,并且console打印了vue的warn。

- vite进程中断了,终端控制台报错:
vbnet
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
是在handleHotUpdate
和genScriptCode
调用resolveScript
的,显然一个是热更新用的,一个是打包编译用的。而resolveScript
代码里面有两个前置return的判断条件:
- 没有
script
- 存在缓存
在resolveScript
打了个日志,再跑一下复现流程,发现修改App.vue
文件之后,执行了两次resolveScript
第一次由热更新触发,第二次由build构建触发。
(忘记讲了,wxt框架针对插件的content-script脚本,是使用build构建之后,再插入到页面中,并且wxt启动的时候,是background.js content-script.js popup 三种插件模式都使用)
第二次执行resolveScript
的时候,没调用compiler-sfc
的compileScript

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

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

原来,vite的createServer
和build
共用同个vite进程,那@vitejs/plugin-vue
也只会在vite.config.ts中执行那一次,所以@vitejs/plugin-vue
的cached也就被api.createServer
和api.build
共用了。
所以,要解决此问题,只能从根源上解决,那就是将wxt的build
模式,由node子进程去执行!
意外的解决方案
在最初看到vue单文件解析器的inlineTemplate
属性之后,我在wxt框架源码中搜索inline
/template
/inlineTemplate
,发现它默认会给vite
的build.sourceMap
属性设置成内联inline
。程序员的下意识思路就是,试试改成其他的会怎么样?
然后我将其改为false
,开发插件要个嘚sourceMap
啊...
结果,它就正常工作了!!! 到现在我还没找到sourceMap和解析器inlineTemplate
的关联...
后续
给它issue回复了这个临时解决方案~ wxt-issue#538
写在最后
debug源于好奇心,愿好奇心不减,代码永存,脑机永生。
