Justin's Words

前端旁路系统

为什么要有旁路系统

痛点

  • 活动越来越多,每次活动都要给页面加各种弹窗各种浮层各种提示,没有一个地方统一管理
  • 每次要加一些无关主流程的代码都要去修改主流程的代码,代码侵入性和代码耦合性太强
  • 每次修改完成旁路逻辑后都要统一发一次代码,非常担心会拖累主逻辑

旁路系统要怎么设计

旁路管理

需要有一个地方来统一管理所有旁路的功能,这个地方应该包含每个旁路都需要的属性,主要包括以下方面,后续有需要可以再加:

  • 名称
  • 标识
  • 生效路由
  • 模块id
  • 开始时间
  • 结束时间

旁路过滤

旁路过滤不建议在前端做,因为我们需要先在前端拉取到所有配置,然后根据约定好的过滤规则进行过滤,如果配置很多的话,前端就需要耗时很久,所以这一步会提供一个cgi。

过滤应该要包括以下三个方面:

  • 是否在有效时间。有效时间会通过旁路配置里面设置好的生效时间来判断
  • 是否在合适路由。合适的路由即是会把配置好的路由和当前路由进行对比,如果应当在当前路由生效,就判断为有效
  • 是否debug模式。我们是通过判断url某个参数来判断是否debug模式

旁路模块加载

前端收到cgi返回的旁路模块列表后,就开始根据每个模块里面的模块id来加载需要的模块,这里的模块加载会通过异步拉取,并且在拉取的过程中抛出相应事件,方便其他地方监听。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 加载旁路模块
* @param {*} data
* @param {*} callback
*/
function loadModule(data, callback) {
var moduleName = data.actModule;

require.async(moduleName, function (module) {
callback && callback(module);
});
}

loadModule(config, function (module) {
Event.emit('loadModule.module.loaded', config); // 抛出相应事件
...
});

模块执行

模块加载回来后,需要分别执行,有些模块是模板模块,设计到操作DOM,有些则不需要,那我们给每个模块创建一个节点,让它在这个节点内执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 执行旁落模块
* @param {*} module
* @param {*} options
*/
function execModule(module, options, callback) {
// 创建执行节点
var container = document.querySelector('#page-wrapper') || document.body;
var byPassContainer = document.createElement('div');
byPassContainer.setAttribute('id', 'byPassModuleWrapper_' + module.actId);
container.appendChild(byPassContainer);

module.init(byPassContainer, options);
callback && callback();
}

execModule(module, options, function () {
Event.emit('loadModule.module.executed', config); // 抛出相应事件
});

与主流程交互的情况

有时候一些旁路逻辑还会涉及到和主流程的交互,需要取到主流程某些值或者执行主流程里面某些动作的情况,所以我们可以提供一些接口给旁路模块使用,旁路模块就可以使用这个接口与主流程进行交互。这里我使用的是 action。

action 是 reflux/redux 里面的概念,是用来对业务数据进行具体操作的接口

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
define("activity.whatever", function (require, exports, module) {
exports.init = init;

function init(container, options) {
var myActions = options && options.myActions; // 主流程传进来的接口
if (!myActions) {
return;
}

var myTypes = [{
id: 1,
name: 'MyTypes'
}];
myActions.changeTypes(myTypes); // 与主流程进行交互
}
});