不到100行!站在潮流前沿,快速實(shí)現(xiàn)一個(gè)簡(jiǎn)易版 vite
張宇航,微醫(yī)前端技術(shù)部醫(yī)保支撐組,一個(gè)不文藝的處女座程序員。
目錄
| 標(biāo)題 | 模塊 | 內(nèi)容 |
|---|---|---|
| 90 行代碼的webpack,你確定不學(xué)嗎? | webpack | 簡(jiǎn)易實(shí)現(xiàn) |
寫在最前面
本文最終實(shí)現(xiàn)的簡(jiǎn)易版 vite 可通過(guò)github 地址(https://github.com/levelyu/simple-vite)下載,代碼實(shí)現(xiàn)地較為簡(jiǎn)單(不到 100 行)可運(yùn)行后再看此文,閱讀效果可能更佳~
要解決的問(wèn)題
首先我們參照官方文檔啟動(dòng)一個(gè) vue 模板的 vite-demo 項(xiàng)目
yarn create @vitejs/app vite-demo --template vue
cd vite-demo
yarn
yarn dev
然后打開(kāi)瀏覽器查看網(wǎng)絡(luò)請(qǐng)求,我們不難發(fā)現(xiàn) vite 正如官方文檔所述利用瀏覽器支持原生 ES 模塊的特性,讓瀏覽器解析了 import 語(yǔ)句并發(fā)出網(wǎng)絡(luò)請(qǐng)求避免了本地編譯打包的過(guò)程,因此啟動(dòng)速度非常之快。

常言道“先知其然,然后知其所以然”,在打開(kāi)了 vite 模板工程的源文件再對(duì)照上述的網(wǎng)絡(luò)請(qǐng)求后,有的同學(xué)可能有以下幾個(gè)疑問(wèn):

1:main.js 返回的內(nèi)容 其中 impor 語(yǔ)句為什么被改寫成了 import { createApp } from '/node_modules/.vite/vue.js?
2:查看本地文件也會(huì)發(fā)現(xiàn) node_modules 文件夾下為什么會(huì)多出了一個(gè).vit 文件夾?

3:.vue 文件的請(qǐng)求是怎么處理并返回能正常運(yùn)行的 js 呢?
4: 為什么會(huì)多出兩個(gè) js 文件請(qǐng)求 /@vite/client 和 /node_modules/vite/dist/client/env.js 以及一個(gè) websocket 連接?
對(duì)于問(wèn)題 4,實(shí)際上是 vite devServer 的熱更新相關(guān)的功能,不在本文的研究重點(diǎn),因此本文的目的就是帶著問(wèn)題 1,2,3,參照源碼實(shí)現(xiàn)一個(gè)沒(méi)有熱更新沒(méi)有打包功能的極簡(jiǎn)易的 vite。(注:本文參考的 vite 源碼版本號(hào)為 2.3.0)
準(zhǔn)備工作
工欲善其事,必先利其器。既然是從源碼分析問(wèn)題,那就先準(zhǔn)備好調(diào)試工作。參照官方文檔:
首先克隆 vite 倉(cāng)庫(kù)并創(chuàng)建一個(gè)軟鏈接
git clone [email protected]:vitejs/vite.git
cd vite && yarn
cd packages/vite && yarn build && yarn link
yarn dev
進(jìn)入之前初始化好的 vite-demo 項(xiàng)目并鏈接到本地 vite 倉(cāng)庫(kù)地址
cd vite-demo
yarn link vite
從 vite bin 目錄下 vite.js 文件不難發(fā)現(xiàn) vite 命令對(duì)應(yīng)的入口文件在 node_modules/vite/dist/node/cli.js

因此我們可以在 vite-demo 的 package.json 文件中加入以下腳本命令:
"debug": "node --inspect-brk node_modules/vite/dist/node/cli.js"
并運(yùn)行命令 yarn debug 后打開(kāi)瀏覽器控制臺(tái)即可看到 node 的圖標(biāo),點(diǎn)擊后,我們就可以開(kāi)始進(jìn)行源碼調(diào)試的工作了:

源碼分析
注意:為方便理解,本文對(duì)應(yīng)的源碼均為截取后的偽代碼
server 的創(chuàng)建過(guò)程
// src/node/cli.ts
cli
.command('[root]')
.alias('serve')
.action(async () => {
const { createServer } = await import('./server')
const server = await createServer({
// ...
})
await server.listen()
})
不難看出上述入口文件代碼是從 src/node/server/index.ts 引入了一個(gè) createServer 方法并調(diào)用返回了一個(gè) server 對(duì)象,緊接著調(diào)用了 server 的 listen 方法。ok,那就讓我們看看這個(gè) createServer 方法內(nèi)部做了哪些事情:
// src/node/server/index.ts
// ....
import connect from 'connect';
import { transformMiddleware } from './middlewares/transform'
//...
export async function createServer() {
// ...
const middlewares = connect();
const httpServer = await resolveHttpServer({}, middlewares)
// 實(shí)際 server 的配置會(huì)讀取 vite.config.js 以及各種插件中的配置 本文力求通俗簡(jiǎn)易就不再詳細(xì)分析贅述...
const server = {
httpServer,
listen() {
return startServer(server);
},
};
// ...
middlewares.use(transformMiddleware(server))
// ...
await runOptimize();
return server;
}
async function startServer(server) {
// ...
const httpServer = server.httpServer;
// ...
const port = 3000;
const hostname = '127.0.0.1';
return new Promise((resolve, reject) =>{
httpServer.listen(port, hostname, () => {
resolve(server);
});
});
};
// src/node/server/http.ts
export async function resolveHttpServer(serveroptions, app) {
return require('http').createServer(app)
}
通過(guò)上述偽代碼可以發(fā)現(xiàn),vite2 最終是調(diào)用了 http.ts 中的 resolveHttpServer 方法,通過(guò) node 原生的 http 模塊創(chuàng)建的 server。同時(shí)在 createServer 方法內(nèi)部,使用了 connect 框架作為中間件。
依賴預(yù)構(gòu)建
細(xì)心的同學(xué)不難發(fā)現(xiàn)在上述 createServer 方法的偽代碼中有個(gè) runOptimize 方法,下面讓我們看看這個(gè)函數(shù)里具體做了哪些事情:
// src/node/server/index.ts
const runOptimize = async () => {
if (config.cacheDir) {
server._isRunningOptimizer = true
try {
server._optimizeDepsMetadata = await optimizeDeps(config)
} finally {
server._isRunningOptimizer = false
}
server._registerMissingImport = createMissingImporterRegisterFn(server)
}
}
實(shí)際該方法最重要的是調(diào)用了依賴預(yù)構(gòu)建的核心方法:optimizeDeps, 其定義在 src/node/optimizer/index.ts 中,并且在 server 啟動(dòng)前就已調(diào)用。
那么何為依賴預(yù)構(gòu)建呢,vite 不是 No Bundle 嗎?對(duì)此,官方文檔做出了詳細(xì)解釋:點(diǎn)此查看原因,簡(jiǎn)而言之其目的有二:
兼容 CommonJS 和 AMD 模塊的依賴 減少模塊間依賴引用導(dǎo)致過(guò)多的請(qǐng)求次數(shù)
再結(jié)合以下偽代碼分析:
// src/node/optimizer/index.ts
import { build } from 'esbuild';
import { scanImports } from './scan';
export async function optimizeDeps() {
// cacheDir 的定義在 src/node/config.ts
const cacheDir = `node_modules/.vite`;
// optimizeDeps 函數(shù)依賴預(yù)構(gòu)建的重要函數(shù)
const dataPath = path.join(cacheDir, '_metadata.json');
if (fs.existsSync(cacheDir)) {
emptyDir(cacheDir)
} else {
// 創(chuàng)建 cacheDir 目錄
fs.mkdirSync(cacheDir, { recursive: true })
}
;({ deps, missing } = await scanImports(config))
// eg: deps = {vue: "C:/code/sourcecode/vite-demo/node_modules/vue/dist/vue.runtime.esm-bundler.js"}
const result = await build({
entryPoints: Object.keys(flatIdDeps),
outdir: cacheDir,
})
writeFile(dataPath, JSON.stringify(data, null, 2))
}
至此,我們基本可以得到開(kāi)篇問(wèn)題 2(為什么 node_modules 下多出了一個(gè).vite 文件夾)的答案了。那么,可能又有同學(xué)有以下兩個(gè)疑問(wèn):
1.vite 是如何分析找到哪些模塊是需要預(yù)構(gòu)建的呢?
2.vite 是如何完成預(yù)構(gòu)建的同時(shí)保證構(gòu)建速度的呢?
帶著這兩個(gè)問(wèn)題,繼續(xù)一路 debug 下去,不難發(fā)現(xiàn)答案就是esbuild,關(guān)于 esbuild 是什么這里就不再贅述了,這里就貼一張官方文檔的對(duì)比圖感受下,總之就是一個(gè)字:快?。。?/p>
繼續(xù)回到剛才 src/node/optimizer/index.ts 中的偽代碼,實(shí)際上 scanImports 函數(shù)其實(shí)就是完成對(duì) import 語(yǔ)句的掃描,并返回了需要構(gòu)建的依賴 deps, 下圖則說(shuō)明了這個(gè) deps 其實(shí)就是 main.js 中唯一的依賴 vue 對(duì)應(yīng)的路徑:

那么這個(gè) scanImports 是如何找到我們的唯一依賴 vue 呢:進(jìn)入 scanImports 函數(shù)有以下偽代碼:
// src/node/optimizer/scan.ts
import { Loader, Plugin, build, transform } from 'esbuild'
export async function scanImports() {
cosnt entry = await globEntries('**/*.html', config)
const plugin = esbuildScanPlugin()
build({
write: false,
entryPoints: [entry],
bundle: true,
format: 'esm',
// ...
})
}
function esbuildScanPlugin() {
return {
name: 'vite:dep-scan',
setup(build) {
build.onLoad(
{ filter: htmlTypesRE, namespace: 'html' },
// 讀取 html 內(nèi)容 正則匹配到 <script> 內(nèi)的內(nèi)容
return {
loader: 'js',
content: 'import "/src/main.js" export default {}"
}
)
build.onLoad({ filter: JS_TYPES_RE }, ({ path: id } => {
// eg: id = 'C:\\code\\sourcecode\\vite-demo\\src\\main.js'
return {
loader: 'js',
content: '' // eg: 讀取 main.js 內(nèi)容 內(nèi)有 import vue from 'vue'
}
})
build.onResolve( {filter: /^[\w@][^:]/},async ({ path: id, importer }) => {
// eg: id = "vue"
// eg: importer = "C:\\code\\sourcecode\\vite-demo\\src\\main.js"
// 加入依賴
depImports[id] = await resolve(id, importer); // eg: 返回"C:/code/sourcecode/vite-demo/node_modules/vue/dist/vue.runtime.esm-bundler.js"
return {
path: 'C:/code/sourcecode/vite-demo/node_modules/vue/dist/vue.runtime.esm-bundler.js'
}
})
}
};
}
對(duì)照代碼注釋并結(jié)合以下流程圖

至此我們可以得出結(jié)論:vite 主要是通過(guò)一個(gè)內(nèi)置的 vite:dep-scan esbuild 插件分析依賴項(xiàng)并將其寫入一個(gè)_metadata.json 文件中,并通過(guò) esbuild 將依賴的模塊(如將 vue.runtime.esm-bundler.js)打包至.vite 文件中(產(chǎn)生一個(gè) vue.js 和 vue.js.map 文件),這也就是開(kāi)篇問(wèn)題 2(本地多了一個(gè).vite 文件夾)的答案。
transformMiddleware
在上節(jié)中我們分析了 vite 預(yù)構(gòu)建的過(guò)程,最終其將打包后的文件寫入在.vite 文件夾內(nèi)解決了開(kāi)篇提出的問(wèn)題 2。那么讓我們繼續(xù)回到開(kāi)篇提到的問(wèn)題 1:main.js 中的 import vue from 'vue'是如何改寫成 import vue from '/node_modules/.vite/vue.js'的:
還記得我們?cè)?src/node/optimizer/index.ts 中的一段代碼嗎:
//src/node/optimizer/index.ts
import { transformMiddleware } from './middlewares/transform'
const middlewares = connect();
middlewares.use(transformMiddleware);
實(shí)際上 transformMiddleware 正是 vite devServer 核心的中間件,簡(jiǎn)而言之它負(fù)責(zé)攔截處理各種文件的請(qǐng)求并將其內(nèi)容轉(zhuǎn)換成瀏覽器能識(shí)別的正確代碼,下面讓我們看下 transformMiddleware 做了哪些事情:
// src/node/server/middlewares/transform.ts
import { transformRequest } from '../transformRequest'
export function transformMiddleware(server) {
// ....
if (isJSRequest(url) ) {
const result = await transformRequest(url)
return send(
req,
res,
result.code,
type,
result.etag,
// allow browser to cache npm deps!
isDep ? 'max-age=31536000,immutable' : 'no-cache',
result.map
)
}
}
可以看得出它對(duì) js 的請(qǐng)求,是通過(guò) vite 中間件的一個(gè)核心方法 transformRequest 處理的,并將結(jié)果發(fā)送至瀏覽器
// src/node/server/transformRequest.ts
export async function transformRequest(url) {
code = await fs.readFile(url, 'utf-8')
const transformResult = await pluginContainer.transform(code)
code = transformResult.code!
return {
code
}
}
transformRequest 中代碼的核心處理是 pluginContainer.transform 方法,而 transform 方法會(huì)遍歷 vite 內(nèi)置的所有插件以及用戶配置的插件處理轉(zhuǎn)換 code,其中內(nèi)置的一個(gè)核心的插件為 import-analysis
// src/node/plugins/importAnalysis.ts
import { parse as parseImports } from 'es-module-lexer'
export function importAnalysisPlugin() {
return {
name: 'vite:import-analysis',
async transform(source, importer, ssr) {
const specifier = parseImports(source); // specifier = vue
await normalizeUrl(specifier);
const normalizeUrl = async (specifier)=> {
const resolved = await this.resolve(specifier)
// eg: resolved = {id: "C:/code/sourcecode/vite-demo/node_modules/.vite/vue.js?v=82c5917e"}
}
}
}
}
對(duì) importAnalysisPlugin 函數(shù)內(nèi)部做的事情可簡(jiǎn)單歸納如下:
使用一個(gè)詞法分析利器 es-module-lexer 對(duì)源代碼進(jìn)行詞法分析,并最終能拿到 main.js 中的語(yǔ)句 import vue from 'vue'中的 vue 調(diào)用 reslove 方法最終其會(huì)先后調(diào)用 vite 內(nèi)置的兩個(gè) plugin:vite:pre-alias 及 vite:resolve 最終在 vite:resolve 內(nèi)的鉤子函數(shù) resolveId 內(nèi)部調(diào)用 tryOptimizedResolve tryOptimizedResolve 最終會(huì)通過(guò)讀取依賴構(gòu)建階段的緩存的依賴映射對(duì)象,拿到 vue 對(duì)應(yīng)的路徑

小結(jié)一下
至此我們已經(jīng)通過(guò)源碼分析解決了開(kāi)篇所提到的問(wèn)題 1 和問(wèn)題 2,簡(jiǎn)單地總結(jié)下就是:
vite 在啟動(dòng)服務(wù)器之前通過(guò) esbuild 及內(nèi)置的 vite:dep-scan esbuild 插件將 main.js 中的依賴 vue 預(yù)構(gòu)建打包至 /node_modules/.vite/下 核心中間件 transformMiddleware 攔截 main.js 請(qǐng)求,讀取其內(nèi)容,在 import-analysis 的插件內(nèi)部通過(guò) es-module-lexer 分析 import 語(yǔ)句讀取到依賴 vue,再通過(guò)一系列的內(nèi)置 plugin 最終將 import 語(yǔ)句中的 vue 轉(zhuǎn)換成 vue 對(duì)應(yīng)預(yù)構(gòu)建的真實(shí)路徑
對(duì)于問(wèn)題 3vite 是如何轉(zhuǎn)換.vue 文件的請(qǐng)求,vite 同樣是通過(guò) transformMiddleware 攔截.vue 請(qǐng)求并調(diào)用外部插件@vitejs/plugin-vue 處理轉(zhuǎn)換的,感興趣的同學(xué)可以查看 plugin-vue 的源碼, 本文就不再贅述了而是通過(guò)下文的實(shí)踐章節(jié)以代碼來(lái)解釋。
實(shí)踐一下
ok,在一頓分析之后我們終于來(lái)到了 coding 的環(huán)節(jié)了,廢話不多說(shuō),我們先創(chuàng)建一個(gè) server
// simple-vite/vit/index.js
const http = require('http');
const connect = require('connect');
const middlewares = connect();
const createServer = async ()=> {
// 依賴預(yù)構(gòu)建
await optimizeDeps();
http.createServer(middlewares).listen(3000, () => {
console.log('simple-vite-dev-server start at localhost: 3000!');
});
};
// 用于返回 html 的中間件
middlewares.use(indexHtmlMiddleware);
// 處理 js 和 vue 請(qǐng)求的中間件
middlewares.use(transformMiddleware);
createServer();
接著我們寫下依賴預(yù)構(gòu)建的函數(shù) optimizeDeps
// simple-vite/vit/index.js
const fs = require('fs');
const path = require('path');
const esbuild = require('esbuild');
// 因?yàn)槲覀兊?nbsp;vite 目錄和測(cè)試的 src 目錄在同一層,因此加了個(gè)../
const cacheDir = path.join(__dirname, '../', 'node_modules/.vite');
const optimizeDeps = async () => {
if (fs.existsSync(cacheDir)) return false;
fs.mkdirSync(cacheDir, { recursive: true });
// 在分析依賴的時(shí)候 這里為簡(jiǎn)單實(shí)現(xiàn)就沒(méi)按照源碼使用 esbuild 插件去分析
// 而是直接簡(jiǎn)單粗暴的讀取了上級(jí) package.json 的 dependencies 字段
const deps = Object.keys(require('../package.json').dependencies);
// 關(guān)于 esbuild 的參數(shù)可參考官方文檔
const result = await esbuild.build({
entryPoints: deps,
bundle: true,
format: 'esm',
logLevel: 'error',
splitting: true,
sourcemap: true,
outdir: cacheDir,
treeShaking: 'ignore-annotations',
metafile: true,
define: {'process.env.NODE_ENV': "\"development\""}
});
const outputs = Object.keys(result.metafile.outputs);
const data = {};
deps.forEach((dep) => {
data[dep] = '/' + outputs.find(output => output.endsWith(`${dep}.js`));
});
const dataPath = path.join(cacheDir, '_metadata.json');
fs.writeFileSync(dataPath, JSON.stringify(data, null, 2));
};
至此依賴預(yù)構(gòu)建的函數(shù)已寫完,當(dāng)我們運(yùn)行命令后會(huì)發(fā)現(xiàn)有打包后的依賴包及依賴映射的 json 文件,而且整個(gè)過(guò)程非???/p>
再然后我們來(lái)實(shí)現(xiàn)下中間件函數(shù),indexHtmlMiddleware 沒(méi)什么好說(shuō)的就是讀取返回根目錄的 index.html
// simple-vite/vit/index.js
const indexHtmlMiddleware = (req, res, next) => {
if (req.url === '/') {
const htmlPath = path.join(__dirname, '../index.html');
const htmlContent = fs.readFileSync(htmlPath, 'utf-8');
res.setHeader('Content-Type', 'text/html');
res.statusCode = 200;
return res.end(htmlContent);
}
next();
};
最核心的當(dāng)屬 transformMiddleware 了,首先讓我們處理下 js 文件
// simple-vite/vit/index.js
const transformMiddleware = async (req, res, next) => {
// 因?yàn)轭A(yù)構(gòu)建我們配置生成了 map 文件所以同樣要處理下 map 文件
if (req.url.endsWith('.js') || req.url.endsWith('.map')) {
const jsPath = path.join(__dirname, '../', req.url);
const code = fs.readFileSync(jsPath, 'utf-8');
res.setHeader('Content-Type', 'application/javascript');
res.statusCode = 200;
// map 文件不需要分析 import 語(yǔ)句
const transformCode = req.url.endsWith('.map') ? code : await importAnalysis(code);
return res.end(transformCode);
}
next();
};
transformMiddleware 最關(guān)鍵的就是 importAnalysis 函數(shù)了,正如 vite2 源碼里一樣其正是處理分析源代碼中的 import 語(yǔ)句,并將依賴包替換成預(yù)構(gòu)建包的路徑
// simple-vite/vit/index.js
const { init, parse } = require('es-module-lexer');
const MagicString = require('magic-string');
const importAnalysis = async (code) => {
// es-module-lexer 的 init 必須在 parse 前 Resolve
await init;
// 通過(guò) es-module-lexer 分析源 code 中所有的 import 語(yǔ)句
const [imports] = parse(code);
// 如果沒(méi)有 import 語(yǔ)句我們直接返回源 code
if (!imports || !imports.length) return code;
// 定義依賴映射的對(duì)象
const metaData = require(path.join(cacheDir, '_metadata.json'));
// magic-string vite2 源碼中使用到的一個(gè)工具 主要適用于將源代碼中的某些輕微修改或者替換
let transformCode = new MagicString(code);
imports.forEach((importer) => {
// n: 表示模塊的名稱 如 vue
// s: 模塊名稱在導(dǎo)入語(yǔ)句中的起始位置
// e: 模塊名稱在導(dǎo)入語(yǔ)句中的結(jié)束位置
const { n, s, e } = importer;
// 得到模塊對(duì)應(yīng)預(yù)構(gòu)建后的真實(shí)路徑 如
const replacePath = metaData[n] || n;
// 將模塊名稱替換成真實(shí)路徑如/node_modules/.vite
transformCode = transformCode.overwrite(s, e, replacePath);
});
return transformCode.toString();
};
至此,對(duì)于 js 請(qǐng)求已處理完畢,其中主要用到的兩個(gè)包 es-module-lexer 和 magic-string 感興趣的同學(xué)可以去對(duì)應(yīng)的 github 地址了解。最后讓我們?cè)偬幚硐?vue 文件吧:
// simple-vite/vit/index.js
const compileSFC = require('@vue/compiler-sfc');
const compileDom = require('@vue/compiler-dom');
const transformMiddleware = async (req, res, next) => {
if (req.url.indexOf('.vue')!==-1) {
const vuePath = path.join(__dirname, '../', req.url.split('?')[0]);
// 拿到 vue 文件中的內(nèi)容
const vueContent = fs.readFileSync(vuePath, 'utf-8');
// 通過(guò)@vue/compiler-sfc 將 vue 中的內(nèi)容解析成 AST
const vueParseContet = compileSFC.parse(vueContent);
// 得到 vue 文件中 script 內(nèi)的 code
const scriptContent = vueParseContet.descriptor.script.content;
const replaceScript = scriptContent.replace('export default ', 'const __script = ');
// 得到 vue 文件中 template 內(nèi)的內(nèi)容
const tpl = vueParseContet.descriptor.template.content;
// 通過(guò)@vue/compiler-dom 將其解析成 render 函數(shù)
const tplCode = compileDom.compile(tpl, { mode: 'module' }).code;
const tplCodeReplace = tplCode.replace('export function render(_ctx, _cache)', '__script.render=(_ctx, _cache)=>');
// 最后不要忘了 script 內(nèi)的 code 還要再一次進(jìn)行 import 語(yǔ)句分析替換
const code = `
${await importAnalysis(replaceScript)}
${tplCodeReplace}
export default __script;
`;
res.setHeader('Content-Type', 'application/javascript');
res.statusCode = 200;
return res.end(await importAnalysis(code));
}
next();
};
關(guān)于.vue 文件的處理好像也沒(méi)什么好說(shuō)的了,看代碼看注釋就完事了,想深入了解的同學(xué)可查看@vitejs/plugin-vue。然后讓我們看下此代碼實(shí)現(xiàn)的最終效果吧:

