三大前端構建工具橫評,誰是性能之王!

點擊上方“ 藍色字體 ”,選擇“設爲星標
做積極向上的前端人!

Vite一經發布就吸引了很多人的關注,NPM下載量一路攀升:

而在Vite之前,還有Snowpack也同樣採用了No-Bundler構建方案。那麼No-Bundler模式與傳統老牌構建工具Webpack孰優孰劣呢?能否實現平滑遷移和完美取代?

下面就帶着問題一起分析一下 Vite2、Snowpack3 和 Webpack5 吧!

Webpack

Webpack是近年來使用量最大,同時社區最完善的前端打包構建工具,5.x版本對構建細節進行了優化,某些場景下打包速度提升明顯,但也沒能解決之前一直被人詬病的大項目編譯慢的問題,這也和Webpack本身的機制相關。

已經有很多文章講解Webpack的運行原理了,本文就不再贅述,我們着重分析一下後起之秀。

Snowpack

什麼是Snowpack?

首次提出利用瀏覽器原生ESM能力的工具並非是Vite,而是一個叫做Snowpack的工具。前身是@pika/web,從1.x版本開始更名爲Snowpack。

Snowpack在其官網是這樣進行自我介紹的:“Snowpack是一種閃電般快速的前端構建工具,專爲現代Web設計。 它是開發工作流程較重,較複雜的打包工具(如Webpack或Parcel)的替代方案。Snowpack利用JavaScript的本機模塊系統(稱爲ESM)來避免不必要的工作並保持流暢的開發體驗”。

Snowpack的理念是減少或避免整個bundle的打包,每次保存單個文件時,傳統的JavaScript構建工具(例如Webpack和Parcel)都需要重新構建和重新打包應用程序的整個bundle。重新打包時增加了在保存更改和看到更改反映在瀏覽器之間的時間間隔。在開發過程中,Snowpack爲你的應用程序提供unbundled server。每個文件只需要構建一次,就可以永久緩存。文件更改時,Snowpack會重新構建該單個文件。在重新構建每次變更時沒有任何的時間浪費,只需要在瀏覽器中進行HMR更新。

再瞭解一下發明Snowpack的團隊Pika,Pika團隊有一個宏偉的使命:讓Web應用提速90%:

爲此,Pika團隊開發並維護了兩個技術體系:構建相關的Snowpack和造福大衆的Skypack。

在這裏我們簡單聊一下Skypack的初衷,當前許多Web應用都是在不同NPM包的基礎上進行構建的,而這些NPM包都被Webpack之類的打包工具打成了一個bundle,如果這些NPM包都來源於同一個CDN地址,且支持跨域緩存,那麼這些NPM包在緩存生效期內都只需要加載一次,其他網站用到了同樣的NPM包,就不需要重新下載,而是直接讀取本地緩存。

例如,智聯的官網與B端都是基於vue+vuex開發的,當HR在B端發完職位後,進入官網預覽自己的公司對外主頁都不用重新下載,只需要下載智聯官網相關的一些業務代碼即可。爲此,Pika專門建立了一個CDN(Skypack)用來下載NPM上的ESM模塊。

後來Snowpack發佈的時候,Pika團隊順便發表了一篇名爲《A Future Without Webpack》 的文章,告訴大家可以嘗試拋棄Webpack,採用全新的打包構建方案,下圖取自其官網,展示了bundled與unbundled之間的區別。

在HTTP/2和5G網絡的加持下,我們可以預見到HTTP請求數量不再成爲問題,而隨着Web領域新標準的普及,瀏覽器也在逐步支持ESM。

源碼分析

啓動構建時會調用源碼src/index.ts中的cli方法,該方法的代碼刪減版如下:

import {command as buildCommand} from'./commands/build';

exportasyncfunction cli(args: string[]) {
const cliFlags = yargs(args, {
array: ['install', 'env', 'exclude', 'external']
}) as CLIFlags;

if (cmd === 'build') {
await buildCommand(commandOptions);
return process.exit(0);
}
}

進入commands/build文件,執行大致邏輯如下:

