jingzhiMo.github.io
jingzhiMo.github.io copied to clipboard
angularJs 配合ui-router,webpack, ocLazyLoad懒加载
前言
自从工作之后就极少写文章了,因为空闲时间没这么多,到了周末又想轻松两天,但是周末其实并不轻松,或许归根到底最后就是一个字:懒!这周回家之后,感觉总算可以静下心来做点东西,把之前在项目用ng1.x按需加载的实现整理一下。
需求背景
最近工作用到angularJs
,也就是ng1.x
版本开发一个网站,这个项目其中用ui-router来控制路由,webpack来构建项目。有个比较致命的痛点,ng1.x官方不支持懒加载!
这个项目经过webpack打包之后主要形成两个js文件,一个是vendor.js
,是引入的node_modules
的公用文件,另外一个是app.js
,是自己写的js文件。
处理之前项目的js入口文件大概是这样子的;
// 引入主要工具和框架
import angular from './angular'
import 'angular-ui-router'
// 引入一些工具库
import tool1 from './tool1'
// 引入ng的一些指令,组件,service等等
import aComponent from './a.component'
import aService from './a.service'
import aDirective from './a.directive'
// 引入路由
import router from './router'
// 项目模块
angular.module('app', [
'ui.router',
// ... 其他一些依赖
])
.config(router)
.component('aComponent', aComponent)
// ...
从这个入口文件就可以看出,现在所有依赖的外部工具库和自己编写的内容都是一次性引入进来,尽管通过webpack来分开了两个文件,但是在入口的html文件还是一次引入了。特别是进入首页介绍页面的时候,逻辑功能比较少,但是却要加载全部功能。
解决思路
路由分模块定义
因为项目通过一级url分工明显,每个url可以分离成一个模块,处理起来就更直观,例如:
/foo
/bar
/baz
// ...
因此需要从ui-router
先下手,能够指定一级url之后,交给对应的模块处理,对应的模块内部再处理子url,这样子每个模块之间就更加明确。从ui-router官网参考的例子如下:
var contactsFutureState = {
name: 'contacts.**',
url: '/contacts',
lazyLoad: function() {
// lazy load the contacts module here
}
}
这里的例子大概意思是url为/contact
的命名是contact.**
,然后通过layLoad
的函数加载对应的模块逻辑处理。这里要引入一个概念,叫futureState
,字面上的意思是未来的状态,就是预先定义的。在配置页面全局路由的时候,大概就是这样子:
angular.module('app', ['ui-router'])
.config(['$stateProvider', function ($stateProvider) {
let states = [
{
name: 'foo.**',
url: '/foo',
lazyLoad: function() {
// 引入对应的模块
}
},
{
name: 'bar.**',
url: '/bar',
lazyLoad: function() {
// 引入对应的模块
}
}
// ...
]
// 定义相关url
states.forEach(state => $stateProvider.state(state))
}])
然后具体foo
模块就定义对应的二级url:
angular.module('foo', ['ui-router'])
.config(['$stateProvider', function ($stateProvider) {
let states = [
{
name: 'foo',
url: '/foo',
component: 'foo'
},
{
name: 'foo.second',
// 实际访问的url是 /foo/second
url: '/second',
component: 'fooSecond'
}
// ...
]
// 定义相关url
states.forEach(state => $stateProvider.state(state))
}])
// foo和fooSecond也是在该模块引入,这里没有写出
.component('foo', foo)
.component('fooSecond', fooSecond)
但是,这样子并跑不通,会报一个multiple define
的多重定义的错,然而ui-router官方并没有给出相关例子,刚才的foo
模块定义是根据之前定义全局模块的定义。直到后来在stackflow找到了ui-router注入对象$stateRegistry
,替换子模块的$stateProvider
,因此子模块定义url的时候,就变成了:
// 注册相关url,依赖的注入也要修改
states.forEach(state => $stateRegistry.state(state))
动态注入
路由处理完毕之后,就要考虑一下怎么把刚才的子模块在对应路由触发的时候,动态注入,这个在ui-router给出了一个参考,就是利用第三方的ocLazyLoad来支撑,在定义全局路由的时候,表明懒加载,例如:
[{
name: 'foo.**',
url: '/foo',
lazyLoad: function ($transition$) {
return $transition$.injector().get('$ocLazyLoad').load('./fooModule.js');
}
}
}]
分离代码
动态注入ok了,然后就是用webpack工具来打包分离代码,分离比较简单,教程在这里,有使用import
和require.ensure
的方法,这里就使用了import
的方法,修改lazyLoad
的动态注入方法:
[{
name: 'foo.**',
url: '/foo',
lazyLoad: function ($transition$) {
return import(/* webpackChunkName: "foo" */ './fooModule.js')
.then(mod => {
// mod.defatut 是因为fooModule.js export default ...
$transition$.injector().get('$ocLazyLoad').load(mod.default)
})
}
}
}]
这样子webpack打包文件的时候会分割代码,当该模块触发的时候,再请求该模块的文件,然后给到ocLazyLoad来动态注入,实现按需加载,当访问过该模块的时候,下次进入已加载过的模块,也不会再次发出请求模块文件
总结
到这里,整个流程就跑通了,回顾一下几个关键点:
- 预定义路由future state;
- 子模块使用
$stateRegistry
来注册路由; - 使用
ocLazyLoad
实现动态注入; - 使用webpack分离代码打包
这次也是在填ng1.x的一些坑,使用相对较旧的框架实现一些看起来比较简单的需求,有时候也是挺折腾的。或许像同事所说的这是旧框架与人民日益增长的需求之间的矛盾。
安利
回顾之前用hexo写文章的时候,换了电脑之后,源文件又要重新找,而且过程搭建也是挺麻烦的,所以这次写博客用到同事贡献自动化博客,安利一下地址,仅仅用github的issue就可以写了,而且不怕丢失了,而且一次配置,绝无手尾,写完issue就能更新到我们的博客了。END