0
点赞
收藏
分享

微信扫一扫

vite原理

vue3构建工具vite原理 之 手写vite

vite实现原理是什么?

当声明一个 ​​script​​ 标签类型为 ​​module​​ 时

如: ​​<script type="module" src="/src/main.js"></script>​

浏览器就会像服务器发起一个​​GET​​ ​​​http://localhost:3000/src/main.js​​​请求main.js文件:

// /src/main.js:
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

浏览器请求到了​​main.js​​文件,检测到内部含有​​import​​引入的包,又会对其内部的 ​​import ​​引用发起 ​​HTTP​​ 请求获取模块的内容文件

如: ​​GET​​ ​​​http://localhost:3000/@modules/vue.js​​

如: ​​GET​​ ​​​http://localhost:3000/src/App.vue​​

其​​Vite​​ 的主要功能就是通过劫持浏览器的这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再返回给浏览器渲染页面,​​vite​​整个过程中没有对文件进行打包编译,所以其运行速度比原始的​​webpack​​开发编译速度快出许多!

vite做了哪些事?

1. 重写引入模块路径前面加上/@modules/, 重写后浏览器会再次发送请求

原main.js文件:

vite原理_css

通过vite构建后请求的main.js文件: vite原理_构建工具_02

2. 拦截含有/@modules/的请求, 去node_modules引入对应的模块并返回

vite原理_构建工具_03

3. 解析.vue文件

如app.vue文件如下:

<template>
<HelloWorld msg="Hello Vue 3.0 + Vite" />
</template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
name: 'App',
components: {
HelloWorld
}
}
</script>

被解析成render函数返回给浏览器渲染页面:

请求:​​​http://localhost:3000/src/App.vue​​

vue文件时,koa中间件检测到请求的是vue模板文件,则会在请求后面添加一个​​type=template​​参数

如: ​​​http://localhost:3000/src/App.vue?type=template​​

koa通过这个参数来判断是请求vue模板文件,并编译成js文件返回给浏览器 vite原理_构建工具_04

4. 静态服务插件 实现可以返回静态文件的功能

app.use(static(root))
app.use(static(path.resolve(root, 'public')))

手写vite代码 实现以上4种功能:

新建一个vite项目:

npm instal -g create-vite-app    //全局安装最新vite构建工具 (默认最新)
create-vite-app my-vite-vue3 //创建一个名为myvitevue3的项目

cd my-vite-vue3 //进入项目
yarn install //安装项目依赖
yarn dev //启动项目

下面我们在根目录新建​​vite/index.js​​文件

通过运行​​node vite/index.js​​代替​​yarn dev​​启动项目

使用自实现的vite来模拟​​vite​​的这4个功能

如图所述则是使用自写vite渲染的页面:

vite原理_css_05

//vite/index.js
const fs = require('fs').promises
const Koa = require('koa')
const path = require('path')
const chalk = require('chalk')
const static = require('koa-static')
const { parse } = require('es-module-lexer')
const MagicString = require('magic-string')
const { Readable } = require('stream')

//读取body方法
async function readBody(stream) {
if (stream instanceof Readable) {
return new Promise((resolve) => {
let res = ''
stream.on('data', function (chunk) {
res += chunk
});
stream.on('end', function () {
resolve(res)
})
})
} else {
return stream;
}
}

//koa中间件
const resolvePlugin = [
// 1. 重写引入模块路径前面加上/@modules/vue, 重写后浏览器会再次发送请求
({ app, root }) => {
function rewriteImports(source) {
let imports = parse(source)[0];
let ms = new MagicString(source);
if (imports.length > 0) {
for (let i = 0; i < imports.length; i++) {
let { s, e } = imports[i];
let id = source.slice(s, e); // 应用的标识 vue ./App.vue
// 不是./ 或者 /
if (/^[^\/\.]/.test(id)) {
id = `/@modules/${id}`;
ms.overwrite(s, e, id)
}
}
}
return ms.toString();
}
app.use(async (ctx, next) => {
await next(); // 静态服务
// 默认会先执行 静态服务中间件 会将结果放到 ctx.body
// 需要将流转换成字符串 , 只需要处理js中的引用问题
if (ctx.body && ctx.response.is('js')) {
let r = await readBody(ctx.body); // vue => /@modules
const result = rewriteImports(r);
ctx.body = result;
}
})
},

// 2. 拦截含有/@modules/vue的请求, 去node_modules引入对应的模块并返回
({ app, root }) => {
const reg = /^\/@modules\//
app.use(async (ctx, next) => {
// 如果没有匹配到 /@modules/vue 就往下执行即可
if (!reg.test(ctx.path)) {
return next();
}
const id = ctx.path.replace(reg, '');

let mapping = {
vue: path.resolve(root, 'node_modules', '@vue/runtime-dom/dist/runtime-dom.esm-browser.js'),
}
const content = await fs.readFile(mapping[id], 'utf8');
ctx.type = 'js'; // 返回的文件是js
ctx.body = content;
})
},

// 3. 解析.vue文件
({ app, root }) => {
app.use(async (ctx, next) => {
if (!ctx.path.endsWith('.vue')) {
return next();
}
const filePath = path.join(root, ctx.path);
const content = await fs.readFile(filePath, 'utf8');
// 引入.vue文件解析模板
const { compileTemplate, parse } = require(path.resolve(root, 'node_modules', '@vue/compiler-sfc/dist/compiler-sfc.cjs'))
let { descriptor } = parse(content);
if (!ctx.query.type) {
//App.vue
let code = ''
if (descriptor.script) {
let content = descriptor.script.content;
code += content.replace(/((?:^|\n|;)\s*)export default/, '$1const __script=');
}
if (descriptor.template) {
const requestPath = ctx.path + `?type=template`;
code += `\nimport { render as __render } from "${requestPath}"`;
code += `\n__script.render = __render`
}
code += `\nexport default __script`
ctx.type = 'js';
ctx.body = code
}
if (ctx.query.type == 'template') {
ctx.type = 'js';
let content = descriptor.template.content
const { code } = compileTemplate({ source: content }); // 将app.vue中的模板 转换成render函数
ctx.body = code;
}
})
},

// 4. 静态服务插件 实现可以返回文件的功能
({ app, root }) => {
app.use(static(root))
app.use(static(path.resolve(root, 'public')))
}
]

function createServer() {
let app = new Koa()
const context = { // 直接创建一个上下文 来给不同的插件共享功能
app,
root: process.cwd() // C:\Users\...\my-vite-vue3
}

// 运行中间件
resolvePlugin.forEach(plugin => plugin(context))

return app
}

createServer().listen(4000, () => {
console.log(' Dev server running at:')
console.log(` > Local: ${chalk.cyan('http://localhost:4000/')}`)
})

图片和css文件我们还没有处理,所以除去​​app.vue​​​引入的图片与​​main.js​​内引入的css即可实现对应的功能


vite原理_中间件_06

vite原理_中间件_07​文章就分享到这,欢迎关注“前端大神之路”​vite原理_中间件_07

vite原理_中间件_09




举报

相关推荐

0 条评论