2021-03-27 补充视频投稿 2021-03-17 补充原理浅析 2021-02-17 初稿 2021-02-11 我跑通了 vite2+vue2+ts+全家桶 Vite2搞Vue2?这题我会(官方新动作)
按:
2021-02-17 尤雨溪在 dev.to
上发表了文章《Announcing Vite 2.0》,标志着 vite2 正式丢掉beta的帽子。
这也意味着我的文章再不整理,又要过时了。
接下来的内容,有视频版了,内容虽然不是很完美(口误、啰嗦小问题很多),但能勉强够用。
对应开源仓库
0 前置知识
ESM
对应 B站视频的 p1 ESM 部分
ESM是js的模块标准,目前现代浏览器都原生支持模块:使用 export/import
,遇到 from
会加载对应资源。这样就不需要考虑 webpack的基础打包功能来统一了。
ESM的支持情况,图片来自https://caniuse.com/?search=esm
需要注意: from
的路径只支持相对路径( /xx.js
)、绝对路径(远程http网址),必须要加后缀。
借用 esm 的能力,让项目构建速度和项目规模无关变得可行。理论上我们需要一个编译器,编译器随用随编译,达到 O(1)
的编译速度。
请看视频的 p1 部分和仓库中的 share-repo/01-vite/esm/esm.demo.html
部分。
看一下Network请求:
打开一个小项目和大项目的时间都是 O(1)
,但这请求hold不住。基于这个特点,有不同的工具来完成这件事,他们有 Snowpack``Vite1
和今天的 Vite2
。
Snowpack
首先是 Snowpack
, 2020年10月份我曾经发过一个很简陋的笔记《讲座《Snowpack进化论》》
(咦,2021-01-13 《snopack v3.0》,回头看看,自动导入cdn挺有意思)。
import-maps
其实 esm支持在 from的时候支持远程url,但这一点不太符合现有的编辑器辅助功能。
后来看到 wicg
正在进行新提案: import-maps
,还不是正式标准:通过提供map映射,来实现模块路径替换。
请看下面的示例:
<script type="importmap">
{
"imports": {
"react": "https://cdn.pika.dev/react",
"react-dom": "https://cdn.pika.dev/react-dom"
}
}
</script>
<script type="module">
// 支持 bare import
import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render('Hello World!', document.getElementById('root'))
</script>
这样我们from的模块就不必强制要求后缀了。map文件让程序去自动生成。
参考 https://www.jianshu.com/p/b23d823a183a
esbuild
编译新贵:用go写的编译工具,特点是快,碾压式地秒杀一切编译工具。能搞定js jsx ts文件,目前似乎对css的支持还不够好
2021-08-17 vite2.5 可以用esbuild 压缩 css了
Vite 使用 esbuild 将 TypeScript 翻译到 JavaScript,约是 tsc 速度的 20~30 倍,同时 HMR 更新反映到浏览器的时间小于 50ms。
基于这个能力,让随用随编译更加无感。
Vite1
Vite1 用的是 esbuild 做转换,打包用 rollup 。
后面考虑做成通用性的web工具,技术栈无关,和snowpack是竞品了。
Vite1 的源码分析大致看过:
- 使用koa2 做中间层,拦截请求操作
- 当我们请求 .ts 会解析成js再返回给用户, .vue 一样
- 遇到来自 node_modules 的请求会调整请求
把第三方模块,比如 ‘vue’ 模块转换成 /@modules/vue.js
补齐了路径,然后拦截 /@modules
开头的请求。
把 vue组件放到js脚本 _render 上
思路大纲
- 手动实现 bin.vite 命令注册
- 安装
- koa 启用后端服务
- es-module-lerxer import解析
- koa-static 静态文件处理
- magic-string 把字符串变成对象
- chokidar 跨平台的 fs.watch 监听文件变化
- watch 位置
- e: add change unlink 等事件
- 创建 server 服务
- 准备中间件
注册ctx上下文方便插件中使用,比如
const context = {
app, // koa 实例
root: process.cwd(), // 当前命令的运行目录
}
解析import 重写路径
- 利用 es-module-lexer.parse解析用户提供的code源码,得到import的数组引用
- s:start
- e:end
- 利用 magic-stirng 提供overwrite方法重写部分内容
- 利用 s-e 就能获得import里的字符串 比如 vue ./app
- 如果 / 或者 . 开头的不用重写
- 否则 @/modules
- 如果是 @modules 开头,拼接 当前目录+node_modules+ @vue/compiler-sfc/package.json.module
- 利用 es-module-lexer.parse解析用户提供的code源码,得到import的数组引用
optmize 预优化,启动时候自动分析依赖进行一次优化,把没有提供esm的依赖、有大量模块的依赖提前导报
减少 import 的请求数量,预处理文件 OPTIMIZE_CACHE_DIR
DEBUG=vite:optimize npx vite --force
预优化的思路:
- 从package.json 开发分析依赖
- 如果模块有 main module 两个字段说明支持 cjs esm,优先匹配 esm里的值
- 如果不包含 export 认定为cjs
Vite2
区分依赖和源码:
- 依赖一般不动,使用esbuild 预构建依赖
- 源码需要转换、经常被编辑
模块变成了 /node_modules/.vite/vue.js?v=xxx
现代web开发离不开第三方框架的支持:ESM 是不支持下列语法的
import { someMethod } from 'my-dep'
因为这可能意味着模块来自各级 node_modules 目录,但 ESM 不支持路径别名或者自动查找。
Vite需要做一些事,也就是补齐路径:
// from
import { createApp } from 'vue'
// to
import {createApp} from "/node_modules/.vite/vue.js?v=f1832269";
比着官网demo起一个例子,这是一个vue3 的初始化开发环境网络请求:
顺着第一个请求,可以看到下面的请求链:localhost:3000
页面返回的页面,包含了两个请求。Etag协商缓存
- client 继续导入env.js 协商缓存
- env.js 协商缓存
- ws连接,不同type处理
- main.ts 协商缓存
- vue.js 强缓存+协商缓存
- app.vue
- 其他组件链接
Vite 仅执行 .ts 文件的翻译工作,并不执行任何类型检查。并假设类型检查已经被你的 IDE 或构建过程接管了。
细节处理
冷启动项目,区分依赖和源代码
- 依赖,开发时候不会变动,有的依赖特别大,特别散,vite使用了 esbuild进行
预构建依赖
,使用 esbuild 是因为它比webpack构建依赖快 - 源代码,非js文件,需要转换,经常被编辑,不是所有代码都会被同时加载
用户请求数据,vite把请求的源代码转换并动态导入,也就是说有代码分割,还是做了转换,最麻烦的构建部分因为esm的缘故原本就不需要处理。
如果修改了文件,默认会刷新页面,引入hrm模块热重载,随着应用大而性能下降。vite里因为esm的关系,只需要让已编辑的模块和?失效
同时利用http cache,利用协商缓存。依赖的模块用强缓存(这块原本有翻译错误顺手修改了)
目前打包构建用的还是 rollup 而不是esbuild,以后可能会切换过去
系统变量 import.meta.env
来获取了。
推荐使用 css变量,也内置了预编译期的支持,安装对应的工具就能跑。
一个debug的流程
以后慢慢看
vite:resolve 模块转换 可以看到
- 来自node_modules 的模块补齐了后缀和路径
- .env 转成内置的 client/env.js
- vue element-plus 加上了hash放到 .vite
vite:resolve | 3ms | /app/node_modules/vite/dist/client/client -> /app/node_modules/vite/dist/client/client.js | +0ms |
---|---|---|---|
vite:resolve | 6ms | /@vite/client -> /app/node_modules/vite/dist/client/client.js | +1ms |
vite:resolve | 2ms | /src/main.ts -> /app/src/main.ts | +8ms |
vite:resolve | 1ms | ./env -> /app/node_modules/vite/dist/client/env.js | +19ms |
vite:resolve | 1ms | /node_modules/vite/dist/client/env.js -> /app/node_modules/vite/dist/client/env.js | +4ms |
vite:resolve | 1ms | vue -> /app/node_modules/.vite/vue.js?v=1b2eff55 | +25ms |
vite:resolve | 2ms | /node_modules/.vite/vue.js?v=1b2eff55 -> /app/node_modules/.vite/vue.js?v=1b2eff55 | +2ms |
vite:resolve | 2ms | ./App.vue -> /app/src/App.vue | +3ms |
vite:resolve | 2ms | /src/App.vue -> /app/src/App.vue | +2ms |
vite:resolve | 3ms | /app/src/plugins/element -> /app/src/plugins/element.ts | +4ms |
vite:resolve | 4ms | @/plugins/element -> /app/src/plugins/element.ts | +0ms |
vite:resolve | 1ms | /src/plugins/element.ts -> /app/src/plugins/element.ts | +2ms |
vite:resolve | 2ms | /src/App.vue?vue&type=style&index=0&lang.less -> /app/src/App.vue?vue&type=style&index=0&lang.less | +124ms |
vite:resolve | 6ms | element-plus/lib/theme-chalk/index.css -> /app/node_modules/element-plus/lib/theme-chalk/index.css | +17ms |
vite:resolve | 2ms | /node_modules/element-plus/lib/theme-chalk/index.css -> /app/node_modules/element-plus/lib/theme-chalk/index.css | +3ms |
vite:resolve | 0ms | element-plus -> /app/node_modules/.vite/element-plus.js?v=1b2eff55 | +1ms |
vite:resolve | 2ms | /node_modules/.vite/element-plus.js?v=1b2eff55 -> /app/node_modules/.vite/element-plus.js?v=1b2eff55 | +2ms |
vite:resolve | 1ms | fonts/element-icons.woff -> /app/node_modules/element-plus/lib/theme-chalk/fonts/element-icons.woff | +0ms |
vite:resolve | 1ms | fonts/element-icons.ttf -> /app/node_modules/element-plus/lib/theme-chalk/fonts/element-icons.ttf | +1ms |
vite: transform 这个部分就是转换, app.vue
vite:transform | 18ms | /@vite/client | +0ms |
---|---|---|---|
vite:transform | 23ms | /src/main.ts | +36ms |
vite:transform | 1ms | /node_modules/vite/dist/client/env.js | +23ms |
vite:transform | 87ms | /src/App.vue | +103ms |
vite:transform | 69ms | /src/plugins/element.ts | +20ms |
vite:transform | 290ms | /src/App.vue?vue&type=style&index=0&lang.less | +352ms |
vite:transform | 478ms | /node_modules/element-plus/lib/theme-chalk/index.css | +209ms |
vite:time
vite:time | 24ms | /index.html | +0ms |
---|---|---|---|
vite:time | 48ms | /@vite/client | +2s |
vite:time | 69ms | /src/main.ts | +36ms |
vite:time | 12ms | /node_modules/vite/dist/client/env.js | +22ms |
vite:time | 100ms | /src/App.vue | +103ms |
vite:time | 114ms | /src/plugins/element.ts | +20ms |
vite:time | 300ms | /src/App.vue?vue&type=style&index=0&lang.less | +352ms |
vite:time | 2ms | /favicon.ico | +343ms |
vite:time | 496ms | /node_modules/element-plus/lib/theme-chalk/index.css | +211ms |
vite:load
vite:load | 11ms | [fs] /@vite/client | +0ms |
---|---|---|---|
vite:load | 29ms | [fs] /src/main.ts | +26ms |
vite:load | 3ms | [fs] /node_modules/vite/dist/client/env.js | +53ms |
vite:load | 7ms | [fs] /src/App.vue | +18ms |
vite:load | 39ms | [fs] /src/plugins/element.ts | +37ms |
vite:load | 0ms | [plugin] /src/App.vue?vue&type=style&index=0&lang.less | +129ms |
vite:load | 9ms | [fs] /node_modules/element-plus/lib/theme-chalk/index.css | +23ms |
其他
插件 | 耗时1 | 路径 | 耗时 |
---|---|---|---|
vite:spa-fallback | Rewriting GET / to /index.html | +0ms | |
vite:rewrite | 1ms | [no imports] node_modules\vite\dist\client\env.js | +0ms |
vite:hmr | [self-accepts] src\App.vue | +0ms | |
vite:hmr | [self-accepts] src\App.vue?vue&type=style&index=0&lang.less | +377ms | |
vite:hmr | [self-accepts] node_modules\element-plus\lib\theme-chalk\index.css | +208ms | |
vite:spa-fallback | Not rewriting GET /favicon.ico because the path includes a dot (.) character. | +4s |
预加载构建
- vite需要将 CommonJS或者umd规范的代码,转成esm
- 为了性能
例如,lodash-es 有超过 600 个内置模块!当我们执行 import { debounce } from ‘lodash-es’ 时,浏览器同时发出 600 多个 HTTP 请求!尽管服务器在处理这些请求时没有问题,但大量的请求会在浏览器端造成网络拥塞,导致页面的加载速度相当慢。
通过预构建 lodash-es 成为一个模块,我们就只需要一个 HTTP 请求了!
构建
生产版本假设浏览器对 原生 ES 模块动态导入 有基本支持。默认情况下,所有代码构建都会以 支持原生 ESM script 标签的浏览器 为目标。
一个轻量级的 动态导入 polyfill 也会同时自动注入。
传统浏览器可以通过插件 @vitejs/plugin-legacy 来支持esm
也支持 lib模式,这是否意味着vue3+lib渐进式开发
-1 参考链接
- Vite中文文档 https://cn.vitejs.dev/
- vite1 的源码解读repohttps://vite-design.surge.sh/guide/
- snowpack https://github.com/pikapkg/snowpack
- 学习材料 p81 p170~174