Vite 特性和部分源码解析

发表于 3年以前  | 总阅读数:232 次

Vite 的特性

Vite 的主要特性就是 Bundleless。基于浏览器开始原生的支持 JavaScript 模块功能 (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules?fileGuid=DDr3GGh6QRvQgWQC),JavaScript 模块依赖于 importexport 的特性,目前主流浏览器基本都支持;

想要查看具体支持的版本可以 点击这里 (https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Modules?fileGuid=DDr3GGh6QRvQgWQC);

那这有什么优势呢?

去掉打包步骤

打包是开发者利用打包工具将应用各个模块集合在一起形成 bundle,以一定规则读取模块的代码,以便在不支持模块化的浏览器里使用,并且可以减少 http 请求的数量。但其实在本地开发过程中打包反而增加了我们排查问题的难度,增加了响应时长,Vite 在本地开发命令中去除了打包步骤,从而缩短构建时长。

按需加载

为了减少 bundle 大小,一般会想要按需加载,主要有两种方式:

  1. 使用动态引入 import() 的方式异步的加载模块,被引入模块依然需要提前编译打包;
  2. 使用 tree shaking 等方式尽力的去掉未引用的模块;

而 Vite 的方式更为直接,它只在某个模块被 import 的时候动态的加载它,实现了真正的按需加载,减少了加载文件的体积,缩短了时长;

Vite 开发环境主体流程

下图是 Vite 在开发环境运行时加载文件的主体流程。

Vite 部分源码解析

总体目录结构

|-CHANGELOG.md
|-LICENSE.md
|-README.md
|-bin
|  |-openChrome.applescript
|  |-vite.js
|-client.d.ts
|-package.json
|-rollup.config.js #打包配置文件
|-scripts
|  |-patchTypes.js
|-src
|  |-client #客户端
|  |  |-client.ts
|  |  |-env.ts
|  |  |-overlay.ts
|  |  |-tsconfig.json
|  |-node #服务端
|  |  |-build.ts
|  |  |-cli.ts #命令入口文件
|  |  |-config.ts
|  |  |-constants.ts #常量
|  |  |-importGlob.ts
|  |  |-index.ts
|  |  |-logger.ts
|  |  |-optimizer
|  |  |  |-esbuildDepPlugin.ts
|  |  |  |-index.ts
|  |  |  |-registerMissing.ts
|  |  |  |-scan.ts
|  |  |-plugin.ts #rollup 插件
|  |  |-plugins   #插件相关文件
|  |  |  |-asset.ts
|  |  |  |-clientInjections.ts
|  |  |  |-css.ts
|  |  |  |-esbuild.ts
|  |  |  |-html.ts
|  |  |  |-index.ts 
|  |  |  |-...
|  |  |-preview.ts
|  |  |-server
|  |  |  |-hmr.ts #热更新
|  |  |  |-http.ts
|  |  |  |-index.ts
|  |  |  |-middlewares #中间件
|  |  |  |  |-...
|  |  |  |-moduleGraph.ts #模块间关系组装(树形)
|  |  |  |-openBrowser.ts #打开浏览器
|  |  |  |-pluginContainer.ts
|  |  |  |-send.ts
|  |  |  |-sourcemap.ts
|  |  |  |-transformRequest.ts
|  |  |  |-ws.ts
|  |  |-ssr
|  |  |  |-__tests__
|  |  |  |  |-ssrTransform.spec.ts
|  |  |  |-ssrExternal.ts
|  |  |  |-ssrManifestPlugin.ts
|  |  |  |-ssrModuleLoader.ts
|  |  |  |-ssrStacktrace.ts
|  |  |  |-ssrTransform.ts
|  |  |-tsconfig.json
|  |  |-utils.ts
|-tsconfig.base.json
|-types
|  |-...                  

server 核心方法

从入口文件 cli.ts,可以看到三个命令对应了 3 个核心的文件&方法;

  1. dev 命令

文件路径:./server/index.ts;

主要方法:createServer;

主要功能:项目的本地开发命令,基于 httpServer 启动服务,Vite 通过对请求路径的劫持获取资源的内容返回给浏览器,服务端将文件路径进行了重写。例如:

项目源码如下:

import { createApp } from 'vue';
import App from './index.vue';

经服务端重写后,node_modules 文件夹下的三方包代码路径也会被拼接完整。

import __vite__cjsImport0_vue from "/node_modules/.vite/vue.js?v=ed69bae0"; 
const createApp = __vite__cjsImport0_vue["createApp"];
import App from '/src/pages/back-sky/index.vue';

2. build 命令 文件路径:./build.ts;

主要方法:build;

主要功能:使用 rollup 打包编译

3. optimize 命令

文件路径:./optimizer/index.ts;

主要方法:optimizeDeps;

