vue-router 路由组件懒加载完整指南异步组件、Code-Splitting 与 Chunk 分组实战【免费下载链接】vue-router The official router for Vue 2项目地址: https://gitcode.com/gh_mirrors/vu/vue-router本文以 vue-routerVue 2 官方路由仓库中的 Lazy Loading 文档德文版与 英文版指南 为核心脉络讲解如何利用 Vue 的异步组件机制与 Webpack 的代码分割Code-Splitting能力把每个路由的组件拆分为独立 chunk、仅在路由被访问时才加载从而显著减小首屏 bundle 体积、缩短页面加载时间。读完本文你将掌握require.ensure、AMD 风格require与动态import()三种懒加载写法学会用命名 chunk 将同一路由下的多个组件合并为单个异步 chunk并理解 vue-router 在导航过程中等待异步组件解析完成的底层实现原理。为什么需要懒加载Bundle 膨胀与首屏性能使用打包器Bundler构建应用时所有 JavaScript 会被打包进一个或多个 bundle。随着应用规模增长这个 bundle 会变得相当大直接影响页面加载时间。更高效的做法是把每个路由的组件拆分成独立的 chunk只在路由被访问时才按需加载。这正是 Webpack Code-Splitting代码分割的用武之地。所谓分割就是把原本一个整体的大 bundle按照一定规则切成多个小文件配合 Vue 的异步组件机制vue-router 可以做到路由匹配到哪个组件才去请求哪个 chunk。从仓库的示例配置可以看到 Webpack 对 chunk 命名的支持examples/webpack.config.js 中设置了output.chunkFilename: [id].chunk.js与publicPath: /__build__/动态加载的异步 chunk 会按此规则生成独立的.chunk.js文件与主 bundle 分离存放。基础组合Vue 异步组件 × Webpack Code-Splitting实现路由懒加载的技术基础是两个成熟特性的组合Vue 异步组件Vue 2 允许组件以工厂函数形式定义函数被调用时才真正解析出组件定义。Webpack Code-SplittingWebpack 提供专门语法标注分割点split point被标注的模块会被自动拆成独立异步 chunk并在代码执行到该处时才发起请求。组合方式很简单把路由组件定义成一个异步解析函数。以原德文文档中的经典写法为例使用 Webpack 1 时代的require.ensure语法const Foo resolve { // require.ensure 是 Webpack 用于代码分割的特殊语法 require.ensure([./Foo.vue], () { resolve(require(./Foo.vue)) }) }这里Foo不再是一个普通组件对象而是一个接收resolve回调的工厂函数。Webpack 会把require.ensure的依赖列表中的模块此处为./Foo.vue单独打包成一个异步 chunk当路由首次需要渲染Foo时vue-router 会调用这个工厂函数Webpack 随即发起该 chunk 的网络请求加载完成后通过resolve把组件定义交还给路由。三种懒加载写法对比原德文文档给出了 Webpack 1 时代的两种写法而仓库英文文档与示例则补充了更现代的动态import()语法。三种方式本质等价可按工程年代与团队习惯选用。写法一require.ensureWebpack 1德文文档主推const Foo resolve { // 依赖数组 回调函数回调内 resolve 组件 require.ensure([./Foo.vue], () { resolve(require(./Foo.vue)) }) }写法二AMD 风格require简写德文文档指出还有一种 AMD 风格的简化写法把整个流程压缩成一行const Foo resolve require([./Foo.vue], resolve)require接收依赖数组和回调加载完成后直接把模块传给resolve。这一写法同样在仓库示例中被标注为 Webpack 1 的替代方案见 examples/lazy-loading/app.js 中的注释// If using Webpack 1, you will have to use AMD syntax or require.ensure。写法三动态import()Webpack 2推荐仓库英文文档与示例共同推荐动态import()语法——它本身就是 ES 提案dynamic-importWebpack 2 起支持用它标注代码分割点且返回一个 Promise// 单组件最简写法返回 Promise 的工厂函数 const Foo () import(./Foo.vue)这一写法直接对应 Vue 2.3 异步组件的新形态——工厂函数返回 Promise。在 examples/lazy-loading/app.js 中仓库正是用它定义懒加载组件Foo并注明该语法是已废弃的System.import()的替代。Babel 注意事项英文文档明确指出若使用 Babel 转译需要添加syntax-dynamic-import插件或较新版本 Babel 的对应支持否则 Babel 无法正确解析import()动态导入语法。返回 Promise 的工厂函数写法英文文档还给出了一种不依赖打包器、完全由 Promise 驱动的定义方式可用于演示异步组件的本质工厂函数返回 PromisePromise resolve 出组件定义const Foo () Promise.resolve({ /* component definition */ })仓库 examples/lazy-loading-before-mount/app.js 中有一个更生动的实战版本——用setTimeout模拟异步加载组件在 10ms 后才 resolveconst Foo () new Promise(resolve { setTimeout(() resolve({ template: div classfooThis is Foo/div }) , 10) })路由配置无需任何改动懒加载的核心便利在于组件定义方式变了但路由配置完全不变。原德文文档强调我们照常使用Foo即可const router new VueRouter({ routes: [ { path: /foo, component: Foo } ] })英文文档给出了同样的结论——Nothing needs to change in the route config, just useFooas usual。Foo究竟是普通组件对象还是异步工厂函数对路由表完全透明这让懒加载可以零成本地渐进引入到既有项目中。将同一路由下的组件分组到同一个 Chunk有时我们希望把同一条路由下嵌套的所有组件合并进同一个异步 chunk减少网络请求次数。Webpack 提供了命名 chunknamed chunks特性。Webpack 1require.ensure第三参数指定 chunk 名德文文档给出的是把 chunk 名作为require.ensure的第三个参数const Foo r require.ensure([], () r(require(./Foo.vue)), group-foo) const Bar r require.ensure([], () r(require(./Bar.vue)), group-foo) const Baz r require.ensure([], () r(require(./Baz.vue)), group-foo)Webpack 会把所有具有相同 chunk 名的异步模块打进同一个异步 chunk。同时德文文档特别指出因为 chunk 名已经承担了分组职责require.ensure的依赖数组不再需要显式列出依赖因此传空数组[]即可。Webpack 2.4动态import()的 Magic Comment英文文档展示了 Webpack 2.4 的等价写法——借助import()的 magic comment 语法webpackChunkNameconst Foo () import(/* webpackChunkName: group-foo */ ./Foo.vue) const Bar () import(/* webpackChunkName: group-foo */ ./Bar.vue) const Baz () import(/* webpackChunkName: group-foo */ ./Baz.vue)webpack 会将 chunk 名相同的异步模块归入同一异步 chunk。仓库 examples/lazy-loading/app.js 正是这样实现的Bar与Baz都声明了webpackChunkName: bar随后在路由表中把Baz嵌套为Bar的子路由const Bar () import(/* webpackChunkName: bar */ ./Bar.vue) const Baz () import(/* webpackChunkName: bar */ ./Baz.vue) const router new VueRouter({ mode: history, base: __dirname, routes: [ { path: /, component: Home }, { path: /foo, component: Foo }, { path: /bar, component: Bar, children: [ { path: baz, component: Baz } ] } ] })对应组件 Bar.vue 中渲染router-viewBaz.vue 的模板注释直接写明 Im loaded in the same chunk with Bar.实证了分组效果。源码原理导航如何等待异步组件解析懒加载之所以能无缝融入路由系统是因为 vue-router 在导航确认流程中加入了专门的异步组件解析环节。核心实现在 src/util/resolve-components.js 的resolveAsyncComponents函数中它的执行位置位于 src/history/base.js 的导航守卫队列const queue: Array?NavigationGuard [].concat( // in-component leave guards extractLeaveGuards(deactivated), // global before hooks this.router.beforeHooks, // in-component update hooks extractUpdateHooks(updated), // in-config enter guards activated.map(m m.beforeEnter), // async components resolveAsyncComponents(activated) // ← 异步组件解析挂在这里 )也就是说异步组件解析被当作导航流程中的一个守卫排在全局beforeEach钩子之后执行。resolveAsyncComponents内部做了几件关键的事识别异步组件遍历所有匹配到的路由记录matched若组件定义def是函数且没有cid属性def.cid undefined就认定它是异步解析函数——而不是 Vue 构造器。源码注释解释了这个设计vue-router 刻意不使用 Vue 默认的异步解析机制而是自行挂起导航直到组件解析完成。收集并逐一解析对每个异步组件执行def(resolve, reject)支持回调风格resolve/reject与 Promise 风格检测返回值res.then是否为函数两种异步组件形态与文档中resolve ...回调和() Promise两种写法一一对应。ES Module 兼容解析结果若带__esModule标记或Symbol.toStringTag Module会自动取其.default导出——这保证了import(./Foo.vue)这种 ESM 加载结果能被正确使用。缓存解析结果def.resolved被写回工厂函数match.components[key]被替换为最终组件避免重复加载解析出的普通对象还会经过_Vue.extend转换为组件构造器。全部就绪才放行用pending计数所有异步组件都 resolve 后才调用next()继续导航若任一解析失败reject或被同步throw则构造错误并next(error)中止导航配合导航失败处理机制向调用方暴露错误。防重复回调once包装确保resolve/reject只会生效一次——源码注释提到Webpack 2 中require.ensure也返回 Promise箭头函数简写可能让回调被额外触发一次once正好兜住这种情况。解析完成后组件通过 src/components/view.js 中router-view的渲染逻辑matched.components[name]挂载到视图上。整个链路确保了进入路由 → 按需加载 chunk → 组件就绪 → 渲染用户无需感知加载过程。完整可运行示例与验证方式仓库在 examples/lazy-loading 目录提供了一个可直接运行的完整示例覆盖了本文讨论的所有要点app.js路由配置包含普通组件Home、动态import()懒加载的Foo、命名 chunk 分组的Bar/Baz以及一个带动态参数的懒加载路由/a/:tags*通过setTimeout200ms 模拟异步解析对应 GitHub issue #2719 中动态参数与懒加载结合的边界场景。Foo.vue/Bar.vue/Baz.vue三个被懒加载的组件模板页面文案提示开发者在 Chrome DevTools 的 Network 面板中观察懒加载效果。index.html示例入口页通过script src/__build__/lazy-loading.js加载主 bundle异步 chunk 则按需请求。启动方式在仓库根目录安装依赖后运行 Webpack 开发服务器即可访问/lazy-loading/示例页切换路由时观察 Network 面板能看到对应的.chunk.js文件在首次访问该路由时才被请求。端到端测试 test/e2e/specs/lazy-loading.js 给出了可验证的行为断言包括点击导航切换/foo、/bar、/bar/baz断言对应懒加载组件文本渲染正确This is Foo!、This is Bar!、Baz。直接访问深层懒加载路由/lazy-loading/foo、/lazy-loading/bar/baz验证刷新页面时异步 chunk 同样能按需加载并渲染。验证/a/b/c这种带动态参数的路由无论是直接访问还是从首页点击进入懒加载组件的$route.path均正确显示/a/b/c。这些断言覆盖了首次访问触发加载与直接 URL 进入两条路径说明懒加载不仅对站内跳转有效对深链接deep link同样成立。实践建议与注意事项综合原文档与仓库实现落地懒加载时有几点值得留意工具链版本匹配Webpack 1 项目使用require.ensure或 AMD 风格requireWebpack 2 推荐动态import()。命名 chunk 的 magic comment 写法要求Webpack 2.4英文文档明确标注老版本只能使用require.ensure第三参数。Babel 转译使用 Babel 时需启用动态导入解析插件如syntax-dynamic-import否则import()语法无法被正确解析。分组粒度把同一嵌套路由下的兄弟组件放进同一个命名 chunk如示例中的Bar/Baz既能按路由粒度按需加载又能把同一次导航需要的多个组件合并为一次请求在加载次数与拆包粒度之间取得平衡。异步失败处理从 src/util/resolve-components.js 的实现可见异步组件加载失败会触发导航中止next(error)因此网络异常等场景下建议配合路由错误处理或全局错误上报机制。动态参数场景懒加载与动态路径参数如/a/:tags*可以正常组合但注意如示例所示参数路径不应被错误编码仓库 e2e 测试对此有专门覆盖。围绕按路由拆包、按需加载这一目标vue-router 通过把异步组件解析注入导航守卫队列让懒加载对路由配置完全透明——开发者只需用异步工厂函数替换组件定义即可在几乎零改造成本下获得显著的首屏加载性能收益。相关参考资料Lazy Loading 德文原文档、英文版指南、完整示例、异步解析实现、导航守卫队列、e2e 测试。【免费下载链接】vue-router The official router for Vue 2项目地址: https://gitcode.com/gh_mirrors/vu/vue-router创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考