vite
什么是 vite
vite 是一个基于 Vue3 单文件组件的非打包开发服务器
这和传统基于打包(例如 Webpack)的开发服务器有什么区别
vite 在开发的时候没有打包的过程,ES 模块源码直接传输给浏览器,浏览器使用自带的 <script module> 进行解析支持,通过 HTTP 请求进行每次 import,开发服务器拦截请求和对需要转换的代码进行转换。
例如:*.vue 文件会在发回浏览器之前进行编译
这样操作有许多优势:
开发服务器启动后不需要进行打包操作,启动会变得非常迅速
代码在需要的时候进行编译,所以只有代码真正在屏幕上展现的时候才进行编译。开始开发的时候再也不需要等待整个应用编译完成,这对大型应用是一个巨大的改变
热模块替换的性能和模块的数量之间的关系解耦,热模块替换变得非常快
导入本地 ES 模块可能会引发深层的导入链路,整个页面重新加载会比依赖打包的开发服务器略慢。然而这是一个本地开发服务器,这部分增加的时间和实际编译的时间相比应该非常小(编译的文件会被缓存在内存中)
vite 的编译本质上还是的 Node.js 中进行,从技术上讲它可以支持打包工具能支持的各种代码转换,没有什么可以阻止你将代码包用于生产,实际上,vite 提供了vite build的脚本用于这个操作,因此不会在生产环境中遭遇到网络流爆炸的问题
当前 vite 尚处于实验性阶段,不适合用于生产环境,但希望有一天能做到这个目标
特性
模块解析
本地 ES 模块导入不支持如下的导入方式
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 很慢