exportasyncfunction build(commandOptions: CommandOptions): Promise<SnowpackBuildResult> {
// 讀取config代碼
// ...
for (const runPlugin of config.plugins) {
if (runPlugin.run) {
// 執行插件
}
}

// 將 `import.meta.env` 的內容寫入文件
await fs.writeFile(
path.join(internalFilESbuildLoc, 'env.js'),
generateEnvModule({mode: 'production', isSSR}),
);

// 如果 HMR,則加載 hmr 工具文件
if (getIsHmrEnabled(config)) {
await fs.writeFile(path.resolve(internalFilESbuildLoc, 'hmr-client.js'), HMR_CLIENT_CODE);
await fs.writeFile(
path.resolve(internalFilESbuildLoc, 'hmr-error-overlay.js'),
HMR_OVERLAY_CODE,
);
hmrEngine = new EsmHmrEngine({port: config.devOptions.hmrPort});
}

// 開始構建源文件
logger.info(colors.yellow('! building source files...'));
const buildStart = performance.now();
const buildPipelineFiles: Record<string, FileBuilder> = {};

// 根據主 buildPipelineFiles 列表安裝所有需要的依賴項,對應下面第三部
asyncfunction installDependencies() {
const scannedFiles = Object.values(buildPipelineFiles)
.map((f) =>Object.values(f.filesToResolve))
.reduce((flat, item) => flat.concat(item), []);

// 指定安裝的目標文件夾
const installDest = path.join(buildDirectoryLoc, config.buildOptions.metaUrlPath, 'pkg');

// installOptimizedDependencies 方法調用了 esinstall 包,包內部調用了 rollup 進行模塊分析及 commonjs 轉 esm
const installResult = await installOptimizedDependencies(
scannedFiles,
installDest,
commandOptions,
);

return installResult
}

// 下面因代碼繁多,僅展示源碼中的註釋
// 0. Find all source files.
// 1. Build all files for the first time, from source.
// 2. Install all dependencies. This gets us the import map we need to resolve imports.
// 3. Resolve all built file imports.
// 4. Write files to disk.
// 5. Optimize the build.

// "--watch" mode - Start watching the file system.
// Defer "chokidar" loading to here, to reduce impact on overall startup time
logger.info(colors.cyan('watching for changes...'));
const chokidar = awaitimport('chokidar');

// 本地文件刪除時清除 buildPipelineFiles 對應的文件
function onDeleteEvent(fileLoc: string) {
delete buildPipelineFiles[fileLoc];
}

// 本地文件創建及修改時觸發
asyncfunction onWatchEvent(fileLoc: string) {
// 1. Build the file.
// 2. Resolve any ESM imports. Handle new imports by triggering a re-install.
// 3. Write to disk. If any proxy imports are needed, write those as well.

// 觸發 HMR
if (hmrEngine) {
hmrEngine.broadcastMessage({type: 'reload'});
}
}

// 創建文件監聽器
const watcher = chokidar.watch(Object.keys(config.mount), {
ignored: config.exclude,
ignoreInitial: true,
persistent: true,
disableGlobbing: false,
useFsEvents: isFsEventsEnabled(),
});
watcher.on('add', (fileLoc) => onWatchEvent(fileLoc));
watcher.on('change', (fileLoc) => onWatchEvent(fileLoc));
watcher.on('unlink', (fileLoc) => onDeleteEvent(fileLoc));

// 返回一些方法給 plugin 使用
return {
result: buildResultManifest,
onFileChange: (callback) => (onFileChangeCallback = callback),
async shutdown() {
await watcher.close();
},
};
}

exportasyncfunction command(commandOptions: CommandOptions) {
try {
await build(commandOptions);
} catch (err) {
logger.error(err.message);
logger.debug(err.stack);
process.exit(1);
}

if (commandOptions.config.buildOptions.watch) {
// We intentionally never want to exit in watch mode!
returnnewPromise(() => {});
}
}

所有的模塊會經過install進行安裝,此處的安裝是將模塊轉換成ESM放在pkg目錄下,並不是npm包安裝的概念。

在Snowpack3中增加了一些老版本不支持的能力,如:內部默認集成Node服務、支持CSS Modules、支持HMR等。

Vite

什麼是Vite?

Vite(法語單詞“ fast”,發音爲/vit/)是一種構建工具,旨在爲現代Web項目提供更快,更精簡的開發體驗。它包括兩個主要部分:

  1. 開發服務器,它在本機ESM上提供了豐富的功能增強,例如,極快的Hot Module Replacement(HMR)。
  2. 構建命令,它將代碼使用Rollup進行構建。

隨着vue3的推出,Vite也隨之成名,起初是一個針對Vue3的打包編譯工具,目前2.x版本發佈面向了任何前端框架,不侷限於Vue,在Vite的README中也提到了在某些想法上參考了Snowpack。

