下图是一个单页面多模块的工程目录结构图:css
. ├── Gruntfile.js ├── package.json ├── build └── src ├── base │ ├── base.sass │ └── global.js ├── mods │ ├── preference │ │ ├── index.js │ │ ├── index.sass │ │ └── index.xtpl.html │ ├── promo │ ├── qr │ └── response └── index.js
咱们把源码放在 src 文件夹里面,公共的文件(iconfont 、sprite 图片、CSS 和 JS 等)放到 base 目录下,页面中的每一个模块都会在 mods 下新建一个文件夹,使用 index.js
来管理模块的渲染。html
// index.js define(function(require){ var Lazyload = require('lazyload'); var Preference = require('./mods/preference/index'); var Qr = require('./mods/qr/index'); var Promo = require('./mods/promo/index'); var Response = require('./mods/response/index'); new Response(); if(xxx){ new Promo(); } Lazyload(function(){ new Qr(); new Preference(); }); });
这样的工程结构是十分通用,结构也比较清晰的,不过在模块的管理上,这里会存在两个问题:前端
其实说到底仍是模块的耦合度太高,只要模块之间存在交集,一个模块的改动就可能会影响到其余模块。多人开发中,这里还存在其余方面的问题:git
index.js
中注释了这个模块的初始化。日积月累,冗余代码便会渗入到项目的各个地方…去耦合的方式就是让模块之间共用的东西减小,当模块之间不存在共用内容时,耦合度基本就是零了。编程
. ├── init.js ├── build └── src ├── preference <git> │ ├── index.js │ ├── index.sass │ └── index.xtpl.html ├── promo <git> ├── qr <git> └── response <git>
如上图所示,与以前的结构相比,已经少了不少东西:json
index.js
初始化模块的东西不见了,多了一个 init.js
base
目录不见了先看看 init.js
在干啥:缓存
// init.js var $mods = $("[tb-mods]"); $mods.each(functon($mod){ if($mod.attr("finish") !== FINISH_TAG) { $mod.attr("finish", FINISH_TAG); // 须要懒加载便懒加载 if($mod.attr("lazyload")){ Lazyload($mod); return; } // 不然直接初始化 S.use($mod.attr("path"), function(S, Mod){ new Mod($mod); }); } }); function Lazyload(){ // code here.. }
init.js
再也不对模块进行精确初始化,文档从上往下遍历,找到模块便直接初始化,若是须要懒加载就加入到懒加载队列,开发者不用理会页面上有多少模块,更不用理会各个模块叫作什么名字。sass
index.js
中 require 不少不少模块,每次添加一个模块或者删除模块都要改动这个文件,而是用init.js
不会存在这个问题。架构
<!-- index.xtpl.html --> <div tb-mods lazyload path="tb/promo/1.0.0"></div> <div tb-mods lazyload path="tb/qr/2.0.0"></div> <div tb-mods lazyload path="tb/preference/2.2.1"></div> <div tb-mods path="tb/response/3.0.2"></div>
页面上的 DOM 就是标识,存在 DOM 属性标识就执行这个标识对应的脚本,执行顺序就是 DOM 的摆放顺序。less
每一个模块代码都使用单个 git 仓库管理,这样可以更好地追踪单个模块的修改记录和版本,也能够解决上面提出的问题(依次修改 ABC 模块,并上线了三次,若是须要回滚 A 模块,则 BC 模块的修改也要跟着滚回去)。
修改一个模块后,只须要修改他在 DOM 的版本号便可上线。若是遇到 ABTest 的需求,那也十分好办了:
<!-- index.xtpl.html --> {{#if condition}} <div tb-mods lazyload path="tb/promo/1.0.0"></div> {{else}} <div tb-mods path="tb/promo/2.0.0"></div> {{/if}} <div tb-mods lazyload path="tb/qr/2.0.0"></div> <div tb-mods path="tb/response/3.0.2"></div>
tb/promo
目前有两个版本,1.0.0 和 2.0.0,需求是两个版本以 50% 的几率出现,直接在index.xtpl.html
作如上修改,程序是十分清晰的。
那么,公共的代码跑哪里去了?其实咱们并不但愿有公共的代码产生,上一节中已经提出了耦合给咱们带来的维护问题,可是一个项目中必然会有大量可复用的东西,尤为是当页面出现不少类似模块的时候。
1)模块的复用
一个模块的渲染,须要两样东西,渲染壳子(模板) + 数据
,渲染的壳子多是同样的,只是数据源不同,不少状况下咱们能够复用一套 CSS 和 JS 代码,经过下面的方式:
<!-- index.xtpl.html --> <div tb-mods lazyload path="tb/promo/1.0.0" source="data/st/json/v2"></div> <div tb-mods lazyload path="tb/promo/1.0.0" source="data/wt/json/v1"></div>
在两个类似模块中,咱们使用的是同一套 js - tb/promo/1.0.0
,可是使用了两个不一样的数据源data/st/json/v2
, data/wt/json/v1
。
// init.js $mods.each(functon($mod){ if($mod.attr("finish") !== FINISH_TAG) { //... S.use($mod.attr("path"), function(S, Mod){ // 将数据源传入 new Mod($mod, $mod.attr("source")); }); //... } });
在初始化脚本中,咱们将模块须要用到的数据源传入到模块初始化程序中,这样页面就成功的复用了tb/promo/1.0.0
的资源。
2)CSS 的复用问题使用 less 的 mixin 处理
@a: red; @b: white; .s1(){ color: @a; background: @b; } .s2 { color: @a; background: @b; }
LESS 是 CSS 的预处理语言,上面的代码打包以后,.s1
是不存在的,只有 .s2
会被打包出来,可是二者均可以 mixin 到其余类中:
.s { .s1; .s2; }
利用这个特色,咱们能够把共用的 css 都包装成相似 .s1
的 less 代码,模块中须要的时候就 mixin,不须要的话,放在那里也不要紧,不会形成代码冗余。
3)JavaScript 的代码复用问题
页面级别的 JS 代码其实并很少,好比咱们平时用的比较频繁的有 Slide、Lazyload、Tab、Storage 等,但这些东西都是以组件的形式引入到页面中。仔细想想,JS 中哪些代码是须要页面共用的?相对整个项目的文件大小,共用的部分又有多少?
咱们使用的基础库方法并不全面,好比:没有对 URL 解析的 unparam
方法,而这个方法用的也比较多,但愿放到公共部分中去。回头想一想,这样的小函数实现起来有啥难度么,三四行代码就能写出来的东西,建议放到组件内部搞定。这会形成必定的代码冗余,可是带来的解耦收益与费力写几行代码的成本相比,这彻底是能够接受的。
页面共用的统计代码、错误收集代码、数据缓存方案、组件通信代码等,这些量比较大、使用颇为频繁的内容,能够封装成组件,以组件形式引入进来。
这里还须要不少思考…
模块之间的通信最让人纠结的是,A 模块想跟 B 模块说话,可是 B 模块尚未初始化出来。因此咱们须要引入一个中间人 S,每一个模块初始化成功以后都去问一问 。
// B 给 A 留言,若是 A 存在,则直接将 msg 发给 A // 若是不存在则送入 S 的消息队列 S.tell("A", { from : "B", msg: {} }); // A 模块初始化的时候,获取其余模块的留言 S.getMessage("A", function(msg){ // dosomething... });
还有不少东西不在主题的讨论范围内,就不一一列举出来了。
项目开发参与的人越多,代码就越难维护,约束只是一时的,编程方式、编码格式等的约束并不能从根本上解决问题,一旦约束的点未覆盖,结构就会开始散乱,最后必然又会迎来一次总体的重构。
方法和结果不能改变习惯,因此咱们应该从模式出发。