尤雨溪寫的100多行的“玩具 vite”,十分有助于理解 vite 原理

1. 前言

大家好,我是若川。最近組織了源碼共讀活動,感興趣的可以加我微信 ruochuan12

想學源碼,極力推薦之前我寫的《學習源碼整體架構系列》jQueryunderscorelodashvuexsentryaxiosreduxkoavue-devtoolsvuex4koa-composevue-next-releasevue-thiscreate-vue等10余篇源碼文章。

最近組織了源碼共讀活動

在 vuejs組織[1] 下,找到了尤雨溪幾年前寫的“玩具 vite”vue-dev-server[2],發現100來行代碼,很值得學習。于是有了這篇文章。

閱讀本文,你將學到:

1.?學會?vite?簡單原理
2.?學會使用?VSCode?調試源碼
3.?學會如何編譯?Vue?單文件組件
4.?學會如何使用?recast?生成?ast?轉換文件
5.?如何加載包文件
6.?等等

2. vue-dev-server 它的原理是什么

vue-dev-server#how-it-works[3]README 文檔上有四句英文介紹。

發現谷歌翻譯[4]的還比較準確,我就原封不動的搬運過來。

  • 瀏覽器請求導入作為原生 ES 模塊導入 - 沒有捆綁。

  • 服務器攔截對 *.vue 文件的請求,即時編譯它們,然后將它們作為 JavaScript 發回。

  • 對于提供在瀏覽器中工作的 ES 模塊構建的庫,只需直接從 CDN 導入它們。

  • 導入到 .js 文件中的 npm 包(僅包名稱)會即時重寫以指向本地安裝的文件。 目前,僅支持 vue 作為特例。 其他包可能需要進行轉換才能作為本地瀏覽器目標 ES 模塊公開。

也可以看看vitejs 文檔[5],了解下原理,文檔中圖畫得非常好。

05b6e94ab69c41d13acfb84848101425.png

看完本文后,我相信你會有一個比較深刻的理解。

3. 準備工作

3.1 克隆項目

本文倉庫 vue-dev-server-analysis,求個star^_^[6]

#?推薦克隆我的倉庫
git?clone?https://github.com/lxchuan12/vue-dev-server-analysis.git
cd?vue-dev-server-analysis/vue-dev-server
#?npm?i?-g?yarn
#?安裝依賴
yarn#?或者克隆官方倉庫
git?clone?https://github.com/vuejs/vue-dev-server.git
cd?vue-dev-server
#?npm?i?-g?yarn
#?安裝依賴
yarn

一般來說,我們看源碼先從package.json文件開始:

//?vue-dev-server/package.json
{"name":?"@vue/dev-server","version":?"0.1.1","description":?"Instant?dev?server?for?Vue?single?file?components","main":?"middleware.js",//?指定可執行的命令"bin":?{"vue-dev-server":?"./bin/vue-dev-server.js"},"scripts":?{//?先跳轉到?test?文件夾,再用?Node?執行?vue-dev-server?文件"test":?"cd?test?&&?node?../bin/vue-dev-server.js"}
}

根據 scripts test 命令。我們來看 test 文件夾。

3.2 test 文件夾

vue-dev-server/test 文件夾下有三個文件,代碼不長。

  • index.html

  • main.js

  • text.vue

如圖下圖所示。

cf7482e916ef34631626ebff18247183.png
test文件夾三個文件

接著我們找到 vue-dev-server/bin/vue-dev-server.js 文件,代碼也不長。

3.3 vue-dev-server.js

//?vue-dev-server/bin/vue-dev-server.js
#!/usr/bin/env?nodeconst?express?=?require('express')
const?{?vueMiddleware?}?=?require('../middleware')const?app?=?express()
const?root?=?process.cwd();app.use(vueMiddleware())app.use(express.static(root))app.listen(3000,?()?=>?{console.log('server?running?at?http://localhost:3000')
})

原來就是express啟動了端口3000的服務。重點在 vueMiddleware 中間件。接著我們來調試這個中間件。

鑒于估計很多小伙伴沒有用過VSCode調試,這里詳細敘述下如何調試源碼。學會調試源碼后,源碼并沒有想象中的那么難

3.4 用 VSCode 調試項目

vue-dev-server/bin/vue-dev-server.js 文件中這行 app.use(vueMiddleware()) 打上斷點。

找到 vue-dev-server/package.jsonscripts,把鼠標移動到 test 命令上,會出現運行腳本調試腳本命令。如下圖所示,選擇調試腳本。