在已有方案上Vue本可以拋棄Webpack選擇Snowpack,但選擇開發Vite來造一個新的輪子也有Vue團隊自己的考量。

在Vite官方文檔列舉了Vite與Snowpack的異同,其實本質是說明Vite相較於Snowpack的優勢。

相同點,引用Vite官方的話:

Snowpack is also a no-bundle native ESM dev server that is very similar in scope to Vite。

不同點:

  1. Snowpack的build默認是不打包的,好處是可以靈活選擇Rollup、Webpack等打包工具,壞處就是不同打包工具帶來了不同的體驗,當前ESbuild作爲生產環境打包尚不穩定,Rollup也沒有官方支持Snowpack,不同的工具會產生不同的配置文件;
  2. Vite支持多page打包;
  3. Vite支持Library Mode;
  4. Vite支持CSS代碼拆分,Snowpack默認是CSS in JS;
  5. Vite優化了異步代碼加載;
  6. Vite支持動態引入 polyfill;
  7. Vite官方的 legacy mode plugin,可以同時生成 ESM 和 NO ESM;
  8. First Class Vue Support。

第5點Vite官網有詳細介紹,在非優化方案中,當A導入異步塊時,瀏覽器必須先請求並解析,A然後才能確定它也需要公共塊C。這會導致額外的網絡往返:

Entry ---> A ---> C

Vite通過預加載步驟自動重寫代碼拆分的動態導入調用,以便在A請求時並行C獲取:

Entry ---> (A + C)

可能C會多次導入,這將導致在未優化的情況下發出多次請求。Vite的優化將跟蹤所有import,以完全消除重複請求,示意圖如下:

第8點的First Class Vue Support,雖然在列表的最後一項,實則是點睛之筆。

源碼分析

Vite在啓動時,如果不是中間件模式,內部默認會啓一個http server。

exportasyncfunction createServer(
inlineConfig: InlineConfig = {}
): Promise<ViteDevServer>
{
// 獲取 config
const config = await resolveConfig(inlineConfig, 'serve', 'development')
const root = config.root
const serverConfig = config.server || {}

// 判斷是否是中間件模式
const middlewareMode = !!serverConfig.middlewareMode
const middlewares = connect() as Connect.Server

// 中間件模式不創建 http 服務,允許外部以中間件形式調用:https://Vitejs.dev/guide/api-javascript.html#using-the-Vite-server-as-a-middleware
const httpServer = middlewareMode
? null
: await resolveHttpServer(serverConfig, middlewares)

// 創建 websocket 服務
const ws = createWebSocketServer(httpServer, config)

// 創建文件監聽器
const { ignored = [], ...watchOptions } = serverConfig.watch || {}
const watcher = chokidar.watch(path.resolve(root), {
ignored: ['**/node_modules/**', '**/.git/**', ...ignored],
ignoreInitial: true,
ignorePermissionErrors: true,
...watchOptions
}) as FSWatcher


const plugins = config.plugins
const container = await createPluginContainer(config, watcher)
const moduleGraph = new ModuleGraph(container)
const closeHttpServer = createSeverCloseFn(httpServer)

const server: ViteDevServer = {
// 前面定義的常量,包含:config、中間件、websocket、文件監聽器、ESbuild 等
}

// 監聽進程關閉
process.once('SIGTERM', async () => {
try {
await server.close()
} finally {
process.exit(0)
}
})

watcher.on('change', async (file) => {
file = normalizePath(file)

// 文件更改時使模塊圖緩存無效
moduleGraph.onFileChange(file)

if (serverConfig.hmr !== false) {
try {
// 大致邏輯是修改 env 文件時直接重啓 server,根據 moduleGraph 精準刷新,必要時全部刷新
await handleHMRUpdate(file, server)
} catch (err) {
ws.send({
type: 'error',
err: prepareError(err)
})
}
}
})

// 監聽文件創建
watcher.on('add', (file) => {
handleFileAddUnlink(normalizePath(file), server)
})

// 監聽文件刪除
watcher.on('unlink', (file) => {
handleFileAddUnlink(normalizePath(file), server, true)
})

// 掛載插件的服務配置鉤子
const postHooks: ((() => void) | void)[] = []
for (const plugin of plugins) {
if (plugin.configureServer) {
postHooks.push(await plugin.configureServer(server))
}
}

// 加載多箇中間件,包含 corsproxyopen-in-editor、靜態文件服務等

// 運行post鉤子,在html中間件之前應用的,這樣外部中間件就可以提供自定義內容取代 index.html
postHooks.forEach((fn) => fn && fn())

if (!middlewareMode) {
// 轉換 html
middlewares.use(indexHtmlMiddleware(server, plugins))
// 處理 404
middlewares.use((_, res) => {
res.statusCode = 404
res.end()
}
)
}

// errorHandler 中間件
middlewares.use(errorMiddleware(server, middlewareMode))

// 執行優化邏輯
const runOptimize = async () =>
{
if (config.optimizeCacheDir) {
// 將使用 ESbuild 將依賴打包並寫入 node_modules/.Vite/xxx
await optimizeDeps(config)
// 更新 metadata 文件
const dataPath = path.resolve(config.optimizeCacheDir, 'metadata.json')
if (fs.existsSync(dataPath)) {
server._optimizeDepsMetadata = JSON.parse(
fs.readFileSync(dataPath, 'utf-8')
)
}
}
}

if (!middlewareMode && httpServer) {
// 在服務器啓動前覆蓋listen方法並運行優化器
const listen = httpServer.listen.bind(httpServer)
httpServer.listen = (async (port: number, ...args: any[]) => {
await container.buildStart({})
await runOptimize()
return listen(port, ...args)
}
) as any

httpServer.once('listening', () => {
// 更新實際端口,因爲這可能與初始端口不同
serverConfig.port = (httpServer.address() as AddressInfo).port
}
)
} else {
await runOptimize()
}

// 最後返回服務
return server
}