主要功能:主要针对第三方包,Vite 在执行 runOptimize 的时候中会使用 rollup 对三方包重新编译,将编译成符合 esm 模块规范的新的包放入 node_modules 下的 .vite 中,然后配合 resolver 对三方包的导入进行处理:使用编译后的包内容代替原来包的内容,这样就解决了 Vite 中不能使用 cjs 包的问题。

下面是 .vite 文件夹中的 _metadata.json 文件,它在预编译的过程中生成,罗列了所有被预编译完成的文件及其路径。例如:

{
  "hash": "31d458ff",
  "browserHash": "ed69bae0",
  "optimized": {
    "element-plus/lib/utils/dom": {
      "file": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/element-plus_lib_utils_dom.js",
      "src": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/utils/dom.js",
      "needsInterop": true
    },
    "element-plus": {
      "file": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/element-plus.js",
      "src": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/index.esm.js",
      "needsInterop": false
    },
    "vue": {
      "file": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/vue.js",
      "src": "/Users/zcy/Documents/workspace/back-sky-front/node_modules/vue/dist/vue.runtime.esm-bundler.js",
      "needsInterop": true
    },
    ......
    }
  }
}

模块解析

预构建 (https://cn.vitejs.dev/guide/dep-pre-bundling.html?fileGuid=DDr3GGh6QRvQgWQC) 是用来提升页面重载速度,它将 CommonJS、UMD 等转换为 ESM 格式。预构建这一步由 esbuild (http://esbuild.github.io/?fileGuid=DDr3GGh6QRvQgWQC) 执行,这使得 Vite 的冷启动时间比任何基于 JavaScript 的打包程序都要快得多。

为什么 ESbuild 会更快?

  1. 使用 Go 语言
  2. 重度并行,使用 CPU
  3. 高效使用内存
  4. Scratch 编写,减少使用三方库,避免导致性能不可控

重写导入为合法的 URL,例如 /node_modules/.vite/my-dep.js?v=f3sf2ebd 以便浏览器能够正确导入它们

热更新

热更新主体流程如下:

  1. 服务端基于 watcher 监听文件改动,根据类型判断更新方式,并编译资源
  2. 客户端通过 WebSocket 监听到一些更新的消息类型
  3. 客户端收到资源信息,根据消息类型执行热更新逻辑

下面是服务端热更新的核心 hmr.ts 中的部分判断逻辑;

如果配置文件或者环境文件发生修改时,会触发服务重启,才能让配置生效。

if (file === config.configFile || file.endsWith('.env')) {
  // auto restart server 配置&环境文件修改则自动重启服务
  debugHmr(`[config change] ${chalk.dim(shortFile)}`)
  config.logger.info(
    chalk.green('config or .env file changed, restarting server...'),
    { clear: true, timestamp: true }
  )
  await restartServer(server)
  return
}

html 文件更新时,将会触发页面的重新加载。

if (file.endsWith('.html')) { // html 文件更新
  config.logger.info(chalk.green(`page reload `) +         chalk.dim(shortFile), {
    clear: true,
    timestamp: true
  })
  ws.send({
    type: 'full-reload',
    path: config.server.middlewareMode
    ? '*'
    : '/' + normalizePath(path.relative(config.root, file))
  })
} else {
  // loaded but not in the module graph, probably not js
  debugHmr(`[no modules matched] ${chalk.dim(shortFile)}`)
}

Vue 等文件更新时,都会进入 updateModules 方法,正常情况下只会触发 update,实现热更新,热替换;

function updateModules(
  file: string,
  modules: ModuleNode[],
  timestamp: number,
  { config, ws }: ViteDevServer
) {
  const updates: Update[] = []
  const invalidatedModules = new Set<ModuleNode>()
 // 遍历插件数组,关联下面的片段
  for (const mod of modules) {
    const boundaries = new Set<{
      boundary: ModuleNode
      acceptedVia: ModuleNode
    }>()
    // 设置时间戳
    invalidate(mod, timestamp, invalidatedModules)
    // 查找引用模块,判断是否需要重载页面
    const hasDeadEnd = propagateUpdate(mod, timestamp, boundaries)
    // 找不到引用者则会发起刷新
    if (hasDeadEnd) {
      config.logger.info(chalk.green(`page reload `) + chalk.dim(file), {
        clear: true,
        timestamp: true
      })
      ws.send({
        type: 'full-reload'
      })
      return
    }
    updates.push(
      ...[...boundaries].map(({ boundary, acceptedVia }) => ({
        type: `${boundary.type}-update` as Update['type'],
        timestamp,
        path: boundary.url,
        acceptedPath: acceptedVia.url
      }))
    )
  }
  // 日志输出
  config.logger.info(
    updates
      .map(({ path }) => chalk.green(`hmr update `) + chalk.dim(path))
      .join('\n'),
    { clear: true, timestamp: true }
  )
  // 向客户端发送消息,进行热更新操作
  ws.send({
    type: 'update',
    updates
  })
}

上面代码中的 modules 是热更新时需要执行的各个插件

for (const plugin of config.plugins) {
  if (plugin.handleHotUpdate) {
    const filteredModules = await plugin.handleHotUpdate(hmrContext)
    if (filteredModules) {
      hmrContext.modules = filteredModules
    }
  }
}

Vite 会把模块的依赖关系组合成 moduleGraph,它的结构类似树形,热更新中判断哪些文件需要更新也会依赖 moduleGraph;它的文件内容大致如下:

// moduleGraph 返回的 ModuleNode 大致结构
 ModuleNode {
  id: '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.js',
  file: '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.js',
  importers: Set {},
  importedModules: Set {
    ModuleNode {
      id: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/vue.js?v=32cfd30c',
      file: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/.vite/vue.js',
      ......
      lastHMRTimestamp: 0,
      url: '/node_modules/.vite/vue.js?v=32cfd30c',
      type: 'js'
    },
    ModuleNode {
      id: '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.vue',
      file: '/Users/zcy/Documents/workspace/back-sky-front/src/pages/back-sky/index.vue',
      ......
      url: '/src/pages/back-sky/index.vue',
      type: 'js'
    },
    ModuleNode {
      id: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/theme-chalk/index.css',
      file: '/Users/zcy/Documents/workspace/back-sky-front/node_modules/element-plus/lib/theme-chalk/index.css',
      importers: [Set],
      importedModules: Set {},
      acceptedHmrDeps: Set {},
      isSelfAccepting: true,
      transformResult: [Object],
      ssrTransformResult: null,
      ssrModule: null,
      lastHMRTimestamp: 0,
      url: '/node_modules/element-plus/lib/theme-chalk/index.css',
      type: 'js'
    },
    ......
  },
  acceptedHmrDeps: Set {},
  isSelfAccepting: false,
  transformResult: {
    code: 'import __vite__cjsImport0_vue from ' +
      '"/node_modules/.vite/vue.js?v=32cfd30c"; const createApp = ' +
      '__vite__cjsImport0_vue["createApp"];\nimport App from ' +
      "'/src/pages/back-sky/index.vue';\nimport " + ...,
    map: null,
    etag: 'W/"846-Qa424gJKl3YCqHDWXXsM1mFHRqg"'
  },
  ssrTransformResult: null,
  ssrModule: null,
  lastHMRTimestamp: 0,
  url: '/src/pages/back-sky/index.js',
  type: 'js'
}

原有项目切换

最后我们来看下如何使用 Vite 去打包一个旧的 Vue 项目;

首先我们需要升级 Vue3

npm install vue@next

并为项目添加 Vite 配置文件,在根目录下创建 vite.config.js,并为它添加一些基础的配置。

// vite.config.js
// vite2.1.5
const path = require('path');
import vue from '@vitejs/plugin-vue';

export default {
  // 配置选项
  resolve: {
    alias: {
      '@utils': path.resolve(__dirname, './src/utils')
    },
  },
  plugins: [vue()],
};

引用的第三方组件库可能也会需要升级,例如:升 element-ui 至 element-plus

npm install element-plus

Vue3 在 import 时,需使用 createApp 方法进行初始化

import { createApp } from 'vue';
import App from './index.vue';
const app = createApp(App);
import {
  ElInput,
  ElLoading,
} from 'element-plus';

app.use(ElButton);
app.use(ElLoading);
......

到这里就可以将项目运行起来了。注意:Vite 官方不允许省略 .vue 后缀,否则就会报错;

[plugin:vite:import-analysis] Failed to resolve import "./todoList" from "src/pages/back-sky/components/header/index.vue". Does the file exist?
/components/header/index.vue:2:23
1  |  
2  |  import todoList from './todoList';
import todoList from './todoList.vue';

最后我们来对比一下该项目两种构建方式时间的对比;

Webpack 冷启动,耗时 7513ms:

⚠ 「wdm」: Hash: 1ad1dd54289cfad8ecbe
Version: webpack 4.46.0
Time: 7513ms
Built at: 2021-05-24 13:59:35

相同项目 Vite 冷启动,耗时 924ms:

> vite
Pre-bundling dependencies:
  vue
  element-plus
  @zcy/zcy-request
  element-plus/lib/utils/dom
(this will be run only when your dependencies or config have changed)
  vite v2.3.3 dev server running at:
  > Local: http://localhost:3000/
  > Network: use `--host` to expose
  ready in 924ms.

二次启动(预编译的依赖已存在),耗时 407ms;

> vite
  vite v2.3.3 dev server running at:
  > Local: http://localhost:3000/
  > Network: use `--host` to expose
  ready in 407ms.

总结

使用 Vite 进行本地服务启动和热更新都会有明显的提效,至于编译打包环节的差异点有哪些?效果如何?你们还踩过哪些坑?留言告诉我吧。

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/5WlbjoJrVGFhDBeNCZa6Ng

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录