vite原理


vue3構建工具vite原理 之 手寫vite

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做了哪些事?

1. 重寫引入模塊路徑前面加上/@modules/, 重寫后瀏覽器會再次發送請求

原main.js文件:

通過vite構建后請求的main.js文件: 

2. 攔截含有/@modules/的請求, 去node_modules引入對應的模塊並返回

3. 解析.vue文件

如app.vue文件如下:

<template> <HelloWorld msg="Hello Vue 3.0 + Vite" /> </template> <script> import HelloWorld from './components/HelloWorld.vue' export default { name: 'App', components: { HelloWorld } } </script> 

被解析成render函數返回給瀏覽器渲染頁面:
請求:http://localhost:3000/src/App.vue
vue文件時,koa中間件檢測到請求的是vue模板文件,則會在請求后面添加一個type=template參數
如: http://localhost:3000/src/App.vue?type=template
koa通過這個參數來判斷是請求vue模板文件,並編譯成js文件返回給瀏覽器 

4. 靜態服務插件 實現可以返回靜態文件的功能

app.use(static(root)) app.use(static(path.resolve(root, 'public'))) 

手寫vite代碼 實現以上4種功能:

新建一個vite項目:

npm instal -g create-vite-app    //全局安裝最新vite構建工具 (默認最新)
create-vite-app my-vite-vue3     //創建一個名為myvitevue3的項目

cd my-vite-vue3  //進入項目
yarn install     //安裝項目依賴
yarn dev         //啟動項目

下面我們在根目錄新建vite/index.js文件
通過運行node vite/index.js代替yarn dev啟動項目
使用自實現的vite來模擬vite的這4個功能
如圖所述則是使用自寫vite渲染的頁面:

//vite/index.js const fs = require('fs').promises const Koa = require('koa') const path = require('path') const chalk = require('chalk') const static = require('koa-static') const { parse } = require('es-module-lexer') const MagicString = require('magic-string') const { Readable } = require('stream') //讀取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; } } //koa中間件 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(); } 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; } }) }, // 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; }) }, // 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 } createServer().listen(4000, () => { console.log(' Dev server running at:') console.log(` > Local: ${chalk.cyan('http://localhost:4000/')}`) }) 

圖片和css文件我們還沒有處理,所以除去app.vue引入的圖片與main.js內引入的css即可實現對應的功能

 


文章就分享到這,歡迎關注“前端大神之路


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM