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 里有段这样的代码:
\n
\n\n\n
当 html 里嵌入上面的 script 标签时候,浏览器会发起 http 请求,请求 htttp server 托管的 bar.js ,在 bar.js 里,我们用 named export 导出 bar
变量,在上面的 script 中能获取到 bar 的定义。
\n\n
\n// bar.js \nexport const bar = 'bar';\n
\n在 vite 中的作用
\n打开运行中的 vite 项目,访问 view-source 可以发现 html 里有段这样的代码:
\n\n\n\n
\n\n
\n\n\n
当 html 里嵌入上面的 script 标签时候,浏览器会发起 http 请求,请求 htttp server 托管的 bar.js ,在 bar.js 里,我们用 named export 导出 bar
变量,在上面的 script 中能获取到 bar 的定义。
\n\n
\n// bar.js \nexport const bar = 'bar';\n
\n在 vite 中的作用
\n打开运行中的 vite 项目,访问 view-source 可以发现 html 里有段这样的代码:
\n\n\n\n
\n