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> ![](./assets/logo.png) <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的表格編輯系統(組件集成篇)