本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
本文是对Vue-dev-server的学习整理,源码仓库地址:vue-dev-server
该仓库已由所有者存档。 它现在是只读的。
vue-dev-server 是什么
一个 POC(proof of concept) 开发服务器,允许您通过原生 ES 模块导入来导入 `*.vue` 文件。
This is a proof of concept.
Imagine you can import Vue single-file components natively in your browser... without a build step.
这是一个概念证明。
想象一下,您可以在浏览器中本地导入 Vue 单文件组件……无需构建步骤。
怎么运作
- 浏览器请求导入作为原生 ES 模块导入 - 没有捆绑。
- 服务器拦截对 *.vue 文件的请求,即时编译它们,然后将它们作为 JavaScript 发送回来。
- 对于提供在浏览器中工作的 ES 模块构建的库,只需直接从 CDN 导入它们。
- 对 .js 文件(仅包名)内的 npm 包的导入会动态重写,以指向本地安装的文件。 目前,仅支持 vue 作为特例。 其他包可能需要转换为以原生浏览器为目标的 ES 模块。
对于上面一段话,可以不用完全懂,可以先结合代码理解
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": "cd test && node ../bin/vue-dev-server.js" }, "author": "Evan You", "license": "MIT", "dependencies": { "@vue/component-compiler": "^3.6.0", "express": "^4.16.4", "lru-cache": "^5.1.1", "recast": "^0.17.3", "validate-npm-package-name": "^3.0.0", "vue": "^2.6.8", "vue-template-compiler": "^2.6.8" }}
package.json常用的配置属性:
main
"main": "middleware.js",
定义了npm包的入口文件middleware.js
bin
"bin": { "vue-dev-server": "./bin/vue-dev-server.js" },
bin属性用来将可执行文件加载到全局环境中,指定了bin字段的npm包,一旦在全局安装,就会被加载到全局环境中,可以通过别名来执行该文件。
比如@vue/dev-server的npm包:
"bin": { "vue-dev-server": "./bin/vue-dev-server.js"},
一旦在全局安装了@vue/dev-server,就可以直接通过vue-dev-server来执行相应的命令,比如
npm i @vue/dev-server
npx vue-dev-server
如果非全局安装,那么会自动连接到项目的node_module/.bin目录中。
script
"scripts": { "test": "cd test && node ../bin/vue-dev-server.js"},
npm 允许在package.json文件里面,使用scripts字段定义脚本命令。
上面代码是package.json文件的一个片段,里面的scripts字段是一个对象。它的每一个属性,对应一段脚本。比如,test命令对应的脚本是cd test && node ../bin/vue-dev-server.js。
命令行下使用npm run 或者yarn 命令,就可以执行这段脚本。
yarn test
# 等同于
cd test && node ../bin/vue-dev-server.js
怎么使用?
在一个目录中,创建一个 index.html:
<div id="app"></div>
<script type="module">
import Vue from 'https://unpkg.com/vue/dist/vue.esm.browser.js'
import App from './App.vue'
new Vue({
render: h => h(App)
}).$mount('#app')
</script>
在 App.vue 中:
<template>
<div>{{ msg }}</div>
</template>
<script>
export default {
data() {
return {
msg: 'Hi from the Vue file!'
}
}
}
</script>
<style scoped>
div {
color: red;
}
</style>
然后:
npm i @vue/dev-server
npx vue-dev-server
vue-dev-server.js
#!/usr/bin/env node
const express = require('express')
const { vueMiddleware } = require('../middleware')
// 创建一个 Express 应用程序
const app = express()
// 当前工作目录
const root = process.cwd();// 中间件 这是处理文件的核心
app.use(vueMiddleware())
// 传递一个包含静态资源的目录给express.static中间件用于立即开始提供文件
// 这里的root是test目录
app.use(express.static(root))// 我们的服务http://localhost:3000会打到test的index.html中app.listen(3000, () => { console.log('server running at http://localhost:3000')})
middleware.js
这里我直接copy了另外一篇文章,【若川视野 x 源码共读】第11期 | vue-dev-server的源码解读
接着,我们可以看到vueMiddleware这个中间件,这个是核心,对请求进行拦截,然后对具体请求的文件进行解析返回。后面再详细讲解。
大概的流程如下所示。
client请求serverserver将请求会经过中间件。Middleware会根据文件类型进行解析并响应。
vueMiddleware function
核心代码
const vueMiddleware = (options = defaultOptions) => {
return async (req, res, next) => {
// vue 文件类型处理
if (req.path.endsWith('.vue')) {
// 拿到请求的path 生成key
const key = parseUrl(req).pathname
// 查看是否有缓存
let out = await tryCache(key)
// 没有缓存,则生成
if (!out) {
// Bundle Single-File Component
const result = await bundleSFC(req)
out = result
// LRU 设置缓存信息
cacheData(key, out, result.updateTime)
}
// 返回
send(res, out.code, 'application/javascript')
} else if (req.path.endsWith('.js')) { // .js 文件类型处理
// 拿到请求的path 生成key
const key = parseUrl(req).pathname
// 查看是否有缓存
let out = await tryCache(key)
// 没有缓存,则生成
if (!out) {
// transform import statements
// result : { filepath, source, updateTime } 读取js source
const result = await readSource(req)
// 对import的模块语句进行转换
// 比如,import Vue from 'vue'; 转换:import Vue from '/__modules/vue';
out = transformModuleImports(result.source)
// LRU 设置缓存信息
cacheData(key, out, result.updateTime)
}
// 返回
send(res, out, 'application/javascript')
} else if (req.path.startsWith('/__modules/')) { // 对/__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')
} else {
next()
}
}
}
总结
本文只对vue-dev-server梳理了下流程,核心代码vueMiddleware没有做过深解析,了解了中间件对请求处理,
通过package.json,了解了bin, script等属性,通过vue-dev-server.js源码,了解怎么搭建express应用程序实例,express.js中间件,并且现学现卖,最近把一个N年前的项目,用express跑起来了,很有成就感。
通过对middleware.js源码阅读,了解前端构建思想。