Vite 如今正在瘋狂的更新,目前在 Beta
中,可能很快就會發布 1.0。javascript
Vite
,一個基於瀏覽器原生 ES imports
的開發服務器。利用瀏覽器去解析 imports
,在服務器端按需編譯返回,徹底跳過了打包這個概念,服務器隨起隨用。同時不只有 Vue
文件支持,還搞定了熱更新,並且熱更新的速度不會隨着模塊增多而變慢。針對生產環境則能夠把同一份代碼用 rollup
打包。雖然如今還比較粗糙,但這個方向我以爲是有潛力的,作得好能夠完全解決改一行代碼等半天熱更新的問題。css
天生的懶加載呀!html
首先,你須要把 type="module"
放到 <script>
標籤中, 來聲明這個腳本是一個模塊:vue
<script type="module" src="main.js"></script>
複製代碼
當 script.type
爲 module
時,經過 src
及 import
導入的文件會發送 http
請求。java
Vite
會攔截這些請求,並對請求的文件進行特殊的處理。node
import Vue from 'vue'
複製代碼
當經過 import
試圖導入 node_modules
內的文件時,Vite
會對路徑進行替換,由於在瀏覽器中只有 相對路徑 和 絕對路徑。webpack
import Vue from '/@modules/vue'
複製代碼
// server.js
const Koa = require('koa'); const fs = require('fs'); const path = require('path'); const app = new Koa(); app.use(async (ctx) => { const { request: { url, query } } = ctx; if (url == '/') { // 返回靜態資源 ctx.type = 'text/html'; ctx.body = fs.readFileSync('./index.html', 'utf-8'); } if (url.endsWith('.js')) { // 處理 js 文件 const p = path.resolve(__dirname, url.slice(1)); const res = fs.readFileSync(p, 'utf-8'); ctx.type = 'application/javascript'; // 返回替換路徑後的文件 ctx.body = rewriteImports(res); } }); function rewriteImports(content) { return content.replace(/from ['|"]([^'"]+)['|"]/g, function ($0, $1) { // 要訪問 node_modules 裏的文件 if ($1[0] !== '.' && $1[1] !== '/') { return `from '/@modules/${$1}'`; } else { return $0; } }); } app.listen(3000, function () { console.log('success listen 3000'); }); 複製代碼
接下來就是要把 /@modules
開頭的路徑解析爲真正的文件地址,而且返回給瀏覽器。以前是 webpack
幫咱們作了這件事。git
經過 import
導入的文件 webpack
會去 package.json
文件內找 moduel
屬性。github
{
"license": "MIT", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", "name": "vue", "repository": { "type": "git", "url": "git+https://github.com/vuejs/vue-next.git" }, "types": "dist/vue.d.ts", "unpkg": "dist/vue.global.js", "version": "3.0.0-beta.15" } 複製代碼
咱們只須要把這個 dist/vue.runtime.esm-bundler.js
地址的文件返回就好。web
if (url.startsWith('/@modules/')) {
// 找到 node_modules 內的文件夾 const prefix = path.resolve( __dirname, 'node_modules', url.replace('/@modules/', '') ); // 獲取 package.json 內的 module 屬性 const module = require(prefix + '/package.json').module; const p = path.resolve(prefix, module); // 讀取文件 const res = fs.readFileSync(p, 'utf-8'); ctx.type = 'application/javascript'; // 讀取的文件內還經過 import 導入了其餘的依賴,繼續把路徑替換爲 /@modules/ ctx.body = rewriteImports(res); } 複製代碼
你們都知道 vue
文件包含了三個部分,分別是 template
script
style
。
Vite
對這幾個部分分別進行了處理。
接下來咱們就要實現對這幾個部分的處理。
@vue/compiler-sfc
是用來解析單文件組件的,就像是 vue-loader
作的事情。
它解析的結果就像是下面這樣。
const compilerSfc = require('@vue/compiler-sfc');
if (url.includes('.vue')) { const p = path.resolve(__dirname, url.slice(1)); const { descriptor } = compilerSfc.parse(fs.readFileSync(p, 'utf-8')); if (!query.type) { ctx.type = 'application/javascript'; ctx.body = ` // 拿到 script 的內容 const __script = ${descriptor.script.content.replace('export default ', '')} // 若是有 style 就發送請求獲取 style 的部分 ${descriptor.styles.length ? `import "${url}?type=style"` : ''} // 發送請求獲取 template 的部分 import { render as __render } from "${url}?type=template" // 渲染 template 的內容 __script.render = __render; export default __script; `; } } 複製代碼
@vue/compiler-dom
是用來編譯 template
的。
由於返回給瀏覽器的 vue
是 runtime
版本,是沒有 編譯器 的,全部要在服務端編譯後返回給瀏覽器。
const compilerDom = require('@vue/compiler-dom');
... if (query.type === 'template') { const template = descriptor.template; // 在服務端編譯 template 而且返回 const render = compilerDom.compile(template.content, { mode: 'module', }).code; ctx.type = 'application/javascript'; ctx.body = render; } 複製代碼
對 style
的處理有一丟丟特殊,能夠看到返回的內容中調用了 updateStyle
方法,在 Vite
中是把它放在了 熱更新 的模塊中,在這裏咱們尚未實現熱更新,因此先 hash
下,在 client
實現該功能。
// server.js
if (query.type === 'style') { const styleBlock = descriptor.styles[0]; ctx.type = 'application/javascript'; ctx.body = ` const css = ${JSON.stringify(styleBlock.content)}; updateStyle(css); export default css; `; } 複製代碼
方法1使用了 可構造樣式表 在這裏放兩個資料供你們參考。
方法2就不 balabala 了。
<body>
<div id="app"></div> <script> // hash: 規避 shared 文件內的環境判斷 window.process = { env: { NODE_ENV: 'dev', } }; function updateStyle(content) { // 方法1 let style = new CSSStyleSheet(); style.replaceSync(content); document.adoptedStyleSheets = [ ...document.adoptedStyleSheets, style, ]; // 方法2 let style = document.createElement('style') style.setAttribute('type', 'text/css') style.innerHTML = content document.head.appendChild(style) } </script> <script type="module" src="./main.js"></script> </body> 複製代碼
終於等到你~ 還好我沒放棄~
」
本文使用 mdnice 排版