vite2.svg

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的帽子。
这也意味着我的文章再不整理,又要过时了。

接下来的内容,有视频版了,内容虽然不是很完美(口误、啰嗦小问题很多),但能勉强够用。
对应开源仓库

点击查看【bilibili】

0 前置知识

ESM

对应 B站视频的 p1 ESM 部分

ESM是js的模块标准,目前现代浏览器都原生支持模块:使用 export/import ,遇到 from 会加载对应资源。这样就不需要考虑 webpack的基础打包功能来统一了。
image.png
ESM的支持情况,图片来自https://caniuse.com/?search=esm

需要注意: from 的路径只支持相对路径( /xx.js)、绝对路径(远程http网址),必须要加后缀。

借用 esm 的能力,让项目构建速度和项目规模无关变得可行。理论上我们需要一个编译器,编译器随用随编译,达到 O(1)的编译速度。

请看视频的 p1 部分和仓库中的 share-repo/01-vite/esm/esm.demo.html 部分。

看一下Network请求:
image.png

打开一个小项目和大项目的时间都是 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映射,来实现模块路径替换。

请看下面的示例:

  1. <script type="importmap">
  2. {
  3. "imports": {
  4. "react": "https://cdn.pika.dev/react",
  5. "react-dom": "https://cdn.pika.dev/react-dom"
  6. }
  7. }
  8. </script>
  9. <script type="module">
  10. // 支持 bare import
  11. import React from 'react'
  12. import ReactDOM from 'react-dom'
  13. ReactDOM.render('Hello World!', document.getElementById('root'))
  14. </script>

这样我们from的模块就不必强制要求后缀了。map文件让程序去自动生成。

参考 https://www.jianshu.com/p/b23d823a183a

es-dev-server 略

esbuild

编译新贵:用go写的编译工具,特点是快,碾压式地秒杀一切编译工具。能搞定js jsx ts文件,目前似乎对css的支持还不够好

2021-08-17 vite2.5 可以用esbuild 压缩 css了

image.png

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上下文方便插件中使用,比如

    1. const context = {
    2. app, // koa 实例
    3. root: process.cwd(), // 当前命令的运行目录
    4. }
  • 解析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

optmize 预优化,启动时候自动分析依赖进行一次优化,把没有提供esm的依赖、有大量模块的依赖提前导报
减少 import 的请求数量,预处理文件 OPTIMIZE_CACHE_DIR

  1. 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 是不支持下列语法的

  1. import { someMethod } from 'my-dep'

因为这可能意味着模块来自各级 node_modules 目录,但 ESM 不支持路径别名或者自动查找。

image.png

Vite需要做一些事,也就是补齐路径:

  1. // from
  2. import { createApp } from 'vue'
  3. // to
  4. import {createApp} from "/node_modules/.vite/vue.js?v=f1832269";

比着官网demo起一个例子,这是一个vue3 的初始化开发环境网络请求:
image.png
顺着第一个请求,可以看到下面的请求链:
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 参考链接