訪問Vite服務的時候,默認會返回index.html:

<!DOCTYPE html>
<html lang="en">
<head>
<script type="module" src="/@Vite/client"></script>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

處理 import 文件邏輯,在 node/plugins/importAnalysis.ts 文件內:

exportfunction importAnalysisPlugin(config: ResolvedConfig): Plugin {
const clientPublicPath = path.posix.join(config.base, CLIENT_PUBLIC_PATH)
let server: ViteDevServer
return {
name: 'Vite:import-analysis',
configureServer(_server) {
server = _server
},
async transform(source, importer, ssr) {
const rewriteStart = Date.now()
// 使用 es-module-lexer 進行語法解析

await init
let imports: ImportSpecifier[] = []
try {
imports = parseImports(source)[0]
} catch (e) {
const isVue = importer.endsWith('.vue')
const maybeJSX = !isVue && isJSRequest(importer)


// 判斷文件後綴給不同的提示信息
const msg = isVue
? `Install @Vitejs/plugin-vue to handle .vue files.`
: maybeJSX
? `If you are using JSX, make sure to name the file with the .jsx or .tsx extension.`
: `You may need to install appropriate plugins to handle the ${path.extname(
importer
)}
file format.`

this.error(
`Failed to parse source for import analysis because the content ` +
`contains invalid JS syntax. ` +
msg,
e.idx
)
}

// 將代碼字符串取出
let s: MagicString | undefined
const str = () => s || (s = new MagicString(source))

// 解析 env、glob 等並處理
// 轉換 cjs 成 esm
}
}
}

拿Vue的NPM包舉例經優化器處理後的路徑如下:

// before
- import { createApp } from'vue'
+ import { createApp } from'/node_modules/.Vite/vue.runtime.esm-bundler.js?v=d17c1aa4'
import App from'/src/App.vue'

createApp(App).mount('#app')

截圖中的/src/App.vue路徑經過Vite處理髮生了什麼?

首先需要引用 @Vitejs/plugin-vue 來處理,內部使用Vue官方的編譯器@vue/compiler-sfc,plugin處理邏輯同rollup的plugin,Vite在Rollup的插件機制上進行了擴展。

詳細可參考:https://Vitejs.dev/guide/api-plugin.html,這裏不做展開。

編譯後的App.vue文件如下:

import { createHotContext as __Vite__createHotContext } from"/@Vite/client";
import.meta.hot = __Vite__createHotContext("/src/App.vue");
import HelloWorld from'/src/components/HelloWorld.vue'

const _sfc_main = {
expose: [],
setup(__props) {
return { HelloWorld }
}
}

import {
createVNode as _createVNode,
Fragment as _Fragment,
openBlock as _openBlock,
createBlock as _createBlock
} from"/node_modules/.Vite/vue.runtime.esm-bundler.js?v=d17c1aa4"

const _hoisted_1 = /*#__PURE__*/_createVNode("img", {
alt: "Vue logo",
src: "/src/assets/logo.png"
}, null, -1/* HOISTED */)

