UNPKG

neuronjs

Version:

Neuron is A Full Feature CommonJS Module Manager, Dependency Graph Handler and Loader for Browsers

1,394 lines (1,101 loc) 35.3 kB
(function(ENV){ /** * @preserve Neuron JavaScript Framework (c) Kael Zhang <i@kael.me> */ // Goal // Manage module dependencies and initialization // Non-goal // > What neuron will never do // 1. Neuron will never care about non-browser environment // 2. Neuron core will never care about module loading 'use strict'; var neuron = { version: '10.0.0' }; var NULL = null; var FALSE = !1; // // Check and make sure the module is downloaded, // // if not, it will download the module // neuron.load = function (module, callback){ // callback(); // } // ## ECMAScript5 implementation ////////////////////////////////////////////////////////////////////// // - methods native object implemented // - methods native object extends // codes from mootools, MDC or by Kael Zhang // ## Indexes // ### Array.prototype // - indexOf // - lastIndexOf // - filter // - forEach // - every // - map // - some // - reduce // - reduceRight // ### Object // - keys // - create: removed // ### String.prototype // - trim // - trimLeft // - trimRight // ## Specification // ### STANDALONE language enhancement // - always has no dependencies on Neuron // - always follow ECMA standard strictly, including logic, exception type // - throw the same error hint as webkit on a certain exception function extend(host, methods) { for (var name in methods) { if (!host[name]) { host[name] = methods[name]; } } } function implement(host, methods) { extend(host.prototype, methods); } var TYPE_ERROR = TypeError; // ref: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array implement(Array, { // Accessor methods ------------------------ indexOf: function(value, from) { var len = this.length >>> 0; from = Number(from) || 0; from = Math[from < 0 ? 'ceil' : 'floor'](from); if (from < 0) { from = Math.max(from + len, 0); } for (; from < len; from++) { if (from in this && this[from] === value) { return from; } } return -1; }, lastIndexOf: function(value, from) { var len = this.length >>> 0; from = Number(from) || len - 1; from = Math[from < 0 ? 'ceil' : 'floor'](from); if (from < 0) { from += len; } from = Math.min(from, len - 1); for (; from >= 0; from--) { if (from in this && this[from] === value) { return from; } } return -1; }, // Iteration methods ----------------------- filter: function(fn, thisObject) { var ret = []; for (var i = 0, len = this.length; i < len; i++) { // Kael: // Some people might ask: "why we use a `i in this` here?". // ECMA: // > callback is invoked only for indexes of the array which have assigned values; // > it is not invoked for indexes which have been deleted or which have never been assigned values // Besides, `filter` method is not always used with real Arrays, invocations below might happen: // var obj = {length: 4}; obj[3] = 1; // Array.prototype.filter.call({length: 4}); // Array.prototype.filter.call($('body')); // as well as the lines below if ((i in this) && fn.call(thisObject, this[i], i, this)) { ret.push(this[i]); } } return ret; }, forEach: function(fn, thisObject) { for (var i = 0, len = this.length; i < len; i++) { if (i in this) { // if fn is not callable, it will throw fn.call(thisObject, this[i], i, this); } } }, every: function(fn, thisObject) { for (var i = 0, len = this.length; i < len; i++) { if ((i in this) && !fn.call(thisObject, this[i], i, this)) { return false; } } return true; }, map: function(fn, thisObject) { var ret = [], i = 0, l = this.length; for (; i < l; i++) { // if the subject of the index i is deleted, index i should not be contained in the result of array.map() if (i in this) { ret[i] = fn.call(thisObject, this[i], i, this); } } return ret; }, some: function(fn, thisObject) { for (var i = 0, l = this.length; i < l; i++) { if ((i in this) && fn.call(thisObject, this[i], i, this)) { return true; } } return false; }, reduce: function(fn) { if (typeof fn !== 'function') { throw new TYPE_ERROR(fn + ' is not an function'); } var self = this, len = self.length >>> 0, i = 0, ret; if (arguments.length > 1) { ret = arguments[1]; } else { do { if (i in self) { ret = self[i++]; break; } // if array contains no values, no initial value to return if (++i >= len) { throw new TYPE_ERROR('Reduce of empty array with on initial value'); } } while (true); } for (; i < len; i++) { if (i in self) { ret = fn.call(NULL, ret, self[i], i, self); } } return ret; }, reduceRight: function(fn) { if (typeof fn !== 'function') { throw new TYPE_ERROR(fn + ' is not an function'); } var self = this, len = self.length >>> 0, i = len - 1, ret; if (arguments.length > 1) { ret = arguments[1]; } else { do { if (i in self) { ret = self[i--]; break; } // if array contains no values, no initial value to return if (--i < 0) { throw new TYPE_ERROR('Reduce of empty array with on initial value'); } } while (true); } for (; i >= 0; i--) { if (i in self) { ret = fn.call(NULL, ret, self[i], i, self); } } return ret; } }); extend(Object, { // ~ https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/create ~ // create: function(o){ // if(o !== Object(o) && o !== NULL){ // throw new TYPE_ERROR('Object prototype may only be an Object or NULL'); // } // function F() {} // F.prototype = o; // return new F(); // }, // refs: // http://ejohn.org/blog/ecmascript-5-objects-and-properties/ // http://whattheheadsaid.com/2010/10/a-safer-object-keys-compatibility-implementation // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys // https://developer.mozilla.org/en/ECMAScript_DontEnum_attribute // http://msdn.microsoft.com/en-us/library/adebfyya(v=vs.94).aspx keys: (function() { var hasOwnProperty = Object.prototype.hasOwnProperty, has_dontEnumBug = !{ toString: '' }.propertyIsEnumerable('toString'), // In some old browsers, such as OLD IE, keys below might not be able to iterated with `for-in`, // even if each of them is one of current object's own properties NONT_ENUMS = [ 'toString', 'toLocaleString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'constructor' ], NONT_ENUMS_LENGTH = NONT_ENUMS.length; return function(o) { if (o !== Object(o)) { throw new TYPE_ERROR('Object.keys called on non-object'); } var ret = [], name; for (name in o) { if (hasOwnProperty.call(o, name)) { ret.push(name); } } if (has_dontEnumBug) { for (var i = 0; i < NONT_ENUMS_LENGTH; i++) { if (hasOwnProperty.call(o, NONT_ENUMS[i])) { ret.push(NONT_ENUMS[i]); } } } return ret; }; })() // for our current OOP pattern, we don't reply on Object based inheritance // so Neuron has not implemented the methods of Object such as Object.defineProperty, etc. }); implement(String, { trimLeft: function() { return this.replace(/^\s+/, ''); }, trimRight: function() { return this.replace(/\s+$/, ''); }, trim: function() { return this.trimLeft().trimRight(); } }); // common code slice ////////////////////////////////////////////////////////////////////// // - constants // - common methods // @const // 'a@1.2.3/abc' -> // ['a@1.2.3/abc', 'a', '1.2.3', '/abc'] // 0 1 2 3 var REGEX_PARSE_ID = /^((?:[^\/])+?)(?:@([^\/]+))?(\/.*)?$/; // On android 2.2, // `[^\/]+?` will fail to do the lazy match, but `(?:[^\/])+?` works. // Shit, go to hell! // Parses a module id into an object // @param {string} id path-resolved module identifier // 'a@1.0.0' -> 'a@1.0.0' // 'a' -> 'a@*' // 'a/inner' -> 'a@*/inner' function parse_module_id (id) { var match = id.match(REGEX_PARSE_ID); var name = match[1]; // 'a/inner' -> 'a@latest/inner' var version = match[2] || '*'; var path = match[3] || ''; // There always be matches return format_parsed({ n: name, v: version, p: path }); } // Format package id and pkg // `parsed` -> 'a@1.1.0' function format_parsed(parsed) { var pkg = parsed.n + '@' + parsed.v; parsed.id = pkg + parsed.p; parsed.k = pkg; return parsed; } // Legacy // Old neuron modules will not define a real resolved id. // We determine the new version by `env.map` // Since 6.2.0, actually, neuron will and should no longer add file extension arbitrarily, // because `commonjs-walker@3.x` will do the require resolve during parsing stage. // But old version of neuron did and will add a `config.ext` to the end of the file. // So, if commonjs-walker does so, we adds a '.js' extension to the end of the identifier. // function legacy_transform_id (id, env) { // return env.map // ? id // : id + '.js'; // } // A very simple `mix` method // copy all properties in the supplier to the receiver // @param {Object} receiver // @param {Object} supplier // @returns {mixed} receiver function mix(receiver, supplier) { for (var c in supplier) { receiver[c] = supplier[c]; } } // greedy match: var REGEX_DIR_MATCHER = /.*(?=\/.*$)/; // Get the current directory from the location // // http://jsperf.com/regex-vs-split/2 // vs: http://jsperf.com/regex-vs-split function dirname(uri) { var m = uri.match(REGEX_DIR_MATCHER); // abc/def -> abc // abc -> abc // which is different with `path.dirname` of node.js // abc/ -> abc return m ? m[0] : uri; } // Get the relative path to the root of the env // @returns {string} a module path function resolve_path (path, env) { // '', 'a.png' -> 'a.png' // '', './a.png' -> 'a.png' // '', '../a.png' -> '../a.png' // '/index.js', 'a.png' -> 'a.png' return path_join( // '' -> '' -> '' // '/index.js' -> '/' -> '' dirname(env.p).slice(1), path ); } // Resolves an id according to env // @returns {string} a module id function resolve_id (path, env) { path = resolve_path(path, env); return path ? env.k + '/' + path : env.k; } // Canonicalize path // The same as `path.resolve()` of node.js. // For example: // path_join('a', 'b') -> 'a/b' // path_join('a/b', './c') -> 'a/b/c' // path_join('a/b', '../c') -> 'a/c' // path_join('a//b', './c') -> 'a/b/c' // #75: // path_join('../abc', './c') -> '../abc/c', // path_join('', './c') -> 'c' // path_join('', '../c') -> '../c' function path_join(from, to) { var parts = (from + '/' + to) .split('/') // Filter empty string: // ['', '.', 'c'] -> ['.', 'c'] .filter(Boolean); return normalize_array(parts).join('/'); } // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // resolves . and .. elements in a path array with directory names there // must be no slashes, empty elements, or device names (c:\) in the array // (so also no leading and trailing slashes - it does not distinguish // relative and absolute paths) function normalize_array(parts) { // if the path tries to go above the root, `up` ends up > 0 var up = 0; var i = parts.length - 1; for (; i >= 0; i--) { var last = parts[i]; if (last === '.') { parts.splice(i, 1); } else if (last === '..') { parts.splice(i, 1); up++; } else if (up) { parts.splice(i, 1); up--; } } // if the path is allowed to go above the root, restore leading ..s while (up--) { parts.unshift('..'); } return parts; } // @param {string} path function is_path_relative(path) { return path.indexOf('./') === 0 || path.indexOf('../') === 0; } function err (message) { throw new Error('neuron: ' + message); } function module_not_found (id) { err("Cannot find module '" + id + "'"); } // ## A very simple EventEmitter ////////////////////////////////////////////////////////////////////// var events = {}; // @param {this} self // @param {string} type // @returns {Array.<function()>} function get_event_storage_by_type(type) { return events[type] || (events[type] = []); } // Register an event once function on(type, fn) { get_event_storage_by_type(type).push(fn); } // Emits an event function emit(type, data) { var handlers = get_event_storage_by_type(type); handlers.forEach(function(handler) { handler(data); }); } // ## Neuron Core: Module Manager ////////////////////////////////////////////////////////////////////// // ## CommonJS // Neuron 3.x or newer is not the implementation of any CommonJs proposals // but only [module/1.0](http://wiki.commonjs.org/wiki/Modules/1.0), one of the stable CommonJs standards. // And by using neuron and [cortex](http://github.com/cortexjs/cortex), user could write module/1.0 modules. // Just FORGET `define`. // ## Naming Conventions of Variables // All naming of variables should accord to this. // Take `'a@1.0.0/relative'` for example: // ### package // The package which the current module belongs to. // - name or package name: {string} package `name`: 'a' // - package or package id: {string} contains package `name` and `version` and the splitter `'@'`. // 'a@1.0.0' for instance. // ### module // A package is consist of several module objects. // - mod: {object} the module object. use `mod` instead of `module` to avoid confliction // - id or module id: the descripter that contains package name, version, and path information // {string} for example, `'a@1.0.0/relative'` is a module id(entifier) // ### version // Package version: '1.0.0' // ### main entry // The module of a package that designed to be accessed from the outside // ### shadow module and module // A single module may have different contexts and runtime results // - mod: the original module definition, including factory function, dependencies and so on // - module: the shadow module, which is inherited from `mod` //////////////////////////////////////////////////////////////////////////////////////////////// // Parse an id within an environment, and do range mapping, resolving, applying aliases. // Returns {Object} parsed object // @param {string} id // @param {Object=} env the environment module function parse_id(id, env) { // commonjs parser could not parse non-literal argument of `require` id || err('null id'); env || (env = {}); // { // alias: { // // id -> path // './a' -> './a.js' // } // } var map = env.map || {}; id = map[id] || id; // Two kinds of id: // - relative module path // - package name // - a module with path loaded by facade or _use var parsed; var relative = is_path_relative(id); // `env` exists, which means the module is accessed by requiring within another module. // `id` is something like '../abc' if (relative) { env.id || module_not_found(id); id = resolve_id(id, env); // Legacy // If >= 6.2.0, there is always a map, // and a value of map is always a top level module id. // So, only if it is old wrappings, it would come here. // Besides, if not a relative id, we should not adds `'.js'` even it is an old wrapping. // How ever, we pass `env` to have a double check. // id = legacy_transform_id(id, env); parsed = parse_module_id(id); // `id` is something like 'jquery' } else { // 1. id is a package name // 'jquery' -> 'jquery@~1.9.3' // 2. id may be is a package id // 'jquery@^1.9.3' -> 'jquery@^1.9.3' id = env.m && env.m[id] || id; // 'jquery' -> {n: 'jquery', v: '*', p: ''} // 'jquery@~1.9.3' -> {n: 'jquery', v: '~1.9.3', p: ''} parsed = parse_module_id(id); } if (parsed.k === env.k) { // if inside the same package of the parent module, // it uses a same sub graph of the package parsed.graph = env.graph; } else { // We route a package of certain range to a specific version according to `config.graph` // so several modules may point to a same exports // if is foreign module, we should parses the graph to the the sub graph var sub_graph = get_sub_graph(parsed.k, env.graph) // If sub_graph not found, set it as `[]` || []; parsed.graph = sub_graph; parsed.v = sub_graph[0] || parsed.v; format_parsed(parsed); } return parsed; } function get_sub_graph (pkg, graph) { var global_graph = NEURON_CONF.graph._; var deps = graph ? graph[1] // If `graph` is undefined, fallback to global_graph : global_graph; return deps && (pkg in deps) // `deps[pkg]` is the graph id for the subtle graph ? NEURON_CONF.graph[deps[pkg]] : global_graph; } // Get the exports // @param {Object} module function get_exports(module) { // Since 6.0.0, neuron will not emit a "cyclic" event. // But, detecing static cyclic dependencies is a piece of cake for compilers, // such as [cortex](http://github.com/cortexjs/cortex) return module.loaded ? module.exports // #82: since 4.5.0, a module only initialize factory functions when `require()`d. : generate_exports(module); } // Generate the exports of the module function generate_exports (module) { // # 85 // Before module factory being invoked, mark the module as `loaded` // so we will not execute the factory function again. // `mod.loaded` indicates that a module has already been `require()`d // When there are cyclic dependencies, neuron will not fail. module.loaded = true; // During the execution of factory, // the reference of `module.exports` might be changed. // But we still set the `module.exports` as `{}`, // because the module might be `require()`d during the execution of factory // if cyclic dependency occurs. var exports = module.exports = {}; // TODO: // Calculate `filename` ahead of time var __filename // = module.filename = NEURON_CONF.resolve(module.id); var __dirname = dirname(__filename); // to keep the object mod away from the executing context of factory, // use `factory` instead `mod.factory`, // preventing user from fetching runtime data by 'this' var factory = module.factory; factory(create_require(module), exports, module, __filename, __dirname); return module.exports; } var guid = 1; // Get a shadow module or create a new one if not exists // facade({ entry: 'a' }) function get_module (id, env, strict) { var parsed = parse_id(id, env); var graph = parsed.graph; var mod = get_mod(parsed); var real_id = mod.main // if is main module, then use `pkg` as `real_id` ? parsed.k : parsed.id; // `graph` is the list of modules for a certain package var module = graph[real_id]; if (!module) { !strict || module_not_found(id); // So that `module` could be linked with a unique graph module = graph[real_id] = create_shadow_module(mod); module.graph = graph; // guid module.g || (module.g = guid ++); } return module; } // @param {Object} module // @param {function(exports)} callback function use_module (module, callback) { ready(module, function () { callback(get_exports(module)); }); } // Create a mod function get_mod(parsed) { var id = parsed.id; return mods[id] || (mods[id] = { // package name: 'a' n: parsed.n, // package version: '1.1.0' v: parsed.v, // module path: '/b' p: parsed.p, // module id: 'a@1.1.0/b' id: id, // package id: 'a@1.1.0' k: parsed.k, // version map of the current module m: {}, // loading queue l: [], // If no path, it must be a main entry. // Actually, it actually won't happen when defining a module main: !parsed.p // map: {Object} The map of aliases to real module id }); } // @param {Object} mod Defined data of mod function create_shadow_module (mod) { function F () { // callbacks this.r = []; } F.prototype = mod; return new F; } // Since 4.2.0, neuron would not allow to require an id with version // TODO: // for scoped packages function test_require_id (id) { !~id.indexOf('@') || err("id with '@' is prohibited"); } // use the sandbox to specify the environment for every id that required in the current module // @param {Object} env The object of the current module. // @return {function} function create_require(env) { var require = function(id) { // `require('a@0.0.0')` is prohibited. test_require_id(id); var module = get_module(id, env, true); return get_exports(module); }; // @param {string} id Module identifier. // Since 4.2.0, we only allow to asynchronously load a single module require.async = function(id, callback) { var origin = id; if (callback) { // `require.async('a@0.0.0')` is prohibited test_require_id(id); var relative = is_path_relative(id); if (relative) { id = resolve_id(id, env); var entries = env.entries; id = entries ? test_entries(id, entries) || test_entries(id + '.js', entries) || test_entries(id + '.json', entries) || module_not_found(origin) // : legacy_transform_id(id, env); : id; } var module = get_module(id, env); if (!module.main) { if (relative) { // If user try to load a non-entry module, it will get a 404 response module.a = true; } else { // We only allow to `require.async` main module or entries of the current package return; } } use_module(module, callback); } }; // @param {string} path // @returns // - {string} if valid // - otherwise `undefined` require.resolve = function (path) { // NO, you should not do this: // `require.resolve('jquery')` // We only allow to resolve a relative path // Trying to load the resources of a foreign package is evil. if (is_path_relative(path)) { path = resolve_path(path, env); // If user try to resolve a url outside the current package // it fails silently if (!~path.indexOf('../')) { return NEURON_CONF.resolve(env.k + '/' + path); } } }; return require; } function test_entries (path, entries) { return ~entries.indexOf(path) ? path : FALSE; } // ## Script Loader ////////////////////////////////////////////////////////////////////// var DOC = document; // never use `document.body` which might be NULL during downloading of the document. var HEAD = DOC.getElementsByTagName('head')[0]; function load_js(src) { var node = DOC.createElement('script'); node.src = src; node.async = true; js_onload(node, function() { HEAD.removeChild(node); }); // A very tricky way to avoid several problems in iOS webviews, including: // - webpage could not scroll down in iOS6 // - could not maintain vertial offset when history goes back. setTimeout(function () { HEAD.insertBefore(node, HEAD.firstChild); }, 0); } var js_onload = DOC.createElement('script').readyState // @param {DOMElement} node // @param {!function()} callback asset.js makes sure callback is not NULL ? function(node, callback) { node.onreadystatechange = function() { var rs = node.readyState; if (rs === 'loaded' || rs === 'complete') { node.onreadystatechange = NULL; callback.call(this); } }; } : function(node, callback) { node.addEventListener('load', callback, FALSE); }; // module define // --------------------------------------------------------------------------------------------------- // Method to define a module. // **NOTICE** that `define` has no fault tolerance and type checking since neuron 2.0, // because `define` method is no longer designed for human developers to use directly. // `define` should be generated by some develop environment such as [cortex](http://github.com/cortexjs/cortex) // @private // @param {string} id (optional) module identifier // @param {Array.<string>} dependencies ATTENSION! `dependencies` must be array of standard // module id and there will be NO fault tolerance for argument `dependencies`. Be carefull! // @param {function(...[*])} factory (require, exports, module) // @param {Object=} options // @return {undefined} function define(id, dependencies, factory, options) { options || (options = {}); var parsed = parse_id(id); if (parsed.p) { // Legacy // in old times, main entry: // - define(id_without_ext) // - define(pkg) <- even older // now, main entry: define(id_with_ext) // parsed.p = legacy_transform_id(parsed.p, options); format_parsed(parsed); } var pkg = parsed.k; var modMain; if (options.main) { modMain = mods[pkg]; } // `mod['a@1.1.0']` must be USED before `mod['a@1.1.0/index.js']`, // because nobody knows which module is the main entry of 'a@1.1.0' // But `mod['a@1.1.0/index.js']` might be DEFINED first. var mod = mods[parsed.id] = modMain || mods[parsed.id] || get_mod(parsed); if (options.main) { mods[pkg] = mod; // Set the real id and path mix(mod, parsed); } mix(mod, options); // A single module might be defined more than once. // use this trick to prevent module redefining, avoiding the subsequent side effect. // mod.factory -> already defined // X mod.exports -> the module initialization is done if (!mod.factory) { mod.factory = factory; mod.deps = dependencies; // ['a@0.0.1'] -> {'a' -> 'a@0.0.1'} generate_module_version_map(dependencies, mod.m); var asyncDeps = options.asyncDeps; if (asyncDeps) { generate_module_version_map(asyncDeps, mod.m); } run_callbacks(mod, 'l'); } } // @private // create version info of the dependencies of current module into current sandbox // @param {Array.<string>} modules no type detecting // @param {Object} host // ['a@~0.1.0', 'b@~2.3.9'] // -> // { // a: '~0.1.0', // b: '~2.3.9' // } function generate_module_version_map(modules, host) { modules.forEach(function(mod) { var name = mod.split('@')[0]; host[name] = mod; }); } // Run the callbacks function run_callbacks (object, key) { var callbacks = object[key]; var callback; // Mark the module is ready // `delete module.c` is not safe // #135 // Android 2.2 might treat `null` as [object Global] and equal it to true, // So, never confuse `null` and `false` object[key] = FALSE; while(callback = callbacks.pop()){ callback(); } } // The logic to load the javascript file of a package ////////////////////////////////////////////////////////////////////// function load_module (module, callback) { var mod = mods[module.id]; mod.f = module.f; mod.a = module.a; var callbacks = mod.l; if (callbacks) { callbacks.push(callback); if (callbacks.length < 2) { load_by_module(mod); } } } // Scenarios: // 1. facade('a/path'); // -> load a/path -> always // 2. facade('a'); // -> load a.main // 3. require('a'); // -> deps on a // 4. require('./path') // -> deps on a // 5. require.async('a') // -> load a.main -> // 6. require.async('./path') // -> load a/path // 7. require.async('b/path'): the entry of a foreign module // -> forbidden var pkgs = []; // Load the script file of a module into the current document // @param {string} id module identifier function load_by_module(mod) { if (mod.d) { return; } // (D)ownloaded // flag to mark the status that a module has already been downloaded mod.d = true; var isFacade = mod.f; var isAsync = mod.a; var pkg = mod.k; // if one of the current package's entries has already been loaded, // and if the current module is not an entry(facade or async) if (~pkgs.indexOf(pkg)) { if (!isFacade && !isAsync) { return; } } else { pkgs.push(pkg); } var loaded = NEURON_CONF.loaded; // is facade ? var evidence = isFacade // if a facade is loaded, we will push `mod.id` of the facade instead of package id // into `loaded` ? mod.id : pkg; if (~loaded.indexOf(evidence)) { if (!isAsync) { // If the main entrance of the package is already loaded // and the current module is not an async module, skip loading. // see: declaration of `require.async` return; } // load packages } else { loaded.push(evidence); } load_js(module_to_absolute_url(mod)); } function module_to_absolute_url(mod) { var id = mod.main // if is a main module, we will load the source file by package // 1. // on use: 'a@1.0.0' (async or sync) // -> 'a/1.0.0/a.js' // 2. // on use: 'a@1.0.0/relative' (sync) // -> not an async module, so the module is already packaged inside: // -> 'a/1.0.0/a.js' ? mod.k + '/' + mod.n + '.js' // if is an async module, we will load the source file by module id : mod.id; return NEURON_CONF.resolve(id); } // ## Graph Isomorphism and Dependency resolving ////////////////////////////////////////////////////////////////////// // ### module.defined <==> module.factory // Indicates that a module is defined, but its dependencies might not defined. // ### module.ready // Indicates that a module is ready to be `require()`d which may occurs in two cases // - A module is defined but has no dependencies // - A module is defined, and its dependencies are defined, ready or loaded // ### module.loaded // Indicates that module.exports has already been generated // Register the ready callback for a module, and recursively prepares // @param {Object} module // @param {function()} callback // @param {Array=} stack function ready (module, callback, stack) { emit('beforeready', module_id(module) + ':' + module.g); if (!module.factory) { emit('beforeload', module.id); return load_module(module, function () { emit('load', module_id(module)); ready(module, callback, stack); }); } var deps = module.deps; var counter = deps.length; var callbacks = module.r; // `module.r` is `[]` in origin. // `!callbacks` means the module is ready if (!counter || !callbacks) { module.r = FALSE; emit_ready(module); return callback(); } callbacks.push(callback); // if already registered, skip checking if (callbacks.length > 1) { return; } var cb = function () { if (!-- counter) { stack.length = 0; stack = NULL; emit_ready(module); run_callbacks(module, 'r'); } }; stack = stack ? [module].concat(stack) : [module]; deps.forEach(function (dep) { var child = get_module(dep, module); // If the child is already in the stack, // which means there might be cyclic dependency, skip it. if (~stack.indexOf(child)) { return cb(); } ready(child, cb, stack); }); } function emit_ready (module) { emit('ready', module_id(module) + ':' + module.g); } function module_id (module) { return module.main ? module.k : module.id; } // Manage configurations ////////////////////////////////////////////////////////////////////// // var neuron_loaded = []; var NEURON_CONF = { loaded: [], // If `config.graph` is not specified, graph: { _: {} }, resolve: module_id_to_absolute_url }; // server: 'http://localhost/abc', // -> http://localhost/abc/<relative> // @param {string} relative relative module url function module_id_to_absolute_url(id) { var pathname = id.replace('@', '/'); var base = NEURON_CONF.path; base || err('config.path must be specified'); base = base.replace('{n}', pathname.length % 3 + 1); return base + pathname; } var SETTERS = { // The server where loader will fetch modules from // Make sure the path is a standard pathname 'path': function(path) { // Make sure // - there's one and only one slash at the end // - `conf.path` is a directory return path.replace(/\/*$/, '/'); }, 'loaded': justReturn, 'graph': justReturn, 'resolve': justReturn }; function justReturn(subject) { return subject; } function config(conf) { var key; var setter; for (key in conf) { setter = SETTERS[key]; if (setter) { NEURON_CONF[key] = setter(conf[key]); } } } // ## Explose public methods ////////////////////////////////////////////////////////////////////// // map of id -> defined module data var mods = neuron._mods = {}; neuron.config = config; neuron.error = err; neuron._conf = NEURON_CONF; neuron.on = on; neuron.loadJs = load_js; // private methods only for testing // avoid using this method in product environment // @expose neuron._use = function (id, callback) { use_module_by_id(id, callback); }; ENV.neuron = neuron; // @expose ENV.define = define; // @expose // Attach a module for business facade, for configurations of inline scripts // if you want a certain biz module to be initialized automatically, the module's exports should contain a method named 'init' // ### Usage // ``` // // require biz modules with configs // facade('app-main-header-bar', { // icon: 'http://kael.me/u/2012-03/icon.png' // }); // ``` ENV.facade = function (entry, data) { use_module_by_id(entry, function(method) { method.init && method.init(data); }); }; function use_module_by_id (id, callback) { var module = get_module(id); module.f = true; use_module(module, callback); } // Use `this`, and never cares about the environment. })(this);