Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

模块的加载启动 #260

Closed
lifesinger opened this issue Jul 6, 2012 · 56 comments
Closed

模块的加载启动 #260

lifesinger opened this issue Jul 6, 2012 · 56 comments
Milestone

Comments

@lifesinger
Copy link
Member

模块的加载启动

Sea.js 是一个模块加载器,模块加载器需要实现两个基本功能:

  1. 实现模块定义规范,这是模块系统的基础。
  2. 模块系统的启动与运行。

模块定义规范的实现

这就是 definerequireexportsmodule 的实现。具体实现细节,有兴趣的可以看 Sea.js 的源码:seajs/src。可以按照 Gruntfile.js 中声明的合并顺序阅读,核心是 module.js 文件。

define 等方法的具体使用,请阅读:CMD 模块定义规范

模块系统的启动

有了 define 等模块定义规范的实现,我们可以开发出很多模块。但光有一堆模块不管用,我们还得让它们能跑起来。

首先就是启动问题。比如在 Node 中,启动很简单:

$ node main.js

这就是启动。

再举一个例子,操作系统的启动:大家都知道的,按一下开机键就好。

在 Sea.js 里,要启动模块系统很简单:

<script src="path/to/sea.js"></script>
<script>
  seajs.use('./main');
</script>

seajs.use Function

用来在页面中加载模块。

seajs.use seajs.use(id, callback?)

通过 use 方法,可以在页面中加载任意模块:

// 加载模块 main,并在加载完成时,执行指定回调
seajs.use('./main', function(main) {
  main.init();
});

use 方法还可以一次加载多个模块:

// // 并发加载模块 a 和模块 b,并在都加载完成时,执行指定回调
seajs.use(['./a', './b'], function(a, b) {
  a.init();
  b.init();
});

callback 参数可选,省略时,表示无需回调。

与 DOM ready 的关系

注意seajs.useDOM ready 事件没有任何关系。如果某些操作要确保在 DOM ready 后执行,需要使用 jquery 等类库来保证,比如:

seajs.use(['jquery', './main'], function($, main) {
  $(document).ready(function() {
    main.init();
  });
});

sea.js 的引入

在调用 seajs.use 之前,需要先引入 sea.js 文件,推荐直接使用 script 标签同步引入:

<script src="path/to/sea.js"></script>

为了满足某些场景下的性能优化需求,也可以将 sea.js 的源码内嵌:

<script>
// sea.js 的源码
</script>

注意:代码内嵌时,需要通过 seajs.config 手动配置 base 路径。

最佳实践

  1. seajs.use 理论上只用于加载启动,不应该出现在 define 中的模块代码里。在模块代码里需要异步加载其他模块时,推荐使用 require.async 方法。
  2. 引入 sea.js 时,可以把 sea.js 与其他文件打包在一起,可提前合并好,或利用 combo 服务动态合并。无论哪一种方式,为了让 sea.js 内部能快速获取到自身路径,推荐手动加上 id 属性:
<script src="path/to/sea.js" id="seajsnode"></script>

加上 seajsnode 值,可以让 sea.js 直接获取到自身路径,而不需要通过其他机制去自动获取。这对性能和稳定性会有一定提升,推荐默认都加上。

小结

seajs.use 是模块加载器必备的一个接口。在 seajs 上,还有用于配置的 config 方法、方便调试的 cache 等接口,这些会在接下来的文档中详细阐述。

@lifesinger
Copy link
Member Author

有任何问题,欢迎留言交流。
注意:已解决的问题,会在整理后删除掉。

@Huitimkit
Copy link

请教一下,我使用combo服务合并了各个模块,但是在主文件通过require来调用接口却调用不到,在console里输入

seajs.cache查看各个模块的exports为null,文件是有加载了的。

例如main.js
image

然后测试的时候,除了require('../../../ued/tool/jquery')的exports为object外,其它文件的exports都变成nul了,

我调用popLogin.js插件里的exports出来的popLogin方法会报

Uncaught TypeError: Cannot call method 'popLogin' of null

或者想调用popbox.js里exports出来的pop方法也会报

Uncaught TypeError: Cannot call method 'pop' of null

这个问题纠结了很久,没合并之前是可以调用的,合并之后就出现问题了

@lifesinger
Copy link
Member Author

合并之前要打包。

参考: #426

@fe-ninja
Copy link

fe-ninja commented Sep 4, 2013

在使用seajs的过程中,会产生请求seajs.js.map的请求是什么情况
a
是seajs内部产生的请求么

@lifesinger
Copy link
Member Author

@sourcebank 这个只有开发者模式且开启了 sourceMap 时才会请求,是浏览器主动请求的。不会影响普通用户,忽略就好。

2.2 版本会把 map 文件去掉。

@lepture
Copy link
Contributor

