Vue.js 框架源码与进阶 - Vue.js 3.0 Vite 实现原理

本文介绍了Vite,一种轻量级的Web应用开发工具,重点讲解了其快速冷启动、模块热更新和按需编译等特性,以及其实现原理,包括利用ES Modules和Koa构建的静态服务器和单文件组件编译过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Vite 概念

  • Vite 是一个面向现代浏览器的一个更轻、更快的 Web 应用开发工具
  • 它基于 ECMAScript 标准原生模块系统(ES Modules)实现
  • 它的出现是为了解决 Webpack 在开发阶段使用 Webpack devserver 冷启动时间过长,另外 Webpack HMR 热更新反映慢的问题

二、Vite 项目依赖

  • Vite
  • @vue/compiler-sfc(用来编译项目中的 .vue 结尾的单文件组件,Vue2 中使用的是 vue-template-compiler)
  • Vite 目前只支持 Vue 3.0 的版本,在创建项目时,通过指定使用不同的模版,也可以支持其它的框架

三、Vite 基础使用

  • vite serve

在这里插入图片描述
回顾 vue-cli-service serve

在这里插入图片描述

  • HMR
    • Vite HMR
      • 立即编译当前所修改的文件
    • Webpack HMR
      • 会自动以这个文件为入口重写 build 一次,所有的涉及到的依赖也都会被加载一遍
  • vite build
    • Rollup
    • Dynamic import
      • Polyfill
  • 打包 or 不打包
    • 使用 Webpack 打包的两个原因
      • 浏览器环境并不支持模块化(绝大多数目前已支持)
      • 零散的模块文件会产生大量的 HTTP 请求(HTTP2 已解决)

浏览器对 ES Module 的支持

在这里插入图片描述

四、Vite 特性

  • 快速冷启动
  • 模块热更新
  • 按需编译
  • 开箱即用
    • TypeScript - 内置支持
    • less / sass / stylus / postcss - 内置支持(需要单独安装)
    • JSX
    • Web Assembly

五、Vite 实现原理

Vite 核心功能

  • 静态 Web 服务器
  • 编译单文件组件
    • 拦截浏览器不识别的模块,并处理
  • HMR

在这里插入图片描述
package.json

{
  "name": "vite-cli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@vue/compiler-sfc": "^3.0.0-rc.10",
    "koa": "^2.13.0",
    "koa-send": "^5.0.1"
  }
}

index.js

#!/usr/bin/env node
const path = require("path");
const { Readable } = require("stream");

// 基于 Koa 开发一个静态服务器
const Koa = require("koa");
const send = require("koa-send");

// 导入编译单文件组件模块
const compilerSFC = require("@vue/compiler-sfc");

// 创建 Koa 实例
const app = new Koa();

// 把流转换成字符串
const streamToString = (stream) =>
  new Promise((resolve, reject) => {
    const chunks = [];
    stream.on("data", (chunk) => chunks.push(chunk));
    stream.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
    stream.on("error", reject);
  });

// 把字符串转换成流
const stringToStream = (text) => {
  const stream = new Readable();
  stream.push(text);
  stream.push(null);
  return stream;
};

// 3. 加载第三方模块
app.use(async (ctx, next) => {
  // ctx.path --> /@modules/vue
  if (ctx.path.startsWith("/@modules/")) {
    const moduleName = ctx.path.substr(10);
    const pkgPath = path.join(
      process.cwd(),
      "node_modules",
      moduleName,
      "package.json"
    );
    const pkg = require(pkgPath);
    ctx.path = path.join("/node_modules", moduleName, pkg.module);
  }
  await next();
});

// 1. 静态文件服务器(使用 Koa 开发静态服务器,默认返回根目录中的 index.html)
app.use(async (ctx, next) => {
  // 参数:上下文、当前请求的路径、配置根目录
  await send(ctx, ctx.path, { root: process.cwd(), index: "index.html" });
  // 执行下一个中间件
  await next();
});

// 4. 处理单文件组件
app.use(async (ctx, next) => {
  // 是否是 .vue 结尾
  if (ctx.path.endsWith(".vue")) {
    // 获取单文件组件内容
    const contents = await streamToString(ctx.body);
    // 编译单文件组件
    const { descriptor } = compilerSFC.parse(contents);
    let code;
    // 单文件请求分两次,一次没 type,一次有 type
    if (!ctx.query.type) {
      code = descriptor.script.content;
      // console.log(code)
      code = code.replace(/export\s+default\s+/g, "const __script = ");
      code += `
      import { render as __render } from "${ctx.path}?type=template"
      __script.render = __render
      export default __script
      `;
    } else if (ctx.query.type === "template") {
      const templateRender = compilerSFC.compileTemplate({
        source: descriptor.template.content,
      });
      code = templateRender.code;
    }
    ctx.type = "application/javascript";
    ctx.body = stringToStream(code);
  }
  await next();
});

// 2. 修改第三方模块的路径
app.use(async (ctx, next) => {
  // 判断当前返回给浏览器的文件是否是 javascript
  if (ctx.type === "application/javascript") {
    // ctx.body 转换成字符串
    const contents = await streamToString(ctx.body);
    // import vue from 'vue'
    // import App from './App.vue'
    ctx.body = contents
      .replace(/(from\s+['"])(?![\.\/])/g, "$1/@modules/") // 通过正则表达式,匹配第三方模块,然后替换,$1 表示拿第一个分组
      .replace(/process\.env\.NODE_ENV/g, '"development"'); // 表示当前是开发环境
  }
});

// 监听的端口
app.listen(3000);
console.log("Server running @ http://localhost:3000");
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

每天内卷一点点

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值