neuronjs
Version:
Neuron is A Full Feature CommonJS Module Manager, Dependency Graph Handler and Loader for Browsers
287 lines (228 loc) • 8.41 kB
JavaScript
// ## 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) {
var origin = id;
// commonjs parser could not parse non-literal argument of `require`
// So, users might pass a null value into `require()`
id || err('null id');
env || (env = {});
var map = env.map || {};
// 3 kinds of id:
// - relative module path
// - package name
// - a module with path loaded by `facade` or `neuron._use`
// './a' -> 'module@1.0.0/a.js'
// 'jquery' -> 'jquery'
id = map[id] || id;
// If the `id` is still a relative path,
// there must be something wrong.
// For most cases,
is_path_relative(id) && module_not_found(origin);
// Adds version to a package name
// 'jquery' -> '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: ''}
var 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
// For more details about graph, see '../doc/graph.md'
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 prohibit_require_id_with_version (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.
prohibit_require_id_with_version(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) {
if (callback) {
// `require.async('a@0.0.0')` is prohibited
prohibit_require_id_with_version(id);
var module = get_module(id, env);
// If `require.async` a foreign module, it must be a main entry
if (!module.main) {
// Or it should be a module inside the current package
if (module.n !== env.n) {
// Otherwise, we will stop that.
return;
}
module.a = true;
}
use_module(module, callback);
}
};
// @param {string} path
require.resolve = function (path) {
return NEURON_CONF.resolve(parse_id(path, env).id);
};
return require;
}