当前位置:网站首页>通过模拟Vite一起深入其工作原理
通过模拟Vite一起深入其工作原理
2022-08-05 02:47:00 【懒得跟猪打架】
Vite实现原理(静态web服务器)
vite的核心功能
- 静态web服务器
- 启动Vite的时候,会将当前目录作文静态文件服务器的根目录。
- 静态文件服务器会拦截部分请求,例如的单文件组件,拦截请求进行即时编译并返回结果。以及样式预编译模块代码。
- 内部使用的是
koa
框架来开启静态文件服务器
- 编译单文件组件
- 拦截浏览器不识别的模块并处理
- HMR
- 通过web socket实现模块热替换。
模拟vite实现一个开启静态文件服务器的CLI
vite-cli 模拟项目
首先我们开一下vite项目的前端请求资源的路径及内容和项目目录文件的差异:
浏览器请求:
第2个是client是HMR所用到的web socket 的client,这里我们仅模拟实现基本的核心功能,此处忽略HMR相关的内容。
来看第三个main.js
// main.js
import {
createApp } from '/node_modules/.vite/deps/vue.js?v=b039f9b3'
import App from '/src/App.vue'
createApp(App).mount('#app')
可以看到其中的路径跟我们的源代码是有区别的。我们手写源代码的内容如下:
import {
createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
import
后面导入的资源路径差异:
如果是第三方模块:请求路径会转化为/node_modules/
开头真正的模块文件在当前项目的第三方资源相对路径。
自编写的模块:请求路径./
会转化为/src/
开头
再看后续请求到的.vue
后缀的单文件组件资源:
// App.vue
import {
createHotContext as __vite__createHotContext } from "/@vite/client";import.meta.hot = __vite__createHotContext("/src/App.vue");import HelloWorld from '/src/components/HelloWorld.vue'
// This starter template is using Vue 3 experimental <script setup> SFCs
// Check out https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md
const _sfc_main = {
__name: 'App',
setup(__props, {
expose }) {
expose();
const __returned__ = {
HelloWorld }
Object.defineProperty(__returned__, '__isScriptSetup', {
enumerable: false, value: true })
return __returned__
}
}
import {
createElementVNode as _createElementVNode, createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "/node_modules/.vite/deps/vue.js?v=b039f9b3"
const _hoisted_1 = /*#__PURE__*/_createElementVNode("img", {
alt: "Vue logo",
src: "/src/assets/logo.png"
}, null, -1 /* HOISTED */)
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_hoisted_1,
_createVNode($setup["HelloWorld"], {
msg: "Hello Vue 3 + Vite" })
], 64 /* STABLE_FRAGMENT */))
}
import "/src/App.vue?vue&type=style&index=0&lang.css"
_sfc_main.__hmrId = "7ba5bd90"
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
import.meta.hot.accept(({
default: updated, _rerender_only }) => {
if (_rerender_only) {
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})
import _export_sfc from '/@id/plugin-vue:export-helper'
export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__file',"/Users/huiquandeng/projects/lg-fed-lp/my-module/vite-cli-demo/vite-demo/src/App.vue"]])
//# sourceMappingURL=data:application/json;base64,eyJ2...
我们自编写的App.vue,内容如下:
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<HelloWorld msg="Hello Vue 3 + Vite" />
</template>
<script setup> import HelloWorld from './components/HelloWorld.vue' // This starter template is using Vue 3 experimental <script setup> SFCs // Check out https://github.com/vuejs/rfcs/blob/master/active-rfcs/0040-script-setup.md </script>
<style> #app {
font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>
差别在于服务端返回了@vue/compiler-sfc编译的内容,内部的自定义组件访问路径被修改为了/src/App.vue?vue&type=style&index=0&lang.css
,并且返回的内容最后添加了//# sourceMappingURL=data:application/json;base64,eyJ2Z...
的sourceMap项目内容。
后面的HelloWorld.vue
也是同样情况。
请求的HelloWorld.vue
内容:
import {
createHotContext as __vite__createHotContext } from "/@vite/client";import.meta.hot = __vite__createHotContext("/src/components/HelloWorld.vue");import {
reactive } from '/node_modules/.vite/deps/vue.js?v=b039f9b3'
const _sfc_main = {
__name: 'HelloWorld',
props: {
msg: String
},
setup(__props, {
expose }) {
expose();
const state = reactive({
count: 0 })
const __returned__ = {
state, reactive }
Object.defineProperty(__returned__, '__isScriptSetup', {
enumerable: false, value: true })
return __returned__
}
}
import {
toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, createTextVNode as _createTextVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "/node_modules/.vite/deps/vue.js?v=b039f9b3"
const _withScopeId = n => (_pushScopeId("data-v-469af010"),n=n(),_popScopeId(),n)
const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("p", null, [
/*#__PURE__*/_createElementVNode("a", {
href: "https://vitejs.dev/guide/features.html",
target: "_blank"
}, " Vite Documentation "),
/*#__PURE__*/_createTextVNode(" | "),
/*#__PURE__*/_createElementVNode("a", {
href: "https://v3.vuejs.org/",
target: "_blank"
}, "Vue 3 Documentation")
], -1 /* HOISTED */))
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("p", null, [
/*#__PURE__*/_createTextVNode(" Edit "),
/*#__PURE__*/_createElementVNode("code", null, "components/HelloWorld.vue"),
/*#__PURE__*/_createTextVNode(" to test hot module replacement. ")
], -1 /* HOISTED */))
function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("h1", null, _toDisplayString($props.msg), 1 /* TEXT */),
_hoisted_1,
_createElementVNode("button", {
type: "button",
onClick: _cache[0] || (_cache[0] = $event => ($setup.state.count++))
}, " count is: " + _toDisplayString($setup.state.count), 1 /* TEXT */),
_hoisted_2
], 64 /* STABLE_FRAGMENT */))
}
import "/src/components/HelloWorld.vue?vue&type=style&index=0&scoped=true&lang.css"
_sfc_main.__hmrId = "469af010"
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
import.meta.hot.accept(({
default: updated, _rerender_only }) => {
if (_rerender_only) {
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})
import _export_sfc from '/@id/plugin-vue:export-helper'
export default /*#__PURE__*/_export_sfc(_sfc_main, [['render',_sfc_render],['__scopeId',"data-v-469af010"],['__file',"/Users/huiquandeng/projects/lg-fed-lp/my-module/vite-cli-demo/vite-demo/src/components/HelloWorld.vue"]])
//# sourceMappingURL=data:application/json;base64,eyJ2Z...
我们手编的HelloWorld.vue
,内容如下:
<template>
<h1>{
{ msg }}</h1>
<p>
<a href="https://vitejs.dev/guide/features.html" target="_blank">
Vite Documentation
</a>
|
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Documentation</a>
</p>
<button type="button" @click="state.count++">
count is: {
{ state.count }}
</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test hot module replacement.
</p>
</template>
<script setup> import {
defineProps, reactive } from 'vue' defineProps({
msg: String }) const state = reactive({
count: 0 }) </script>
<style scoped> a {
color: #42b983; } </style>
其中的路径同样是被转换成了/src/components/HelloWorld.vue?vue&type=style&index=0&scoped=true&lang.css
所以紧接着上面两个.vue
后缀的当文件组件会再次请求,并且带着一些参数如下:vue&type=style&index=0&scoped=true&lang.css
,解析Query string parameters值如下:
vue:
type: style
index: 0
scoped: true
lang.css:
vue
指示当前单文件组件使用的模版的框架类型type
指示当前气球模版中的资源类型,处理该单文件组件时需要使用对应vue
的compiler进行编译,获得的类型有template/style/script等。index
这个是不同不同类型资源可能存在多部分,index是数组的索引scoped
指示的是当前组件文件中的样式文件的是局部作用域还是全局的lang.css
指示当前的样式所使用的样式预编译语言类型。如果是scss/less/stylus/postcss等则需要相应的预编译处理。
模拟实现vite-cli
根据以上分析,我们模拟vite-cli的步骤大概可以分为以下几步:
- 使用koa框架创建一个静态文件服务器。
- 利用中间件出修改第三方模块的路径
- 加载第三方模块
- 编译单文件组件
- 处理其他类型的静态资源文件的的请求路径
- 要想速度优化得更快,还需要为各个转化过程添加相对应的缓存,以避免相同文件进行相同步骤的转换操作。
实现代码
首先创建一个目录,使用npm init -y
进行初始化后,package.json
中配置CLI项目的必须选项bin
, 如下:{“bin”: "index.js}"
所以我们项目的入口文件就是index.js
,内容如下:
// index.js
// 这个家伙太懒了,看完源码都懒得实现了
// 源码vite中还实现了plugin形式配置使用的框架
全文终…
边栏推荐
- 从零到一快速学会三子棋
- 腾讯云【Hiflow】新时代自动化工具
- 2022-08-04:输入:去重数组arr,里面的数只包含0~9。limit,一个数字。 返回:要求比limit小的情况下,能够用arr拼出来的最大数字。 来自字节。
- 1667. Fix names in tables
- Quickly learn chess from zero to one
- 1527. Patients suffering from a disease
- lua learning
- PostgreSQL数据库 用navicat 打开表结构的时候报错 cannot update secondarysnapshot during a parallel operation 怎么解决?
- private封装
- sql server 安装提示用户名不存在
猜你喜欢
随机推荐
Everyone in China said data, you need to focus on core characteristic is what?
行业案例|世界 500 强险企如何建设指标驱动的经营分析系统
mysql没法Execute 大拿们求解
How to transfer a single node of Youxuan database to a cluster
RAID disk array
Solve connect: The requested address is not valid in its context
torch.roll()
Apache DolphinScheduler, a new generation of distributed workflow task scheduling platform in practice - Medium
C student management system Find student nodes based on student ID
Compressed storage of special matrices
Beidou no. 3 short message terminal high slope in open-pit mine monitoring programme
[Decryption] Can the NFTs created by OpenSea for free appear in my wallet without being chained?
1484. Sell Products by Date
The design idea of DMicro, the Go microservice development framework
程序员的七夕浪漫时刻
post-study program
QStyle平台风格
继承关系下构造方法的访问特点
Chinese characters to Pinyin
DAY23: Command Execution & Code Execution Vulnerability