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(<script module>)。
源码分析
启动构建时会调用源码src/index.ts
中的 cli 方法,该方法的代码删减版如下:
import {command as buildCommand} from './commands/build';
export async function 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 文件,执行大致逻辑如下:
export async function 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 列表安装所有需要的依赖项,对应下面第三部
async function 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 = await import('chokidar');
// 本地文件删除时清除 buildPipelineFiles 对应的文件
function onDeleteEvent(fileLoc: string) {
delete buildPipelineFiles[fileLoc];
}
// 本地文件创建及修改时触发
async function 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();
},
};
}
export async function 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!
return new Promise(() => {});
}
}
复制代码
所有的模块会经过 install 进行安装,此处的安装是将模块转换成 ESM 放在 pkg 目录下,并不是 npm 包安装的概念。
在 Snowpack3 中增加了一些老版本不支持的能力,如:内部默认集成 Node 服务、支持 CSS Modules、支持 HMR 等。
Vite
什么是 Vite?
Vite(法语单词“ fast”,发音为/vit/)是一种构建工具,旨在为现代 Web 项目提供更快,更精简的开发体验。它包括两个主要部分:
开发服务器,它在本机 ESM 上提供了丰富的功能增强,例如,极快的 Hot Module Replacement(HMR)。
构建命令,它将代码使用 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。
不同点:
Snowpack 的 build 默认是不打包的,好处是可以灵活选择 Rollup、Webpack 等打包工具,坏处就是不同打包工具带来了不同的体验,当前 ESbuild 作为生产环境打包尚不稳定,Rollup 也没有官方支持 Snowpack,不同的工具会产生不同的配置文件;
Vite 支持多 page 打包;
Vite 支持 Library Mode;
Vite 支持 CSS 代码拆分,Snowpack 默认是 CSS in JS;
Vite 优化了异步代码加载;
Vite 支持动态引入 polyfill;
Vite 官方的 legacy mode plugin,可以同时生成 ESM 和 NO ESM;
First Class Vue Support。
第 5 点 Vite 官网有详细介绍,在非优化方案中,当A
导入异步块时,浏览器必须先请求并解析,A
然后才能确定它也需要公共块C
。这会导致额外的网络往返:
Vite 通过预加载步骤自动重写代码拆分的动态导入调用,以便在A
请求时并行C
获取:
可能C
会多次导入,这将导致在未优化的情况下发出多次请求。Vite 的优化将跟踪所有 import,以完全消除重复请求,示意图如下:
第 8 点的 First Class Vue Support,虽然在列表的最后一项,实则是点睛之笔。
源码分析
Vite 在启动时,如果不是中间件模式,内部默认会启一个 http server。
export async function 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))
}
}
// 加载多个中间件,包含 cors、proxy、open-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 文件内:
export function 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"
export default _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,但复杂工程的首次编译到完全可运行的时间需要进一步测试,不同场景下可能产生截然不同的结果。
功能对比
| |Vite@2.0.3 | Webpack@5.24.2 | Snowpack@3.0.13 |
| --- | --- | --- | --- |
|支持 Vue2 | 非官方支持: https://github.com/underfin/vite-plugin-vue2 | 支持:vue-loader@^15.0.0 | 非官方支持: [https://www.npmjs.com/package/@lepzulnag/Snowpack-plugin-vue-2](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](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](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](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 调用能力 | 支持 | 支持 | 支持 |
启动时编译速度对比
下面一组测试的代码完全相同,都是 Hellow 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](https://link.zhihu.com/?target=https%3A//github.com/swc-project/swc)等,都是用编译成 Native 代码的方式进行提速,用来弥补 Node 在密集 CPU 计算场景的短板。
ESbuild 有两个功能,bundler 和 minifier。bundler 的功能和 babel 以及 Webpack 相比差异很大,直接使用对现有业务的风险较大;而 minifier 可以尝试,在 Webpack 和 babel 产物的基础上做一次生产环境压缩,可以节省 terser plugin 的压缩时间。
同时针对 Webpack 提供了 esbuild-webpack-plugin,可以在 Webpack 内直接使用 ESbuild。
优缺点及总结
Snowpack
缺点:
社区不够完善,无法支撑我们后续的业务演进;
编译速度提效不明显。
Vite
优点:
因其与 rollup 联合,社区里 rolllup 的插件基本都可以直接使用,社区相对完善;
编译速度快。
缺点:
目前 Vite 处于 2.0 初期,BUG 比较多;
本地的 ESbuild 与生产环境的 babel 编译结果差距较大,可能会导致功能差异。
Webpack5
优点:
从实际测试要比 Webpack4 快许多;
可借助 ESbuild 的代码压缩机制。
缺点:
相较 Vite 的本地开发编译速度有写不足(其实算不上缺点,因为解决了生产环境差异)。
回到我们文章开始的问题,经过上文的迁移测试环节,我们需要调整大量代码进行 Vite 迁移适配,由于原有 Vue 工程未遵循 Vite 的开发范式,迁移过程比较坎坷,新工程只要遵循 Vite 官方的开发文档,会规避上文提到的大部分问题。
所以已有 Webpack 工程迁移成本还是较大的,另一方面也是需要重点关注的就是本地开发与生产环境的差异问题,如果本机开发环境采用 No-Bundle 机制,而生产发布环境采用 Bundle 机制,这种不一致性会给测试和排查问题带来困扰和风险,在生态尚未齐备之前,建议审慎尝试。
评论