深入 vite 原理:尤大最新力作到底是如何实现的?
共 12856字,需浏览 26分钟
·
2020-08-09 07:40
NO.1
vite 是什么
vite —— 一个由 vue 作者尤雨溪开发的 web 开发工具,它具有以下特点:
快速的冷启动
即时的模块热更新
真正的按需编译
从作者在微博上的发言:
Vite,一个基于浏览器原生 ES imports 的开发服务器。利用浏览器去解析 imports,在服务器端按需编译返回,完全跳过了打包这个概念,服务器随起随用。同时不仅有 Vue 文件支持,还搞定了热更新,而且热更新的速度不会随着模块增多而变慢。针对生产环境则可以把同一份代码用 rollup 打。虽然现在还比较粗糙,但这个方向我觉得是有潜力的,做得好可以彻底解决改一行代码等半天热更新的问题。
中可以看出 vite 主要特点是基于浏览器 native 的 ES module (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 来开发,省略打包这个步骤,因为需要什么资源直接在浏览器里引入即可。
基于浏览器 ES module 来开发 web 应用也不是什么新鲜事,snowpack 也基于此,不过目前此项目社区中并没有流行起来,vite 的出现也许会让这种开发方式再火一阵子。
有趣的是 vite 算是革了 webpack 的命了(生产环境用 rollup),所以 webpack 的开发者直接喊大哥了...
作者注:本文完成于 vite 早期时候,vite 部分功能和原理有更新。
NO.2
vite 的使用方式
同常见的开发工具一样,vite 提供了用 npm 或者 yarn 一建生成项目结构的方式,使用 yarn 在终端执行:
$ yarn create vite-app
$ cd
$ yarn
$ yarn dev
即可初始化一个 vite 项目(默认应用模板为 vue3.x),生成的项目结构十分简洁:
|____node_modules
|____App.vue // 应用入口
|____index.html // 页面入口
|____vite.config.js // 配置文件
|____package.json
执行 yarn dev
即可启动应用 。
NO.3
vite 启动链路
命令解析
这部分代码在 src/node/cli.ts 里,主要内容是借助 minimist —— 一个轻量级的命令解析工具解析 npm scripts,解析的函数是 resolveOptions
,精简后的代码片段如下。
function resolveOptions() {
// command 可以是 dev/build/optimize
if (argv._[0]) {
argv.command = argv._[0];
}
return argv;
}
拿到 options 后,会根据 options.command
的值判断是执行在开发环境需要的 runServe 命令或生产环境需要的 runBuild 命令。
if (!options.command || options.command === 'serve') {
runServe(options)
} else if (options.command === 'build') {
runBuild(options)
} else if (options.command === 'optimize') {
runOptimize(options)
}
在 runServe
方法中,执行 server 模块的创建开发服务器方法,同样在 runBuild
中执行 build 模块的构建方法。
最新的版本中还增加了 optimize 命令的支持,关于 optimize 做了什么,我们下文再说。
server
这部分代码在 src/node/server/index.ts 里,主要暴露一个 createServer
方法。
vite 使用 koa 作 web server,使用 clmloader 创建了一个监听文件改动的 watcher,同时实现了一个插件机制,将 koa-app 和 watcher 以及其他必要工具组合成一个 context 对象注入到每个 plugin 中。
context 组成如下:
plugin 依次从 context 里获取上面这些组成部分,有的 plugin 在 koa 实例添加了几个 middleware,有的借助 watcher 实现对文件的改动监听,这种插件机制带来的好处是整个应用结构清晰,同时每个插件处理不同的事情,职责更分明,
plugin
上文我们说到 plugin,那么有哪些 plugin 呢?它们分别是:
用户注入的 plugins —— 自定义 plugin
hmrPlugin —— 处理 hmr
htmlRewritePlugin —— 重写 html 内的 script 内容
moduleRewritePlugin —— 重写模块中的 import 导入
moduleResolvePlugin ——获取模块内容
vuePlugin —— 处理 vue 单文件组件
esbuildPlugin —— 使用 esbuild 处理资源
assetPathPlugin —— 处理静态资源
serveStaticPlugin —— 托管静态资源
cssPlugin —— 处理 css/less/sass 等引用
...
我们来看 plugin 的实现方式,开发一个用来拦截 json 文件 plugin 可以这么实现:
interface ServerPluginContext {
root: string
app: Koa
server: Server
watcher: HMRWatcher
resolver: InternalResolver
config: ServerConfig
}
type ServerPlugin = (ctx:ServerPluginContext)=> void;
const JsonInterceptPlugin:ServerPlugin = ({app})=>{
app.use(async (ctx, next) => {
await next()
if (ctx.path.endsWith('.json') && ctx.body) {
ctx.type = 'js'
ctx.body = `export default json`
}
})
}
vite 背后的原理都在 plugin 里,这里不再一一解释每个 plugin 的作用,会放在下文背后的原理中一并讨论。
build
这部分代码在 node/build/index.ts 中,build 目录的结构虽然与 server 相似,同样导出一个 build 方法,同样也有许多 plugin,不过这些 plugin 与 server 中的用途不一样,因为 build 使用了 rollup ,所以这些 plugin 也是为 rollup 打包的 plugin ,本文就不再多提。
NO.4
vite 运行原理
ES module
要了解 vite 的运行原理,首先要知道什么是 ES module,目前流览器对其的支持如下:
主流的浏览器(IE11除外)均已经支持,其最大的特点是在浏览器端使用 export
import
的方式导入和导出模块,在 script 标签里设置 type="module"
,然后使用模块内容。
当 html 里嵌入上面的 script 标签时候,浏览器会发起 http 请求,请求 htttp server 托管的 bar.js ,在 bar.js 里,我们用 named export 导出 bar
变量,在上面的 script 中能获取到 bar 的定义。
// bar.js
export const bar = 'bar';
在 vite 中的作用
打开运行中的 vite 项目,访问 view-source 可以发现 html 里有段这样的代码: