Files
blog-press/docs/Web-Front/Others/Vite-Principle.md
2025-12-16 18:07:42 +08:00

8.6 KiB
Raw Blame History

title, date
title date
前端包管理器 2025-12-13

一、Vite简介

Vite是新一代的前端构建工具在尤雨溪开发Vue3.0的时候诞生。类似于Webpack+ Webpack-dev-server。
  其主要利用浏览器ESModule特性导入组织代码在服务器端按需编译返回完全跳过了打包这个概念服务器随起随用。生产中利用Rollup作为打包工具号称下一代的前端构建工具。

二、核心原理

2.1 ESModule

ESModule是JavaScript提出的官方标准化模块系统不同于之前的CJSAMDCMD等等ESModule提供了更原生以及更动态的模块加载方案最重要的就是它是浏览器原生支持的也就是说我们可以直接在浏览器中去执行import,动态引入我们需要的模块,而不是把所有模块打包在一起。
例如声明一个script标签<script type="module" src="/src/main.js"></script>当浏览器解析资源时会往当前域名发起一个GET请求main.js文件:

// 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代码打包分发在网页上运行。但其打包速度却是其他工具的10100倍。
在开发环境中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

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,会首先解析<script type="module" src="main.js"></script>,获取main.js,然后会把import { createApp } from 'vue'中的Vue换成替换成:./node_modules/vue/dist/vue.esm-browser.js',在遇到Vue文件时,会利用Vue的模板编译器转成Javascript文件
mini-vue-parser.js

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
  2. 深入理解Vite核心原理
  3. step-vite