寫在開頭
由于
vite這個構建工具被用在了vue3上門,而且它的構建思路我覺得優于webpack,底層也是使用了esbuild,性能上更優那么為了照顧一些小伙伴之前沒有學習過
vite的,我們先來看看什么是vite
什么是vite
Vite,一個基于瀏覽器原生 ES imports 的開發服務器,利用瀏覽器去決議 imports,在服務器端按需編譯回傳,完全跳過了打包這個概念,服務器隨起隨用,支持熱更新,而且熱更新的速度不會隨著模塊增多而變慢,針對生產環境則可以把同一份代碼用 rollup 打包
vite的天然優勢:快速冷啟動服務器
即時熱模塊更換(HMR)
真正的按需編譯
vite作業原理
當宣告一個 script 標簽型別為 module 時
如: <script type="module" src="/src/main.js"></script>
瀏覽器就會像服務器發起一個GET
http://localhost:3000/src/main.js請求main.js檔案:
// /src/main.js:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
瀏覽器請求到了main.js檔案,檢測到內部含有import引入的包,又會對其內部的 import 參考發起 HTTP 請求獲取模塊的內容檔案
如:
GET http://localhost:3000/@modules/vue.js如:
GET http://localhost:3000/src/App.vue其Vite 的主要功能就是通過劫持瀏覽器的這些請求,并在后端進行相應的處理將專案中使用的檔案通過簡單的分解與整合,然后再回傳給瀏覽器渲染頁面,vite整個程序中沒有對檔案進行打包編譯,所以其運行速度比原始的webpack開發編譯速度快出許多
簡單實作vite
由于代碼量有一些大,我就不自己去寫了,直接拿了別人的代碼過來,原文地址是:
https://juejin.cn/post/6898116372887240712
首先是koa啟動監聽埠,用于訪問熱更新服務
function createServer() {
let app = new Koa()
const context = { // 直接創建一個背景關系 來給不同的插件共享功能
app,
root: process.cwd() //執行node命令的那個命令路徑
}
// 運行koa中間件(就是我們的vite插件)
resolvePlugin.forEach(plugin => plugin(context))
return app
}
createServer().listen(4000, () => {
})
撰寫對應插件處理
首先處理模塊的參考,因為瀏覽器只有相對路徑和絕對路徑
這里
readBody其實就是一個讀取檔案流的方法,封裝過而已,看成普通的讀取流方法即可
koa中間件處理
首先處理重寫路徑,因為瀏覽器只有絕對路徑和相對路徑
app.use(async (ctx, next) => {
await next(); // 靜態服務
// 默認會先執行 靜態服務中間件 會將結果放到 ctx.body
// 需要將流轉換成字串 , 只需要處理js中的參考問題
if (ctx.body && ctx.response.is('js')) {
let r = await readBody(ctx.body); // vue => /@modules
const result = rewriteImports(r);
ctx.body = result;
}
})
},
重寫完了路徑后,需要攔截
.vue檔案和帶@module(重寫路徑之前就是node_modules里面的檔案)
// 2. 攔截含有/@modules/vue的請求, 去node_modules引入對應的模塊并回傳
({ app, root }) => {
const reg = /^\/@modules\//
app.use(async (ctx, next) => {
// 如果沒有匹配到 /@modules/vue 就往下執行即可
if (!reg.test(ctx.path)) {
return next();
}
const id = ctx.path.replace(reg, '');
let mapping = {
vue: path.resolve(root, 'node_modules', '@vue/runtime-dom/dist/runtime-dom.esm-browser.js'),
}
const content = await fs.readFile(mapping[id], 'utf8');
ctx.type = 'js'; // 回傳的檔案是js
ctx.body = content;
})
},
當決議處理完路徑后,我們需要決議vue的模板檔案,(如果是react的jsx代碼,同理)
// 3. 決議.vue檔案
({ app, root }) => {
app.use(async (ctx, next) => {
if (!ctx.path.endsWith('.vue')) {
return next();
}
const filePath = path.join(root, ctx.path);
const content = await fs.readFile(filePath, 'utf8');
// 引入.vue檔案決議模板
const { compileTemplate, parse } = require(path.resolve(root, 'node_modules', '@vue/compiler-sfc/dist/compiler-sfc.cjs'))
let { descriptor } = parse(content);
if (!ctx.query.type) {
//App.vue
let code = ''
if (descriptor.script) {
let content = descriptor.script.content;
code += content.replace(/((?:^|\n|;)\s*)export default/, '$1const __script=');
}
if (descriptor.template) {
const requestPath = ctx.path + `?type=template`;
code += `\nimport { render as __render } from "${requestPath}"`;
code += `\n__script.render = __render`
}
code += `\nexport default __script`
ctx.type = 'js';
ctx.body = code
}
if (ctx.query.type == 'template') {
ctx.type = 'js';
let content = descriptor.template.content
const { code } = compileTemplate({ source: content }); // 將app.vue中的模板 轉換成render函式
ctx.body = code;
}
})
},
// 4. 靜態服務插件 實作可以回傳檔案的功能
({ app, root }) => {
app.use(static(root))
app.use(static(path.resolve(root, 'public')))
}
]
function createServer() {
let app = new Koa()
const context = { // 直接創建一個背景關系 來給不同的插件共享功能
app,
root: process.cwd() // C:\Users\...\my-vite-vue3
}
// 運行中間件
resolvePlugin.forEach(plugin => plugin(context))
return app
}
下面是兩個工具函式:一個是流的讀取,一個是重寫路徑的函式
//讀取body方法
async function readBody(stream) {
if (stream instanceof Readable) {
return new Promise((resolve) => {
let res = ''
stream.on('data', function (chunk) {
res += chunk
});
stream.on('end', function () {
resolve(res)
})
})
} else {
return stream;
}
}
重寫路徑中間件
const resolvePlugin = [
// 1. 重寫引入模塊路徑前面加上/@modules/vue, 重寫后瀏覽器會再次發送請求
({ app, root }) => {
function rewriteImports(source) {
let imports = parse(source)[0];
let ms = new MagicString(source);
if (imports.length > 0) {
for (let i = 0; i < imports.length; i++) {
let { s, e } = imports[i];
let id = source.slice(s, e); // 應用的標識 vue ./App.vue
// 不是./ 或者 /
if (/^[^\/\.]/.test(id)) {
id = `/@modules/${id}`;
ms.overwrite(s, e, id)
}
}
}
return ms.toString();
}
這樣一個簡單的vite就完成了
開始在react中使用
vite算是一個新的技術,而且在國內目前不夠流行,為了避免踩坑,我們直接采用官方推薦的模板生成
npm init vite-app --template react
生成模板完成后,執行命令啟動專案
yarn
yarn dev
這樣一個
react的專案就搭建好了,默認使用的是17.0.0版本的react,這樣createElement方法再也不用從react里面匯出了,我想這樣jsx風格代碼也會更容易被遷移到其他框架專案中
"dependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"devDependencies": {
"vite": "^1.0.0-rc.13",
"vite-plugin-react": "^4.0.0"
}
這個模板生成的是自帶熱更新的,相對比較簡單,如果是有特殊需求,可以使用更多的plugin,在vite.config.js中設定
默認的配置
// @ts-check
import reactPlugin from 'vite-plugin-react'
/**
* @type { import('vite').UserConfig }
*/
const config = {
jsx: 'react',
plugins: [reactPlugin]
}
export default config
寫在最后
本文更多是在講
vite的實作原理,目前我還沒有把它使用在生產環境中在我看來,
vite如果生態能發展起來,可能我們就用不到wepback6這個版本了(當然未來不可猜測)通過閱讀本文,你肯定能清楚了解vite的原理和react構建使用了,感覺不錯的話,幫我點個
贊/在看,關注一下【前端巔峰】公眾號吧
參考資料:
https://juejin.cn/post/6898116372887240712
https://juejin.cn/post/6844904146915573773
https://github.com/vitejs/vite
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/242408.html
標籤:其他
