精华 javascript 模块加载器——coolie
发布于 10 年前 作者 cloudcome 9388 次浏览 最后一次编辑是 8 年前 来自 分享

1、javascript模块化

javascript 模块化是随着 javascript 的工作量逐步增加而产生的必然结果,近年来,javascript 模块化已经诞生了部分标准,也已经在下一代的 javascript 官方标准里提出了草案。

目前,主流的模块化规范是 AMD、CommonJS等,他们良好的规范,优雅的接口设计,合理的设计模式被很多前端开发工程师所接受,并且也有很多模块化加载器也支持他们,例如 requireJS、seaJS等。

2、javascript模块加载器

2.1、coolie 优势

  • coolie (苦力)是作者在近期书写的一款模块加载器,相比市面上的其他模块加载器,有以下优势:
  • 轻量,良好的身材,仅支持高级现代浏览器,没有繁多的路径别名,和 NodeJS 一样,仅支持本地模块。
  • 小巧,尽量少的接口(2个接口)和配置(2个配置)。
  • 优雅,合理的接口和配置。
  • 安全,尽量不侵入全局变量(2个,分别是 define 和 coolie),放心编程。
  • 方便,配套的发布构建工具,为之量身打造。
  • 省心,一键构建,无须人工干预,没有繁多和难以理解的构建配置。

2.2、本地开发环境

开发环境下,不显式模块ID和模块依赖,模块路径必须写完整:

模块入口:./index.js

// 开发环境不能写模块ID
define(function (require){
    // 模块依赖,不能省略文件后缀
    var num = require('./abc.js');
    var text = require('text!./def.txt');

    alert(num + text);
    // => "123这里是一串纯文本"
});

依赖模块:./abc.js

// 开发环境不能写模块ID
define(function (require, exports, module){
    // 模块导出
    module.exports = 123;
});

依赖文本:./def.txt

这里是一串纯文本

2.3、生产环境下:

发布之后,模块入口:./index.js?v=abc123

define('0', ['1', '2'], function (a){
    var b = a('1');
    var c = a('2');

    alert(b+c);
    // => "123这里是一串纯文本"
});
define('1', [], function (a, b, c){
    c.exports = 123;
});
define('2', [], function (a, b, c){
    c.exports = '这里是一串纯文本';
});

2.4、coolie配置

coolie.config({
    // * 入口模块的基准路径,也可以写绝对路径
    // `base`是相对于`coolie.js`所在的路径的,因此只要`coolie.js`路径不变,配置文件无论被哪个文件引用都没关系
    // 可选,默认为`coolie.js`所在的路径
    base: './',

    // * 模块文件版本,用于清除文件缓存,常用于发布到生产环境上,具体细节查看`coolie`发布工具
    // 比如入口文件是`index.js`,那么实际请求的路径为`/path/to/index.js?_=abc123`
    // 可选,默认为空
    version: 'abc123'
});

注意点

  • base参数配置,与sea.js有些出入,请注意。
  • base参数是相对于coolie.js所在路径的。
  • base参数也是入口模块的相对路径。
  • version版本号在开发环境下可以为空,构建时会自动更新版本号以便清除生产环境下的缓存。

2.5、coolie 使用

// 运行入口模块路径,注意这里没有回调
// 模块路径相对于`config.base`
coolie.use('./index.js');

注意点

  • 必须手动调用.use()方法。
  • 参数是入口模块路径,不是入口模块名称。
  • 为了配置文件的重用性,当coolie.js所在的script指定了data-main属性,内联属性优先级最高,因此.use参数此时可以为空。 如:<script src="./coolie.js" data-main="./index2.js"></script>,此时入口模块就为相对于config.base的./index2.js文件。 此种情况会在控制台输出提示。

3、模块构建工具

see

3.1、介绍

配套的模块构建工具 coolie(苦力),已在 npmjs 官方上线。作者之前也使用过 spm3 打包,感觉还是太复杂了,后来自制了 apb,再后来就索性自己写了一整套东西,包括模块加载器和模块构建工具。尽量保持它们的良好身材,只做它们擅长和分内的事情。

3.2、优势

相比其他构建工具,他有以下优势:

  • 优雅,大众化和易理解的API。
  • 易用,全部自动生成配置文件,每一步都有详细说明。
  • 安全,不修改任何源文件。
  • 极致,全部模块,压缩所有可压缩的内容,包括模块ID、模块依赖路径、局部变量等。

3.3、特点

更多API说明,请移步 github 项目,链接如下。

4、链接

27 回复

觉得构建很奇怪,模块id是0,1,2这样的序号,那么这是要求页面上只能use一个js不然会有模块id重名?

连核心匹配的正则都是seajs的,还是乖乖用seajs吧,方便

@airyland

  • 构建之后模块ID会变成0-z的单个字符。
  • 一个页面只能有一个入口模块,减少了复杂度,这样更加的纯粹。

cube 来围观 ,https://github.com/node-cube/cube AMD,CMD写起来多类,要像node一样写,才省心

@cloudcome 明白这样做的意图。但是实际上会增加了迁移成本。当然只是应用于新页面倒没什么问题。

@fish 挺好的。CommonJS 在浏览器端开发有哪些小不足?

  • 无法完美调试,在某些浏览器下无法正确显示脚本路径,甚至丢失断点。
  • 需要额外包装一层,虽然有加载器自动包裹。
  • cude 里用到了 node_modules 里面的模块?会不会造成混淆和误解?

前后端虽然都用了 JS,但还是不必趋同,以目前的情形来看。前端用 webstorm 写一个 cmd 模块,也不差写一个 define。个人拙见。

