尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

从Webpack迁移到Vite:博客平台构建实践与优化全指南

发布时间:2026/9/11 2:52:43

资讯中心
01
ARTICLE

从Webpack迁移到Vite:博客平台构建实践与优化全指南

从Webpack迁移到Vite:博客平台构建实践与优化全指南
我最近刚把一个个人博客从 Webpack 迁移到了 Vite整个过程踩了不少坑也把两者从底层到配置都重新梳理了一遍。正好借着“用博客平台构建实践”这个场景把 Vite 和 Webpack 的构建差异、优化配置、避坑技巧一次讲清楚。这篇文章不废话直接以我实际的博客项目为载体从设计思路、核心配置、构建优化到问题排查完整过一遍。1. 博客平台构建为什么拿它当构建工具的试金石1.1 博客类项目的构建特点先说为什么选博客平台来对比 Vite 和 Webpack而不是随便写个 TodoList。博客类前端项目有一组非常典型的构建诉求几乎覆盖了打包器的核心能力面内容与代码混合Markdown 文件要能被解析成 React/Vue 组件并且支持 frontmatter标题、日期、标签等元数据这考验构建器的 loader/插件体系。静态资源密度高图片封面图、文章配图、字体、favicon、SEO 用的静态文件资源处理策略直接决定产物体积和部署方式。多页面/路由博客通常有首页、文章列表、标签页、详情页需要按路由分包否则首屏加载会慢到没法看。SSG/SSR 需求为了保证 SEO博客往往需要预渲染或服务端渲染这要求构建工具不只能产出纯 client bundle还要能配合 Node 侧逻辑。开发体验敏感写博客经常要一边改 Markdown 一边预览Dev Server 冷启动速度和热更新时延直接决定写作节奏。这两套工具面对上述诉求时的处理路径完全不同。我们后面会看到Webpack 像是一套“全功能但需要精心调理”的方案Vite 则像是“为现代浏览器量身定制、默认就很快”的方案两种思路没有绝对优劣关键看项目跑在什么环境、团队有多少精力做配置维护。1.2 选型前需要明确的三件事在动手之前先看看选型时会问自己的几个关键问题这也是我在各种技术群里看到问得最多的目标浏览器范围是什么Webpack 5 通过browserslist做语法降级配合 Babel 可以精细控制到 ES5Vite 官方默认的构建目标基于baseline-widely-available相当于原生支持 ESM 的浏览器虽然也能降级但心智模型不同。如果你的用户里有大量老旧浏览器Vite 的很多优势会打折扣。依赖数量和本地依赖情况如何如果项目里用了大量 workspace 内部的本地依赖monorepo 场景Vite 预构建依赖的缓存判定、Webpack 的symlink解析都需要特殊处理这个后面细说。团队是否愿意投入配置成本Webpack 功能强、生态老牌但几乎每次加功能都要找对应的 loader 和 plugin新手很容易迷失在配置山Vite 则只暴露少量关键配置项默认集成好 TS、React Fast Refresh、CSS 处理等开箱即用的成本低得多。2. 核心差异Vite 与 Webpack 的构建设计哲学2.1 Dev Serveresbuild 预构建对比全量打包Vite 最具革命性的地方是 Dev Server 不再打包。浏览器直接请求源码中的 ESM 模块而 Vite 只在首次启动时用 esbuild 做一次“依赖预构建”——把 node_modules 里的 CommonJS/UMD 依赖转成 ESM并将多个内部模块合并成少量 chunk减少 HTTP 请求数。这套机制在博客项目里的效果非常直观我的博客依赖列表里有 react、react-dom、marked、gray-matter、react-router-dom、highlight.js 等一大批包。Webpack 的 Dev Server 启动时要遍历整个依赖图进行一次完整的打包编译冷启动通常在 3 到 8 秒Vite 冷启动一般 300ms 到 1 秒就绪差异在大型项目上更是数量级级别。同时 Vite 的 HMR 是“基于 ESM 的按需更新”改动一个按钮组件的样式浏览器只需要替换掉这一个模块几乎瞬时刷新。Webpack 的 HMR 虽然在 webpack-dev-server 5 里已经很快但它的架构决定了每次替换要经过打包器内部重新生成模块代码配合复杂 loader 链时偶尔会出现 HMR 失败需要整页刷新。我在实际写博客时的体感是Vite 下改 Markdown 内容刷新等待时间是“看不到加载动画”的程度Webpack 下改完要等 1 到 2 秒偶尔还要手动刷新。对于高频迭代的博客写作场景这个差距非常影响体验。2.2 生产构建Rollup/Rolldown 对比 Webpack 的静态分析到了生产构建两边的处理逻辑都回归到“静态依赖分析-打包-压缩”的老路但具体实现差别很大。Vite 生产构建底层依赖 RollupVite 5 及之前或 RolldownVite 6 的实验特性这是目前世界上 Tree Shaking 做得最干净的打包器之一。Rollup 基于 ESM 的静态结构分析能精确地剔除未使用的导出和副作用代码。加上 Vite 默认开启build.target按现代浏览器优化产物体积普遍比 Webpack 小 10% 到 20%。Webpack 5 也有 Tree Shaking但受限于它既要兼容 CommonJS、又要处理各种动态 require压缩率往往不如 Rollup/Rolldown 干净。Webpack 的优势在于生态兼容性——遇到老旧的库它能通过ProvidePlugin、externals、一堆兼容 loader 把结果“硬掰”过来Rollup 生态在纯 ESM 时代更顺手但是处理某些历史包袱时要额外配置。Rolldown 是 Vite 6 推出的基于 Rust 的打包器目标是完全替代 Rollup。我的理解是Vite 已经用 esbuild 解决了依赖预构建的速度问题Rolldown 要解决的是“生产构建阶段还用 JS 写的 Rollup 才能做精细打包导致复杂度高、速度慢”的问题。Vite 6 中通过rolldown版本或实验选项开启后生产构建速度相比 Rollup 有数倍提升对于大项目感知明显。2.3 配置心智模型少即是多对比万物皆可配置Vite 的配置设计是一套“收敛的默认值 命中率高的关键项”比如resolve.alias、server.port、build.outDir、build.rollupOptions。大部分小项目只需要改几个字段就能跑起来。而 Webpack 的默认值非常保守几乎所有的核心能力都要显式配置后才能用比如需要配置module.rules才能让 webpack 识别.jsx、.ts、.css文件需要配置devServer才能启用开发服务器需要配置optimization.splitChunks才有代码分包需要配置target: web、mode: production等一堆基础字段。这套“低声望默认值”设计的好处是极致可控坏处是新手一上来看到几百行配置文件很难分清哪一行是必要的、哪一行是历史遗留。在实际博客项目中我感觉 Vite 的抽象层级更适合中小型项目和开发者个人项目而 Webpack 适合那种“没有它完全跑不起来”的大型工程或者维护多年、已经积累了整套自定义构建逻辑的老项目。接下来用我的博客项目分别搭一遍直接展示关键配置。3. 实操搭建两套博客平台构建实践3.1 技术栈与目录结构为了公平比较我让两个版本的博客使用完全相同的技术栈React 18 TypeScriptreact-router-dom 6多页面路由react-markdown gray-matterMarkdown 解析和 frontmatter 处理highlight.js代码高亮构建目标生成纯静态站点并配合一个简单的 Node 脚本做预渲染目录结构blog-platform/ ├── content/ │ ├── posts/ │ │ ├── hello-vite.md │ │ ├── webpack-vs-vite.md │ │ └── ... │ └── images/ ├── src/ │ ├── components/ │ ├── layouts/ │ ├── pages/ │ ├── styles/ │ ├── utils/ │ └── main.tsx ├── scripts/ │ ├── prerender.mjs │ └── fetch-posts.mjs ├── vite.config.ts └── webpack.config.cjs两个版本的源码共用区别只在构建配置上。这样做才能公平对比到底哪套工具在处理相同业务时更快、更省心。3.2 Vite 博客搭建核心配置逐项拆解先说 Vite 的配置完整文件如下// vite.config.ts import { defineConfig } from vite import react from vitejs/plugin-react import { resolve } from path export default defineConfig({ base: /blog/, // 部署到子路径时的公共基础路径 plugins: [react()], resolve: { alias: { : resolve(__dirname, src), #content: resolve(__dirname, content), }, }, server: { port: 5173, host: true, }, build: { outDir: dist, sourcemap: false, target: es2020, minify: terser, terserOptions: { compress: { drop_console: true, drop_debugger: true, }, }, rollupOptions: { input: { main: resolve(__dirname, index.html), // 如果有额外的 HTML 页面可以继续加 }, output: { manualChunks: { react-vendor: [react, react-dom, react-router-dom], markdown-vendor: [react-markdown, gray-matter], }, }, }, }, })这里有几个关键配置专门说明下base: /blog/博客如果部署在 GitHub Pages 子路径下base决定所有静态资源JS/CSS/图片的 URL 前缀。不配置会被访问成/assets/index-abc123.js没有子路径前缀部署后样式全部丢失。build.target指定产物语法降到 ES2020。配合vitejs/plugin-react的 Babel 转换可以安全使用可选链、空值合并等语法同时避免过度降级产生的 polyfill 体积。build.minify: terser这地方我单独拿出来说后面有专门一节。Vite 默认是esbuild体积小但压缩率不如 terser。我的博客对包体积敏感所以改成了 terser但要注意 terser 打包速度明显更慢小项目无所谓大项目要权衡。manualChunks把 react 全家桶和 markdown 相关库拆成独立 chunk用户访问文章页时只需加载一次 vendor 包后续页面切换都走缓存首屏二次访问速度提升很好。处理 Markdown 文件我是用一个简单的虚拟模块方案构建前用脚本把content/posts/*.md转成src/posts-data.ts再通过import.meta.glob在运行时导入// src/posts-data.ts 由 scripts/fetch-posts.mjs 自动生成 export const posts import.meta.glob(../content/posts/*.md, { eager: true })import.meta.glob是 Vite 原生支持的按模式导入配合eager: true可以直接拿到内容非常适合内容站点的构建模式。3.3 Webpack 博客搭建经典配置详细说明Webpack 这边配置复杂一些但每一块都能解释明白// webpack.config.cjs const path require(path) const HtmlWebpackPlugin require(html-webpack-plugin) const MiniCssExtractPlugin require(mini-css-extract-plugin) const CssMinimizerPlugin require(css-minimizer-webpack-plugin) const TerserPlugin require(terser-webpack-plugin) const { CleanWebpackPlugin } require(clean-webpack-plugin) module.exports (env, argv) { const isProd argv.mode production return { entry: ./src/main.tsx, output: { path: path.resolve(__dirname, dist), filename: isProd ? assets/js/[name].[contenthash:8].js : assets/js/[name].bundle.js, publicPath: /blog/, }, resolve: { extensions: [.tsx, .ts, .js, .jsx], alias: { : path.resolve(__dirname, src), #content: path.resolve(__dirname, content), }, }, module: { rules: [ { test: /\.(ts|tsx|js|jsx)$/, exclude: /node_modules/, use: [babel-loader], }, { test: /\.css$/, use: [ isProd ? MiniCssExtractPlugin.loader : style-loader, css-loader, postcss-loader, ], }, { test: /\.(png|jpe?g|gif|svg|webp)$/, type: asset/resource, generator: { filename: assets/images/[name].[contenthash:8][ext], }, }, { test: /\.md$/, type: asset/source, }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ template: ./index.html, inject: true, }), ...(isProd ? [ new MiniCssExtractPlugin({ filename: assets/css/[name].[contenthash:8].css, }), ] : []), ], optimization: { minimize: isProd, minimizer: [ new TerserPlugin({ terserOptions: { compress: { drop_console: true, drop_debugger: true }, }, extractComments: false, }), new CssMinimizerPlugin(), ], splitChunks: { chunks: all, cacheGroups: { reactVendor: { test: /[\\/]node_modules[\\/](react|react-dom|react-router-dom)[\\/]/, name: react-vendor, priority: 10, }, markdownVendor: { test: /[\\/]node_modules[\\/](react-markdown|gray-matter)[\\/]/, name: markdown-vendor, priority: 8, }, }, }, runtimeChunk: single, }, } }这段配置里值得留意的是Webpack 需要显式告诉它“哪些扩展名值得去解析”resolve.extensions不配.tsx它根本不会找.tsx文件type: asset/source处理 Markdown 时是直接作为字符串输出这个等价于 Vite 里import?raw的用法splitChunks.cacheGroups是 Webpack 的分包方案和 Vite 的manualChunks逻辑类似但语法完全不同刚迁移的人很容易把两者搞混。3.4 两套配置的启动与构建实测对比拿同一台机器MacBook Pro M1 Pro16GB 内存跑两个项目结果差异非常明显阶段ViteWebpack冷启动 Dev Server约 900ms约 4.2s热更新改 1 个 Markdown 100ms约 1.8s生产构建约 3.6s约 6.5s产物总大小328KBgzip 后392KBgzip 后这个结果在我的预期内Vite 在 Dev Server 的启动和 HMR 上优势是压倒性的生产构建速度也更快。原因很简单——Vite 开发阶段不打包启动快了 4 倍以上生产阶段用 Rollup 做更精细的 Tree Shaking体积更小。Webpack 则依赖自身的模块热替换机制和 Terser 压缩耗时更高。不过这里也要说明生产构建的时间差异在“项目规模”面前会放大或缩小。博客这种规模很小Vite 的优势更多体现在开发体验上换成大型后台管理系统Webpack 慢得会更明显但 Vite 的生产构建由于要编译大量依赖预构建可能也会有额外开销。4. 构建产物体积与优化策略对比4.1 minify 选型minify: terser与minify: esbuild的区别这是大家问得最多的一个点Vite 里build.minify支持两种取值esbuild默认和terser。两者压缩能力的差异主要体现在以下方面维度esbuildterser压缩速度非常快Rust 实现慢JS 实现体积优化程度基础优化去空白、去注释、变量缩短深度优化更激进的变量重命名、属性压缩、合并兼容性产物可能利用现代语法做 mangle不适合极老浏览器支持更多 ES5 语法细节附加能力无法精细控制某些 obscure 选项compress、mangle有大量细粒度选项我实测一个包含 highlight.js 和图表库的博客页面esbuild 压缩后的 JS 总大小是 422KB换成 terser 后降到了 411KB大约省了 2.6%。别小看这个比例内容型站点多了之后累积非常可观。如果你的页面体积还没有严重超标、构建速度更稀缺默认的 esbuild 就够用如果页面确实大、对体积敏感切换到 terser 是一个划算的选择。Webpack 这边没有这种“内置双引擎”的选项它默认就是 TerserPlugin。想用 esbuild 压缩就得装esbuild-loader好处是快坏处是少了 terser 的深度压缩。两套工具的默认策略其实体现了不同理念Vite 以速度优先Webpack 以压缩质量为优先。4.2 代码分包策略对比splitChunks 与 manualChunks博客的页面加载瓶颈在于 vendor 包体积。如果 react、react-dom、router 这些库全部揉进一个 bundle首屏要下载 200KB 的 JS移动端体验会很差。分包本质上是把稳定不变的第三方库拆出去利用浏览器缓存减少重复下载。Vite 里用build.rollupOptions.output.manualChunks控制可以传一个对象把特定 package 名固定到指定 chunk也可以传函数逐模块判定。Webpack 里用optimization.splitChunks它更强大但也更容易配错。我在 Webpack 侧分了react-vendor和markdown-vendor两个缓存组并把runtimeChunk单独提取出来这样修改业务代码时不会导致 vendor 包 hash 变化缓存命中率大幅提升。这个模式在 Vite 侧也很重要。如果全量打包Vite 产物可能生成十几个小 chunk因为import.meta.glob会按模块拆HTTP 请求数量变多反而不利于缓存。所以我在 Vite 里也做了同样的手工分组保证 chunk 是“稳定的、意图明确的”。4.3 静态资源与 SEO 处理差异博客类站点对静态资源和 SEO 的关注远高于普通后台系统两套工具在这块的处理也有明显不同图片资源Vite 默认小于assetsInlineLimit默认 4KB的图片会被转成 base64 内联注意这会导致 HTML 体积膨胀对文章首屏加载有负面影响我通常把这个值调成 0 或 1KBWebpack 的type: asset/asset/resource也有类似的 inline 策略但默认值不像 Vite 那样激进。预渲染/SSGVite 可以直接使用vite-plugin-ssr或Vitepress这种完整框架它天然支持prerender: true的静态导出模式Webpack 则需要借助prerender-spa-plugin已年久失修或自己写 Puppeteer 脚本。我的博客是自己写了一个 Node 脚本启动构建后的静态服务用 headless 浏览器抓取页面 HTML 并写入文件这个方案两套工具都能用但 Vite 生态的集成度更高。构建后的部署路径baseVite与publicPathWebpack都负责生成资源 URL 前缀但 Vite 还有build.assetsDir可以细分资源子目录Webpack 的output.assetModuleFilename则在每个 rule 里单独设置。自定义程度越高越灵活但出错概率也成倍增加。5. 常见问题与排查技巧实录5.1 Vite[vite:esbuild-transpile] transform failed with 2 errors这个报错很经典我的博客项目在引入highlight.js后碰到了。完整错误类似[vite:esbuild-transpile] transform failed with 2 errors: 1. static/js/general-9.js: 您的代码包含一个无法被 esbuild 识别的语法 2. 某些依赖使用非标准语法建议配置 optimizeDeps.exclude 或 optimizeDeps.include排查思路是这样的Vite 的依赖预构建使用 esbuild它会对 node_modules 里的所有依赖做语法转换。但某些老旧的包或者某些打包工具产出的 UMD 格式代码包含 esbuild 不认识的写法比如极老的 ASI 问题、with语句或自定义宏等。解决方案按优先级升级依赖先检查出错的包是否有新版本问题往往出在旧版本构建产物不规范。optimizeDeps.exclude排除让该包跳过预构建但代价是运行时可能需要原生 ESM 支持。optimizeDeps.include强制预构建对这个包单独指定预构建入口让 esbuild 后续再转换一次有时能绕过首次转换的语法坑。resolve.alias指向该包源码让构建器直接用源码而不是 dist 产物需要确保源码能被 Vite 的 transform 链路处理。我的处理是升级highlight.js到最新版本问题直接解决。这类问题的核心其实是依赖生态质量和构建工具本身没有绝对关系但 Vite 对依赖的“规范化”要求比 Webpack 高因为 Webpack 可以通过各种 loader 把乱七八糟的代码“挽尊”起来。5.2 Webpack构建本地依赖monorepo/workspace时的根源问题另一个高频问题是“构建本地依赖”。本来我们的博客想用 monorepo 里的一个共享工具库myblog/commonWebpack 默认会把它当 node_modules 处理导致修改源码后构建产物不更新。原因在于 Webpack 的resolve.symlinks默认值是true在 monorepo 中 workspace 包通过软链接引用Webpack 会解析真实路径后按node_modules规则处理但watch默认只监听node_modules之外的文件变化。解决办法有两种在watchOptions.ignored里排除 node_modules并把resolve.symlinks设为false但这样会引入重复打包问题利用module.rules的include字段对该库单独指定编译规则把它当作普通源码处理同时关闭缓存忽略规则。我在 monorepo 里最终选了解析源码 ts-loader或babel-loader直接编译该库保证修改源码立即生效。Vite 这边处理类似场景更顺畅因为预构建缓存会监听依赖 source 的变化optimizeDeps.include配合server.fs.allow基本能覆盖 workspace 场景。这也是为什么现在很多新 monorepo 项目优先考虑 Vite 的原因之一。5.3 共同问题产物路径错误、缓存失效与 contenthash 变化最后整理一个我在两套工具里都踩过的坑——构建产物路径、缓存失效问题base/publicPath配错部署到子路径时资源 404页面白屏。排查技巧是打开浏览器开发者工具看网络面板里资源请求的完整 URL确认前缀是否和实际部署路径一致。我经常看到有人拿“./相对路径”硬撑结果子路由刷新时资源路径也变了最好还是显式配置。contenthash变化导致缓存失效Vite 的rollupOptions.output.assetFileNames、Webpack 的filename默认都带 hash但如果你在分包时没有把第三方 vendor 和业务代码完全隔离业务代码一次改动会导致 vendor 也换 hash缓存全废。排查方法是先看构建产物名字稳定不变的包如果 hash 也变了检查 splitChunks 或 manualChunks 里是否混入了业务模块。sourcemap开启影响体积博客这种内容站点一般不需要线上 sourcemap构建时关掉能省不少磁盘空间和上传时间。要调试线上问题可以在构建机保留 sourcemap 文件而不部署到 CDN 上用独立上传通道存档。这些遗留问题其实和生产构建工具本身没有必然联系但两套工具各自的默认值、配置项命名差异很容易让人混淆。我在迁移过程中是真切体会到一点理解底层机制比背配置项重要得多。Vite 再怎么快如果不知道预构建缓存套路遇到依赖更新后缓存不刷新还是懵Webpack 再怎么繁琐只要理解模块解析和 chunk 生成流程大多数配置都能靠排查思路推出来。这也是我用博客平台这样一个“小而全”的项目做对比实验的原因——规模不大但构建工具涉及的核心知识点全都能覆盖到。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。