如上圖所示,所有請(qǐng)求的文件最終都轉(zhuǎn)換成了瀏覽器能成功運(yùn)行的 js 代碼。
最后的總結(jié)
本文的最終目的是參照 vite2 源碼實(shí)現(xiàn)一個(gè)極其簡(jiǎn)易版的 vite,其主要功能簡(jiǎn)而言之是以下兩點(diǎn):
利用 esbuild 進(jìn)行預(yù)構(gòu)建工作,其目的是能將我們依賴的瀏覽器不支持運(yùn)行的 CJS 和 AMD 模塊的代碼打包轉(zhuǎn)換為瀏覽器支持的 ES 模塊代碼,同時(shí)避免了過(guò)多的網(wǎng)絡(luò)請(qǐng)求次數(shù)。 模擬源碼實(shí)現(xiàn)一個(gè) transformMiddleware,其目的是能將源代碼進(jìn)行轉(zhuǎn)換瀏覽器能支持運(yùn)行的代碼,如:分析源代碼的 import 語(yǔ)句并其替換為瀏覽器可執(zhí)行的 import 語(yǔ)句以及將 vue 文件轉(zhuǎn)換為可執(zhí)行的 js 代碼。
最后感謝您能抽出寶貴的時(shí)間來(lái)看此文章,希望能給您帶來(lái)收獲。
往期推薦

Vite 太快了,煩死了,是時(shí)候該小睡一會(huì)了。

如何實(shí)現(xiàn)比 setTimeout 快 80 倍的定時(shí)器?

萬(wàn)字長(zhǎng)文!總結(jié)Vue 性能優(yōu)化方式及原理

90 行代碼的 webpack,你確定不學(xué)嗎?
最后
如果你覺(jué)得這篇內(nèi)容對(duì)你挺有啟發(fā),我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)個(gè)「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點(diǎn)在看,都是耍流氓 -_-)
歡迎加我微信「huab119」拉你進(jìn)技術(shù)群,長(zhǎng)期交流學(xué)習(xí)...
關(guān)注公眾號(hào)「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時(shí)聊騷。

