一 概述
??在构建大型VUE项目时,比如好几百个路由,将路由页面打包到一起,JavaScript包会变得非常大,影响首页的加载性能,而这些路由页面中的绝大多数并非首页加载所必须的,这时我们需要使用路由懒加载的功能,把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件。结合 Vue 的异步组件和 Webpack 的代码分割功能,可以轻松实现路由组件的懒加载。
二 分析
??vue中推荐使用 import() 来实现路由懒加载,不仅可以充分利用webpack的代码分割功能,还能发挥浏览器的 prefetch 预加载功能。 ??如上图,preload 表示对应js文件是加载当前页面所必须的,需要立即加载;prefetch 所标记的js就是我们对应的路由懒加载页面,表示这个js文件并非加载当前页面所必须的,在用户以后的交互中有可能会用到,浏览器会在当前页面资源加载并渲染完成以后利用空闲的时间加载这些js文件并缓存,以备将来使用,这样即提高了首页的加载性能,在跳转到其它页面时也不会因需加载js资源而影响其它页面的加载性能,如下图中的"about.js",第一次请求是prefetch预加载,此时我们并没有访问about页面,当我们跳转到about页面时,第二次请求"about.js"中的size显示"prefetch cache",Status Code显示为"200 OK (from prefetch cache)",表示来自于预加载缓存。(预加载功能并非所有浏览器都支持)
三 实现
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: () => import( '../views/Home')
},
{
path: '/about',
name: 'About',
component: () => import( '../views/About')
}
]
const router = new VueRouter({
routes
})
export default router
四 进阶
??上述代码中我们发现有一段注释: /* webpackChunkName: “about” */,此注释是指定打包后的模块名,我们在webpack代码拆分时会指定模块命名规则,比如:
chunkFilename: [name].[contenthash:10].js
而这里的webpackChunkName正是指定上述中的name。当然,如果我们在路由中将多个webpackChunkName设置为了相同的值,则这些路由会被打包到同一个文件中。webpackChunkName是webpack中的一种魔法注释(估且这么翻译吧),除此之外,还有如下几种:
import(
`./locale/${language}`
)
- webpackInclude: 正则匹配包含哪些
- webpackExclude: 正则匹配不包含哪些
- webpackMode: 指定以不同的模式解析动态导入
- lazy:默认,为每个 import() 导入的模块,生成一个可延迟加载(lazy-loadable) chunk。
- lazy-once:生成一个可以满足多个 import() 调用的单个可延迟加载(lazy-loadable) chunk。此 chunk 将在第一次 import() 调用时获取,随后的 import() 调用将使用相同的网络响应。注意,这种模式仅在部分动态语句中有意义,例如 ,其中可能含有多个被请求的模块路径。
- eager:不会生成额外的 chunk,所有模块都被当前 chunk 引入,并且没有额外的网络请求。仍然会返回 Promise,但是是 resolved 状态。和静态导入相对比,在调用 import()完成之前,该模块不会被执行。
- weak:尝试加载模块,如果该模块函数已经以其他方式加载(即,另一个 chunk 导入过此模块,或包含模块的脚本被加载)。仍然会返回 Promise,但是只有在客户端上已经有该 chunk 时才成功解析。如果该模块不可用,Promise 将会是 rejected 状态,并且网络请求永远不会执行。当需要的 chunks 始终在(嵌入在页面中的)初始请求中手动提供,而不是在应用程序导航在最初没有提供的模块导入的情况触发,这对于通用渲染(SSR)是非常有用的。
五 动态添加路由
??假如我们需要编写一些前端框架或组件库,在组件库中,我们需要根据运行时的条件或配置动态添加路由,或者是用户在进行某些操作后触发安装某些路由,我们需要用到vue-router用到的两个方法,addRoute 和 addRoutes,如:
let routerArray = [{
path: '/other',
name: 'Other',
component: () => import( '../views/Other')
}]
router.addRoutes(routerArray )
六 路由动态生成
??假如我们需要通过后端接口返回的数据来加载路由,比如后端返回如下数据:
[{
path: '/demo',
name: 'Demo',
url: 'Demo'
}]
此时我们可以这样写:
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: () => import( '../views/Home')
},
{
path: '/about',
name: 'About',
component: () => import( '../views/About')
}
]
function generateRouter (url) {
return new Promise((resolve, reject) => {
import( '@/views/' + url + '.vue').then(module => {
resolve(module)
}).catch(e => {
reject(new Error('动态加载路由失败'))
})
})
}
function createRouters (items) {
const dynamicItems = items.map(({ path, name, url }) => {
return {
path,
name,
component: () => generateRouter(url)
}
})
routes.push(...dynamicItems)
return new VueRouter({ routes })
}
export default createRouters
import createRouters from './router'
const dynamicRoutes = [{
path: '/demo',
name: 'Demo',
url: 'Demo'
}]
const router = createRouters(dynamicRoutes)
??其中 webpackChunkName: "[request] 中的 request 指的是后面表达式传入的第一个参数,此处指的就是url的值。 需要注意的是webpack在打包时, import(/* webpackChunkName: “[request]” */ ‘@/views/’ + url + ‘.vue’) 会将满足条件的所有文件全部打包,以确保运行时能够正确使用,此处的条件就是根据表达式匹配,即:@/views/目录下所有以.vue结尾的文件,所以为尽可能减少打包不必要的文件,应尽可能缩小上述的范围。
七 禁用指定路由的prefetch功能
??假如我们需要取消某个路由js的预加载功能,不要天真的以为将四中的 webpackPrefetch: true 改为 webpackPrefetch: false 就可以了,这样设置是不起作用的,需要从webpack添加prefetch功能的方式入手。webpack实现prefetch和preload功能是使用了插件 preload-webpack-plugin,解铃还须系铃人,我们需要从这个插件入手。 ??如果需全局禁用prefetch功能,我们可以这样配置:
chainWebpack: config => {
config.plugins.delete('prefetch')
}
??如果需要禁用匹配指定规则的路由的prefetch功能,我们可以这样设置:
chainWebpack: config => {
config.plugin('prefetch').tap(options => {
options[0].fileBlacklist = options[0].fileBlacklist || []
options[0].fileBlacklist.push(/about(.)*?\.js/)
return options
})
}
|