• 前言:

在上一篇文章中我介绍了我们对N产品性能优化的整个历程,主要偏重优化方法。本篇我将介绍在这一过程中,我们的代码出现了什么样的问题,以及我们是如何通过前端重构来解决掉这些问题,并产生了哪些收益。


  • 痛点

按需加载为我们的页面带来了很大的性能提升,但同时也为代码结构带来了很大的冲击,很多直接调用的方式被改为了模块化的调用形式(先判断模块是否存在,不存在就先加载对应的js,再执行回调)。

 

而js代码本身又不是模块化形式的。就使得代码结构很混乱,各种调用方式都存在,开发人员在写代码的时候不知道该直接调用还是模块化调用。

打包配置混乱,散落在各个目录结构中,经常出现重复打包和漏打包的现象,严重的还造成线上问题。

因为长期的产品策略变更,导致代码不同功能块之间耦合严重,一些关键方法不知道都在哪里被调用过,如果修改的时候有遗漏,就会出现问题。

没有文档。

没办法做自动化测试。

  • 思考

因为以上问题,我们每次产品升级都如履薄冰,需要非常小心谨慎,测试也很耗费精力。为了提高效率,我们必须要重构。

这次重构迫切要解决的问题有三点:

1. 代码调用方式统一,希望加载方式可以对开发人员透明。

2. 模块职责明确、模块之间依赖关系清晰。

3. 按需加载的配置可以统一管理,不影响开发时的效率。

这三点,让我很自然地就想到了JS模块化开发

  • 模块化开发:

关于模块化框架和CommonJS规范,在网上有很多介绍的文章,我就不在这里赘述了。我只说最关键的一点:每个模块都有明确的定义(模块名、输入、输出),模块之间的依赖和调用必须通过require或use的形式。如下图是一个common/pop模块的示例代码:


这种设计带给我们最大的好处是:规范了代码之间的调用方式,开发人员在写use的时候,不用担心这个模块是否已经被加载,所有的加载策略和打包策略都对他是透明的。

  • 模块依赖 & 自定义事件

如上面说到的,按照CommonJS规范,模块之间有require和use两种依赖形式,我们加入了第三种:fire(事件触发)。通过自定义事件的监听和触发,我们实现一种弱依赖的形式。

fire适用于投统计、异常处理等场景,不会触发代码加载。

  • 打包配置:

将以前分散在各个地方的打包策略合并到同一个配置文件中,如下图(示例代码):

它起到两个作用:1. 保证项目编译的时候将模块打包到对应文件;2. 在使用模块的时候,去检测它所在的js文件是否已经加载,如果没有则自动加载。

可以看到,我们对css也做了同样的模块化加载的处理。

  • 文件加载 & CSS模块化

对于按需加载的组织形式而言,文件的加载监控很重要。

早期我们对CSS做延迟加载的时候,碰到一个情况:由于这部分CSS加载是异步的,如果网络状况不好,就可能出现JS已经将页面元素组装好了,但是CSS还没加载完成,这一部分页面就会乱掉。后来我们采用了“先隐藏元素,等CSS加载结束再重写display属性来显示元素”的方法,避免了这种case。不过这毕竟不是一个系统化的解决办法。

如何鉴定文件加载的结束,特别是CSS文件的加载结束是个蛮有学问的事情。在框架中对于文件的下载监控,我们借鉴了SeaJS的思想,对于这块感兴趣的同学可以深入研究一下。

  • 整体架构

基础层是对一些基础模块和session数据的封装,这些方法每个模块都会用到,就不再做单独的require,打包成ctx变量传入每个模块的定义中进行使用。

应用层是具体的产品功能开发,对于数据的处理统一使用view/model/service的形式,由view从模板的textarea(性能优化考虑)中提取数据建立model,由service统一向server端发送异步请求进行持久化。

通用层是业务逻辑无关的组件,很多产品都可以复用,如分页、截字、弹层、类定义等。

物理层:通过config.js来统一配置按需加载的打包策略,并在模板中对编译加戳后的物理文件进行引用。

  • 代码文档:

既然每个模块都有统一的定义方式,模块之间的依赖和调用也有统一的形式,那么我们通过对代码进行自动分析,就生成了类似如下形式的代码文档

每个模块的依赖与被依赖、调用与被调用信息,一目了然。

开发人员利用这个文档可以清楚的知道他的每次修改都可能会影响到哪些模块。

QA人员可以根据每次提测的模块列表,评估出可能会影响到的case范围。

这对于开发和测试的效率提升不言而喻。

最重要的是,这是有生命力的文档,不需要维护,每次代码写完后就可以自动生成。

  • 单元测试:

按照模块化进行重构之后,我们可以针对单个组件写case进行测试。这里用到了Qunit框架。效果如下图:

  • 问题:

当然,模块化框架并非前端开发的银弹。我们在重构过程中也碰到了“不好调试”、“出现循环依赖后不好定位”等问题。这也是我们接下来要尝试解决的方向。

另外,重构后我们发现,框架本身和模块配置打包的代码,合并压缩后也有4-5k,这对于性能优化并没有太大的帮助。

  • 总结:

模块化框架对于按需加载、模块划分、代码复用、自动文档、单元测试、团队合作等都有很大的帮助。

本次重构规范了JS和CSS的模块化使用,让模块划分变得更清晰,让开发变得更单纯,为以后的升级维护奠定了一个很好的基础,提高了测试的效率,并且使得“单元测试”,“自动化测试”,和“敏捷开发持续集成”等 成为了可能。

本次重构还没有对HTML做模块化拆分,所以还没办法做到“模板的独立渲染”。这会是我们团队后续的工作方向之一。

by lizhouquan