Vue是一套用于构建用户界面的渐进式框架,与其它大型 JS 框架不一样,Vue 被设计为能够自底向上逐层应用,更易上手,还便于与第三方库或既有项目整合,所以,Vue彻底可以为复杂的单页应用提供驱动。css
2020年09月18日,Vue.js 3.0 正式发布,做者尤雨溪将其描述为:更快、更小、更易于维护。html
本次发布, Vue框架自己迎来了多项更新,如Vue 此前的反应系统是使用 Object.defineProperty 的 getter 和 setter。 可是,在 Vue 3中,将使用 ES2015 Proxy 做为其观察者机制,这样作的好处是消除了之前存在的警告,使速度加倍,并节省了一半的内存开销。vue
除了基于 Proxy 的观察者机制,Vue 3的其余新特性还包括:node
1. Performance(性能提高)react
在Vue 2中,当某个DOM须要更新时,须要遍历整个虚拟DOM树才能判断更新点。而在Vue 3中,无需此项操做,仅需经过静态标记,对比虚拟节点上带有patch flag的节点,便可定位更新位置。webpack
对比Vue 2和Vue 3的性能差别,官方文档中给出了具体数听说明:git
· SSR速度提升了2~3倍github
· Update性能提升1.3~2倍web
2. Composition API(组合API)vue-cli
Vue 2中有data、methods、mounted等存储数据和方法的对象,咱们对此应该不陌生了。好比说要实现一个轮播图的功能,首先须要在data里定义与此功能相关的数据,在methods里定义该功能的方法,在mounted里定义进入页面自动开启轮播的代码…… 有一个显而易见的问题,就是同一个功能的代码却要分散在页面的不一样地方,维护起来会至关麻烦。
为了解决上述问题,Vue 3推出了具有清晰的代码结构,并可消除重复逻辑的 Composition API,以及两个全新的函数setup和ref。
Setup 函数可将属性和方法返回到模板,在组件初始化的时候执行,其效果相似于Vue 2中的beforeCreate 和 created。若是想使用setup里的数据,须要将值return出来,没有从setup函数返回的内容在模板中不可用。
Ref函数的做用是建立一个引用值,主要是对String、Number、Boolean的数据响应作引用。
相对于Vue 2,Vue 3的生命周期函数也发生了变动,以下所示:
· beforeCreate -> 请使用 setup()
· created -> 请使用 setup()
· beforeMount -> onBeforeMount
· mounted -> onMounted
· beforeUpdate -> onBeforeUpdate
· updated -> onUpdated
· beforeDestroy -> onBeforeUnmount
· destroyed -> onUnmounted
· errorCaptured -> onErrorCaptured
须要注意的是,Vue 2使用生命周期函数时是直接在页面中写入生命周期函数,而在Vue 3则直接引用便可:
import {reactive, ref, onMounted} from 'vue'
3. Tree shaking support(按需打包模块)
有人将“Tree shaking” 称之为“摇树优化”,其实就是把无用的模块进行“剪枝”,剪去没有用到的API,所以“Tree shaking”以后,打包的体积将大幅度减小。
官方将Vue 2和Vue 3进行了对比,Vue 2若只写了Hello World,且没有用到任何的模块API,打包后的大小约为32kb,而Vue 3 打包后仅有13.5kb。
4. 全新的脚手架工具:Vite
Vite 是一个由原生 ESM 驱动的 Web 开发构建工具。在开发环境下基于浏览器原生 ES imports 开发,在生产环境下基于 Rollup 打包。
和 Webpack相比,具备如下特色:
· 快速的冷启动,不须要等待打包
· 即时的热模块更新
· 真正的按需编译,不用等待整个项目编译完成
因为彻底跳过了打包这个概念,Vite的出现大大的撼动了Webpack的地位,且真正作到了服务器随起随用。看来,连尤大神都难逃“真香”理论。
Vite究竟有什么魔力?不妨让咱们经过实际搭建一款基于Vue 3 组件的表格编辑系统,亲自体验一把。
1. 执行代码:
$ npm init vite-app <project-name> $ cd <project-name> //进入项目目录 $ npm install //安装项目所需依赖 $ npm run dev //启动项目
咱们来看下生成的代码, 由于 vite 会尽量多地镜像 vue-cli 中的默认配置, 因此,这段代码看上去和 vue-cli 生成的代码没有太大区别。
├── index.html ├── package.json ├── public │ └── favicon.ico └── src ├── App.vue ├── assets │ └── logo.png ├── components │ └── HelloWorld.vue ├── index.css └── main.js
2. 执行下列命令:
此时若是不经过 npm run dev 来启动项目,而是直接经过浏览器打开 index.html, 会看到下面的报错:
报错的缘由:浏览器的 ES module 是经过 http 请求拿到模块的,因此 vite 的一个任务就是启动一个 web server 去代理这些模块,在 vite 里是借用了 koa 来启动的服务。
export function createServer(config: ServerConfig): Server { // ... const app = new Koa<State, Context>() const server = resolveServer(config, app.callback()) // ... const listen = server.listen.bind(server) server.listen = (async (...args: any[]) => { if (optimizeDeps.auto !== false) { await require('../optimizer').optimizeDeps(config) } return listen(...args) }) as any return server }
因为浏览器中的 ESM 是获取不到导入的模块内容的,须要借助Webpack 等工具,若是咱们没有引用相对路径的模块,而是引用 node_modules,并直接 import xxx from 'xxx',浏览器便没法得知你项目里有 node_modules,只能经过相对路径或者绝对路径去寻找模块。
这即是vite 的实现核心:拦截浏览器对模块的请求并返回处理后的结果(关于vite 的实现机制,文末会深刻讲解)。
3. 生成项目结构:
入口 index.html 和 main.js 代码结构为:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="icon" href="/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vite App</title> </head> <body> <div id="app"></div> <script type="module" src="/src/main.js"></script> </body> </html> // main.js // 只是引用的是最新的 vue3 语法,其他相同 import { createApp } from 'vue' import App from './App.vue' import './index.css' createApp(App).mount('#app')
4. 进入项目目录:cd myVue3
5. 安装相关模块:npm install
6. 下载模块:
7. 启动项目:npm run dev
8. 进入地址,当咱们看到这个页面时,说明项目已经成功启动了。
1. /@module/ 前缀
对比工程下的 main.js 和开发环境下实际加载的 main.js,能够发现代码发生了变化。
工程下的 main.js:
import { createApp } from 'vue' import App from './App.vue' import './index.css' createApp(App).mount('#app')
实际加载的 main.js:
import { createApp } from '/@modules/vue.js' import App from '/src/App.vue' import '/src/index.css?import' createApp(App).mount('#app')
为了解决 import xxx from 'xxx' 报错的问题,vite 对这种资源路径作了统一处理,即添加一个/@module/前缀。
在 src/node/server/serverPluginModuleRewrite.ts 源码的 koa 中间件里能够看到 vite 对 import 作了一层处理,其过程以下:
· 在 koa 中间件里获取请求 body
· 经过 es-module-lexer 解析资源 ast 拿到 import 的内容
· 判断 import 的资源是不是绝对路径,绝对视为 npm 模块
· 返回处理后的资源路径:"vue" => "/@modules/vue"
2. 支持 /@module/
在 /src/node/server/serverPluginModuleResolve.ts 里能够看到大概的处理逻辑:
· 在 koa 中间件里获取请求 body
· 判断路径是否以 /@module/ 开头,若是是取出包名
· 去node_module里找到这个库,基于 package.json 返回对应的内容
3. 文件编译
经过前文,咱们知道了 js module 的处理过程,对于vue、css、ts等文件,其又是如何处理的呢?
以 vue 文件为例,在 webpack 里使用 vue-loader 对单文件组件进行编译,在这里 vite 一样拦截了对模块的请求并执行了一个实时编译。
经过工程下的 App.vue 和实际加载的 App.vue,便发现改变。
工程下的 App.vue:
<template>  <HelloWorld msg="Hello Vue 3.0 + Vite" /> </template> <script> import HelloWorld from './components/HelloWorld.vue'; export default { name: 'App', components: { HelloWorld, }, }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
实际加载的 App.vue:
import HelloWorld from '/src/components/HelloWorld.vue'; const __script = { name: 'App', components: { HelloWorld, }, }; import "/src/App.vue?type=style&index=0&t=1592811240845" import {render as __render} from "/src/App.vue?type=template&t=1592811240845" __script.render = __render __script.__hmrId = "/src/App.vue" __script.__file = "/Users/wang/qdcares/test/vite-demo/src/App.vue" export default __script
可见,一个 .vue 文件被拆成了三个请求(分别对应 script、style 和template) ,浏览器会先收到包含 script 逻辑的 App.vue 的响应,而后解析到 template 和 style 的路径后,再次发起 HTTP 请求来请求对应的资源,此时 Vite 对其拦截并再次处理后返回相应的内容。
// App.vue?type=style import { updateStyle } from "/vite/hmr" const css = "\n#app {\n font-family: Avenir, Helvetica, Arial, sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-align: center;\n color: #2c3e50;\n margin-top: 60px;\n}\n" updateStyle("7ac74a55-0", css) export default css // App.vue?type=template import {createVNode as _createVNode, resolveComponent as _resolveComponent, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock} from "/@modules/vue.js" const _hoisted_1 = /*#__PURE__*/ _createVNode("img", { alt: "Vue logo", src: "/src/assets/logo.png" }, null, -1 /* HOISTED */ ) export function render(_ctx, _cache) { const _component_HelloWorld = _resolveComponent("HelloWorld") return (_openBlock(), _createBlock(_Fragment, null, [_hoisted_1, _createVNode(_component_HelloWorld, { msg: "Hello Vue 3.0 + Vite" })], 64 /* STABLE_FRAGMENT */ )) }
vite对于其余的类型文件的处理几乎都是相似的逻辑,即根据请求的不一样文件类型,作出不一样的编译处理结果。
· Vue 3 组件开发实战:搭建基于SpreadJS的表格编辑系统(组件集成篇)