一、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 HMR
- vite build
- Rollup
- Dynamic import
- Polyfill
- 打包 or 不打包
- 使用 Webpack 打包的两个原因
- 浏览器环境并不支持模块化(绝大多数目前已支持)
- 零散的模块文件会产生大量的 HTTP 请求(HTTP2 已解决)
- 使用 Webpack 打包的两个原因
浏览器对 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");