源碼層面探索Vue3初始化
本期內(nèi)容小編將給大家?guī)恚?/p>
- 分析啟動腳本
- 查找入口文件
看看首次編譯過程,從源碼層面解析初始化細節(jié)
在我們開始這期內(nèi)容之前,小編先帶領大家回顧一下上期的知識點。
還是從這張圖開始,在上期內(nèi)容中我們談到,Vue3.0的源碼中最為核心的包就是叫做以 vue命名的文件包,在這個包內(nèi)有三個依賴關系,其中 runtime-dom 和 compiler-dom 是明顯的依賴關系,但其中還有一個隱性的依賴叫做reactivity 的依賴,這里面主要是做響應式的一些處理,后期小編會帶領大家著重探討這個依賴包的原理,而其實真正的依賴在 runtime-core 這個包里,這個包才是真正告訴你createApp 是怎么由來的,內(nèi)部又做了哪些事情。
小編這里梳理了一下邏輯關系圖,createApp 這個方法最終在 createAppAPI 內(nèi)部有一個工廠函數(shù)在這里得到了一次擴展 createApp 的方法,擴展此方法最為重要的一點就是為了讓這個函數(shù)變得更加通用,我們知道在 createApp 里面?zhèn)魅胍粋€ render 函數(shù),但是對于 createApp 來說它只管調用傳入進來的參數(shù),而并不關心你這個參數(shù)做了哪些的邏輯處理,真正我們能夠使用實例上的方法就是來源這個 createAPI 擴展之后得到的屬性,例如 component mixin use。
正文經(jīng)過之前的內(nèi)容回顧,想必大家對 createApp已經(jīng)有了深刻的了解,接下來將進入到本章內(nèi)容的核心部分,首先帶領大家分析啟動腳本,看看啟動腳本都做了哪些事情。
"dev": "node scripts/dev.js --sourcemap",
這行代碼想必大家應該都不陌生吧,這就是我們啟動項目的運行腳本我們就要從 dev.js 這個腳本文件開始下手。
WeChat46b3d93f7c154ae5e1b95022c46eb57b.png
minimist 是專門用來解析傳進來的參數(shù),什么意思呢?當我們在運行 npm run dev 這行命令的時候實際上是運行 node scripts/dev.js 當后面的路徑傳入 -- 或者 - 的時候會被解析成為第一個參數(shù),否則會打包成 vue 的包。
WeChat96a3eefa625cef34b030b68fe2353724.png
在這張圖中也能看出 dev-sfc 部分使用的打包格式為 esModule 格式,在瀏覽器中則會以type=module 出現(xiàn)。那么我么現(xiàn)在知道指令映射的代碼中的含義了,那么問題來了下一步我們該干什么呢?哈哈哈~ 如果對工程化的框架比較了解的話,我們就要看看打包工具的配置文件,看看打包過后你的 entry 入口文件和你的export 打包后輸出文件是怎樣配置的,在源碼中使用的是 rollup 打包生成的,所以我們要瀏覽一下 rollup.config.js 這個配置文件。
WeChatc8eb0ccf03ff5fb6b79d0ed338c01de1.png
在配置系文件中我們就可以看到,所有獲取包目錄都是從 packages 中得來的,通過process.env.TARGET 進行包名的路徑拼接,就會得到 /packages/xxx
WeChat024e26087aec3918dc0ecf425d3c7580.png
這里就是要配置的一些打包選項,例如CommonJs ECMAScript 格式,這里講一下 iife格式,就是打包后生成一個匿名函數(shù)自調的格式,(()=>{})()
WeChat465926fb4d72cd8107ca6bf763ffba32.png
這里我們看到真正打包的是以 runtime 開頭的包名稱,這時候他會根據(jù)你的指令中是否包含 -f runtime-xxx 這種格式的映射,生成兩種第一個是運行時打包,沒有在初始化的時候把編譯器打包進去,第二個則是全量打包。
WeChatee256bb4095f1e2a86c2e2321b505971.png
所以,我們就找到了在 packages/vue/index.ts這個路徑下的包,在這個包里面有一個叫做compileToFunction 編譯函數(shù)其真正的作用就是一個是解析 template 中的 innerHhtml,另一個就是生成一個 render 渲染函數(shù),另外,在這個函數(shù)中他會判斷你傳進來的參數(shù)是不是以字符串形式的模板另外一個是不是 dom,就是說支持的寫法 mount("< div>xxx") mount('#app') 或者 mount(app)。
WeChat98c822fa89560f875415a68ff6276a83.png
這里在 callStack 中就真正的解析了compileToFunction 是如何一步一步的變成渲染函數(shù)。
WeChat00a9bbcb59649cbbf8b1ce99b6c81d43.png
WechatIMG81.jpeg
從這個上面我們就看出,template 真正就是傳入的 innterHtml。
WeChat70bfdad15d56582ac1f31f3ea6ccab68.png
通過瀏覽器調試后我們就得出compileToFunction 是在 finishComponentSetup這個函數(shù)中調用的,而 template 就是要解析的innerHTML。
WeChat5f3653a52a450b5ece0d35a757cabe17.png
vue包中使用 registerRuntimeCompiler 將 compileToFunction 這個函數(shù)注冊,這是為了初始化需要編譯時能夠調用它。這里原封不動導出 runtime-dom,因此有了vue -> runtime-dom -> runtime-core這樣的依賴關系。
WeChat581967ab4ceb37c6fefa2ed599d85bad.png
WeChat802ba115543725ce52877e96d64aa870.png
隨后的流程就是:
createRenderer() => baseCreateRenderer() =>
createAppAPI(render, hydrate) => createApp()
app.mount() => render() => patch(n1, n2) =>
processComponent() => mountComponent()
這里我們主要研究一下 mountComponent() 掛載組件做了哪些事情。
WechatIMG82.jpeg
WeChat280ea252a066a6ad52f94d5f261b9ba3.png
在這個函數(shù)內(nèi)主要有兩個函數(shù)調用,一個是createComponentInstance 創(chuàng)建組件實例,另一個是 setupComponent 初始化組件實例,從這個函數(shù)開始找尋。
WechatIMG86.jpeg
WechatIMG87.jpeg
在這里我們就看到組件初始化接收的就是一個setup 這塊對傳入不同方法做了各種處理,例如promise隨后會在 handleSetupResult() 這個方法內(nèi)處理 setup 的返回值。
WechatIMG88.jpeg
根據(jù) setup 返回不同的類型作出處理,比如 返回的是一個函數(shù),對象或者字符串之類的。
WechatIMG89.jpeg
執(zhí)行完畢后都會走 finishComponentSetup() 這個方法。
WeChatde62b32d0810ec4d669cb295a41fd91a.png
WeChat3cc852f1a9e27e9a71503075b277a1c8.png
所以在這個函數(shù)內(nèi)真正的是 返回一個 render 函數(shù),并且兼容 Vue2.0 的方法。
到這里,基本上初始化所有的邏輯就理順啦~
總結
這期內(nèi)容小編就告訴大家從代碼的層面一步步的深入了解初始化過程,首先是從 package.json目錄文件中鎖定執(zhí)行的哪個腳本文件,我們通常使用的打包工具如:webpack vite 或者 rollup之類的都會有一個配置文件 xxx.config.js 從這個文件中我們就能鎖定 entry 的入口文件,通過確定入口文件之后我們就能找到各個包之間的依賴關系,根據(jù)依賴包,一步步深挖理解初始化的整體流程。
順便說一句 Vue3.0 在2022年2月7日將正式成為默認版本,屆時我們安裝的 vue 將會是3.x這個版本,屬于Vue3的時代將正式開啟!