Vite 是什麼

本文基於 vite@0.7.0 編寫,可能與目前代碼不符,若有疑問歡迎郵件或評論溝通

什麼是 vite

vite 是一個基於 Vue3 單文件組件的非打包開發服務器css

這和傳統基於打包(例如 Webpack)的開發服務器有什麼區別

vite 在開發的時候沒有打包的過程,ES 模塊源碼直接傳輸給瀏覽器,瀏覽器使用自帶的 <script module> 進行解析支持,經過 HTTP 請求進行每次 import,開發服務器攔截請求和對須要轉換的代碼進行轉換。html

例如:*.vue 文件會在發回瀏覽器以前進行編譯vue

這樣操做有許多優點:node

  • 開發服務器啓動後不須要進行打包操做,啓動會變得很是迅速
  • 代碼在須要的時候進行編譯,因此只有代碼真正在屏幕上展示的時候才進行編譯。開始開發的時候不再須要等待整個應用編譯完成,這對大型應用是一個巨大的改變
  • 熱模塊替換的性能和模塊的數量之間的關係解耦,熱模塊替換變得很是快

導入本地 ES 模塊可能會引起深層的導入鏈路,整個頁面從新加載會比依賴打包的開發服務器略慢。然而這是一個本地開發服務器,這部分增長的時間和實際編譯的時間相比應該很是小(編譯的文件會被緩存在內存中)webpack

vite 的編譯本質上仍是的 Node.js 中進行,從技術上講它能夠支持打包工具能支持的各類代碼轉換,沒有什麼能夠阻止你將代碼包用於生產,實際上,vite 提供了vite build的腳本用於這個操做,所以不會在生產環境中遭遇到網絡流爆炸的問題git

當前 vite 尚處於實驗性階段,不適合用於生產環境,但但願有一天能作到這個目標github

特性

模塊解析

本地 ES 模塊導入不支持以下的導入方式web

import { createApp } from 'vue'瀏覽器

默認狀況下將會致使一個錯誤,vite 在 js 文件中檢測到這種狀況將會將其改寫爲@modules/{package-name},在這些特殊的路徑下,vite 執行如下的方式找到正確的文件緩存

  • vue 有特殊的處理,你不須要安裝這個模塊,若是須要使用特殊的版本,vite 將會使用node_modules內部的模塊包
  • 若是web_modules目錄存在,將會使用它
  • 若是其它方式都沒有定位到模塊,將會在node_modules中查找

熱模塊替換

  • 對於 *.vue 文件將會獲得開箱即用的替換功能
  • 對於*.js 須要提供相似於 webpack HMR 的 API
import { foo } from "./foo.js";
import { hot } from "@hmr";

foo();

hot.accept("./foo.js", ({ foo }) => {
  // the callback receives the updated './foo.js' module
  foo();
});

CSS 預處理器

安裝模塊便可在 *.vue 中使用

<style lang="scss">
/* use scss */
</style>

生產構建

執行 vite build,當前支持 --root--cdn 兩個參數

API

可使用 API 定製開發服務器,vite 支持插件形式擴展,能夠定製化訪問 vite 內部的 koa 實例和增長相關的中間件

下一步開發計劃

  • Source Map 支持
  • 自動加載 postcss 配置

解析

啓動一個 vite 開發服務器

  • http://localhost:3000/ 首屏頁面
<div id="app"></div>
<script type="module">
  import { createApp } from "/@modules/vue"; // 此模塊中包含相關熱加載邏輯
  import App from "./App.vue"; // 此文件爲SFC主模板

  createApp(App).mount("#app"); // 渲染模版
</script>
  • http://localhost:3000/App.vue 主模板
import { updateStyle } from "/@hmr"; // 加載更新style方法

const __script = {
  data: () => ({ count: 0 })
};

updateStyle("c44b8200-0", "/App.vue?type=style&index=0");
__script.__scopeId = "data-v-c44b8200";
import { render as __render } from "/App.vue?type=template"; // 加載template模板
__script.render = __render;
__script.__hmrId = "/App.vue";
__script.__file = "/Users/shoyuf/work/vite-app/App.vue";
export default __script;
  • /@hmr 更新邏輯
