当前位置:网站首页>通过模拟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形式配置使用的框架
全文终…
边栏推荐
- word分栏小记
- 百日刷题计划 ———— DAY2
- 程序员的七夕浪漫时刻
- 02 【开发服务器 资源模块】
- select 标签自定义样式
- undo problem
- Regular expression to match a certain string in the middle
- Images using redis cache Linux master-slave synchronization server hard drive full of moved to the new directory which points to be modified
- 采用redis缓存的linux主从同步服务器图片硬盘满了移到新目录要修改哪些指向
- word column notes
猜你喜欢
The design idea of DMicro, the Go microservice development framework
DAY22: sqli-labs shooting range clearance wp (Less01~~Less20)
常见的硬件延迟
剑指offer专项突击版第20天
LeetCode uses the minimum cost to climb the stairs----dp problem
What should I do if the self-incrementing id of online MySQL is exhausted?
tree table lookup
Matlab map with color representation module value size arrow
Optimizing the feed flow encountered obstacles, who helped Baidu break the "memory wall"?
QT language file production
随机推荐
Countdown to 2 days|Cloud native Meetup Guangzhou Station, waiting for you!
1527. 患某种疾病的患者
1667. 修复表中的名字
汉字转拼音
[机缘参悟-60]:《兵者,诡道也》-2-孙子兵法解读
How OpenGL works
torch.roll()
Hash table lookup (hash table)
数据增强Mixup原理与代码解读
【解密】OpenSea免费创造的NFT都没上链竟能出现在我的钱包里?
627. 变更性别
HDU 1114:Piggy-Bank ← 完全背包问题
1484. 按日期分组销售产品
注意潍坊开具发票一般需要注意
VSCode Change Default Terminal how to modify the Default Terminal VSCode
tree table lookup
leetcode 15
Error: Not a signal or slot declaration
Syntax basics (variables, input and output, expressions and sequential statements)
常见的硬件延迟