概要
Vite
是一个基于浏览器原生ES imports
的开发服务器。开发环境利用了浏览器原生支持模块化特性,浏览器解析imports
,服务端按需编译返回内容,跳过打包过程。生产环境内置rollup实现打包构建。其特点如下:
- 快速冷启动
- 瞬间热更新
- 真正的按需加载和按需编译
使用
Vite默认搭配vue使用,但是也可以搭配react等其他框架。注意:开发环境下需要使用支持 native ES module imports
的浏览器。
// 默认使用Vue框架
npm init vite-app <project-name>
// react中使用
npm init vite-app --template react <project-name>
cd <project-name>
npm install
// 开发环境
npm run dev
// 生产环境
npm run build
项目根目录下新增配置文件
// vite.config.js
const path = require("path");
module.exports = {
alias: {
"/@/": path.resolve(__dirname, "./src"), // 键必须以斜线开始和结束
},
hostname: "localhost",
port: 5000,
open: true, // 自动在浏览器打开
https: true, // 是否开启 https
ssr: false, // 服务端渲染
outDir: "dist", // 输出路径
proxy: {
'/api': {
target: 'https://api.xxx.com',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
},
plugins: [] // 自定义插件
build: { // 生产环境构建选项,https://cn.vitejs.dev/guide/build.html#public-base-path
target: 'es2015', // 目标语言
rollupOptions: { // 透传给rollup, https://rollupjs.org/guide/en/#big-list-of-options
input: {
},
output: {
}
}
}
};
原理
esbuild
esbuild
是一个「JavaScript」 代码转译和压缩工具,它可以将「JSX」和「TypeScript」等代码编译成目标版本的JavaScript代码。功能上类似babel和tsc。
- 加载器
- 压缩
- 打包
- Tree shaking
- Source map 生成
- 将 JSX 和较新的 JS 语法移植到 ES6
esbuild 基于go语言编写,构建速度远远超过传统打包工具参考。下图是使用不同工具打包three.js的时间。
Vite 使用 Esbuild 来进行代码的编译,其中jsx和tsx都是通过esbuild解析生成AST,再生成最终的es module的代码,commonjs到ES module也使用它来进行转换
Javascript模块
当给script 标签添加type="module"
的属性时如:
<script type="module" src="./main.js"></script>
浏览器就会像服务器发起一个GET http://localhost:3000/main.js请求main.js文件:
// main.js:
import { add } from './add.js'
console.log(add(1,2))
浏览器请求到了main.js文件,检测到内部含有import引入的包,又会对其内部的 import 引用发起 HTTP 请求获取模块的内容文件,如: GET http://localhost:3000/add.js
注意:这里需要使用http协议打开html。如果使用file协议打开会报跨域,原因是file协议不支持跨域,但是type=module的js会默认产生跨域请求。
Vite通过Koa启动服务器并劫持浏览器请求,并在后端进行相应的处理将项目中使用的文件通过不同插件处理后返回给浏览器渲染页面,并能实现真正的按需加载
plugin
Vite插件机制基于KOA洋葱模型,对于一些浏览器不能直接处理的文件类型如vue文件、jsx等分发不同的插件处理成js模块返回给浏览器,plugin大致分类如下
- 用户注入的 plugins —— 自定义 plugin
- hmrPlugin —— 处理 hmr
- htmlRewritePlugin —— 重写 html 内的 script 内容
- moduleRewritePlugin —— 重写模块中的 import 导入
- moduleResolvePlugin ——获取模块内容
- vuePlugin —— 处理 vue 单文件组件
- esbuildPlugin —— 使用 esbuild 处理资源
- assetPathPlugin —— 处理静态资源
- serveStaticPlugin —— 托管静态资源
- cssPlugin —— 处理 css/less/sass 等引用
接下来看看 plugin 的实现方式,以处理css为例。
// node/server/serverPluginCss.ts
export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => {
app.use(async (ctx, next) => {
await next()
if (isCSSRequest(ctx.path)) {
const id = JSON.stringify(hash_sum(ctx.path))
if (isImportRequest(ctx)) {
const { css, modules } = await processCss(root, ctx)
ctx.type = 'js'
// 返回编译后的css代码
ctx.body = codegenCss(id, css, modules)
}
}
})
}
export function codegenCss(
id: string,
css: string,
modules?: Record<string, string>
): string {
// updateStyle是客户端定义的更行样式的方法
let code =
`import { updateStyle } from "${clientPublicPath}"\n` +
`const css = ${JSON.stringify(css)}\n` +
`updateStyle(${JSON.stringify(id)}, css)\n`
if (modules) {
code += dataToEsm(modules, { namedExports: true })
} else {
code += `export default css`
}
return code
}
//client/client.ts
export function updateStyle(id: string, content: string) {
let style = sheetsMap.get(id)
if (supportsConstructedSheet && !content.includes('@import')) {
if (style && !(style instanceof CSSStyleSheet)) {
removeStyle(id)
style = undefined
}
if (!style) {
style = new CSSStyleSheet()
style.replaceSync(content)
// 使用动态注入css对象方式
document.adoptedStyleSheets = [...document.adoptedStyleSheets, style]
} else {
style.replaceSync(content)
}
} else {
if (style && !(style instanceof HTMLStyleElement)) {
removeStyle(id)
style = undefined
}
if (!style) {
style = document.createElement('style')
style.setAttribute('type', 'text/css')
style.innerHTML = content
document.head.appendChild(style)
} else {
style.innerHTML = content
}
}
sheetsMap.set(id, style)
}
线上打包
Vite内置rollup用于线上打包,源码位于 node/build/index.ts 中,build目录导出一个 build 方法,同样也有许多 plugin,不过这些 plugin 与 server 中的用途不一样,因为 build 使用了 rollup ,所以这些 plugin 也是为 rollup 打包的 plugin。可以通过vite.config.js build.rollupOptions直接添加rollup配置。
不足
- Esbuild 现在还不够稳定,无法用于生产环境的打包,项目打包的时候需要使用Rollup,这就造成开发环境和生产环境的代码不一致,有可能一些bug会无法定位
- 浏览器的支持度还有待提升
- ssr还在试验阶段