1 Introduction
Hello everyone, my name is . Recently organized source code reading activity , interested parties can add me to 1617fb3b3a60f1 participate. It has been more than two months to exchange and learn together and make progress together.
I wanted to learn the source code, strongly recommended before I wrote "learning Source overall architecture series" contains jQuery
, underscore
, lodash
, vuex
, sentry
, axios
, redux
, koa
, vue-devtools
, vuex4
, koa-compose
, vue-next-release
, vue-this
, create-vue
more than ten Source code articles.
Recently organized source code reading activity , everyone learns the source code together. So all kinds of searches are worth learning for us, and the source code with few lines of code.
In the vuejs organization , I found the "toy vite" written by You Yuxi a few years ago
vue-dev-server , I found 100 lines of code, it is worth learning. So there is this article.
After reading this article, you will learn:
1. 学会 vite 简单原理
2. 学会使用 VSCode 调试源码
3. 学会如何编译 Vue 单文件组件
4. 学会如何使用 recast 生成 ast 转换文件
5. 如何加载包文件
6. 等等
2. What is the principle of vue-dev-server
vue-dev-server#how-it-works
There are four sentences in English on the README
I found that the Google Translate is still relatively accurate, so I just moved it intact.
- The browser requests the import to be imported as a native ES module-no bundling.
- The server intercepts requests for *.vue files, compiles them on the fly, and then sends them back as JavaScript.
- For libraries built with ES modules that work in the browser, you only need to import them directly from the CDN.
- The npm package (only the package name) imported into the .js file will be rewritten on the fly to point to the locally installed file. Currently, only vue is supported as a special case. Other packages may need to be converted before they can be exposed as local browser target ES modules.
You can also take a look at the vitejs document to understand the principle. The pictures in the document are very good.
After reading this article, I believe you will have a deeper understanding.
3. Preparation
3.1 Clone the project
This article warehouse vue-dev-server-analysis, ask for a star^_^
# 推荐克隆我的仓库
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
Generally speaking, we look at the source code first from the package.json
file:
// 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"
}
}
According to the scripts
test
command. Let's look at the test
folder.
3.2 test folder
vue-dev-server/test
folder, and the code is not long.
- index.html
- main.js
- text.vue
As shown in the figure below.
Then we found the vue-dev-server/bin/vue-dev-server.js
file, the code is not long.
3.3 vue-dev-server.js
// vue-dev-server/bin/vue-dev-server.js
#!/usr/bin/env node
const 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')
})
It turned out that express
started the service 3000
The focus is on vueMiddleware
middleware. Then let's debug this middleware.
Given that it is estimated that many small partners have not used VSCode
debugging, here is how to debug the source code in detail. learning to debug the source code, the source code is not as difficult as .
3.4 Debug the project with VSCode
app.use(vueMiddleware())
vue-dev-server/bin/vue-dev-server.js
file has a breakpoint.
Find vue-dev-server/package.json
of scripts
, move the mouse to the test
command, there will be running script and
debugging script commands. As shown in the figure below, select the debug script.
Click the enter the function (F11) button to enter the
vueMiddleware
function. If you find that the breakpoint is in a file that is not in this project, and you don’t want to see it, you can quit or start over . can use the browser incognito (privacy) mode (shortcut Ctrl + Shift + N
to prevent plug-in interference) to open http://localhost:3000
, you can continue to debug returned by the vueMiddleware
function.
If yourVSCode
is not Chinese (not used to English), you can install the simplified Chinese plug-in .
IfVSCode
does not have this debugging function. It is recommended to update to the latest versionVSCode
(currently the latest versionv1.61.2
).
Then let's follow the debugging to learn the vueMiddleware
source code. You can look at the main line first, and continue to breakpoint debugging where you think it is important.
4. vueMiddleware source code
4.1 Comparison with or without vueMiddleware
Without debugging, we can comment app.use(vueMiddleware())
vue-dev-server/bin/vue-dev-server.js
file, execute npm run test
open http://localhost:3000
.
After enabling the middleware again, as shown below.
Looking at the picture, we probably know what the difference is.
4.2 Overview of vueMiddleware middleware
We can find vue-dev-server/middleware.js
to see an overview of this middleware function.
// vue-dev-server/middleware.js
const 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
finally returns a function. Four things are mainly done in this function:
.vue
files ending with 0617fb3b3a66d3.js
files ending with 0617fb3b3a66ed/__modules/
files starting with 0617fb3b3a6705- If it is not the above three cases, execute the
next
method and transfer the control to the next middleware
Then let's take a look at how to deal with it.
We can also breakpoint these important places to see the implementation. for example:
4.3 Process the files ending in .vue
if (req.path.endsWith('.vue')) {
const key = parseUrl(req).pathname
let out = await tryCache(key)
if (!out) {
// Bundle Single-File Component
const result = await bundleSFC(req)
out = result
cacheData(key, out, result.updateTime)
}
send(res, out.code, 'application/javascript')
}
4.3.1 BundleSFC compile single file components
This function @vue/component-compiler , and finally returns a file that the browser can recognize.
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 }
}
Then we look readSource
implementation of the 0617fb3b3a67db function.
4.3.2 readSource read file resource
The main function of this function is to obtain file resources upon request. Return the file path filepath
, resource source
, and update time 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
Then we look at the processing of .js files
4.4 Process the files ending in .js
if (req.path.endsWith('.js')) {
const key = parseUrl(req).pathname
let 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')
}
For vue-dev-server/test/main.js
conversion
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 transform import introduction
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.value
if (!/^\.\/?/.test(source) && isPkg(source)) {
path.node.source = recast.types.builders.literal(`/__modules/${source}`)
}
this.traverse(path)
}
})
return recast.print(ast).code
}
exports.transformModuleImports = transformModuleImports
That is, for the npm
packet conversion. This is "/__modules/vue"
import Vue from 'vue' => import Vue from "/__modules/vue"
4.5 Process the files beginning with /__modules/
import Vue from "/__modules/vue"
What this code finally returns is to read the file under the vue-dev-server/node_modules/vue/dist/vue.esm.browser.js
if (req.path.startsWith('/__modules/')) {
//
const key = parseUrl(req).pathname
const pkg = req.path.replace(/^\/__modules\//, '')
let out = await tryCache(key, false) // Do not outdate modules
if (!out) {
out = (await loadPkg(pkg)).toString()
cacheData(key, out, false) // Do not outdate modules
}
send(res, out, 'application/javascript')
}
4.5.1 loadPkg load package (only Vue files are supported here)
Currently, only Vue
files are supported, that is, the files under the 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/dist
const 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
At this point, we have basically completed the analysis of the main file and some imported files. Have an understanding of the main process.
5. Summary
Finally, let's look at the two pictures above with or without vueMiddleware middleware to summarize:
After enabling the middleware, as shown below.
The browser supports native type=module
module request loading. vue-dev-server
intercepts it and returns to the browser to support the content, because it does not need to be packaged and built, so the speed is very fast.
<script type="module">
import './main.js'
</script>
5.1 import Vue from'vue' conversion
// vue-dev-server/test/main.js
import Vue from 'vue'
import App from './test.vue'
new Vue({
render: h => h(App)
}).$mount('#app')
import statement in main.js
import Vue from 'vue'
Generate ast by recast convert it to import Vue from "/__modules/vue"
And finally returned to the browser is vue-dev-server/node_modules/vue/dist/vue.esm.browser.js
5.2 import App from'./test.vue' conversion
main.js
in the introduction of .vue
files, import App from './test.vue'
Then use @vue/component-compiler convert to a file supported by the browser.
5.3 What else can be done next?
In view of the limited length of the article, the cache tryCache
is currently not analyzed. Simply put, node-lru-cache least recently used for caching (this algorithm is often tested). The source code of this warehouse should be analyzed in the follow-up, welcome to continue to pay attention to me @若川.
It is highly recommended that readers use VSCode
debug the vue-dev-server
source code according to the method in the article. There are still many details in the source code. Due to limited space, the article has not been fully described.
It is worth mentioning that this warehouse master
branch , especially the rain the river two years ago was written, relative to this article would be more complicated, there is spare capacity readers can learn.
You can also go directly to see the vite
source code.
After reading this article, you may find that there are more and more things that the front-end can do, and you can't help feeling: the front-end is unfathomable, only continuous learning.
Finally, welcome to add me on WeChat ruochuan12 , participate in the source code reading activity, everyone learn the source code together and make progress together.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。