为什么选 Vite
在浏览器支持 ES 模块之前,JavaScript 并没有提供的原生机制让开发者以模块化的方式进行开发。所以出现了webpack, rollup等构建工具来将我们的源码模块串联成可以在浏览器中运行的文件。
然而,当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。于是开始遇到性能瓶颈:使用 JavaScript 开发的工具通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用 HMR,文件修改后的效果也需要几秒钟才能在浏览器中反映出来。
Vite 通过在一开始将应用中的模块区分为 依赖 和 源码 两类,改进了开发服务器启动时间。
-
依赖: 大多为在开发时不会变动的纯 JavaScript。一些较大的依赖(例如有上百个模块的组件库)处理的代价也很高。依赖也通常会存在多种模块化格式(例如 ESM 或者 CommonJS)。
-
源码: 通常包含一些并非直接是 JavaScript 的文件,需要被转换(例如 JSX,CSS 或者 Vue/Svelte 组件),且时常会被编辑。同时,并不是所有的源码都需要同时被加载(例如基于路由拆分的代码模块)。
-
Vite 以 原生 ESM 方式提供源码。这实际上是让浏览器接管了打包程序的部分工作,Vite 只需要在浏览器请求源码时进行转换并按需提供源码。根据情景动态导入代码,即只在当前屏幕上实际使用时才会被处理。
-
Vite 同时利用 HTTP 头来加速整个页面的重新加载(再次让浏览器为我们做更多事情):源码模块的请求会根据
304 Not Modified进行协商缓存,而依赖模块请求则会通过Cache-Control: max-age=31536000,immutable进行强缓存,因此一旦被缓存它们将不需要再次请求。
-
一旦你体验到 Vite 的神速,你是否愿意再忍受像曾经那样使用打包器开发就要打上一个大大的问号了。
为什么生产环境仍需打包
尽管原生 ESM 现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返延迟,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。
为了在生产环境中获得最佳的加载性能,最好还是将代码进行 tree-shaking、懒加载和 chunk 分割(以获得更好的缓存)。
搭建第一个 Vite 项目
// npm 7+, 需要额外的双横线:
npm init vite@latest my-vue-app -- --template vue-ts
Vite 需要 Node.js 版本 >= 12.0.0。
template 表示选择开发的技术栈,我们选择vue-ts来进行开发。
解析 Vite 项目的不同
package.json
{
"scripts": {
"dev": "vite", // 启动开发服务器,别名:`vite dev`,`vite serve`
"build": "vite build", // 为生产环境构建产物
"preview": "vite preview" // 本地预览生产构建产物
}
}
index.html 与项目根目录
在一个 Vite 项目中,index.html 在项目最外层而不是在 public 文件夹内。这是有意而为之的:在开发期间 Vite 是一个服务器,而 index.html 是该 Vite 项目的入口文件。
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
Vite 将 index.html 视为源码和模块图的一部分。Vite 解析 <script type="module" src="/src/main.ts"></script> ,这个标签指向你的 JavaScript 源码,即入口文件main.ts。
另外,index.html 中的 URL 将被自动转换,因此不再需要 %PUBLIC_URL% 占位符了。
与静态 HTTP 服务器类似,Vite 也有 "根目录" 的概念,即服务文件的位置。源码中的绝对 URL 路径将以项目的 "根" 作为基础来解析,因此你可以像在普通的静态文件服务器上一样编写代码。
vite 默认以当前工作目录作为根目录启动开发服务器,你也可以通过 vite serve some/sub/dir 来指定一个替代的根目录。
请求资源的方式不同点
vite项目在开发过程中请求的资源如下图:
首先请求@vite/client,可以把这个文件想象成vite的运行时代码,它在env.d.ts中:
/// <reference types="vite/client" />
接着请求main.ts,在这个文件中又引入了如下几个文件:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
所以就会去请求vue.js的框架源码,然后是App.vue源码。App.vue又引入了helloworld.vue文件:
<script setup lang="ts">
import HelloWorld from './components/HelloWorld.vue'
</script>
最后就是去请求组件里面的style样式文件。
通过一个一个的请求页面所需要的资源,就绘制出完整的页面。这与打包之后再绘制有明显的区别,我们看看打包之后的请求,可以发现所有的资源都被打包成一个js文件了。