小矮人Javascript模块加载器

系统 2280 0

https://github.com/miniflycn/webkit-dwarf

短小精悍的webkit浏览器Javascript模块加载器

Why

  • 我们有许多仅基于webkit浏览器开发的应用
  • 无论是使用requirejs还是seajs都需要先加载完模块加载器本身之后才能加载其他模块
  • 无论出于首屏优化需要,还是手机2G优化需要,我们都需要一个足够小的模块加载器
  • 内置CDN失败回源策略

AMD & CMD

    
      require([module], callback);

    
  

这是AMD规范中模块引用的经典方式。实际上CMD规范在异步引用上也与之类似。

    
      require.async([module], callback);

    
  

实际上它们差别主要在define上,如AMD规范中define是这么用的:

    
      define(['./a', './b'], function (a, b) {
  // do something
});

    
  

而CMD中是这样的:

    
      define(['./a', './b'], function (require, exports, module) {
    var a = require('./a'),
        b = require('./b');
    // do something
});

    
  

AMD规范加载与引用是一体的,而CMD规范加载与引用是分离的。

webkit-dwarf的规范呢?

requirejs和seajs出现的时候,线下构建还不是非常流行。但现在grunt的应用已经随处可见了.特别我们在browserify中看到其精妙的应用。

所以我们的目标是在线下编写的时候,尽量接近NodeJS模块编写风格,但构建后可以满足模块异步加载,串行引用。

线下规范

  • 同步引用:
    
      require(module)

    
  

module  String  模块路径

返回  对应模块

  • 异步引用:

使用require包裹函数体:

    
      require(factory)

    
  

factory  Function  加载后运行的函数

例如:

    
      require(function () {
    var a = require('./a');
    // do something
});

    
  

dwarf将会异步加载完./a.js后,再调用factory

  • 定义:
    
      define(factory)

    
  

factory  Function  模块的定义函数

例如:

./b.js

    
      define(function (require, exports, module) {
    var a = require('./a');
    // do something
    module.exports = function () {
        console.log('module b exports');
    }
})

    
  

线上规范

  • 同步获取
    
      require(module)

    
  

module  String  要获取的模块名

返回  对应模块

例如:

    
      require(['./test'], function () {
    var test = require('./test');
    // do somthing
}, function () {
    throw new Error('Failed to load module');
});

    
  
  • 异步加载
    
      require(modules, success, fail)

    
  

modules  Array  异步加载的模块数组

success  Function  成功回调

fail  Function  失败回调

例如:

    
      require(['./test'], function () {
    // do something
}, function () {
    throw new Error('Failed to load module');
});

    
  
  • 定义模块
    
      define(module, dependencies, factory)
define(module, factory)
define(module, value)

    
  

module  String  模块相对该js文件对应路径,因为有可能在一个js文件中定义多个模块

dependencies  Array  依赖数组

factory  Functino  模块初始化工厂

value  String, Number or Object  模块值

例如:

    
      define('./test', [./util], function (require, exports, module) {
    var util = require('./util');
    // do something
    exports = module.exports = {
        result: 'test'
    };
});

    
  

技术细节

  • 加载方式

我们使用async的srcipt节点插入head中进行加载。由于针对webkit浏览器,所以加载成功回调和失败回调都是现成的。

    
      /**
 * load
 * @param {String} url
 */
load: function (url) {
    var 
        node = document.createElement('script'),
        self = this;
    node.addEventListener('load', _onload, false);
    node.addEventListener('error', _onerror, false);
    node.type = 'text/javascript';
    node.async = 'async';
    node.src = url;
    _head.appendChild(node);
    function _onload() {
        _onend();
        return Def.make(this.src);
    }
    function _onerror() {
        _onend();
        _head.removeChild(node);
        if (_base && !~url.indexOf(_localBase)) {
            return self.load(url.replace(_base, _localBase));
        } else {
            return self.down();
        }
    }
    function _onend() {
        node.removeEventListener('load', _onload, false);
        node.removeEventListener('error', _onerror, false);
    }
},

    
  
  • 文件路径

获取到正确的文件路径,才能正确判断依赖文件路径,现在主要有两种方法获取文件路径:

  1. getCurrentScript方案
  2. 通过srcipt onload将script.src带过去

document.currentScript是firefox 4.0开始出现的非标准API,而Chrome 29+已经支持,也已写入HTML 5规范中:

Returns the script element that is currently executing. In the case of reentrant script execution, returns the one that most recently started executing amongst those that have not yet finished executing.

Returns null if the Document is not currently executing a script element (e.g. because the running script is an event handler, or a timeout).

所以我们可以通过下面代码来获取路径:

    
      function getCurrentScript() {
    if (document.currentScript) {
        return document.currentScript.src;
    }
}

    
  

但毕竟只能满足部分浏览器,所以又产生了另一种通过error stack来获取当前脚本路径的方法:

    
      function getCurrentScript() {
   //取得正在解析的script节点
   if (document.currentScript) { // firefox 4+
       return document.currentScript.src;
   }
   //  参考 https://github.com/samyk/jiagra/blob/master/jiagra.js
   var stack, i, node;
   try {
       a.b.c(); // 强制报错,以便捕获e.stack
   } catch(e) {
       stack = e.stack;
   }
   if (stack) {
       i = stack.lastIndexOf(' at ');
       var a = stack.slice(i + e.length).replace(/\s\s*$/, '').replace(/(:\d+)?:\d+$/i, '');
       return a;
   }
}

    
  

可这个方案无法支持safari,为了避免代码过大,虽然一般而言这会更有效率,但还是使用第二种方案来实现。

结果

webkit-dwarf 情况
源代码 7021b
uglify 2844b
gizp 662b
支持 现代浏览器 & IE10+

小矮人Javascript模块加载器


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论