iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >尤雨溪开发vue dev server理解vite原理
  • 599
分享到

尤雨溪开发vue dev server理解vite原理

2024-04-02 19:04:59 599人浏览 薄情痞子
摘要

目录1.引言2. Vue-dev-server 它的原理是什么3. 准备工作3.1 克隆项目3.2 test 文件夹3.3 vue-dev-server.js3.4 用 vscode

1.引言

在 vuejs组织 下,找到了尤雨溪几年前写的“玩具 vite” vue-dev-server,发现100来行代码,很值得学习。于是有了这篇文章。

阅读本文,你将学到:

1. 学会 vite 简单原理

2. 学会使用 VSCode 调试源码

3. 学会如何编译 Vue 单文件组件

4. 学会如何使用 recast 生成 ast 转换文件

5. 如何加载包文件

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

vue-dev-server#how-it-works README 文档上有四句英文介绍。

发现谷歌翻译的还比较准确,我就原封不动的搬运过来。

  • 浏览器请求导入作为原生 ES 模块导入 - 没有捆绑。
  • 服务器拦截对 *.vue 文件的请求,即时编译它们,然后将它们作为 javascript 发回。
  • 对于提供在浏览器中工作的 ES 模块构建的库,只需直接从 CDN 导入它们。
  • 导入到 .js 文件中的 npm 包(仅包名称)会即时重写以指向本地安装的文件。 目前,仅支持 vue 作为特例。 其他包可能需要进行转换才能作为本地浏览器目标 ES 模块公开。

也可以看看vitejs 文档,了解下原理,文档中图画得非常好。

看完本文后,我相信你会有一个比较深刻的理解。

3. 准备工作

3.1 克隆项目

本文仓库 vue-dev-server-analysis

# 推荐克隆我的仓库
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

如图下图所示。

接着我们找到 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 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')
})

原来就是express启动了端口3000的服务。重点在 vueMiddleware 中间件。接着我们来调试这个中间件。

鉴于估计很多小伙伴没有用过VSCode调试,这里详细叙述下如何调试源码。学会调试源码后,源码并没有想象中的那么难

3.4 用 VSCode 调试项目

vue-dev-server/bin/vue-dev-server.js 文件中这行 app.use(vueMiddleware()) 打上断点。

找到 vue-dev-server/package.jsonscripts,把鼠标移动到 test 命令上,会出现运行脚本调试脚本命令。如下图所示,选择调试脚本。

点击进入函数(F11)按钮可以进入 vueMiddleware 函数。如果发现断点走到不是本项目的文件中,不想看,看不懂的情况,可以退出或者重新来过。可以用浏览器无痕(隐私)模式(快捷键Ctrl + Shift + N,防止插件干扰)打开 http://localhost:3000,可以继续调试 vueMiddleware 函数返回的函数。

如果你的VSCode不是中文(不习惯英文),可以安装简体中文插件。
如果 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

再启用中间件后,如下图。

看图我们大概知道了有哪些区别。

4.2 vueMiddleware 中间件概览

我们可以找到vue-dev-server/middleware.js,查看这个中间件函数的概览。

// 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 最终返回一个函数。这个函数里主要做了四件事:

  • .vue 结尾的文件进行处理
  • .js 结尾的文件进行处理
  • /__modules/ 开头的文件进行处理
  • 如果不是以上三种情况,执行 next 方法,把控制权交给下一个中间件

接着我们来看下具体是怎么处理的。

我们也可以断点这些重要的地方来查看实现。比如:

4.3 对 .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 编译单文件组件

这个函数,根据 @vue/component-compiler 转换单文件组件,最终返回浏览器能够识别的文件。

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).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')
}

针对 vue-dev-server/test/main.js 转换

import Vue from 'vue'
import App from './test.vue'
new Vue({
  render: h => h(App)
}).$mount('#app')
import Vue from "/__modules/vue"
import App from './test.vue'
new Vue({
  render: h => h(App)
}).$mount('#app')

