这篇文章暂时只分析vite的基本和重点分析一下hmr的vue reload基本原理,其他部分暂时还没看。。
我们知道vite是不需要打包的,这是依赖了浏览器的esm模块化的功能,例如如果我们如果在html中这样写
<script type='module' src="/src/main.js"></script>
那么浏览器就会发出一个http://..../src/main.js的一个请求给倒vite的service,这个service是koa写的,这样我们就给它fs.read我们项目中的html,但是vite也有一个client端的代码,用于接受热更新的通知,所以我们同时还要把这个东西给注入进去,这样浏览器端就有了client.js,
然后接下来浏览器就通过这两个插入的script标签继续请求.js,我们的koa接受到之后,再去fs.read文件返回即可,我这里暂时还放的是vue2的,因为公司用的还是vue2的,但是后面分析流程还是会讲vue3,
这是main.js的原文件
import Vue from 'vue'
import App from './components/App.vue'
let app = new Vue({
render: h => h(App) //当h接受obj时候会走createComponent
}).$mount('#app')
main.js会请求一个vue第三方库,对于第三方库我们都知道需要把前缀改为/@module/vue,这样,不然浏览器会报错,这样改完我们分析url带@module,就知道去node_modules里面去取了,对于第三方库的依赖我们也要去reWriteImport,
如果我们自己的App.vue,那么就去src底下读,这没有任何问题,因为浏览器会自动根据当前路径拼接./components/App.vue
const vue = fs.readFileSync(p, 'utf-8')
cacheEntry.set(url, vue)
const ast = compilerSFC.parse(vue)
当然vue不能fs之后直接传,我们要用compilerSFC分析之后得到一个ast对象,把东西拼一下再翻回去,这里主要是对template的处理,需要再单独发一个请求url带上?template后缀
res = ` import {render as _render} from '${url}?type=template' ${rewriteImport(aa)} let compVm _script.render = _render ....`
这样_script.render就是的返回值也是个js,这个当然也需要另外处理
hmr:(配合vue3)
这时候重点来了,当我们去改变文件的时候,我们需要使用第三方库chokidar来监听文件的变化,这里要分几种情况,如果是vue文件或者style文件,暂时先看vue,
cacheEntry是我们在第一次读文件的时候做的一个缓存
chokidar.watch('./src').on('change', (fpath) => { if (fpath.endsWith('vue')) { let vueFile = fpath.replace(path.dirname(fpath), "") let cache = cacheEntry.get(vueFile) if (isTemplateBlocksEqual(vueFile, cache)) { //todo } else { } ws.send(vueFile.slice(1)); } else { //todo } });
我们把它从缓存中拿出来和新的文件做一次比较,从而得出到底是文件的哪里发生了变化,
如果是template变化了,那么就只发一个vue-rerender,意思是把tempalte重新请求一次运行一下,如果不是,那么就是文件里script的东西变了,那么就发vue-reload,我们来看一下这种情况,当client收到之后
可以看到调用_VUE_HMR_RUNTIME_.reaload,然后重新import把新的m.default传入,进入这个函数可以看到我们把新的newComp给extend到老的record.component上去了,
record的数据结构长这样{component:{},instances:[]},一个是数据,一个是所有的组件的实例instances
我们这里的改动component其实是会影响到所有的实例上的instance.type,因为他们是引用关系,具体什么流程后面会说到,然后对于每一个改变的组件**,都会去从父亲开始update**,我觉得也许是跟slot有关系,但是也不确定。。。。然后用一个queueJob去防止parent多次重复upate,注释也写的很明白,
然后看一下update里面,
instance.update = effect(function componentEffect() {
if (!instance.isMounted) { // ... }
else { //... const nextTree = renderComponentRoot(instance);
const prevTree = instance.subTree;
instance.subTree = nextTree;
if ((process.env.NODE_ENV !== 'production')) {
startMeasure(instance, `patch`);
}
patch(prevTree, nextTree, // parent may have changed if it's in a teleport
next.el = nextTree.el; //...
}
这里用effect包裹了一下,应该也是为了收集依赖,
renderComponentRoot里面帮我们拿到了新的vnode,拿的方式就是normalizeVnode(render.call())
然后就进入了pactch的过程
暂时先写到这里,后面会附上github代码地址