function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock(_Fragment, null, [
_hoisted_1,
_createVNode($setup["HelloWorld"], { msg: "Hello Vue 3 + Vite" })
], 64/* STABLE_FRAGMENT */))
}

import"/src/App.vue?vue&type=style&index=0&lang.css"

_sfc_main.render = _sfc_render
_sfc_main.__file = "/Users/orange/build/Vite-vue3/src/App.vue"
exportdefault _sfc_main
_sfc_main.__hmrId = "7ba5bd90"
typeof __VUE_HMR_RUNTIME__ !== 'undefined' && __VUE_HMR_RUNTIME__.createRecord(_sfc_main.__hmrId, _sfc_main)
import.meta.hot.accept(({ default: updated, _rerender_only }) => {
if (_rerender_only) {
__VUE_HMR_RUNTIME__.rerender(updated.__hmrId, updated.render)
} else {
__VUE_HMR_RUNTIME__.reload(updated.__hmrId, updated)
}
})

可以發現,Vite本身並不會遞歸編譯,這個過程交給了瀏覽器,當瀏覽器運行到import HelloWorld from '/src/components/HelloWorld.vue' 時,又會發起新的請求,通過中間件來編譯 vue,以此類推,爲了證明我們的結論,可以看到 HelloWorld.vue 的請求信息:

經過分析源碼後,能斷定的是,Snowpack與Vite在啓動服務的時間會遠超Webpack,但複雜工程的首次編譯到完全可運行的時間需要進一步測試,不同場景下可能產生截然不同的結果。

功能對比