@airyland 嗯,是的,毕竟没有适应所有情况的加载器。coolie 尽可能做的更好更多更快,前提是做的事情足够纯粹,而不是各种别名或捷径。更加严谨的约束,对模块开发来说,势必会更加顺畅。

@cloudcome 必须完美支持调试断点, 混淆模块的问题,是个小麻烦,后续考虑 cube 命令封装掉。

这名字起的,作践自己嘛

@leapon coolie ,这个名字有什么不吗?

lithe

commonjs

互粉吧,啥也别说了。打酱油的路过。

@leapon 哈哈,就是因为这个而取名的。coolie(苦力),模块加载器本身就是一个苦力,把所有模块集合到一起。

@xiaojue

在写 coolie 的时候也参考了你的脚本,不过有一处提取 require 的正则不是很完善。

var jsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g;
'/** require("abc"); **/'.match(jsRequireRegExp);

@cloudcome 一看就是木有好好瞧我的代码。。我在匹配之前不是把注释都干掉了么,所以永远不会有这种情况的。。。

https://github.com/litheModule/lithe/blob/master/lithe.js#L105

我还是觉得分成2段来看,比较好维护和直观。

@xiaojue 嗯,我表述有误,贴的代码对你造成了误解,其实我想表达的意思是,有注释的时候,你的正则匹配是错的:

var commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg;
var jsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g;
'var reg1 = /\\/*/;var abc = require("abc");/*comment*/'.replace(commentRegExp, '').match(jsRequireRegExp);

@cloudcome 你表达的还是不清楚,你把match去掉,你会发现不是require那个regexp导致的,而是去注释那段正则导致的。

所以你只需要贴commentRegExp就好了嘛,而我又发现你的string部分明显又有问题嘛。

你自己拷贝出来执行一下?

untitled1.png

正则这东西,如果非要拿极值去测,任何正则都不是完美滴,所以我目前还不会改,我们线上好几个项目都用的lithe,目前还没有遇到过你说的提取出错的问题。

@cloudcome 当然我们的代码都是有注释的~我们还是比较有节操的程序员……

@xiaojue 为什么有2个反斜杠?因为在字符串里的反斜杠是需要反斜杠转义的。实际代码为:

var reg1 = /\/*/;
var abc = require("abc");
/*commets*/

两个正则搭配起来去取依赖: 1、先去注释,结果为:

"var reg1 = /\"

2、提取依赖,显而易见无法提取依赖了。

一开头说的是提取依赖的正则有问题,可能有些喧宾夺主了,其根源是去除注释有问题。

这方面,不凡借鉴下 seajs,它提取依赖的正则暂时是没有发现问题的,并且也不需要事先去删除注释。 而你说,你的项目暂时没有遇到,不过上述情况是会出现的。

例如:https://github.com/madrobby/zepto/blob/master/src/ajax.js#L225-L296, 高亮区域就形成了一个符合注释正则的非注释区域。简要如下:

"setHeader('Accept', mime || '*/*')\n\n\n$.get = function(/* url, data, success, dataType */){".match(commentRegExp)

怎么加载相关的Css?

@yakczh

var css = require('css!./path/to/some.css');
var html = require('html!./path/to/some.html');
var text = require('text!./path/to/some.txt');
var js = require('./path/to/some.js');

这样写死路径,重构的时候是不是很麻烦呢?

@cloudcome some.css

body{

background:yellow;

}

index.js中加载 var css = require(‘css!./some.css’);

刷页面,并没有效果,控制台也没有报错,firebug网络面板也显示加载了some.css

@yakczh

  • Q 路径写死了,以后重构怎么办?

  • A 重构的话,牵扯到路径都很麻烦,这是无法避免的,因为这些路径都是物理路径,不是逻辑的。

  • Q 加载了some.css,为什么页面没效果?

  • A 模块加载器只加载不渲染,你要对使 CSS 生效,必须自己插入到 DOM 里。

例:

/**
 * 添加样式
 * @param {String} styleText 样式内容
 *
 * @example
 * modification.importStyle('body{padding: 10px;}');
 */
importStyle: function (styleText) {
    var style = this.create('style');

    styleText = String(styleText);

    this.insert(style, head, 'beforeend');

    // IE
    if (style.styleSheet !== undefined) {

        // 此 BUG 仅影响 IE8(含) 以下浏览器
        // http://support.microsoft.com/kb/262161
        // if (document.getElementsByTagName('style').length > 31) {
        //     throw new Error('Exceed the maximal count of style tags in IE')
        // }

        style.styleSheet.cssText += styleText;
    }
    // W3C
    else {
        style.appendChild(this.create('#text', styleText));
    }
}

详细点这里

@cloudcome 你说的对,如果遇到你这个情况,中间的require部分是会丢掉的。但是这个情况是只有在代码全部写成一行的情况下。

简化了你的例子:var reg1=///;var abc = require(“abc”);

如果代码写成了一行这种情况,后面的东西是会被认为是注释而去掉。

正则的问题出在 | 后面的部分 也就是 ([^:]|^)//(.*)$ 这一句。

它并没有考虑到 你说的 『字符串里的反斜杠是需要反斜杠转义的』

我这个正则的出处是requirejs,参见:

https://github.com/jrburke/requirejs/blob/master/require.js#L16

看来requirejs也有这个隐患,参见他查找deps的部分:

https://github.com/jrburke/requirejs/blob/master/require.js#L2023

so。。看来需要改改代码啦。seajs的正则写成了一个,对所在行进行了一些限制,我觉得我还是要找到一个能正确处理正则的正则比较靠谱。

找到了一个能正确处理你这个情况的去注释正则方法:

http://james.padolsey.com/javascript/javascript-comment-removal-revisted/

看来要更新一下啦,哈哈哈。

回到顶部