1dc8cd3644b03d5a06e563a93d4bb3e6.png
調試
2ec0026f4e6de3b24d8177eabc197153.png
VSCode 調試 Node.js 說明

點擊進入函數(F11)按鈕可以進入 vueMiddleware 函數。如果發現斷點走到不是本項目的文件中,不想看,看不懂的情況,可以退出或者重新來過可以用瀏覽器無痕(隱私)模式(快捷鍵Ctrl + Shift + N,防止插件干擾)打開 http://localhost:3000,可以繼續調試 vueMiddleware 函數返回的函數

如果你的VSCode不是中文(不習慣英文),可以安裝簡體中文插件[7]
如果 VSCode 沒有這個調試功能。建議更新到最新版的 VSCode(目前最新版本 v1.61.2)。

接著我們來跟著調試學習 vueMiddleware 源碼。可以先看主線,在你覺得重要的地方繼續斷點調試。

4. vueMiddleware 源碼

4.1 有無 vueMiddleware 中間件對比

不在調試情況狀態下,我們可以在 vue-dev-server/bin/vue-dev-server.js 文件中注釋 app.use(vueMiddleware()),執行 npm run test 打開 http://localhost:3000

08481ac3d5514b97f9aeafd5f207d5b0.png
沒有執行 vueMiddleware 中間件的原始情況

再啟用中間件后,如下圖。

3b64742db1b486c0baec2b6766bd5886.png
執行了 vueMiddleware 中間文件變化

看圖我們大概知道了有哪些區別。

4.2 vueMiddleware 中間件概覽

我們可以找到vue-dev-server/middleware.js,查看這個中間件函數的概覽。

//?vue-dev-server/middleware.jsconst?vueMiddleware?=?(options?=?defaultOptions)?=>?{//?省略return?async?(req,?res,?next)?=>?{//?省略//?對?.vue?結尾的文件進行處理if?(req.path.endsWith('.vue'))?{//?對?.js?結尾的文件進行處理}?else?if?(req.path.endsWith('.js'))?{//?對?/__modules/?開頭的文件進行處理}?else?if?(req.path.startsWith('/__modules/'))?{}?else?{next()}}
}
exports.vueMiddleware?=?vueMiddleware

vueMiddleware 最終返回一個函數。這個函數里主要做了四件事:

  • .vue 結尾的文件進行處理

  • .js 結尾的文件進行處理

  • /__modules/ 開頭的文件進行處理

  • 如果不是以上三種情況,執行 next 方法,把控制權交給下一個中間件

接著我們來看下具體是怎么處理的。

我們也可以斷點這些重要的地方來查看實現。比如:

33d00d9e466b54d0934046bfaa1677a4.png
重要斷點

4.3 對 .vue 結尾的文件進行處理