[email protected] [email protected] [email protected]
支持Vue2 非官方支持: https://github.com/underfin/vite-plugin-vue2 支持:vue-loader@^15.0.0 非官方支持:https://www.npmjs.com/package/@lepzulnag/Snowpack-plugin-vue-2
支持Vue3 支持 支持:vue-loader@^16.0.0(https://github.com/Jamie-Yang/vue3-boilerplate) 支持:https://www.npmjs.com/package/@Snowpack/plugin-vue
支持Typescript 支持:ESbuild (默認無類型檢查) 支持:ts-loader 支持:https://github.com/Snowpackjs/Snowpack/tree/main/create-Snowpack-app/app-template-vue-typescript
支持CSS預處理器 支持:https://vitejs.dev/guide/features.html#css-pre-processors 支持:https://vue-loader.vuejs.org/guide/pre-processors.html 部分支持:官方僅提供了Sass和Postcss,且存在未解決BUG
支持CSS Modules 支持:https://vitejs.dev/guide/features.html#css-modules 支持:https://vue-loader.vuejs.org/guide/css-modules.html 支持
支持靜態文件 支持 支持 支持
開發環境 no-bundle native ESM(CJS → ESM) bundle(CJS/UMD/ESM) no-bundle native ESM(CJS → ESM)
HMR 支持 支持 支持
生產環境 Rollup Webpack Webpack, Rollup, or even ESbuild
Node API 調用能力 支持 支持 支持

啓動時編譯速度對比

下面一組測試的代碼完全相同,都是 Hello World 工程,沒有任何複雜邏輯,Webpack 與 Snowpack 分別引入了對應的 Vue plugin,Vite 無需任何插件。

Webpack5 + vue3(1.62s)

工程目錄:

控制檯輸出:

Snowpack3 + vue3(2.51s)

工程目錄:

控制檯輸出:

Vite2 + vue3(0.99s)

工程目錄:

控制檯輸出:

真實項目遷移

測試案例:已存在的複雜邏輯vue工程

經過簡單的測試及調研結果,首先從生態和性能上排除了Snowpack,下面將測試Webpack5與Vite2。

遷移Vite2遇到的問題:

1.不支持省略.vue後綴,因爲此路由機制與編譯處理強關聯;

2.不支持.vue後綴文件內寫jsx,若寫jsx,需要改文件後綴爲.jsx;

3.不建議import { ... } from "dayjs"與import duration from 'dayjs/plugin/duration'同時使用,從源碼會發現在optimizeDeps階段已經把ESM編譯到了緩存文件夾,若同時使用會報錯:

4.當optimizeDeps忽略後文件路徑錯誤,node_modules/dayjs/dayjs.main.js?version=xxxxx,此處不應該在query中添加version;

5.組件庫中window.$方法找不到,不能強依賴關聯順序,跟請求返回順序有關;

6.當dependencies首次未被寫入緩存時,補充寫入會報錯,需要二次重啓;

7.在依賴關係複雜場景,Vue被多次cache,會出現ESM二次封裝的情況,也就是ESM裏面嵌套ESM的情況;

種種原因,調試到這裏終結了,結論就是Vite2目前處於初期,尚不穩定,處理深層次依賴就目前的moduleGraph機制還有所欠缺,有待完善。

Webpack5

效果和我們之前測試相同代碼在Webpack4下50+秒相比提升明顯,實際場景可能存在誤差,但WebpackConfig配置細節基本一致。

編譯壓縮提速

不知大家是否有遇到這個問題:

<--- Last few GCs --->
[59757:0x103000000] 32063 ms: Mark-sweep 1393.5 (1477.7) -> 1393.5 (1477.7) MB, 109.0 / 0.0 ms allocation failure GC in old space requested
<--- JS stacktrace --->
==== JS stack trace =========================================
Security context: 0x24d9482a5ec1 <JSObject>
...

或者在 92% 的進度裏卡很久:

Webpack chunk asset optimization (92%) TerserPlugin

隨着產物越來越大,編譯上線和CI的時間都越來越長,而其中1/3及更多的時間則是在做壓縮的部分。OOM的問題也通常來源於壓縮。

如何解決壓縮慢和佔內存的問題,一直是逃避不開的話題,Vite採用了ESbuild,接下來分析一下ESbuild。

ESbuild

下面是官方的構建時間對比圖,並沒有說明場景,文件大小等,所以不具備實際參考價值。

之所以快,其中最主要的應該是用go寫,然後編譯爲Native代碼。然後npm安裝時動態去下對應平臺的二進制包,支持Mac、Linux和Windows,比如esbuild-darwin-64。

相同思路的還有es-module-lexer、swc等,都是用編譯成Native代碼的方式進行提速,用來彌補Node在密集CPU計算場景的短板。

ESbuild有兩個功能,bundler和minifier。bundler的功能和babel以及Webpack相比差異很大,直接使用對現有業務的風險較大;而minifier可以嘗試,在Webpack和babel產物的基礎上做一次生產環境壓縮,可以節省terser plugin的壓縮時間。

同時針對Webpack提供了 esbuild-webpack-plugin,可以在 Webpack 內直接使用 ESbuild。

優缺點及總結

Snowpack

缺點:

  1. 社區不夠完善,無法支撐我們後續的業務演進;
  2. 編譯速度提效不明顯。

Vite

優點:

  1. 因其與rollup聯合,社區裏rolllup的插件基本都可以直接使用,社區相對完善;
  2. 編譯速度快。

缺點:

  1. 目前Vite處於2.0初期,BUG比較多;
  2. 本地的 ESbuild 與生產環境的babel編譯結果差距較大,可能會導致功能差異。

Webpack5

優點:

  1. 從實際測試要比Webpack4快許多;
  2. 可藉助ESbuild的代碼壓縮機制。

缺點:

  1. 相較 Vite 的本地開發編譯速度有寫不足(其實算不上缺點,因爲解決了生產環境差異)。

回到我們文章開始的問題,經過上文的遷移測試環節,我們需要調整大量代碼進行Vite遷移適配,由於原有Vue工程未遵循Vite的開發範式,遷移過程比較坎坷,新工程只要遵循Vite官方的開發文檔,會規避上文提到的大部分問題。

所以已有Webpack工程遷移成本還是較大的,另一方面也是需要重點關注的就是本地開發與生產環境的差異問題,如果本機開發環境採用No-Bundle機制,而生產發佈環境採用Bundle機制,這種不一致性會給測試和排查問題帶來困擾和風險,在生態尚未齊備之前,建議審慎嘗試。

點擊關注下方卡片關注我👇👇




哇靠!他竟然把 React 組件跑在命令行終端窗口裏面!
漫畫 | 再見 Java,再見Java之父
基於 React 的可視化編輯平臺實踐
面試官問 Vue 性能優化,我該怎麼回答?
Vue3.0 高頻出現的幾道面試題
面試官問 Vue 性能優化,我該怎麼回答?
漫畫 | 從西遊記看產品經理和程序員的關係!
漫畫 | 公司測試因提Bug不規範,鋃鐺入獄~

如果覺得這篇文章還不錯,來個【分享、點贊、在看】三連吧,讓更多的人也看到~

本文分享自微信公衆號 - 前端佈道師(honeyBadger8)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章