最近开始看xgplayer的源码,分享一下里面的插件模式和批量导入功能的使用,可以借鉴应用在成千上万行代码的SDK文件优化中。
一 看源码从何入手
看源码最重要的就是能够调试,播放器的源码是运行在浏览器端的,所以我们可以直接用浏览器进行调试,node上的源码如何调试我后续再发一篇文章讲解。
二 插件模式的实现
其基本结构如下:
class Player {
constructor() {
// 构造函数的时候调用插件
this.pluginsCall()
}
// 调用插件的方法
pluginsCall () {
let self = this
if (Player.plugins) {
// 遍历插件执行
Object.keys(Player.plugins).forEach(name => {
let descriptor = Player.plugins[name]
descriptor.call(this, this)
}
}
})
}
}
// 静态挂载插件的方法
static install (name, descriptor) {
if (!Player.plugins) {
Player.plugins = {}
}
if (!Player.plugins[name]) {
Player.plugins[name] = descriptor
}
}
}
这里设计的比较好的点是提供类的静态方法挂载插件,然后构造函数中执行插件,这就能让引用者在创建实例之前可以挂载自定义的插件,并在创建实例时执行插件。
源码内的20几个插件统一都在controls
文件夹下,结构如下:
├── cssFullscreen.js
...
├── i18n.js
所有的插件都是统一的结构,引入Player
,声明插件方法,然后调用Player.install
安装插件,以cssFullscreen.js
为例:
import Player from '../player'
let cssFullscreen = function () {
}
Player.install('cssFullscreen', cssFullscreen)
接下来就是执行这些插件代码,并且导出Player的,这里就有知识点了,其导出的文件的源码如下:
import Player from './player'
import * as Controls from './controls/*.js'
export default Player
这里比较巧妙的是 没有直接导出Player
,而是利用这个文件中转了一下,中转的时候import * as Controls from './controls/*.js'
这行代码就会批量打入所有controls
文件夹下的插件并执行,乍一看这个语法没有见过,不知道是哪个过程做了处理,通过在babel-loader中调试发现,经过babel之后就已经处理了,看babel的配置就破案了,是这个插件babel-plugin-bulk-import
。
三 babel-plugin-bulk-import
babel-plugin-bulk-import是一个babel的插件,该插件支持使用glob 的语法进行模块的批量导入,让我们看看它README.md中的用法介绍。
Test case:
.
├── case
| ├── subfolder
| | └── case3.js // module.exports = { case: 999 };
| ├── case1.js // module.exports = { case: 1 };
| ├── case2.js // module.exports = { case: 2 };
| └── case3.js // module.exports = { case: require('./subfolder/case3') };
└── src
└── index.js
Example#1(local imports)
import * as all from './case/**/*.js';
will result in:
all = {
case: {
case1: { case: 1 },
case2: { case: 2 },
case3: { case: { case: 999 } },
subfolder: { case3: { case: 999 } }
}
}
Example #2 (imports from node_modules)
import * as all from 'lodash/{*,**/*}.js';
will result in:
all = {
node_modules : {
lodash: {
LODASH_MODULE1: ITS_EXPORT_CONTENTS
LODASH_MODULE2: ITS_EXPORT_CONTENTS
...
}
}
}
实现原理
如何写babel插件可以再开一篇文章讲解,这里先来看一下插件实现的大概思路,基本上就是匹配glob语法,使用glob获取文件,然后改写引入的逻辑。在babel-loader中 看一下经过babel之后的代码如下:
"'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _cssFullscreen = require('./controls/cssFullscreen.js');
var _cssFullscreen2 = _interopRequireDefault(_cssFullscreen);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var Controls = {};
/**
@param v 引入的整体对象
@param p 引入模块的路径 作为整体对象的属性
@param a 最后的模块导出
**/
function _buildTree(v, p, a) {
var o = v;
p.map(function (_, i) {
o[_] = i == p.length - 1 ? a : o[_] || {};
o = o[_];
});
}
_buildTree(Controls, ['controls', 'cssFullscreen'], _cssFullscreen2.default);
其实xgplayer的插件中没有导出内容,因为只需要执行模块的代码即可。
四 总结应用
有了上面插件模式的设计和批量导入的能力,想到了可以解决开发中SDK单文件过大的问题,一个SDK经常就是几千行的代码:
class SDK {
constructor() {
}
method1() {
}
...
method99() {
}
}
export default SDK
利用批量导入改进之后的情况如下:
├── SDK.js
├── index.js
├── methods
│ ├── method1.js
...
│ └── method99.js
SDK.js
中:
export default class SDK {
constructor() {
}
}
methodx.js
中:
import SDK from '../SDK';
SDK.prototype.methodx = function() {
console.log('methodx');
}
index.js
中:
import SDK from './SDK';
import * as methods from './methods/*.js'
export default SDK;
demo在这里:https://github.com/yylgit/lia...
其实不仅是SDK,所有我们需要大量手动import
的地方都可以进行优化,比如import
路由,vueximport
子模块,redux中的combineReducers时import
每个reducer等等。
如果觉得有收获请关注微信公众号 前端良文 每周都会分享前端开发中的干货知识点。