4.4.1 transformModuleImports 转换 import 引入

recast

validate-npm-package-name

也就是针对 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).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 加载包(这里只支持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/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

至此,我们就基本分析完毕了主文件和一些引入的文件。对主流程有个了解。

5. 总结

最后我们来看上文中有无 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 生成 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 转换成浏览器支持的文件。

5.3 后续还能做什么?

鉴于文章篇幅有限,缓存 tryCache 部分目前没有分析。简单说就是使用了 node-lru-cache 最近最少使用 来做缓存的(这个算法常考)。后续应该会分析这个仓库的源码,欢迎持续关注我@若川。

非常建议读者朋友按照文中方法使用VSCode调试 vue-dev-server 源码。源码中还有很多细节文中由于篇幅有限,未全面展开讲述。

值得一提的是这个仓库的 master 分支,是尤雨溪两年前写的,相对本文会比较复杂,有余力的读者可以学习。

也可以直接去看 vite 源码。

看完本文,也许你就能发现其实前端能做的事情越来越多,不由感慨:前端水深不可测,唯有持续学习,更多关于vue dev server理解vite原理的资料请关注编程网其它相关文章!

--结束END--

本文标题: 尤雨溪开发vue dev server理解vite原理

本文链接: https://www.lsjlt.com/news/164371.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 尤雨溪开发vue dev server理解vite原理
    目录1.引言2. vue-dev-server 它的原理是什么3. 准备工作3.1 克隆项目3.2 test 文件夹3.3 vue-dev-server.js3.4 用 VSCode...
    99+
    2024-04-02
  • Android开发Composeremember原理解析
    目录正文随机色文本原因分析正确实现remember的原理剖析小结正文 看过Compose案例或者源码的你,相信肯定是见过 remember 了的。顾名思义,Compose是要让我们的...
    99+
    2024-04-02
  • 聊聊Vue开发小程序的技术原理
    Vue、 React 和 Angular 是当前应用最广的三大前端框架,仅从 GitHub 趋势来看,Vue 更是排在了第一位,目前已经达到了 17 万的 Star。目前,不管是 BAT 大厂,还是创业公司,Vue 都有广泛的应用,对于任何...
    99+
    2023-05-14
    Vue 小程序
  • web项目开发VUE的混入与继承原理
    目录混入混入注意(重名情况)局部混入全局混入定义及全局注册调用继承混入和继承的区别混入 将多个vue文件内重复使用的功能代码,提取成单个js文件,在需要使用的地方进行调用即...
    99+
    2024-04-02
  • Vue开发小程序的技术原理是什么
    这篇文章主要介绍“Vue开发小程序的技术原理是什么”,在日常操作中,相信很多人在Vue开发小程序的技术原理是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue开发小程序的技术原理是什么”的疑惑有所帮助!...
    99+
    2023-07-05
  • Vue不定高展开动效原理详解
    目录使用场景背景实现transition 组件过渡效果原理解决使用场景 在大多数 APP 中,都有问答模块,类似于下面这种(bilibili 为例): 问答模块的静态页面开发并不复...
    99+
    2024-04-02
  • java开发Dubbo注解Adaptive实现原理
    目录前言什么是@Adaptive实现原理getAdaptiveExtensiongetAdaptiveExtensionClassgenerate前言 前面我们已经分析Dubbo S...
    99+
    2024-04-02
  • SwiftUI开发总结combine原理简单示例详解
    目录引言SwiftUI是什么?如何理解combine@propertyWrapperPublishers 与 subscribersSubject的使用Operators的使用总结引...
    99+
    2023-02-03
    SwiftUI开发combine原理 SwiftUI combine
  • 如何理解FIX协议的原理、消息格式及配置开发
    本篇文章给大家分享的是有关如何理解FIX协议的原理、消息格式及配置开发,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。一、定义FIX协议是由国际FIX协会组织提供的一个开放式协议...
    99+
    2023-06-05
  • java开发RocketMQ消息中间件原理基础详解
    RocketMQ 是什么 Github 上关于 RocketMQ 的介绍: RcoketMQ 是一款低延迟、高可靠、可伸缩、易于使用的消息中间件。具有以下特性: 支持发布/...
    99+
    2024-04-02
  • JavaScript开发简单易懂的Svelte实现原理详解
    目录Demo1create_fragmentSvelteComponent可以改变状态的DemoSvelte问世很久了,一直想写一篇好懂的原理分析文章,拖了这么久终于写了。 Demo...
    99+
    2024-04-02
  • Android开发之Kotlin委托的原理与使用详解
    目录前言一、接口/类委托二、属性委托三、延迟委托四、观察者委托五、Map委托总结前言 在设计模式中,委托模式(Delegate Pattern)与代理模式都是我们常用的设计模式(Pr...
    99+
    2023-03-23
    Kotlin委托原理 Kotlin委托使用 Android Kotlin委托 Kotlin委托
  • 前端开发服务器中的 Proxy 代理跨域实现原理解读
    各位朋友你们好,我是桃小瑞,微信公众 @ 桃小瑞。在这给大家拜个晚年,祝各位朋友新年快乐。 前言 在前端的开发过程中,尤其是在浏览器环境下,跨域是个绕不开的话题,相信每个前端都会涉及到这个问题,记住...
    99+
    2023-09-13
    前端 node.js
  • 揭开 VUE 类型推断的面纱:深入理解编译器的工作原理
    Vue.js 是一个流行的前端 JavaScript 框架,因其简洁的语法和丰富的功能而受到广泛青睐。Vue 的类型推断是其一项重要的特性,可帮助开发者编写更简洁、更易维护的代码。 Vue 的类型推断机制主要由编译器实现。当编译器遇到变...
    99+
    2024-02-15
    Vue.js 类型推断 编译器 JavaScript
  • vue前端开发辅助函数状态管理详解示例
    目录mapStatemapGettersmapMutationsmapActions示例小结mapState 当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗...
    99+
    2024-04-02
  • java开发分布式服务框架Dubbo原理机制详解
    目录前言Dubbo框架有以下部件ConsumerProviderRegistryMonitorContainer架构高可用性框架设计服务暴露过程服务消费过程前言 在介绍Dubbo之前...
    99+
    2024-04-02
  • Laravel 开发者必须了解 ASP 容器的工作原理吗?
    Laravel 是一个流行的 PHP 开发框架,许多开发者都喜欢使用它来构建高质量的 Web 应用程序。但是,当涉及到 ASP 容器时,许多 Laravel 开发者可能并不了解其工作原理。那么,Laravel 开发者是否必须了解 ASP 容...
    99+
    2023-09-30
    容器 关键字 laravel
  • 解析从小程序开发者工具源码看原理实现
    目录如何查看小程序开发者工具源码小程序架构设计1、小程序渲染是在同一个线程吗?双线程机制2、小程序是web渲染吗?界面渲染机制3、小程序是用web的html标签渲染吗?Exparse...
    99+
    2024-04-02
  • Java开发者必须了解的UNIX系统路径配置原理
    在Java开发中,我们经常需要使用一些外部的库和工具。而这些库和工具的安装位置和配置文件的存放位置通常都需要在系统路径中进行配置。因此,作为Java开发者,我们有必要了解UNIX系统路径配置原理,以便更好地管理和使用这些外部资源。 本文将...
    99+
    2023-08-26
    path unix 开发技术
  • Vue考试系统的后台管理功能开发示例解读
    考试系统后台管理项目介绍: 技术选型:Vue2.0+Elemenu-ui 项目功能介绍: 账户信息模块:菜单权限、角色权限设置、角色权限分配、账号设置、公司分组考试管理模块:新增/编...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作