lepture commented Sep 4, 2013

@lifesinger 这个 jquery 的是 jquery 自己的。

@fe-ninja
Copy link

fe-ninja commented Sep 6, 2013

@lepture 嗯,确实是jQuery自己的,做了个demo在没有用seajs的情况下,也出现了
有什么解决之道

@fe-ninja
Copy link

fe-ninja commented Sep 9, 2013

@sourcebank,唉,问了没人回答,今天自己找到了答案,那就自问自答吧
”Source map就是一个Json格式的信息文件,里面储存着位置信息。也就是说,它是压缩后Js的一个字典文件。有了它,出错的时候,调试工具将直接显示原始代码,而不是压缩后的代码。“
hi(chrome设置选项)

@fe-ninja
Copy link

a
b
使用最原始的script加载Raphael时并没有这种问题,使用seajs加载Raphael时会出现这种问题,不知道是不是seajs和Raphael内部的eve冲突了
Raphael :http://raphaeljs.com/

刚才又验证了一下,使用script标签先加载seajs后加载Raphael就有问题,反之则没有

@lizzie
Copy link
Member

lizzie commented Sep 11, 2013

@sourcebank raphael 需要封装成 CMD 模块, 请问你是用的 http://assets.spmjs.org/gallery/raphael/2.1.0/raphael-debug.js 这个吗

@fe-ninja
Copy link

👍 @lizzie 我只是在原来的基础上使用define将代码进行了包装,然后将Raphael绑定到exports上,结果证明不行。
然后使用的你给的那个Raphael-debug结果可以了。
想问一下,之前那种方式有什么问题,有没有哪个issue是讲cmd封装的。

@lizzie
Copy link
Member

lizzie commented Sep 11, 2013

@sourcebank 因为 raphael 中有对 module define 这种处理, 其中一个 module.exports = eve, 没有暴露到全局, 到接下来的逻辑, 没取到 eve 值, 导致报错. 你细看下代码就知道了. 至于怎么封装, raphael 比较特殊, 得另外处理下, 具体你看 这里

另外, 请提的问题到相关的仓库 issue 中. 这个应该提到 cmdjs/gallery 中!

@fe-ninja
Copy link

@lizzie 又出现了问题,同样的代码在新建的测试页面里能运行,但放到项目中就不行了,说是找不到Raphael,但Raphael文件已经加载
a
b
c
d

在require Raphael的地方设了断点,发现运行后返回的为null

@fe-ninja
Copy link

