本文基于 vite@0.7.0 编写,可能与目前代码不符,若有疑问欢迎邮件或评论沟通
vite 是一个基于 Vue3 单文件组件的非打包开发服务器css
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(); });
安装模块便可在 *.vue
中使用
<style lang="scss"> /* use scss */ </style>
执行 vite build
,当前支持 --root
和 --cdn
两个参数
可使用 API 定制开发服务器,vite 支持插件形式扩展,能够定制化访问 vite 内部的 koa 实例和增长相关的中间件
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 部分,包括 scopedIdh1[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
的相关方法一致