if?(req.path.endsWith('.vue'))?{const?key?=?parseUrl(req).pathnamelet?out?=?await?tryCache(key)if?(!out)?{//?Bundle?Single-File?Componentconst?result?=?await?bundleSFC(req)out?=?resultcacheData(key,?out,?result.updateTime)}send(res,?out.code,?'application/javascript')
}

4.3.1 bundleSFC 編譯單文件組件

這個函數,根據 @vue/component-compiler[8] 轉換單文件組件,最終返回瀏覽器能夠識別的文件。

const?vueCompiler?=?require('@vue/component-compiler')
async?function?bundleSFC?(req)?{const?{?filepath,?source,?updateTime?}?=?await?readSource(req)const?descriptorResult?=?compiler.compileToDescriptor(filepath,?source)const?assembledResult?=?vueCompiler.assemble(compiler,?filepath,?{...descriptorResult,script:?injectSourceMapToScript(descriptorResult.script),styles:?injectSourceMapsToStyles(descriptorResult.styles)})return?{?...assembledResult,?updateTime?}
}

接著我們來看 readSource 函數實現。

4.3.2 readSource 讀取文件資源

這個函數主要作用:根據請求獲取文件資源。返回文件路徑 filepath、資源 source、和更新時間 updateTime

const?path?=?require('path')
const?fs?=?require('fs')
const?readFile?=?require('util').promisify(fs.readFile)
const?stat?=?require('util').promisify(fs.stat)
const?parseUrl?=?require('parseurl')
const?root?=?process.cwd()async?function?readSource(req)?{const?{?pathname?}?=?parseUrl(req)const?filepath?=?path.resolve(root,?pathname.replace(/^\//,?''))return?{filepath,source:?await?readFile(filepath,?'utf-8'),updateTime:?(await?stat(filepath)).mtime.getTime()}
}exports.readSource?=?readSource

接著我們來看對 .js 文件的處理

4.4 對 .js 結尾的文件進行處理

if?(req.path.endsWith('.js'))?{const?key?=?parseUrl(req).pathnamelet?out?=?await?tryCache(key)if?(!out)?{//?transform?import?statements//?轉換?import?語句?//?import?Vue?from?'vue'//?=>?import?Vue?from?"/__modules/vue"const?result?=?await?readSource(req)out?=?transformModuleImports(result.source)cacheData(key,?out,?result.updateTime)}send(res,?out,?'application/javascript')
}

針對 vue-dev-server/test/main.js 轉換

import?Vue?from?'vue'
import?App?from?'./test.vue'new?Vue({render:?h?=>?h(App)
}).$mount('#app')//?公眾號:若川視野
//?加微信?ruochuan12
//?參加源碼共讀,一起學習源碼
import?Vue?from?"/__modules/vue"
import?App?from?'./test.vue'new?Vue({render:?h?=>?h(App)
}).$mount('#app')//?公眾號:若川視野
//?加微信?ruochuan12
//?參加源碼共讀,一起學習源碼

4.4.1 transformModuleImports 轉換 import 引入

recast[9]

validate-npm-package-name[10]

const?recast?=?require('recast')
const?isPkg?=?require('validate-npm-package-name')function?transformModuleImports(code)?{const?ast?=?recast.parse(code)recast.types.visit(ast,?{visitImportDeclaration(path)?{const?source?=?path.node.source.valueif?(!/^\.\/?/.test(source)?&&?isPkg(source))?{path.node.source?=?recast.types.builders.literal(`/__modules/${source}`)}this.traverse(path)}})return?recast.print(ast).code
}exports.transformModuleImports?=?transformModuleImports

也就是針對 npm 包轉換。 這里就是 "/__modules/vue"

import?Vue?from?'vue'?=>?import?Vue?from?"/__modules/vue"

4.5 對 /__modules/ 開頭的文件進行處理

import?Vue?from?"/__modules/vue"

這段代碼最終返回的是讀取路徑 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js 下的文件。

if?(req.path.startsWith('/__modules/'))?{//?const?key?=?parseUrl(req).pathnameconst?pkg?=?req.path.replace(/^\/__modules\//,?'')let?out?=?await?tryCache(key,?false)?//?Do?not?outdate?modulesif?(!out)?{out?=?(await?loadPkg(pkg)).toString()cacheData(key,?out,?false)?//?Do?not?outdate?modules}send(res,?out,?'application/javascript')
}

4.5.1 loadPkg 加載包(這里只支持Vue文件)

目前只支持 Vue 文件,也就是讀取路徑 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js 下的文件返回。

//?vue-dev-server/loadPkg.js
const?fs?=?require('fs')
const?path?=?require('path')
const?readFile?=?require('util').promisify(fs.readFile)async?function?loadPkg(pkg)?{if?(pkg?===?'vue')?{//?路徑//?vue-dev-server/node_modules/vue/distconst?dir?=?path.dirname(require.resolve('vue'))const?filepath?=?path.join(dir,?'vue.esm.browser.js')return?readFile(filepath)}else?{//?TODO//?check?if?the?package?has?a?browser?es?module?that?can?be?used//?otherwise?bundle?it?with?rollup?on?the?fly?throw?new?Error('npm?imports?support?are?not?ready?yet.')}
}exports.loadPkg?=?loadPkg

至此,我們就基本分析完畢了主文件和一些引入的文件。對主流程有個了解。

5. 總結

最后我們來看上文中有無 vueMiddleware 中間件的兩張圖總結一下:

81ed8eea7e85601f2fa1ee391143090f.png
沒有執行 vueMiddleware 中間件的原始情況

啟用中間件后,如下圖。

71b3715b5d0f6895b04ea83c43ea7541.png
執行了 vueMiddleware 中間文件變化

瀏覽器支持原生 type=module 模塊請求加載。vue-dev-server 對其攔截處理,返回瀏覽器支持內容,因為無需打包構建,所以速度很快。

<script?type="module">import?'./main.js'
</script>

5.1 import Vue from 'vue' 轉換

//?vue-dev-server/test/main.js
import?Vue?from?'vue'
import?App?from?'./test.vue'new?Vue({render:?h?=>?h(App)
}).$mount('#app')

main.js 中的 import 語句 import Vue from 'vue' 通過 recast[11] 生成 ast 轉換成 import Vue from "/__modules/vue"而最終返回給瀏覽器的是 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js

5.2 import App from './test.vue' 轉換

main.js 中的引入 .vue 的文件,import App from './test.vue'則用 @vue/component-compiler[12] 轉換成瀏覽器支持的文件。

5.3 后續還能做什么?

鑒于文章篇幅有限,緩存 tryCache 部分目前沒有分析。簡單說就是使用了 node-lru-cache[13]最近最少使用 來做緩存的(這個算法常考)。后續應該會分析這個倉庫的源碼,歡迎持續關注我@若川。

非常建議讀者朋友按照文中方法使用VSCode調試 vue-dev-server 源碼。源碼中還有很多細節文中由于篇幅有限,未全面展開講述。

值得一提的是這個倉庫的 `master` 分支[14],是尤雨溪兩年前寫的,相對本文會比較復雜,有余力的讀者可以學習。

也可以直接去看 `vite`[15] 源碼。

看完本文,也許你就能發現其實前端能做的事情越來越多,不由感慨:前端水深不可測,唯有持續學習。

最后歡迎加我微信 ruochuan12源碼共讀 活動,大家一起學習源碼,共同進步。

參考資料

[1]

vuejs組織: https://github.com/vuejs

[2]

vue-dev-server: https://github.com/vuejs/vue-dev-server

[3]

更多鏈接可以點擊閱讀原文查看


最近組建了一個江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進群。

推薦閱讀

1個月,200+人,一起讀了4周源碼
我歷時3年才寫了10余篇源碼文章,但收獲了100w+閱讀

老姚淺談:怎么學JavaScript?

我在阿里招前端,該怎么幫你(可進面試群)

047f8a0e7310cc958826c1ae54977bc0.gif

·················?若川簡介?·················

你好,我是若川,畢業于江西高校。現在是一名前端開發“工程師”。寫有《學習源碼整體架構系列
從2014年起,每年都會寫一篇年度總結,已經寫了7篇,點擊查看年度總結。
同時,最近組織了源碼共讀活動

df3720e4820233bc0200ecb50459faf5.png

識別方二維碼加我微信、拉你進源碼共讀

今日話題

略。歡迎分享、收藏、點贊、在看我的公眾號文章~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/275302.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/275302.shtml
英文地址,請注明出處:http://en.pswp.cn/news/275302.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

webflow如何使用_我如何使用Webflow構建輔助項目以幫助設計人員進行連接

webflow如何使用I launched Designer Slack Communities a while ago, aiming to help designers to connect with likeminded people. By sharing my website with the world, I’ve connected with so many designers. The whole experience is a first time for me, so I wa…

atmega8 例程:T1定時器 CTC模式 方波輸出

/******************************************************************* * 函數庫說明&#xff1a;ATMEGA8 T1定時器 CTC模式 方波輸出 * 版本&#xff1a; v1.00 * 修改&#xff1a; 龐輝 蕪湖聯大飛思卡爾工作室…

antd的table進行列篩選時,更新dataSource,為什么table顯示暫無數據?

我想當然地認為只要dataSource改變&#xff0c;那么<Table>組件就會重新渲染&#xff0c;但是有一種特殊情況例外&#xff1a;在onFilter()中不寫篩選條件&#xff0c;在調用filterDropdown進行列篩選的時候&#xff0c;通過handleSearch改變/保存dataSource的狀態&#…

重新構想原子化 CSS

感謝印記中文的 QC-L[1] 對本文進行翻譯&#xff0c;英文原文: English Version[2]。本文會比往期文章相對長些。這是我個人的一個重大的工具發布&#xff0c;有許多內容我想談論。如果你能花些時間讀完&#xff0c;不勝感激&#xff0c;希望能對你有所幫助 :)推薦訪問 https:/…

cv::mat 顏色空間_網站設計基礎:負空間

cv::mat 顏色空間Let’s start off by answering this question: What is negative space? It is the “empty” space between and around the subjects of an image. In the context of web design, your “subjects” are the pictures, videos, text, buttons and other e…

MVC3 上傳文件

前臺引擎采用Razor 上傳頁View&#xff1a; model System.Web.HttpContextBase{ ViewBag.Title "上傳文件";}<h2>上傳文件</h2><br /><br />*new { enctype "multipart/form-data" }比不可少&#xff0c;否則上傳文件不會成功…

Day07 - Ruby比一比:Symbol符號與String字串

前情提要&#xff1a; 第六天我們透過Ruby代碼練習public&#xff0c;protected和privatemethod時&#xff0c;發現冒號在前面的參數&#xff0c;&#xff1a;mydraft&#xff0c;&#xff1a;myspace&#xff0c;這些就是符號Symbol。在今天&#xff0c;我們就來解釋Symbol吧&…

[知乎回答] 前端是否要學習 Node.js?

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以加我微信 ruochuan12很多小伙伴都表示收獲頗豐。一起學的大多數200行左右的Node.js源碼。今天推薦這篇文章。&#xff08;剛剛在寫明天掘金要發的文章&#xff0c;差點忘記今天還沒發文。在知乎上看…

shields 徽標_我的徽標素描過程

shields 徽標Sketching is arguably the most important part of my process when it comes to logo design. In the beginning of my design career, I would actually skip this step completely and go right to the computer. I’d find myself getting stuck and then goi…

VC編程心得

VC編程心得 開始&#xff1a; 聲明變量要初始化&#xff1b; 指針變量申請空間后是不是為空&#xff08;申請不成功&#xff09;&#xff1b; 過程&#xff1a; CREATE、OPEN了的東西賦給指針變量&#xff0c;要看指針變量是否為空&#xff1b; 指針變量在調用其方法之前&#…

叮咚,系統檢測到 npm 有更新,原理揭秘!

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以加我微信 ruochuan12本文來自V同學投稿的源碼共讀第六期筆記&#xff0c;寫得很有趣。現在已經進行到第十期了。你或許經常看見 npm 更新的提示。npm 更新提示面試官可能也會問你&#xff0c;組件庫…

ui設計未來十年前景_UI設計的10條誡命

ui設計未來十年前景重點 (Top highlight)The year is approximately 1,300 BC when Moses received the 10 UI design commandments from the almighty design gods. The list was comprised of best practices that only the most enlightened designers would be aware of.當…

w3ctech 2011 北京站(組圖)

門前的牌子大廳一推低價技術書籍會場嘉賓席人漸漸到齊準備工作w3c中國區負責人 安琪 第一個演講焦峰同學分享了瀏覽器兼容性的相關問題石川同學分享的是JQuery的相關內容攝影哥微博大屏幕&#xff0c;有亮點哦。。。MBP啊有木有&#xff5e;&#xff5e;&#xff5e;貘大現場提…

Linux設備驅動之IIO子系統——IIO框架及IIO數據結構

Linux設備驅動之IIO子系統——IIO框架及IIO數據結構由于需要對ADC進行驅動設計&#xff0c;因此學習了一下Linux驅動的IIO子系統。本文翻譯自《Linux Device Drivers Development 》--John Madieu&#xff0c;本人水平有限&#xff0c;若有錯誤請大家指出。 IIO Framework 工業…

瀏覽器中的 ESM

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以加我微信 ruochuan12早期的web應用非常簡單&#xff0c;可以直接加載js的形式去實現。隨著需求的越來越多&#xff0c;應用越做越大&#xff0c;需要模塊化去管理項目中的js、css、圖片等資源。這里…

理解面向連接和無連接協議之間的區別

理解面向連接和無連接協議之間的區別 網絡編程中最基本的概念就是面向連接&#xff08;connection-oriented&#xff09;和無連接&#xff08;connectionless&#xff09;協議。 面向連接和無連接指的都是協議。也就是說&#xff0c;這些術語指的并不是無理介質本身&#xff0c…

標記圖標_標記您的圖標

標記圖標Not labeling your icons is the same as assuming that we are all fluent in ancient hieroglyphics. Are you? Can you just walk up to Cleopatras needle and read it like you could read a childrens book? Even emojis, our modern hieroglyphics dont mean …

找出無序數組中最小的k個數(top k問題)

2019獨角獸企業重金招聘Python工程師標準>>> 給定一個無序的整型數組arr&#xff0c;找到其中最小的k個數 該題是互聯網面試中十分高頻的一道題&#xff0c;如果用普通的排序算法&#xff0c;排序之后自然可以得到最小的k個數&#xff0c;但時間復雜度高達O(NlogN)&…

你應該知道的 Node 基礎知識

大家好&#xff0c;我是若川。最近組織了源碼共讀活動&#xff0c;感興趣的可以加我微信 ruochuan12 參與&#xff0c;已進行兩個多月&#xff0c;大家一起交流學習&#xff0c;共同進步。源碼共讀學的多數是 Node.js &#xff0c;今天分享一篇 Node.js 基礎知識的文章。一. N…

C# 中數據緩存總結

在C#嘗試了5種方法進行數據緩存&#xff0c;具體如下&#xff1a;(如有遺漏&#xff0c;錯誤歡迎大家指正&#xff0c;歡迎提建議。)1&#xff1a;Session方法&#xff1a;此方法是針對于每個用戶來的&#xff0c;如果用戶量比較大&#xff0c;那么建議不要采用此方法&#xff…