折腾了几个小时,总算解决了
模块定义为
e(文件名也是raphael-debug
别名处
f
然后加载模块的地方
g
最终加载不了
最后统一使用raphael-debug在加载到,就源码解释

@luhang02
Copy link

他自动弄好的,你下载来就能用了

@lzbSun
Copy link

lzbSun commented Dec 13, 2013

如果我用jqueryui插件或者其他的插件是不是都要封装以后才可以用呢?

@tltwuyu
Copy link

tltwuyu commented Dec 13, 2013

不封装的话引用不到。

@lzbSun
Copy link

lzbSun commented Dec 13, 2013

那怎么封装啊?我初学者。

在 2013-12-13 12:00:01,tltwuyu notifications@github.com 写道:

不封装的话引用不到。


Reply to this email directly or view it on GitHub.

@antife-yinyue
Copy link
Contributor

@lzbSun 「初学者」不是借口,文档看完了没 seajs.org

@Lynn-cc
Copy link

Lynn-cc commented Dec 27, 2013

请问玉伯,我们有一个这样的逻辑:
线上正常是:

defined('first', ['jquery'], function(){})
defined('second', ['jquery', 'first'], function(){})
defined('third', ['jquery', 'first'], functoin(){})
seajs.use('third')

其中 first.js 和 seconed.js 被压缩合并成了 first.js ,就是说first.js里含有两个first和second两个模块。

而开发环境的时候,我们需要替换为本地文件进行调试。所以给alias加一个 second.js 的路径。
而替换到本地目录的 first.js 不再包含second.js文件的内容。third.js 无法require到second模块。
这个时候在第三个文件里,我们就只能修改依赖成

defined('third', ['jquery', 'first', 'second'], functoin(){})

由于第三个文件是一系列页面的js,这样导致每个页面js都要加上一句判断开发环境决定是否加上对第二个文件的依赖。

我们希望能环保一些,只在一个地方加这个环境判断。比如在 first.js 中

考虑在first.js的回调函数最后用 require.async ,但是异步加载可能导致second.js和third.js加载顺序有问题,写法不合理。
但又不能直接在first.js里做环境判断再上依赖second.js, 因为依赖顺序反了。

请问有其他更好的办法么?

@afc163
Copy link
Member

afc163 commented Dec 27, 2013

社区有女神出没

@lifesinger
Copy link
Member Author

@Lynn-cc 有个地方没看懂

而替换到本地目录的 first.js 不再包含second.js文件的内容。third.js 无法require到second模块。

third.js 无法 require 到 second 模块?从依赖上看,third.js 只依赖 jquery 和 first,不依赖 second,为什么 third.js 里又会 require second 呢?

能否将这三个文件里面的 require 和 require.async 通过示例代码也展现出来?以便一起来想想怎么优化。

@Lynn-cc
Copy link

Lynn-cc commented Dec 28, 2013

@lifesinger
其中重点就是:本来每个js文件只有一个模块,现在线上由于一些原因合并了一些基础模块,这样导致依赖于这些基础库的入口js的依赖数组需要根据开发or线上环境增加/减少那些被合并到一起的基础模块名。示例如下:

// first.js
define('first', function() {});

// second.js
define('second', ['first'], function() {});

// third.js
var arr = ['first'];
if (isDevelopment) {
    arr.push('second');
}
define('third', arr, function (require) {
    var first = require('first');
    var second = require('second');
});

// index.html
seajs.use('third');

线上版本的first.js是合并了first.js和second.js的
所以同时包含了first模块和second模块的
合并的原因是他们都是基础库
但是各自的功能不同,且second依赖于first

而开发环境所有文件被替换到本地
所以这时本地的first.js就是原始的first单独模块
所以third.js必须再加一个依赖
而third.js是最终每个页面的use的js
这就使每个third.js开头都需要做一次是否为开发环境的判断

@lvshuang
Copy link

lvshuang commented Jan 5, 2014

从2.0.0升级到2.1.1以后,发现通过data-config和data-main来加载配置文件和主入口文件已经不行了,是不是去掉这个功能了

@lifesinger
Copy link
Member Author

data-config / main 去掉很久了。升级文档里有说明。

2014/1/6 lvshuang notifications@github.com

从2.0.0升级到2.1.1以后,发现通过data-config和data-main来加载配置文件和主入口文件已经不行了,是不是去掉这个功能了


Reply to this email directly or view it on GitHubhttps://github.com//issues/260#issuecomment-31608998
.

王保平 / 玉伯(射雕)
送人玫瑰手有余香

@lvshuang
Copy link

lvshuang commented Jan 7, 2014

我从2.0.0切到2.1.1, 但是发现config里面写的别名不起作用! 暂时切回了2.0.0。但是在做dailog的时候发现第一次加载的时候, 我的模块会被执行两次, 注册的函数在触发时也执行了两次。 普通页面则正常。
main.js

define(function(require, exports, module) {
    window.$ = window.jQuery = require('jquery');

    require('bootstrap');
    require('toastr');

    require('./common/bootstrap-modal-hack.js');

    exports.load = function(name) {
        // console.log('执行两次');// will trigger twice
        require.async('./controller/' + name + '.js?' + window.app.version, function(controller){
            if ($.isFunction(controller.run)) {
                controller.run();
            }
        });
    };
    window.app.load = exports.load;

    if (app.controller) {
        exports.load(app.controller);
    }

    // var Widget = require('arale.widget');
    // $(function(){
    //  Widget.autoRenderAll();
    // });

});

dailogHTML页面加载模块

<script type="text/javascript">
    app.load('question/tag-modal');
</script>

模块内容:

define(function(require, exports, module) {
    var AutoComplete = require('jquery.autocomplete');

    exports.run = function() {
        $('#autocomplete').autocomplete({
            autoFocus: true,
            minLength: 1,
            source: function(request, response) {
                $('#autocomplete').data('url');
                $.get($('#autocomplete').data('url'), {'term' : encodeURI(request.term)}, function(remoteResponse) {
                    response(remoteResponse);
                }, 'json');
            },
            select: function(event, ul) {
                var tagIds = $('input[name="tagIds"]').val();
                tagIds = tagIds.split('|');

                if ($.inArray(ul.item.value.toString(), tagIds) > -1) {
                    return false;
                }
                tagIds.push(ul.item.value.toString());
                $('.tag-list').append('<li class="tag">' + ul.item.label + '<span class="close" data-id="' + ul.item.value + '" title="移除此标签">x</span></li>');
                $('input[name="tagIds"]').val(tagIds.join('|'));
                $('#autocomplete').val('');
            }
        });

        $('.tag-list').on('click', '.close', function(e) {
            e.stopPropagation();
            var self = $(this);
            var tagIds = $('input[name="tagIds"]').val();
                tagIds = tagIds.split('|');

            var newTagIds = new Array();
            $(tagIds).each(function(index, value) {
                if (value != self.data('id')) {
                    newTagIds.push(value.toString());
                }
            });

            self.parents('.tag').remove();
            $('input[name="tagIds"]').val(newTagIds.join('|'));
        });

        $('button[type="submit"]').click(function() {
            var form = $('form.tag-edit');
            $.post(form.attr('action'), form.serialize(), function(response){
                if (response.status == 'ok') {
                    toastr.success('修改成功', '编辑标签');
                    $('#edit-modal').modal('hide');
                } else {
                    toastr.error(response.message, '编辑标签');
                }
            }, 'json');
            return false;
        });

    };

});

模块里的内容在第一次加载的时候都会执行两次, 提交表单也会执行两次,但是第二次打开dailog就正常了, 印象中好像遇到过这个问题, 大概记得是javascript的加载机制, 但是具体是什么想不起来了。无奈javascript烂, 昨天看了一个晚上也没找到问题, 不知道是不是seajs2.0.0的BUG。希望有人帮助下, 非常谢谢!

@afc163
Copy link
Member

afc163 commented Jan 7, 2014

@lvshuang

你的代码里本来就执行了两次,一次是在 main.js 里:

    if (app.controller) {
        exports.load(app.controller);
    }

一次是在 html 页面上:

    app.load('question/tag-modal');

@lvshuang
Copy link

lvshuang commented Jan 7, 2014

@afc163
我把主文件代码修改成如下,还是存在同样的问题

define(function(require, exports, module) {
    window.$ = window.jQuery = require('jquery');

    require('bootstrap');
    require('toastr');

    require('./common/bootstrap-modal-hack.js');

    exports.load = function(name) {

        require.async('./controller/' + name + '.js?' + window.app.version, function(controller){
            if ($.isFunction(controller.run)) {
                controller.run();
            }
        });
    };
    window.app.load = function(name) {
        app.controller = null;
        console.log('aaaaaaa');
        exports.load(name);
    };

    // if (app.controller) {
    //  exports.load(app.controller);
    // }

    // var Widget = require('arale.widget');
    // $(function(){
    //  Widget.autoRenderAll();
    // });

});

if (app.controller) {
exports.load(app.controller);
}
这个地方的app.controller和dailog中加载的模块不是一个模块, 两个是不会影响的

@lifesinger
Copy link
Member Author

很抱歉,没有时间帮你定位问题呢。太忙了。

希望你能先自己分析下,最好单独提 issue,放在这些文档的回复里,很容易被遗忘掉。

@lvshuang
Copy link

今天才看到这个issue, 问题已经解决, 是因为我使用bootstrap的modal时, hack了它的函数注册事件,导致事件注册了两次!!

require('./common/bootstrap-modal-hack.js');
// 事件引发点

因此引发了此问题。 谢谢 @lifesinger @afc163 的热心帮忙

@geekerzhou
Copy link

如何引用其他域名的js
比如在localhost的js里

require("http://localhost:81/js/jquery.js"); 
//jquery已经CMD化,在localhost:81下的页面调用没问题

总是返回null

@army8735
Copy link
Member

army8735 commented Jun 6, 2014

@geekerzhou 应该不会

@geekerzhou
Copy link

@army8735 发现seajs和jquery.js如果是来自同一个域的就没问题,如果seajs在localhost而jquery在localhost:81就不行

@army8735
Copy link
Member

army8735 commented Jun 6, 2014

特地试了下确实可以,远程目录都可以何况换个端口。看看网络请求调试下。

@shaoman
Copy link

shaoman commented Jul 8, 2014

你好,我问下,设置了seajs 的base路径,同时define了一个模块ID为haha
使用seajs.user('haha');这种形式请求,为什么还是会加上 base/haha.js 呢?
有没有办法声明我是以ID的形式,而不是请求文件

@ljl360197197
Copy link

define(function(require, exports, module){
var $ = require('jquery');
$(document).ready(function(){
alert('step1');
});
alert('step2');
});

你好,我在执行上面这段代码时,发现在ie9下会先弹出step1,再弹出step2,其他版本的浏览器都正常,即先弹出step2,再弹出step1。

@terrytang66
Copy link

最近正在学习SeaJS 请问有什么好一点的资料么?有木有课本之类的啊

@lvshuang
Copy link

@terrytang66 seajs没有专门的书籍,但是有人写博客,也有官方文档。自己在网上搜下,多看看官方文档,http://my.oschina.net/centerLife/blog/138251 这是我之前写的博客,是对于老版的seajs的。你可以看看。

@lichunqiang
Copy link

太老了,容易误导人,直接https://github.com/seajs/seajs/issues?q=label%3Adocumentation+is%3Aclosed

@lvshuang
Copy link

@lichunqiang 果断隐了

@lichunqiang
Copy link

@lvshuang 🎱 哈哈

@terrytang66
Copy link

谢谢分享~~ @lvshuang @lichunqiang

@seajs seajs locked and limited conversation to collaborators Sep 19, 2014
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests