尤雨溪写的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.mzph.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…

重新构想原子化 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…

[知乎回答] 前端是否要学习 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…

叮咚,系统检测到 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;貘大现场提…

浏览器中的 ESM

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12早期的web应用非常简单&#xff0c;可以直接加载js的形式去实现。随着需求的越来越多&#xff0c;应用越做越大&#xff0c;需要模块化去管理项目中的js、css、图片等资源。这里…

标记图标_标记您的图标

标记图标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…

react 引入 mobx @babel/core: 7.2.2

为什么80%的码农都做不了架构师&#xff1f;>>> yarn add babel/plugin-proposal-class-propertiesyarn add babel/plugin-proposal-decorators"babel": {"plugins": [["babel/plugin-proposal-decorators", {"legacy": …

面试官问:怎么自动检测你使用的组件库有更新

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12本文来自V同学投稿的源码共读第六期笔记&#xff0c;写得很有趣。现在已经进行到第十期了。你或许经常看见 npm 更新的提示。npm 更新提示面试官可能也会问你&#xff0c;组件库…

使用Microsoft Web Application Stress Tool对web进行压力测试

你的Web服务器和应用到底能够支持多少并发用户访问&#xff1f;在出现大量并发请求的情况下&#xff0c;软件会出现问题吗&#xff1f;这些问题靠通常的测试手段是无法解答的。本文介绍 了Microsoft为这个目的而提供的免费工具WAS及其用法。另外&#xff0c;本文介绍了一种Web应…

2021前端高频面试题整理,附答案

大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12若川视野原意是若川的前端视野。但太长了就留下了四个字&#xff0c;不知道的以为关注的不是技术公众号。今天分享一篇慕课网精英讲师河畔一角的好文章~废话不多说&#xff0c;…

OO第二单元作业小结

总结性博客作业 第一次作业 (1)从多线程的协同和同步控制方面&#xff0c;分析和总结自己三次作业的设计策略。 第一次作业为单电梯傻瓜调度&#xff0c;可以采用生产者——消费者模型&#xff0c;是一个有一个生产者&#xff08;标准输入电梯请求&#xff09;&#xff0c;一个…

dribbble加速vpn_关于Dribbble设计的几点思考

dribbble加速vpn重点 (Top highlight)I’d like to start with the following quote from Paul Adam’s “The Dribbbilisation of Design,” a powerful read that examines the superficiality of modern product design portfolios, often containing Dribbble posts that l…

尤雨溪推荐神器 ni ,能替代 npm/yarn/pnpm ?简单好用!源码揭秘!

1. 前言大家好&#xff0c;我是若川。最近组织了源码共读活动&#xff0c;感兴趣的可以加我微信 ruochuan12想学源码&#xff0c;极力推荐之前我写的《学习源码整体架构系列》jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4、koa-compose、…

如何了解自己的认知偏差_了解吸引力偏差

如何了解自己的认知偏差Let me introduce you the attractiveness bias theory known as cognitive bias.让我向您介绍称为认知偏差的吸引力偏差理论。 Think about a person with outstanding fashion. It will draw our attention, and maybe encourage us to interact with…