console.log("[vite] connecting...");
const socket = new WebSocket(`ws://${location.host}`);
// Listen for messages
socket.addEventListener("message", ({ data }) => {
  const { type, path, id, index, timestamp } = JSON.parse(data);
  switch (type) {
    case "connected": // 鏈接成功
      console.log(`[vite] connected.`);
      break;
    case "vue-reload": // 當script改變的狀況下,須要從新加載
      import(`${path}?t=${timestamp}`).then(m => {
        __VUE_HMR_RUNTIME__.reload(path, m.default);
        console.log(`[vite] ${path} reloaded.`);
      });
      break;
    case "vue-rerender": // 當template改變的狀況下,須要從新渲染
      import(`${path}?type=template&t=${timestamp}`).then(m => {
        __VUE_HMR_RUNTIME__.rerender(path, m.render);
        console.log(`[vite] ${path} template updated.`);
      });
      break;
    case "vue-style-update": // 當css改變狀況下更新style
      updateStyle(id, `${path}?type=style&index=${index}&t=${timestamp}`);
      console.log(
        `[vite] ${path} style${index > 0 ? `#${index}` : ``} updated.`
      );
      break;
    case "vue-style-remove": // css改變後移除舊的css引用
      const link = document.getElementById(`vite-css-${id}`);
      if (link) {
        document.head.removeChild(link);
      }
      break;
    case "js-update": // js 模塊更新從新加載
      const update = jsUpdateMap.get(path);
      if (update) {
        update(timestamp);
        console.log(`[vite]: js module reloaded: `, path);
      } else {
        console.error(
          `[vite] got js update notification but no client callback was registered. Something is wrong.`
        );
      }
      break;
    case "full-reload": // 導入鏈進入死衚衕,須要進行頁面從新加載
      location.reload();
  }
});
// ping server
socket.addEventListener("close", () => {
  console.log(`[vite] server connection lost. polling for restart...`);
  setInterval(() => {
    new WebSocket(`ws://${location.host}`).addEventListener("open", () => {
      location.reload();
    });
  }, 1000);
});
export function updateStyle(id, url) {
  const linkId = `vite-css-${id}`;
  let link = document.getElementById(linkId);
  if (!link) {
    link = document.createElement("link");
    link.id = linkId;
    link.setAttribute("rel", "stylesheet");
    link.setAttribute("type", "text/css");
    document.head.appendChild(link);
  }
  link.setAttribute("href", url);
}
const jsUpdateMap = new Map();
export const hot = {
  accept(importer, deps, callback) {
    jsUpdateMap.set(importer, timestamp => {
      if (Array.isArray(deps)) {
        Promise.all(deps.map(dep => import(dep + `?t=${timestamp}`))).then(
          callback
        );
      } else {
        import(deps + `?t=${timestamp}`).then(callback);
      }
    });
  }
};
  • /App.vue?type=template 主模板 HTML 部分
import {
  createVNode as _createVNode,
  toDisplayString as _toDisplayString,
  Fragment as _Fragment,
  openBlock as _openBlock,
  createBlock as _createBlock,
  withScopeId as _withScopeId,
  pushScopeId as _pushScopeId,
  popScopeId as _popScopeId
} from "/@modules/vue";
const _withId = _withScopeId("data-v-c44b8200");

_pushScopeId("data-v-c44b8200");
const _hoisted_1 = _createVNode(
  // 建立Virtual DOM
  "h1",
  null,
  "Hello Vite + Vue 3!",
  -1 /* HOISTED */
);
const _hoisted_2 = _createVNode(
  "p",
  null,
  "Edit ./App.vue to test hot module replacement (HMR).",
  -1 /* HOISTED */
);
_popScopeId();

export const render = _withId(function render(_ctx, _cache) {
  // 渲染函數
  return (
    _openBlock(),
    _createBlock(
      _Fragment,
      null,
      [
        _hoisted_1,
        _hoisted_2,
        _createVNode("p", null, [
          _createVNode(
            "span",
            null,
            "Count is: " + _toDisplayString(_ctx.count),
            1 /* TEXT */
          ),
          _createVNode(
            "button",
            {
              onClick: _cache[1] || (_cache[1] = $event => _ctx.count++)
            },
            "increment"
          )
        ])
      ],
      64 /* STABLE_FRAGMENT */
    )
  );
});
  • /App.vue?type=style&index=0 主模板 css 部分,包括 scopedId
h1[data-v-c44b8200] {
  color: #4fc08d;
}
h1[data-v-c44b8200],
p[data-v-c44b8200] {
  font-family: Arial, Helvetica, sans-serif;
}
  • ws://localost:3000/ 執行熱替換的數據交互,與/@hmr相聯

Example:

{
  path: "/App.vue",
  timestamp: 1588242356511,
  type: "vue-reload"
}

type 與@hmr的相關方法一致

  • vue-reload
  • vue-rerender
  • vue-style-update
  • vue-style-remove
  • js-update
  • full-reload

webpack 很慢

vite

參考

  1. vuejs/vite: Experimental no-bundle dev server for Vue SFCs
相關文章
相關標籤/搜索