# 一、Vite简介
Vite是新一代的前端构建工具,在尤雨溪开发Vue3.0的时候诞生。类似于Webpack+ Webpack-dev-server。
其主要利用浏览器**ESModule特性**导入组织代码,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。生产中利用Rollup作为打包工具,号称下一代的前端构建工具。
# 二、核心原理
## 2.1 ESModule
ESModule是JavaScript提出的官方标准化模块系统,不同于之前的CJS,AMD,CMD等等,ESModule提供了更原生以及更动态的模块加载方案,最重要的就是它是浏览器原生支持的,也就是说我们可以**直接在浏览器中去执行import**,动态引入我们需要的模块,而不是把所有模块打包在一起。
例如声明一个script标签:``,当浏览器解析资源时,会往当前域名发起一个GET请求`main.js`文件:
```javascript
// main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
```
请求到了main.js文件,会检测到内部含有import引入的包,又会import引用发起HTTP请求获取模块的内容文件,如App.vue文件。
## 2.2 基于ESModule的Dev Server
Vite其核心原理是利用浏览器现在已经支持ES6的import,碰见import就会发送一个HTTP请求去加载文件,**Vite启动一个koa服务器拦截这些请求,并在后端进行相应的处理将项目中使用的文件通过简单的分解与整合,然后再以ESM格式返回返回给浏览器**。
Vite整个过程中没有对文件进行打包编译,做到了真正的按需加载,所以其运行速度比原始的webpack开发编译速度快出许多。
## 2.3 基于ESModule的热更新
热更新主要思路是通过WebSocket创建浏览器和服务器的通信监听文件的改变,当文件被修改时,服务端发送消息通知客户端修改相应的代码,客户端对应不同的文件进行不同的操作的更新。
**Vite 通过 chokidar 来监听文件系统的变更,只用对发生变更的模块重新加载**,只需要精确的使相关模块与其临近的 HMR边界连接失效即可,这样HMR 更新速度就不会因为应用体积的增加而变慢而 Webpack 还要经历一次打包构建。所以 HMR 场景下,Vite 表现也要好于 Webpack。
## 2.4 基于Esbuild的开发环境编译
Esbuild是一个JavaScript Bundler 打包和压缩工具,它提供了与Webpack、Rollup等工具相似的资源打包能力。可以将JavaScript 和TypeScript代码打包分发在网页上运行。但其打包速度却是其他工具的10~100倍。
在开发环境中,Vite 使用 esbuild 进行快速的热重载和模块编译。
### 2.4.1 Esbuild高性能原理
1. Go 语言实现:避开 JavaScript 运行时的性能瓶颈。
2. 并行编译:利用多核 CPU 同时处理多个文件。
3. 内存优化:在单个进程中完成解析、转换和生成,减少内存开销。
### 2.4.2 Esbuild关键特性
1. 语法转换:TypeScript → JS(保留类型信息或移除)、现代 JS 语法降级(如 ?.、?? 转换)等。
2. 压缩优化:变量重命名、死代码消除、支持 CSS 压缩(通过 esbuild-plugin-css-minify)等。
## 2.5 基于Rollup的生产环境打包
Rollup是基于ESM的JavaScript打包工具。相比于其他打包工具如Webpack,他总是能打出更小、更快的包。因为 Rollup 基于 ESM 模块,比 Webpack 和 Browserify 使用的 CommonJS模块机制更高效。Rollup的亮点在于同一个地方,一次性加载。能针对源码进行 Tree Shaking(去除那些已被定义但没被使用的代码),以及 Scope Hoisting 以减小输出文件大小提升运行性能。
在生产环境中,Vite 使用 Rollup 进行最终的打包和优化。
### 2.5.1 Rollup构建流程
1. 解析入口文件:从 index.js 开始,递归分析所有 import 语句。
2. 生成抽象语法树(AST):使用 acorn 解析 JS 文件为 AST。
3. 应用插件转换:如 @rollup/plugin-typescript 处理 TypeScript。
4. 生成依赖图:分析模块间的依赖关系。
5. 输出优化:
Tree-shaking:基于 ESM 静态分析,移除未使用的代码
代码分割:识别动态导入(import())并拆分包。。
# 三、Mini-Vite
## 3.1 初始化工程
初始化NodeJs工程,并添加Vue3依赖
## 3.2 Node Server
```javascript
const http = require('http');
const fs = require('fs');
const path = require('path');
const miniVueParse = require('./mini-vue-parser')
const server = http.createServer((req, res) => {
// 获取请求的路径
const reqPath = req.url;
// 构建本地文件路径
const filePath = path.join(__dirname, reqPath);
console.log('filePath: ', filePath)
// 检查文件是否存在
fs.access(filePath, fs.constants.R_OK, (err) => {
if (err) {
console.error('文件不存在:', err);
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('文件不存在');
} else {
// 读取文件并发送给客户端
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
console.error('读取文件出错:', err);
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('内部服务器错误');
} else {
// 根据文件类型设置合适的 Content-Type
const extname = path.extname(filePath);
let contentType = 'text/plain';
switch (extname) {
case '.html':
contentType = 'text/html';
break;
case '.js':
contentType = 'text/javascript';
break;
case '.vue':
contentType = 'text/javascript';
break;
case '.css':
contentType = 'text/css';
break;
// 添加其他需要支持的文件类型
}
res.writeHead(200, { 'Content-Type': contentType });
if (extname === '.js') {
data = data.replace(/from ['"]vue['"]/g, "from './node_modules/vue/dist/vue.esm-browser.js'");
}
if (extname === '.vue') {
const fileName = path.basename(filePath);
data = miniVueParse.parse(data, fileName)
}
res.end(data);
}
});
}
});
});
const port = 3000;
server.listen(port, () => {
console.log(`代理服务器正在监听端口 ${port}`);
});
```
在浏览器输入`http://localhost:3000/index.html`,会首先解析``,获取`main.js`,然后会把`import { createApp } from 'vue'`中的`Vue`换成替换成:`./node_modules/vue/dist/vue.esm-browser.js'`,在遇到`Vue`文件时,会利用`Vue`的模板编译器转成Javascript文件:
mini-vue-parser.js:
```JavaScript
const compilerSfc = require("@vue/compiler-sfc");
const path = require('path');
function parse(vueFileString,fileName) {
const sfc = compilerSfc.parse(
vueFileString
);
const sfcTemplate = compilerSfc.compileTemplate({
id: 'sfc',
filename: fileName + '.template.vue',
source: sfc.descriptor.template.content
})
const sfcScript = compilerSfc.compileScript(sfc.descriptor, {
filename: 'sfc.script.vue'
})
let result = '';
result += sfcTemplate.code.replace("export", "") + '\n';
result += sfcScript.content.replace("export default ", "const __script = ") + '\n';
result += `__script.render=render;\n`
result += `export default __script;`
result = result.replace(/from ['"]vue['"]/g, "from './node_modules/vue/dist/vue.esm-browser.js'");
return result;
}
module.exports = {
parse
}
```
# 四、参考资料
1. [深入浅出vite(核心原理 + 手撕mini-vite)](https://juejin.cn/post/7026285200766140453#heading-37)
2. [深入理解Vite核心原理](https://juejin.cn/post/7064853960636989454)
3. [step-vite](https://github.com/Llyonss/step-vite)