服务粉丝

我们一直在努力
当前位置:首页 > 财经 >

【第2896期】插件化设计模式在前端领域的应用

日期: 来源:前端早读课收集编辑:洋葱x

前言

基于前端场景,一览其中的设计原理与插件核心执行流程。今日前端早读课文章由 @洋葱分享,公号:洋葱说前端授权。

@洋葱,从事前端开发领域多年。关注前端工程化、研发效能、前端业务架构与领域模型抽象。

正文从这开始~~

软件开发中,随着系统功能变多,复杂度成指数级上升,而复杂度的增高多来源于模块间的耦合过于严重,插件化的设计模式能一定程度解决模块耦合的问题。抽象出系统的核心流程节点,基于这些节点与多个插件进行交互,最终实现整个系统。当然,前端领域的一些场景也有插件化应用的案例,本篇文章我们基于这些案例,一览其中的设计原理与插件核心执行流程。

案例

Babel

Babel 是 JavaScript 编译器,更确切地说是源码到源码的编译器,通常也叫做 “转换编译器(transpiler)”。意思是说你为 Babel 提供一些 JavaScript 代码,Babel 更改这些代码,然后返回给你新生成的代码。

babel 不可能把所有 js 新特性都囊括进去,例如一些未进入标准的,或还在草案中的。所以使用插件化架构,用户需要哪些特性,自行增加 babel plugin 来使用,甚至自定义特定场景插件。

Babel 的三个主要处理步骤分别是:解析(parse),转换(transform),生成(generate)。. 这些步骤具体处理细节本篇文章不会扩展讲述,有兴趣可以查看 babel plugin handbook。

这里最复杂的步骤是 转换,同时也是 插件 的工作阶段。babel 插件通过访问者模式定位具体 AST 节点,并进行节点路径的各种操作。节点的操作类似 DOM ,同样有节点树,与基于整个节点树的增删改查。

【第2597期】如何用JavaScript实现一门编程语言 - AST

babel 插件设计本身并不复杂,插件间完全互相隔离,且无互相拦截阻断通信、异步等互相依赖性、执行时序等问题,是比较纯粹的 AST 转换。假设我们有这么一段代码:

 function square(n) {
return n * n;
}

它的树结构如下:

 - FunctionDeclaration
- Identifier (id)
- Identifier (params[0])
- BlockStatement (body)
- ReturnStatement (body)
- BinaryExpression (argument)
- Identifier (left)
- Identifier (right)

当我们向下遍历这颗树的每一个分支时我们最终会走到尽头,于是我们需要往上遍历回去从而获取到下一个节点。向下遍历这棵树我们进入每个节点,向上遍历回去时我们退出每个节点。

配置的插件数组依次执行,类似这样:

babel 在遍历前后会对应执行 pre hook & post hook 函数,针对所有插件配置的 hook 执行一遍,传递当前文件信息(BabelFile),BabelFile 实例会包含 ast、code、path 等内部信息。遍历 AST 的过程中,遍历到某个节点都会依次执行所有插件对应配置的 visitor type callback,并且提供 enter、exit 更加细节的调用行为,给予插件定义更多的执行时机。

示例代码:

 const babel = require('@babel/core');

/**
* @returns {import('@babel/core').PluginItem}
*/
const createPlugin = (name) => {
const log = (...args) => console.log(`[${name}]:`, ...args);
return {
name,
pre(file) {
log('pre');
},
visitor: {
FunctionDeclaration(path, state) {
log('visit FunctionDeclaration');
},
ReturnStatement(path, state) {
log('visit ReturnStatement');
},
},
post(file) {
log('post');
},
};
};

babel.transform(
`
function a(m) {
return m*m;
}
`,
{
plugins: [createPlugin('A'), createPlugin('B')],
},
);

输出:

 [A]: pre
[B]: pre
[A]: visit FunctionDeclaration
[B]: visit FunctionDeclaration
[A]: visit ReturnStatement
[B]: visit ReturnStatement
[A]: post
[B]: post
Koa

Koa 应用程序是一个包含一组中间件函数的对象,它是按照类似堆栈的方式组织和执行的。

koa-compose 是经典的洋葱模型实现,图中的每一层洋葱圈在 koa 中叫 中间件(middleware)。中间件即函数,其核心包含 context、next 概念。

  • context:即函数共享上下文对象,所有中间件都有完全控制权限;

  • next 即调用内层函数(图中的被包裹洋葱圈),类似调用堆栈, next 即当前栈的上一层栈函数,由当前中间件决定何时调用。

洋葱模型扩展了一次性行为的拦截,预设数据等场景。很方便的对主链路进行数据更新,流程管控,流程校验等操作。对于 http request ,page bootstrap 这种一次执行场景非常适用。

中间件可将独立的逻辑做单独封装,解耦,降低系统复杂度,常用的中间件如:

  • 错误拦截,将友好错误信息返回给前台;

  • 缓存,可做到接口级别的缓存控制;

  • Session 数据预置,用户信息之类的基础数据。

Axios

Axios 是跨平台(node browser)的 http request 库。Axios 也有插件的概念,Axios 中叫 interceptor 拦截器,针对请求相应进行拦截、操作、更新等逻辑。reques interceptor 可拿到 request config,并进行修改;response interceptor 可拿到 reqeust config 与 http response 等信息。

在 Axios 中,拦截器就是普通 js 函数,请求、相应拦截独立。

 // Add a request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});

执行流程为(需要特别注意 request interceptor 的执行顺序),rejected 为 fullfilled 执行失败 or 上一个 interceptor 抛异常时执行,核心就是 Promise.then 的链式执行逻辑。如下图:

Tapable(Webpack)

