乐趣区

关于javascript:如何优化成千上万行SDK代码

最近开始看 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 等等。

如果感觉有播种请关注微信公众号 前端良文 每周都会分享前端开发中的干货知识点。

退出移动版