课程来源:慕课1245,笔记
1.vue3简介
1.1 课程简介
Compiler原理介绍,了解Vue3带来的性能提升,开发架构上的变化和打包编译原理。在课程实战环节中,介绍了最新的API用法、遗弃的API和升级指南。Vite作为Vue3开发环境,可以实现动态加载,冷启动 + 编译,方便开发的同时,使用rollup大幅降低了配置流程,结合Vue3的composition API,更加方便了开发。介绍了这么多,快来试试吧!
1.2 vue3的优势:
1.3 vue3.0带来的变化:
- 按需加载,VDom/reactive 算法,e.g. v-model/Transition
- 组合API
- TS 支持,
- 新增 Fragment,不受根节点限制,渲染函数可以接受array
- 新增Teleport,类似portal,随用随取,e.g. dialog、action
- 新增Suspense,嵌套的异步依赖,async setup
- 性能提升 1.3~2.X
1.4 vue-next-template-explorer 尝鲜:
示例:
可以看到模板中有四个div,也就是四个根节点,在vue2中,一个模板里面只能有一个根节点。编译器使用 createVNode 创建了虚拟节点,并启用 hoistStatic 能力将静态节点和动态节点区分,对静态内容,不再做更新处理。
上面这个示例,不启用 cacheHandlers 之前是将事件绑定在当前上下文,启用了 cacheHandlers 的能力,会将事件进行全局注册。在定义组件的场景下,启用 cacheHandlers 就不会对重复创建的组件上的事件多次实例化。
ssr 模式下,抽离了静态节点,转化为string字符串。
根据上面几个实例,可以看到 compiler 优化的几点:
- 静态节点不在做更新处理(hoistStatic -> SSR)
- 静态绑定的class id等属性不做更新处理
- 结合打包hint,进行更新分析(动态绑定)
- 事件监听器 Cache 缓存处理(cacheHandlers)
- hoistStatic自动针对多静态节点进行优化,输出字符串
- 按需加载,当模板为空时,输出为null
总结起来就是:
- virtual dom机制调整
- 内存优化
- 按需加载,更灵活的组件化
2.vite
vite是一个http服务器,特殊的地方:
- 可以在单文件中写es6的语法
- 支持热更新,请求的内容才会被打包更新
- rollup打包,按需编译
快速启动(冷启动,vite的文件放在内存里,加载更快(需要webpack去打包文件,然后去浏览器侧请求)。
vite尝试步骤:
- 在命令行: nrm use npm
- 在命令行:npm install -g vite
- 在命令行:mkdir vite-demo
- 在命令行:cd vite-demo
- 在命令行: npm init -y
- 新建 APP.vue main.js
- 执行:vite 执行
最近找到一篇文章:vite对浏览器的请求做了什么(https://juejin.cn/post/7033713960784248868)
作者用很简单的方式讲解了vite编译的原理,自创了一个简易版的demo,很好理解,讲解了怎么实现vite的本地server、怎么编译sfc、怎么解决文件导入路径问题、怎么处理type module,当然前提是要了解vue 以及 vue compiler。
文章里 client 的 demo 实现如下:
const Koa = require('koa') const app = new Koa() const fs = require('fs') const path = require('path') const compilerSfc = require('@vue/compiler-sfc') const compilerDom = require('@vue/compiler-dom') // 把能读出来的文件地址变成相对地址 // 正则替换 重写导入 变成相对地址 // import { createApp } from 'vue' => import { createApp } from '/@modules/vue' function rewriteImport (content) { return content.replace(/ from ['|"](.*)['|"]/g, function (s0, s1) { // s0匹配字符串,s1分组内容 // 是否是相对路径 if (s1.startsWith('./') || s1.startsWith('/') || s1.startsWith('../')) { // 直接返回 return s0 } else { return ` from '/@modules/${s1}'` } }) } app.use(async (ctx) => { const { url, query } = ctx.request; // 处理请求资源代码都写这 if (url === '/') { const p = path.join(__dirname, './index.html') // 绝对路径 // 首页 ctx.type = 'text/html' ctx.body = fs.readFileSync(p, 'utf8') } else if (url.endsWith('.js')) { // 响应js请求 const p = path.join(__dirname, url) ctx.type = 'text/javascript' ctx.body = rewriteImport(fs.readFileSync(p, 'utf8')) // 处理依赖函数 } else if (url.indexOf('.vue') > -1) { // 处理vue文件 App.vue?vue&type=style&index=0&lang.css // 读取vue内容 const p = path.join(__dirname, url.split('?')[0]) // compilerSfc解析sfc 获得ast const ret = compilerSfc.parse(fs.readFileSync(p, 'utf8')) // App.vue?type=template // 如果请求没有query.type 说明是sfc if (!query.type) { // 处理内部的script const scriptContent = ret.descriptor.script.content // 将默认导出配置对象转为常量 const script = scriptContent.replace( 'export default ', 'const __script = ', ) ctx.type = 'text/javascript' ctx.body = ` ${rewriteImport(script)} // template解析转换为单独请求一个资源 import {render as __render} from '${url}?type=template' __script.render = __render export default __script` } else if (query.type === 'template') { const tpl = ret.descriptor.template.content // 编译包含render模块 const render = compilerDom.compile(tpl, { mode: 'module' }).code ctx.type = 'text/javascript' ctx.body = rewriteImport(render) } else if (url.endsWith('.png')) { ctx.body = fs.readFileSync('src' + url) } } }) app.listen(3001, () => { console.log('dyVite start!!') })