The tapable package expose many Hook classes, which can be used to create hooks for plugins.

Tapable 可以为插件提供钩子,webpack 的插件化架构就是基于此实现的,不同的执行流程产出不同的 hook 类型,webpack 打包流程中 hooks 点位非常多,并且根据需要,每个 hook 的类型会不同。相比以上案例,Tapable 实现相对会复杂很多,它包含了同步、异步并行 / 串行、可阻断、瀑布流等执行流程相关的概念,是集大成者。

Tapable 的四种同步流程:

除了标准的流程外,其他流程都是基于 插件返回值 做文章:

  • Bail 可阻断流程,返回非 undefined 是执行阻断逻辑;

  • Waterfall 瀑布流,插件输入为前一插件的输出;

Loop 循环执行,所有插件返回值都会 undefined 时,才结束,否则继续从头开始执行。(目前 webpack 暂时没用到)

Tapable 异步流程类型共有 5 种:

  • AsyncParallelHook

  • AsyncParallelBailHook

  • AsyncSeriesHook

  • AsyncSeriesBailHook

  • AsyncSeriesWaterfallHook

增加了异步特有的并行、串行等逻辑,去除了 Loop。流程图与同步流程大致类似,就不重复绘制了。

实战

登录注册

我司登录注册是一个相当复杂的功能,其本身核心功能:

  • 手机号验证码登录

  • 账密登录

  • 手机号验证码注册

  • 三方注册

所有登录中涉及到:多账号绑定、登录风控、图片验证码、滑动验证码、协议弹窗;

所有注册流程涉及:手机绑定、身份选择、行业选择、协议弹窗;

基于此模块衍生出的扩展功能有:

  • 多账号切换;

  • 登录弹窗、登录页面;

  • 多个站点公用统一套核心登录注册逻辑;

  • 某站点可能会限制一些账号类型,权限等(例如仅商家账号可登陆);

  • 某注册来源送 xxx 奖励,注册来源传递问题;

  • 单点登录;

  • 手动埋点;

  • 某站点自定义可跳过某些注册流程,直接注册(拦截);

  • ... 等

基于此,我们只保留核心流程,将其他流程作为插件形式,作为登录注册模块的插件,去修改、增加一些逻辑,去影响核心流程的走向 或单纯 获取事件点等。基于此衍生模块都成为了插件,整个模块复杂度被平摊到了不同插件内部中。

应用启动

我司主站,收敛启动逻辑,主流程仅提供一些启动所需的 hook,例如:应用初始化、挂载、更新、卸载等钩子。基于此我们封装出了:

  • 用户信息插件

  • 导航信息注入插件;

  • 样式隔离插件;

  • 组件库主题包预置插件;

  • 权限判断插件;

  • ... 等

以上实战,可以给予大家一些参考价值,具体实现细节包含敏感信息,暂不细说。

总结

插件式的设计模式在软件开发当中应用相当广泛,插件大多数由不同开发人员、甚至不同团队去开发。它不仅能降低系统模块间的耦合度,更能给予软件全局的约束和规范,这对于大型软件开发相当重要。我们纵览了前端开发领域的一些实战,对插件设计思路有了大致的了解,并且深入了下这些插件的执行流程。最后对我司的一些复杂场景的实战经验做了分享。

希望大家看完有所收获,完。

参考文档

  • https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-introduction

  • https://webpack.docschina.org/api/plugins/#tapable

  • https://github.com/webpack/tapable

  • https://github.com/axios/axios

关于本文
作者:@洋葱
原文:https://mp.weixin.qq.com/s/OqXuNqlBcVAe0faeID8heQ

这期前端早读课
对你有帮助,帮” 赞 “一下,
期待下一期,帮” 在看” 一下 。

相关阅读

  • 精选壁纸 | AI的世界~

  • 「 静静地做自己,让世界发现你 」▼2492期壁纸- 图片来自网络,如侵联删 -- 高清原图获取 -「关注这个号的你可以说是很优秀了 」▼你的每个“在看”,我都认真当成了喜欢
  • 我们不能失去中国!欧洲领导人,争相访华!

  • 时来天地皆同力。形势,比人强。大家应该都还记得,之前德国总理朔尔茨费了不少力气,才终于访华成功。而马克龙就比较惨了——一直想来,没来成;至于冯德莱恩,早在今年1月就表达了访

热门文章

  • “复活”半年后 京东拍拍二手杀入公益事业

  • 京东拍拍二手“复活”半年后,杀入公益事业,试图让企业捐的赠品、家庭闲置品变成实实在在的“爱心”。 把“闲置品”变爱心 6月12日,“益心一益·守护梦想每一步”2018年四

最新文章

  • 【第2897期】ECMAScript 2023 有哪些更新?

  • 前言阴雨绵绵的开始。今日前端早读课文章由 @CUGGZ 分享,公号:前端充电宝授权。正文从这开始~~ECMAScript 规范每年都会更新一次,ECMAScript 2023 预计将于 6 月左右获得批准,这将
  • 【第2896期】插件化设计模式在前端领域的应用

  • 前言基于前端场景,一览其中的设计原理与插件核心执行流程。今日前端早读课文章由 @洋葱分享,公号:洋葱说前端授权。@洋葱,从事前端开发领域多年。关注前端工程化、研发效能、前
  • 【早说】给年轻人的建议

  • 把自己的生活项目化。I offer the following advice to young people: 1. Identify the skills that are relevant to your long-term happiness and try to master them as
  • 【早说】找到擅长的领域

  • 在工作上,尽可能把擅长的做到极致,有时间精力再去补其他的。Find an area where you can play to your strengths and avoid your weaknesses, and amplify your strengths wh