amdjs / amdjs-api

Houses the Asynchronous Module Definition API
https://groups.google.com/group/amd-implement
4.31k stars 499 forks source link

Document entry point for a multi-module program #8

Closed davidaurelio closed 12 years ago

davidaurelio commented 12 years ago

The wiki states that an AMD loader only needs to provide one global: The “define” function.

It’s unclear what the main entry point of a program that uses multiple modules is.

E.g., when writing a program that uses modules “a”, “b”, and “c”, how would that look like?

I assume that

define(['a', 'b', 'c'], function(a, b, c) {
  // main functionality here
});

would never be executed.

eric-brechemier commented 12 years ago

I second this issue. It seems that the only way to bootstrap the dynamic loading sequence is to call top-level require() or rely on another specific mechanism (e.g. data-main attribute on script).

Sadly, the global require() is not part of AMD per se:

Require

The behavior of global require() is not defined by this API, and for interoperability it should not be assumed. Normally there is an implementation-dependent API that will kick off module loading.

patrickcteng commented 12 years ago

I think something like Java or C#'s main() functions would be a nice inclusion. However, then the problem becomes: How do we prevent multiple main methods in the other modules?

rbackhouse commented 12 years ago

Typically the entry point into the loader is implementation specific. For example requirejs and the Dojo 1.7 AMD loader provide a "global" require entry point while curl and the loaders I have written (lsjs and zazl) have their own named entry point. I prefer not using a global require as it is easy to mistake it with the local require given to modules and can result in programming mistakes.

I'm not sure it should be mandated by the spec what this entry point should be named.

eric-brechemier commented 12 years ago

I find it disturbing that the global entry point is required but not defined in the specification. This makes it impossible to write a complete application based on AMD without making some changes when switching to another AMD loader.

jrburke commented 12 years ago

I can see the desire for this, but I do not think we are quite ready for it yet. I think we need to lock down some common config first, and how global config is done. I think we are close to being able to do that with baseUrl and paths.

But in general, I think it is good to still allow experimentation around the basic AMD API, and loaders need their own global space for it.

Hopefully though switching loaders gets down to changing the script tag source and modifying the top level call. I can see something like data-main on the script tag becoming more useful, but I think this show organically via loader implementers deciding to use it.

eric-brechemier commented 12 years ago

I see... What about the specification of out-of-context anonymous defines? Getting back to the example in the original poster's question, could that anonymous define be interpreted like a call to local require()?

define(['a', 'b', 'c'], function(a, b, c) {
  // main functionality here
});

made equivalent to:

/*local*/require(['a', 'b', 'c'], function(a, b, c) {
  // main functionality here
});

This approach may solve both issues:

jrburke commented 12 years ago

For out of context anonymous defines, I think it is best to discard them, since they are not expected by the loader, and the reason to use define() it so define a code unit that can be referred to by others. If the loader is not expecting the module/cannot tie it to a name, I believe it is safer not to execute it. This comes up more in the case a script gets loaded on a page outside a loader call but it contains a define() call. This case is actually not desirable to execute.

A top-level define could be used to bootstrap loading logic, it just has to be named define. define('main', function (){}) would make sense to me.

domenic commented 12 years ago

For the record, the CommonJS Modules/2.0 draft specifies that the main module is the one defined in the "extra module environment," which in the browser means inside an inline <script> tag. So your index.html might look like

<script src="require.js"></script>
<script>
    define(["a", "b", "c"], function () {
        // this code will be executed automatically, once a, b, and c are provided.
    });
</script>

This seems possibly related to @eric-brechemier's ideas; I'm not sure exactly what he meant by "out-of-context" though.

eric-brechemier commented 12 years ago

@DomenicDenicola Yes, this is part of what I meant by out-of-context: loaded outside the loader, using an internal (inline) script or an external script tag.

It is actually better to avoid internal scripts for performance reasons, since "stylesheets followed by an inline script block any subsequent resources from downloading" (from Steve Souder's "Even Faster Web Sites", Chapter 6).

@jrburke That should work.

Is it safe to assume that it would also allow to load requireJS (for example) + several named modules combined in a single file, using an external script?

jrburke commented 12 years ago

Is it safe to assume that it would also allow to load requireJS (for example) + several named modules combined in a single file, using an external script?

@eric-brechemier - yes, if you create a script that embeds require.js, and some define()'ed modules with IDs, then that one script could be added to the page after page load by some other process, and all the the modules should execute. In fact, if you switch out require.js for another loader like curl, or even a build-friendly AMD shim like almond, the modules will execute correctly. This has been proven in practice where I use require.js in dev, but then use almond for builds.

eric-brechemier commented 12 years ago

Great! That closes the issue for me. How do we move forward? Should we post a summary of this discussion to the amd-impl mailing list? Something along these lines maybe:

Subject: Proposal for the boostrap of dynamic loading of AMD modules

The declaration of the mechanism to determine the first modules to load (one or several top-level modules) has been left out of the AMD specification so far. This issue has been discussed on the tracker of the AMD API on GitHub [https://github.com/amdjs/amdjs-api/issues/8], leading to a proposed solution:

Some modules may be defined outside the loader, using a script element, as long as they call define() with an id. Calling define() out of context without an id will trigger an error.

For example, a top level module can be defined with following code, loaded with an internal or an external script tag:

<script>
define("main", ['a','b','c'], function(a,b,c){
  // start the application
  a.setup();
  b.init();
  c.start();
});
</script>

Using this mechanism, several modules may be defined within the same file; for example, an AMD loader may be followed with several define() calls to define modules with different ids in an optimized script. All modules will be defined as expected.

jrburke commented 12 years ago

Sure, feel free to post a summary of the discussion. Although I think it falls out naturally from the define API.

Thinking about this more though, I can see there may be cases where a loader may not run the main module like the above until there is a top-level require() or loader-specific call. requirejs does this because when many modules are in a file, it is best to not trace dependencies until all the modules have called define(), so that a request for a module that is already in that file is not done.

requirejs also does a setTimeout check on first load, so if there is an inline define like the above, as long as it is part of the initial page execution, it will be run. But I can see this as being loader-specific.

eric-brechemier commented 12 years ago

@jrburke This is what I meant: the behavior you describe, not tracing dependencies until all the modules have called define(), should not apply to calls to define() with an id.

How would you rephrase it?

jrburke commented 12 years ago

@eric-brechemier that would be difficult to do, since the primary use case for "define() with id" is for built files with multiple modules, and in those cases, the dependencies should not be traced as soon as define() is called.

So you are probably looking for a different call for the top level, and define() as-is may not be sufficient. It is probably best to move this discussion over to the amd-implement list, since not all amd-implement participants track these issue tickets. I think it is now more about getting implementers to agree on a top-level name, and if they are ready to do so.

eric-brechemier commented 12 years ago

@jrburke Thanks for the feedback.

I will move the discussion to the mailing list and propose to make the local require() accessible as define.require().

eric-brechemier commented 12 years ago

Following discussion on the amd-implement mailing list, the AMD specification has been updated: https://github.com/amdjs/amdjs-api/wiki/require#wiki-localGlobal

There is now a second global expected to be implemented in AMD loaders: require().

Using the global require(), the code of @davidaurelio for the main entry point of a program that uses multiple modules becomes:

require(['a', 'b', 'c'], function(a, b, c) {
  // main functionality here
});
jrburke commented 12 years ago

A small clarification, an AMD loader is not required to implement a global require() but if it does, for interop sake, it should follow the rules in that section.