如何将AI能力与大数据技术结合,助力数据分析治理等工作的效率大幅提升,优化大数据引擎的性能及成本? 了解详情
写点什么

利用 Webpack 插件进行前端 code-splitting

  • 2018-01-03
  • 本文字数:2352 字

    阅读完需:约 8 分钟

写在前面

大家好,我是 LINE 漫画的 JavaScript 开发工程师 @sunderls。

这是 LINE Advent Calendar 2017 的第 22 日的文章。今天我介绍一个 webpack loader - grow-loader 。 之前在 LINE 漫画:通过 Page Stack 实现流畅的页面切换这篇文章中介绍过,LINE 应用内部的漫画 APP 使用的是 web 技术。

为了提供接近于原生 APP 的体验,我们进行了很多尝试。今天介绍下我们是如何实现 code-splitting 的。

为什么要做 code-splitting

LINE 漫画之前一直将 JS 打包为一个 bundle 文件,随着应用变得越来越复杂,页面越来越多,bundle 文件变得越来越大。考虑到未来可能还会更复杂,打包到一个文件这种方式变得很不合适。所以就开始了分割。

一般的实现

最开始我们也尝试了 react-loadable 之类的 HOC(Higher Order Component)方法,不过遇到了一些问题:

1. 页面切换动画效果的即时性消失

原因很清楚。用户的操作之后才开始加载下一个页面,所以这一段加载时间会让人感觉延迟。对于 LINE 漫画而言这是致命的。

2. preload 可以尽心一定程度的改善,但是长页面的话 preload 也无能为力

用户的点击操作发生之前,提前加载 (preload) 可能跳转到的页面的话,可以避免用户能感觉到的加载时间。但是,LINE 漫画应用中页面非常多,每一个页面都去添加 preloa 代码非常麻烦难以管理,而且提前加载一些用户根本没有点击的页面也是某种低效率的体现。

另外如果页面比较长的话,DOM 生成时间比较明显,提前加载也不能解决这个问题。

3. 可以采用统一的加载图标 (Loading Indicator) 来保证即时性,不过 LINE 漫画应用不好利用

LINE 漫画几乎每个页面都有各自的 Placeholder Component,所以使用统一的加载图标将是某种用户体验上的倒退。Placeholder Component 的实现上采用的是模拟数据,而不是 HOC 的方式所以也不能很简单的分割。

4. 自定义 componnet hook 没法很简单的支持

为了实现用户点击的瞬时反馈,LINE 漫画采用了 Page Stack 的页面组织方式,提供了 componentHide 的自定义 hook。如果用 HOC 的方式进行代码分割的话,为了保证这些 hook,Router 等很多部分都需要修改。尝试了过后发现改动太大就放弃了。

总而言之,LINE 漫画非常非常重视体验上的流畅。

LINE 漫画的解决办法

分析了以上问题,我们可以得到如下结论:

  1. 每个页面的 placeholder 如果能够保留在 main bundle 中的话,流畅性不会下降
  2. 每个页面的 placeholder 以外的部分能够分隔开的话,较长的页面也会因为两次 render,初回显示也会变得更快
  3. 手动进行文件分割感觉不太可行,那就用一个 loader 自动分割好了。

于是乎 grow-loader 诞生了。

grow-loader 简介

通过 grow-loader,只需要在需要分割的方法上添加 decorator @grow即可。

以下是例:

复制代码
class SampleClass {
@grow
methodToGrow() {
// ...
}
@grow
methodToGrowAndBind = () => {
// ...
}
methodToBeBundled(){
}
}

使用了 grow-loader 的话,以上的 class 会被如下处理:

  1. @grow标记的两个方法 (methodToGrowmethodToGrowAndBind) 会被分割到另外一个文件
  2. 新方法grow()会被添加到 class 中。调用该方法后,分割出去的两个方法会被 dynamic import 回来。

以下是使用例:

复制代码
const sample = new SampleClass();
console.assert(a.methodToGrow === undefined);
console.assert(a.methodToGrowAndBind === undefined);
sample.grow().then(() => {
sample.methodToGrow();
sample.methodToGrowAndBind();
});

React 中的使用方法

以上例子作为基础,React Component 环境下的代码分割也可以简单的实现。

比如,有如下组件

复制代码
export default class LongPage extends React.Component {
methodToGrow() {
// ..
}
methodToGrowAndBind = () => {
// ..
}
methodToBeBundled(){
// ...
}
render(){
return <div>
this is a very long page
</div>
}
}

首先,做一个公用的 base 组件:

