投身烈火
Keep Walking

闲扯react组件动态加载机制 -- webpack打包方案

上次写的文章被朋友吐槽看不懂了……好吧,这次我们继续说说动态加载组件的打包方案,就算是狗尾续狗,他自己挖的坑填平吧。

前情提要

先总结下上次的文章,其实我上次写的那些,归结起来就是一点:

在用react开发应用时,如果想要动态加载子组件,只需要在componentDidMount或者componentWillReceiveProps中加入异步获取组件的逻辑就可以实现了。再进一次,可以把这个功能单独抽成一个专门做加载的组件。

webpack打包

上篇文章的最后我也说了,之前实现的只是代码逻辑,要想真正用到生产环境,还得过构建打包那一关……说到打包,虽然市面上打包工具不少,不过基本上现在都是用webpack了吧。

根据之前的预测,直接使用System.import()来替换里面的require就可以实现功能了。需要注意的是,根据webpack文档中的描述动态引入:import(),import不能支持完全的动态语句,至少要给他一个范围。所以需要将原来使用变量的语句改为一个拼接的路径,类似下面这样:

1
2
3
4
5
6
7
import(/* webpackChunkName: "loader" */ `./${componentName}`)
.then(Component => {
\\...
})
.catch(err => {
\\...
})

这样webpack在打包时,会将这个路径下所有的文件都作为可能被异步加载chunk来打包。所以最好在目录上做规范,将需要异步加载的组件放到一个特定的目录下,避免打包的时候生成冗余文件。

随之而来的问题

虽然打包的问题解决了,但是随之也暴露了问题。由于webpack打包是基于入口的,不同入口之间是独立的,而用import拆分出来的chunk是无入口的,所以webpack认为他们之间并没有联系,也就不会提取他们之间的公共内容。

比如我写的demo,具体的引用关系如下图

01

最后打包出来的动态加载的组件例会很多重复的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
chunk {0} page2.bundle.js
css-loader/index.js?modules!./styles/Page.css
css-loader/lib/css-base.js
style-loader/lib/addStyles.js
style-loader/lib/urls.js
containers/LoadComponent.js
pages/PageB.js
styles/Page.css
chunk {1} page0.bundle.js
css-loader/index.js?modules!./styles/Page.css
css-loader/lib/css-base.js
style-loader/lib/addStyles.js
style-loader/lib/urls.js
containers/LoadComponent.js
pages/PageA.js
styles/Page.css
chunk {2} component4.bundle.js
css-loader/index.js?modules!./styles/StyleB.css
components/ComponentC.js
lib/BaseComponent.js
styles/StyleB.css
chunk {3} component2.bundle.js
css-loader/index.js?modules!./styles/StyleA.css
components/ComponentB.js
lib/BaseComponent.js
styles/StyleA.css
chunk {4} component0.bundle.js
css-loader/index.js?modules!./styles/StyleA.css
components/ComponentA.js
lib/BaseComponent.js
styles/StyleA.css
chunk {5} index.entry.js
react-dom/index.js
react-hot-loader/patch.js
react/react.js
strip-ansi/index.js
url/url.js
containers/App.js
index.js
stores/dataStore.js

这个时候就得用CommonsChunkPlugin来优化打包结果了。需要注意的是,由于page和comonpent都是异步加载的,没有入口,所以配置CommonsChunkPlugin的时候需要指定引用了异步加载chunk的入口,并加上children:true。另外异步加载chunk里面如果还有有异步加载的chunk需要优化,则需要把父级的chunk也设为入口,再用CommonsChunkPlugin优化。配置类似这样如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
//...
entry: {
index: './index',
pageA: './pages/PageA',
pageB: './pages/PageB'
},
//...
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'index',
children: true,
async: true
}),
new webpack.optimize.CommonsChunkPlugin({
names: ['pageA', 'pageB'],
children: true,
async: true
}),
]
//...
}

加上async: true的配置之后,生成的公共chunk也会用异步的方式加载进来,这样就能完全的实现动态加载了~

最后打包出来的文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
chunk {0} component4.bundle.js
css-loader/index.js?modules!./styles/StyleB.css
components/ComponentC.js
styles/StyleB.css
chunk {1} component2.bundle.js
css-loader/index.js?modules!./styles/StyleA.css
components/ComponentB.js
styles/StyleA.css
chunk {2} component0.bundle.js
css-loader/index.js?modules!./styles/StyleA.css
components/ComponentA.js
styles/StyleA.css
chunk {3} 3.bundle .js
lib/BaseComponent.js
chunk {4} 4.bundle .js
css-loader/index.js?modules!./styles/Page.css
css-loader/lib/css-base.js
style-loader/lib/addStyles.js
style-loader/lib/urls.js
components lazy recursive ^\.\/.*$
containers/LoadComponent.js
styles/Page.css
chunk {5} page2.bundle.js
pages/PageB.js
chunk {6} page0.bundle.js
pages/PageA.js
chunk {7} index.entry.js
mobx-react/index.js
react-hot-loader/patch.js
react/react.js
strip-ansi/index.js
url/url.js
containers/App.js
index.js
stores/dataStore.js
chunk {8} pageB.entry.js
mobx-react/index.js
react-dom/index.js
react/react.js
strip-ansi/index.js
url/url.js
containers/LoadComponent.js
pages/PageB.js
styles/Page.css
chunk {9} pageA.entry.js
mobx-react/index.js
react-dom/index.js
react/react.js
strip-ansi/index.js
url/url.js
containers/LoadComponent.js
pages/PageA.js
styles/Page.css

可以看到3号chunk是component的公共模块,4号chunk是page的公共模块,8号chunk和9号chunk是设置了入口后生成的冗余文件。

可能有人注意到component的里引用的css文件并没有提取出来,这是因为并不是所有的component chunk都引用了它造成的。如果想要把他也提出来,设置一下CommonsChunkPlugin的minChunks属性就行了。

具体的在项目中使用的时候,需要根据业务手动去调整入口配置和CommonsChunkPlugin里的参数了,要想做到完全自动化,CommonsChunkPlugin是支持不了,需要开发新的webpack插件才行。至于怎么开发webpack插件,这个不在本次文章的范畴里,以后有时间了在专门开新坑说明吧。

最后附上我自己做的demo,clone下来之后 npm install && npm start 就能跑起来了。这次算是有示例有真相了吧?就在这儿晒~( ̄▽ ̄)~*

好的那么由于时间不足,本期的博客就先写到这里,如果不出意外的话,maybe可能也许大概下周五会更新吧,能不能准时更新,就全看米娜桑点赞转发安利留言的热情了~!

白了个白~!