
前端路由【免费下载链接】vue-router The official router for Vue 2项目地址https://gitcode.com/gh_mirrors/vu/vue-router点击查看免费下载导读当应用通过模块打包器Webpack构建时所有组件最终会被合并进一个体积庞大的 JavaScript bundle拖慢页面首次加载。本文以 Vue 2 官方路由 vue-router 为背景完整讲解路由组件懒加载Lazy Loading的两种实现语法——require.ensure与动态import()以及如何将多个组件分组进同一异步 chunk并深入仓库源码剖析 vue-router 内部解析异步组件的完整机制。读完本文你将掌握在不改动路由配置的前提下按路由粒度切分代码、按需加载组件的标准工程化方案。为什么需要懒加载路由组件使用 Webpack 等模块打包器构建 SPA 时所有被import的模块都会被打进最终产物。随着路由与页面越来越多生成的 JavaScript 文件会迅速膨胀直接影响页面的加载时间与首屏体验。更高效的做法是把每个路由对应的组件拆分成独立的代码块chunk只有当该路由真正被访问时才去加载对应的组件。这正是路由级代码分割Route-level Code Splitting的核心思想而 vue-router 让这件事变得非常简单——官方文档 docs/guide/advanced/lazy-loading.md 与 西班牙语版 都给出了完整方案将 Vue 的异步组件机制与 Webpack 的代码分割能力结合起来即可。前提Vue 异步组件与 Webpack 代码分割实现懒加载需要两个基础能力Vue 的异步组件Async Components组件可以不直接传入组件对象而是传入一个工厂函数由 Vue 在需要渲染时再去解析它。Webpack 的代码分割Code Splitting通过特殊语法告诉 Webpack 这里是一个分割点打包时把该模块独立成块。在 vue-router 中路由记录RouteRecord里的component字段可以是一个函数。从源码 src/util/resolve-components.js 的resolveAsyncComponents可以看到其判定逻辑// if its a function and doesnt have cid attached, // assume its an async component resolve function. if (typeof def function def.cid undefined) { hasAsync true pending // ... }即只要组件定义是一个函数且没有挂载cidVue 组件构造器标识vue-router 就将其视为异步组件并在导航过程中等待其解析完成。方式一require.ensure——Webpack 1 时代的经典语法最传统的写法是把路由组件定义为异步解析函数配合 Webpack 的require.ensure指定分割点const Foo resolve { // require.ensure 是 Webpack 的特殊语法用于标记代码分割点。 require.ensure([./Foo.vue], () { resolve(require(./Foo.vue)) }) }require.ensure接受三个参数依赖列表、回调函数在依赖加载完成后执行、以及可选的 chunk 名称。回调中通过resolve(require(./Foo.vue))把加载到的组件交给 vue-router 的异步解析器。由于require.ensure在 Webpack 中还支持 AMD 风格的简写形式上述代码可以进一步压缩为一行const Foo resolve require([./Foo.vue], resolve)这种写法与官方文档 docs-gitbook/es/advanced/lazy-loading.md 中给出的示例完全一致也是 Webpack 1 时代推荐的做法。仓库示例 examples/lazy-loading/app.js 的注释里也保留了这段历史写法作为参考// If using Webpack 1, you will have to use AMD syntax or require.ensure: // const Foo resolve require([./Foo.vue], resolve)方式二动态 import()——现代推荐的 Promise 写法如果使用 Webpack 2 及以上版本可以改用 TC39 动态导入提案dynamic import语法import()会返回一个 Promiseimport(./Foo.vue) // 返回一个 Promise将动态 import 与 Vue 异步组件结合最简洁的懒加载写法就是const Foo () import(./Foo.vue)() import(./Foo.vue)本身就是一个返回 Promise 的工厂函数vue-router 会等到 Promise resolve 出组件对象后才继续导航。官方文档 docs/guide/advanced/lazy-loading.md 明确指出Webpack 会在遇到import()时自动进行代码分割。需要特别说明的是如果项目使用 Babel 转译需要安装并配置babel-plugin-syntax-dynamic-import插件否则 Babel 无法正确解析import()语法示例 examples/lazy-loading/app.js 的注释中也提示了这一前提。仓库示例正是采用这种方式定义懒加载组件const Foo () import(./Foo.vue)对应组件文件为 examples/lazy-loading/Foo.vue页面文案提示开发者可以打开 Chrome DevTools 的 Network 面板验证懒加载效果。路由配置无需任何改动懒加载组件与普通组件在路由配置中用法完全一样直接把异步工厂函数当作component传入即可无需任何额外配置const router new VueRouter({ routes: [ { path: /foo, component: Foo } ] })这意味着你可以随时把普通组件改为懒加载组件、或把懒加载组件改回普通组件而不触碰路由表。仓库示例 examples/lazy-loading/app.js 中的路由配置完整展示了这一点包括懒加载组件与普通组件混用的场景const router new VueRouter({ mode: history, base: __dirname, routes: [ { path: /, component: Home }, // 普通组件 { path: /foo, component: Foo }, // () import(./Foo.vue) { path: /a/:tags*, name: tagged, component: () new Promise(resolve { // 也可以手动返回一个 Promise模拟异步解析 setTimeout(() { resolve({ template: div.../div }) }, 200) }) }, // ... ] })示例中还演示了一种特殊情况组件定义不一定非要用import()只要工厂函数返回 Promise 即可。例如() new Promise(resolve setTimeout(() resolve({ ... }), 200))vue-router 同样能正确等待其解析——这印证了源码中对返回 Promise 的异步组件的兼容处理详见下文源码剖析。将多个组件分组进同一个 chunk有时我们希望把同一路由下的多个组件打进同一个异步 chunk减少请求次数。此时需要借助具名 chunknamed chunks机制。require.ensure 语法第三个参数指定分组名在require.ensure中通过第三个参数提供 chunk 名称所有同名模块会被 Webpack 归入同一个异步块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)使用具名 chunk 后依赖列表可以留空[]——因为 Webpack 会依据 chunk 名称自动归组无需再显式列出相互依赖的模块。动态 import 语法magic comment 指定分组名使用import()时通过 Webpack 的 magic comment魔术注释webpackChunkName指定 chunk 名称该特性需要 Webpack 2.4 以上版本const 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 名称相同的异步模块合并进同一个异步块。仓库示例 examples/lazy-loading/app.js 就演示了这一场景Bar与Baz同属一个嵌套路由/bar下的子路由baz通过相同的webpackChunkName: bar被分进同一个 chunkconst Bar () import(/* webpackChunkName: bar */ ./Bar.vue) const Baz () import(/* webpackChunkName: bar */ ./Baz.vue) const router new VueRouter({ routes: [ { path: /bar, component: Bar, children: [ { path: baz, component: Baz } ] } ] })对应的组件文件 Bar.vue 与 Baz.vue 中都有相应注释如 Im loaded in the same chunk with Bar.方便在 Network 面板中核对它们确实被合并加载。源码剖析vue-router 如何解析异步组件了解实现原理有助于排查问题。vue-router 对异步组件的解析集中在 src/util/resolve-components.js 的resolveAsyncComponents函数中它本身返回一个导航守卫核心流程如下扫描所有匹配的组件通过flatMapComponents遍历matched路由记录中的全部组件定义。识别异步组件凡是函数且无cid的定义都被视为异步组件并计数pending。调用工厂函数直接执行def(resolve, reject)获取返回结果若结果是 Promise则挂上.then(resolve, reject)。兼容 Vue 2.3 新语法若返回的是{ component: Promise }结构Vue 2.3 起的异步组件新写法也会提取其中的 Promise 等待其 resolveif (res) { if (typeof res.then function) { res.then(resolve, reject) } else { // new syntax in Vue 2.3 const comp res.component if (comp typeof comp.then function) { comp.then(resolve, reject) } } }ES Module 兼容resolve 出的结果若带有__esModule或Symbol.toStringTag Module标记会自动取.default导出isESModule函数。组件注册与导航继续解析成功后调用_Vue.extend()生成组件构造器并写回match.components[key]当所有待解析组件都完成pending 0才调用next()放行导航解析失败则调用next(error)中止导航并给出告警。防重复调用resolve/reject都经过once包装。源码注释特别说明Webpack 2 中require.ensure也会返回 Promise如果用户使用了恰好返回该 Promise 的箭头函数简写resolve/reject 可能被额外调用一次once用于保证只生效一次。关键设计意图在源码注释中写得很清楚vue-router 不使用 Vue 默认的异步解析机制而是主动挂起导航直到目标组件解析完成we want to halt the navigation until the incoming component has been resolved从而保证组件就绪后再进入页面渲染。异步组件解析在导航流程中的位置在 src/history/base.js 的导航确认流程confirmTransition中resolveAsyncComponents作为导航守卫队列的一员被追加执行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钩子、更新钩子与路由内beforeEnter之后位于确认导航之前。整个队列由 src/util/async.js 的runQueue串行驱动——每个守卫调用next()后才进入下一个守卫因此异步组件未解析完成时导航会一直处于等待状态。仓库中 test/unit/specs/async.spec.js 对runQueue的串行执行语义也有对应单测覆盖。如何验证懒加载是否生效仓库示例 examples/lazy-loading/index.html 与配套页面为验证提供了现成环境。按以下步骤即可观察效果运行示例页面进入首页/此时主 bundle 中不包含Foo.vue、Bar.vue、Baz.vue的代码。打开浏览器 DevTools 的Network 面板。点击导航到/foo可以看到Foo.vue对应的独立 chunk 文件在此时才被请求加载。点击导航到/bar/bazBar与Baz的代码会合并为一个 chunk一次性加载因为它们共享webpackChunkName: bar。组件页面中的提示文案如 Foo.vue 中的 I am lazy-loaded. (check out the Networks tab in Chrome devtools)正是引导开发者用 Network 面板核对这一行为。两种写法的选型建议维度require.ensure动态import()适用版本Webpack 1Webpack 2分组需 2.4返回值无回调驱动Promise写法冗长需显式回调简洁() import(...)一行分组方式require.ensure([], fn, chunk-name)第三参数import(/* webpackChunkName: ... */ ...)魔术注释Babel 配合无需额外插件需babel-plugin-syntax-dynamic-import实用建议新项目优先使用() import(./Component.vue)Webpack 2 以下或维护老项目时使用require.ensure当同一嵌套路由下的多个组件需要合并请求时为它们指定相同的 chunk 名称即可。无论哪种写法路由配置本身都不需要任何改动这是 vue-router 懒加载方案最优雅的一点。赞分享前端路由【免费下载链接】vue-router The official router for Vue 2项目地址https://gitcode.com/gh_mirrors/vu/vue-router点击查看免费下载相关推荐Vue Router 路由懒加载实战动态 import、按组分块与源码级解析Vue Router 路由懒加载实战动态 import、按组分块与源码级解析 路由懒加载Lazy Loading Routes是 Vue Router 应前端路由Omi代码分割策略动态import与路由懒加载的实现Omi代码分割策略动态import与路由懒加载的实现 在现代前端应用开发中随着项目规模的增长JavaScript包体积过大导致的首屏加载缓慢问题日益突出。前端Web框架UI组件Vue Router 路由懒加载Lazy Loading Routes实战指南异步组件、代码分割与分组 ChunkVue Router 路由懒加载Lazy Loading Routes实战指南异步组件、代码分割与分组 Chunk 导读 路由懒加载是 Vue 2 应用中前端路由创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考