复制代码
class GrowablePage extends React.Component {
// componentDidMountの時点でロードを始める。
componentDidMount() {
if (this.grow) {
this.grow().then(() => {
this.hasGrown = true;
this.forceUpdate();
});
}
}
}

然后将render()进行适当的分割,然后在适合动态加载的方法上用@grow标记,比如这样:

复制代码
export default class LongPage extends GrowablePage {
@grow
methodToGrow() {
// ...
}
@grow
methodToGrowAndBind = () => {
// ...
}
methodToBeBundled(){
// ...
}
@grow
renderMore() {
return <div>
this is below the first view
</div>
}
render(){
return <div>
this is basic part
{ this.hasGrown ? this.renderMore() : null}
</div>
}
}

grow-loader 使用后的效果

grow-loader 只是进行了代码变化而已,实际能够让 bundle size 减少多少取决于使用方法。从上面的粒子上可以看出,render()的内容被分割的越多,@grow()使用的越多,分割效率就会越高。

LINE 漫画使用 grow-loader 后,entry bundle 文件 size 下降了 15%。这没有一般的 HOC 分割效果效果,但是由于页面的两次渲染,我们的首页的 mount 时间 (constructor()componentDidMount()的时间下降了 40%,加上使用方法上的简单和灵活,我们觉得可以接受。

写在最后

grow-loader 已经在 GitHub 上公开,有兴趣的同学还请尝试一下,如果有 bug 或者更好的建议,可以直接创建 issue 告诉我们。


感谢徐川对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2018-01-03 18:001853

评论

发布
暂无评论
发现更多内容

时序数据库在博物馆环境检测的应用

CnosDB

IoT 时序数据库 开源社区 CnosDB infra

DevOps系列之 —— 持续规划与设计(二)规划与设计

若尘

DevOps 5月月更

【愚公系列】2022年05月 二十三种设计模式(六)-适配器模式(Adapter Pattern)

愚公搬代码

5月月更

LabVIEW十六进制和字符类型转换

不脱发的程序猿

LabVIEW 进制转换

恒源云 (Gpushare)_【炼丹必备】调参心法(说人话系列)

恒源云

深度学习

在线URL编码加密工具

入门小站

工具

CleanMyMac有没有需要安装电脑?

茶色酒

CleanMyMacX

做SaaS的程序员们,是时候关注企业架构了

汤师爷

企业架构 SaaS 架构设计 5月月更

位运算——Java语言描述

秋名山码民

位运算 java 5月月更

MathType全新免费版数学公式编辑器

茶色酒

MathType

LabVIEW串口通信

不脱发的程序猿

LabVIEW 串口通信 数据通信

PyTorch 开发环境搭建

Emperor_LawD

PyTorch 5月月更

测试人生 | 00后0经验应届毕业生拿下2线城市15W offer,好励志~

伤心的辣条

Python 程序人生 软件测试 自动化测试 接口测试

Hexo+github搭建个人博客,并绑定域名

武师叔

5月月更

技术打开感知世界:当感官数字化,会发生什么?

脑极体

HarmonyOS 2迎来大更新:10个功能升级,这些机型建议更新!

科技汇

查找端口占用并关闭进程(windows)

liuzhen007

端口占用 5月月更

六、高可用之流控降级

穿过生命散发芬芳

5月月更 高可用设计

Django 如何获取 Model 字段列表?

AlwaysBeta

django

Tailor: Generating and Perturbing Text with Semantic Controls

infoQ-LolitaAnn

人工智能 nlp 5月月更

学习kali渗透测试到底该如何学?

网络安全学海

网络安全 安全 渗透测试 WEB安全 漏洞挖掘

MathType2022永久无限试用脚本程序

茶色酒

MathType

LabVIEW应用程序后台运行

不脱发的程序猿

LabVIEW

成功转行测试,分享一下自己的经验【思维导图】初级/中级/高级测试工程师会哪些...

伤心的辣条

Python 程序人生 软件测试 自动化测试 测试开发

Java基础之数组

🧸漫月柒七

5月月更

LabVIEW串口调试助手

不脱发的程序猿

LabVIEW 串口通信 数据通信 串口调试助手 VISA

linux之autojump命令

入门小站

Linux

nginx配置系列(九)nginx中的防盗链

乌龟哥哥

5月月更

[Day37]-[二叉树]- 找树左下角的值

方勇(gopher)

LeetCode 二叉树 数据结构算法

利用Webpack插件进行前端code-splitting_语言 & 开发_sunderls_InfoQ精选文章