vite 学习笔记

参考文章

背景

Vite是一个构建工具,旨在为现代web项目提供更快、更精简的开发体验。

vite主要分两个模块:

  • 通过native ES module实现的本地开发服务,可以提供极快的热更新服务。
  • 通过rollup对生产环境打包,可以极大地优化打包生产环境的代码。

vite 和 webpack 开发环境最大的区别就是vite 在开发环境抛弃了打包这一个理念,直接在开发环境使用Javascript module,减少打包带来的时间损耗,极大地方便了本地开发。

对于vite的学习,我主要总结了以下四个模块进行总结。

  • 模块路径解析
  • 不同格式文件处理
  • 热更新
  • 预打包

模块路径解析

对于一个native es module服务系统而言,不同模块的路径解析非常重要,这里面有以下几个问题:

  • node_modules等特殊路径内容如何处理
  • 相对路径不便于记录唯一文件路径

vite的解决方式:

  • 将裸模块(node_modules)做转换 "vue.js" --> "/@modules/vue.js"
  • 将相对路径转为绝对路径,便于vite统一文件路径识别。 import '../../a.js' --> '/src/a.js'

不同格式文件处理

通过不同格式的处理,我们可以理解类似于webpack loader对于不同文件是如何处理的, 了解vite工作机制。

  • vue
  • css
  • json
  • html

对于不同格式的文件,vite统一都处理成javascript格式,在返回的response 中添加
Content-Type: application/javascript; charset=utf-8

vue:

vue 的组件是一个单文件组件的机制。一个vue组件的定义基本分三个部分:

<template></template>
<script></script>
<style></style>

编译器会将一个vue组件的三部分分别处理。在vite中,请求一个组件的资源:

截屏2021-03-18 下午7.53.28.png

Helloworld.vue script 逻辑部分编译:

// 此文件可以理解为一个组件的script逻辑部分
import string from '/src/string.js'
const __script = {
    name: 'HelloWorld',
    props: {
        msg: String
    },
    data() {
        return {
                age: 123
        }
    }
}
// 这里引入组件的template部分
import "/src/components/HelloWorld.vue?type=style&index=0"
// 这里引入组件的style部分
import {render as __render} from "/src/components/HelloWorld.vue?type=template"
__script.render = __render
__script.__hmrId = "/src/components/HelloWorld.vue"
__script.__file = "/Users/lizhuang/gitcode/vite-test/src/components/HelloWorld.vue"
export default __script

HelloWorld.vue?type=style 样式部分编译:

import { updateStyle } from "/vite/client"
const css = "\nh1 {\n  background: red;\n}\n"
updateStyle("62a9ebed-0", css)
export default css

HelloWorld.vue?type=template 结构部分编译:

import {
  toDisplayString as _toDisplayString,
  createVNode as _createVNode,
  createTextVNode as _createTextVNode,
  Fragment as _Fragment,
  openBlock as _openBlock,
  createBlock as _createBlock
} from "/@modules/vue.js"

const _hoisted_1 = /*#__PURE__*/
_createVNode("p", null, "string1", -1 /* HOISTED */
)
const _hoisted_2 = /*#__PURE__*/
_createVNode("p", null, [/*#__PURE__*/
_createTextVNode("Edit "), /*#__PURE__*/
_createVNode("code", null, "components/HelloWorld.vue"), /*#__PURE__*/
_createTextVNode(" to test hot module replacement.")], -1 /* HOISTED */
)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
    return (_openBlock(),
    _createBlock(_Fragment, null, [_createVNode("h1", null, _toDisplayString($props.msg), 1 /* TEXT */
    ), _createVNode("button", {
        onClick: _cache[1] || (_cache[1] = $event=>($data.count++))
    }, "count is: " + _toDisplayString($data.count), 1 /* TEXT */
    ), _hoisted_1, _createVNode("p", null, _toDisplayString($data.string), 1 /* TEXT */
    ), _hoisted_2], 64 /* STABLE_FRAGMENT */
    ))
}

JSON 文件格式处理

通过 rollup-pluginutils 的dataToEsm方法

{
  custom: 'data',
  to: ['treeshake']
}

转变为:

export const custom = 'data';
export const to = ['treeshake'];
export default { custom, to };

CSS 文件格式处理

其实在vue的样式部分已经有所涉及。

import { updateStyle } from "/vite/client"
const css = "\nh1 {\n  background: red;\n}\n"
updateStyle("62a9ebed-0", css)
export default css

其中 updateStyle 是更新样式的关键函数,我们进行分析:


/**
* content: css文件内容
*/
function updateStyle(id, content) {
  ...
        if (!style) {
      style = new CSSStyleSheet()
      style.replaceSync(content)
      document.adoptedStyleSheets = [...document.adoptedStyleSheets, style]
    } else {
      style.replaceSync(content)
    }
  ...
}    
        

vite利用** CSSStyleSheet **代表一个样式表,利用javascript的接口编辑或者添加相关的样式。

当然还有一个特殊的情况就是css文件中有@import 等操作, 这种特殊的情况,vite直接使用style标签进行样式插入。

cosnt style = document.createElement('style')
style.setAttribute('type', 'text/css')
style.innerHTML = content
document.head.appendChild(style)

热更新

vite1 代码较少,这可以让我们低成本的学习一个开发环境热更新的具体细节

image.png

其中第四部处理不同文件的方式,列在了下方:

async function handleMessage(payload: HMRPayload) {
  const { path, changeSrcPath, timestamp } = payload as UpdatePayload
  switch (payload.type) {
    case 'connected':
      console.log(`[vite] connected.`)
      break
    case 'vue-reload':
      queueUpdate(
        import(`${path}?t=${timestamp}`)
          .catch((err) => warnFailedFetch(err, path))
          .then((m) => () => {
            __VUE_HMR_RUNTIME__.reload(path, m.default)
            console.log(`[vite] ${path} reloaded.`)
          })
      )
      break
    case 'vue-rerender':
      const templatePath = `${path}?type=template`
      import(`${templatePath}&t=${timestamp}`).then((m) => {
        __VUE_HMR_RUNTIME__.rerender(path, m.render)
        console.log(`[vite] ${path} template updated.`)
      })
      break
    case 'style-update':
      // check if this is referenced in html via <link>
      const el = document.querySelector(`link[href*='${path}']`)
      if (el) {
        el.setAttribute(
          'href',
          `${path}${path.includes('?') ? '&' : '?'}t=${timestamp}`
        )
        break
      }
      // imported CSS
      const importQuery = path.includes('?') ? '&import' : '?import'
      await import(`${path}${importQuery}&t=${timestamp}`)
      break
      .... 还有很多,就不一一列举了
  }
}

预打包

vite的预打包优化手段其实和小程序页面预加载技术,以及网页的prefetch,preload等的原理是基本一致的,当我们尽量少的打包过后,那么预打包那些没有处理的文件就是优化的手段之一。

vite 会去分析package.json 当中的依赖项,会将依赖进行打包并缓存:

截屏2021-03-21 下午3.40.26.png

其中lodash较为特殊,因为其文件众多,如果不进行预打包的话,开发项目将会请求很多相关文件,造成网页reload时性能衰减。所以vite预打包的另外一个重要的功能就是通过rollup或者esbuild(vite 不同版本实现不同),将过于零散的文件打包,减少网络请求,提高页面reload性能

截屏2021-03-21 下午3.42.57.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容