This is the core part of seajs loader. Some IE compatible parts are not very clear yet. The main thing is to understand how each module relies on orderly loading and the CMD specification.
The code is a bit long, so you need to read it patiently:
/**
* The core of loader
*/
;(function(seajs, util, config) {
//Module cache
var cachedModules = {}
//Interface modification Cache
var cachedModifiers = {}
// Compile Queue
var compileStack = []
// Module Status
var STATUS = {
'FETCHING': 1, // The module file is fetching now. The module is being downloaded
'FETCHED': 2, // The module file has been fetched. The module has been downloaded
'SAVED': 3, // The module info has been saved. Module Information has been saved
'READY': 4, // All dependencies and self are ready to compile. The module's dependencies have been downloaded and are waiting to be compiled
'COMPILING': 5, // The module is in compiling now . The module is being compiled
'COMPILED': 6 // The module is compiled and module.exports is available. The module is compiled
}
function Module(uri, status) {
this.uri = uri
this.status = status || 0
// this.id is set when saving
// this.dependencies is set when saving
/ / this.factory is set when saving
// this.exports is set when compiling
// this.parent is set when compiling
// this.require is set when compiling
}
Module.prototype._use = function(ids, callback) {
//Convert to array, unified operation
util.isString(ids) && (ids = [ids])
// Use the path resolution mechanism inside the module system to resolve and return the module path
var uris = resolve(ids, this.uri)
this._load(uris, function() {
// Loads preload files introduced in modules before compiling.
// Before compilation, call preload again to preload the module
// Because seajs.config can be called at any time to configure the preload module during code execution
preload(function() {
// Compile each module and pass the exports of each module as parameters to the callback function
var args = util.map(uris, function(uri) {
return uri ? cachedModules[uri]._compile() : null
})
if (callback) {
// null makes this pointer in the callback function window
callback.apply(null, args)
}
})
})
}
// The main module loads dependent modules (called submodules) and executes the callback function
Module. prototype._load = function(uris, callback) {
// Filter the uris array
// Case 1: The module does not exist in the cache, return its uri
// Case 2: The module exists in the cache , but its status < STATUS.READY (i.e. not ready to compile)
var unLoadedUris = util.filter(uris, function(uri) {
return uri && (!cachedModules[uri] ||
cachedModules[uri].status < STATUS.READY)
})
var length = unLoadedUris.length
// If length is 0, it means that the dependencies are 0 or have been downloaded , then perform the callback compilation operation
if (length === 0) {
callback()
return
}
var remain = length
for ( var i = 0; i < length; i ) {
// Closure, providing context for the onFetched function
(function(uri) {
// Create module object
var module = cachedModules[uri] ||
(cachedModules[uri] = new Module(uri, STATUS.FETCHING))
//If the module has been downloaded, then execute onFetched, otherwise execute the fetch operation (request module)
module.status >= STATUS.FETCHED ? onFetched() : fetch(uri, onFetched)
function onFetched() {
// cachedModules[uri] is changed in un-correspondence case
module = cachedModules[uri]
// If the module status is SAVED, it means that the module’s dependencies have been determined, then download the dependent module
if (module.status >= STATUS.SAVED) {
// Get the list of dependent modules from the module information and process circular dependencies
var deps = getPureDependencies(module)
// If dependencies exist, continue downloading
if (deps.length) {
Module.prototype._load(deps, function() {
cb(module)
})
}
// Otherwise, execute cb directly
else {
cb(module)
}
}
// Maybe failed to fetch successfully, such as 404 or non-module.
// In these cases, just call cb function directly.
// If the download module does not Success, such as 404 or module irregularity (code error), the module status may be fetching or fetched
// At this time, the callback function is directly executed. When compiling the module, the module will only return null
else {
cb()
}
}
})(unLoadedUris[i])
}
function cb(module) {
// Change the module status to READY. When remain is 0, it means that the module dependencies have been downloaded, then execute callback
(module || {}).status < STATUS.READY && (module.status = STATUS.READY )
--remain === 0 && callback()
}
}
Module.prototype._compile = function() {
var module = this
// If the module has been compiled, directly return module.exports
if (module.status === STATUS.COMPILED) {
return module.exports
}
// Just return null when:
// 1. the module file is 404.
// 2. the module file is not written with valid module format.
// 3 . other error cases.
// This is to handle some exceptions. In this case, null is returned directly
if (module.status < STATUS.SAVED && !hasModifiers(module)) {
return null
}
// Change the module status to COMPILING, indicating that the module is compiling
module.status = STATUS.COMPILING
// Used internally by the module, it is a method used to obtain the information provided by other modules ( Called submodule) interface, synchronous operation
function require(id) {
// Resolve the module path according to id
var uri = resolve(id, module.uri)
// Get the module from the module cache (note that the submodule here has actually been downloaded as a dependency of the main module)
var child = cachedModules[uri]
// Just return null when uri is invalid.
// If child is empty, it can only mean that the parameter filling is incorrect and the uri is incorrect, then null is returned directly
if (!child) {
return null
}
// Avoids circular calls.
// If the status of the submodule is STATUS.COMPILING, return child.exports directly to avoid repeatedly compiling the module due to circular dependencies
if (child.status === STATUS.COMPILING) {
return child.exports
}
// Points to the module that calls the current module during initialization. According to this attribute, you can get the Call Stack when the module is initialized.
child.parent = module
// Return the compiled child’s module.exports
return child._compile()
}
// Used internally by the module to load the module asynchronously and execute the specified callback after the loading is completed.
require.async = function(ids, callback) {
module._use(ids, callback)
}
// Use the path resolution mechanism inside the module system to parse and return the module path. This function does not load the module and only returns the resolved absolute path.
require.resolve = function(id) {
return resolve(id, module.uri)
}
// Through this attribute, you can view all modules loaded by the module system.
// In some cases, if you need to reload a module, you can get the uri of the module, and then delete its information by delete require.cache[uri]. This will be obtained again the next time you use it.
require.cache = cachedModules
// require is a method used to obtain the interfaces provided by other modules.
module.require = require
// exports is an object used to provide module interfaces to the outside world.
module.exports = {}
var factory = module.factory
// When factory is a function, it represents the construction method of the module. By executing this method, you can get the interface provided by the module.
if (util.isFunction(factory)) {
compileStack.push(module)
runInModuleContext(factory, module)
compileStack.pop()
}
// factory is When the object, string and other non-function types are used, the interface of the module is the object, string and other values.
// For example: define({ "foo": "bar" });
// For example: define('I am a template. My name is {{name}}.');
else if (factory !== undefined) {
module.exports = factory
}
// Change the module status to COMPILED, indicating that the module has been compiled
module.status = STATUS.COMPILED
// Execute module interface modification through seajs.modify()
execModifiers(module)
return module.exports
}
Module._define = function(id , deps, factory) {
var argsLength = arguments.length
// Perform parameter matching according to the number of parameters passed in
// define(factory)
// One parameter Case:
// id: undefined
// deps: undefined (the list of dependent modules will be taken out later according to regular rules)
// factory: function
if (argsLength === 1) {
factory = id
id = undefined
}
// define(id || deps, factory)
// case of two parameters:
else if (argsLength === 2) {
// Default: define(id, factory)
// id : '...'
// deps : undefined
// factory : function
factory = deps
deps = undefined
// define(deps, factory)
// If the first parameter is an array: define(deps, factory)
// id : undefined
// deps : [...]
// factory : function
if (util.isArray(id)) {
deps = id
id = undefined
}
}
// Parses dependencies.
// If deps is not an array (that is, deps does not specify a value), then parse the dependencies through regular expressions
if (!util.isArray( deps) && util.isFunction(factory)) {
deps = util.parseDependencies(factory.toString())
}
// Meta information, which will then be passed to the corresponding module
var meta = { id: id, dependencies: deps, factory: factory } in the object
var derivedUri
// Try to derive uri in IE6-9 for anonymous modules.
/ / For IE6-9, try to get the uri of the module through interactive script
if (document.attachEvent) {
// Try to get the current script.
// Get the current script
var script = util.getCurrentScript()
if (script) {
// UnpareseMap the url of the current script to keep it consistent with the key in the module cache
derivedUri = util.unParseMap(util.getScriptAbsoluteSrc(script) )
}
if (!derivedUri) {
util.log('Failed to derive URI from interactive script for:',
factory.toString(), 'warn')
// NOTE: If the id-deriving methods above is failed, then falls back
// to use onload event to get the uri.
}
}
// Gets uri directly for specific module.
// If the id is given, then the path is parsed according to the id
// Obviously if the id is not specified:
// For non-IE browsers, return undefined (derivedUri is empty)
// For IE browser, the src of CurrentScript is returned
// If id is specified:
// The path url parsed by seajs is returned
var resolvedUri = id ? resolve(id) : derivedUri
// If uri exists, store module information
if (resolvedUri) {
// For IE:
// If the first module in a package is not the cachedModules[derivedUri]
// self, it should assign to the correct module when found.
if (resolvedUri === derivedUri) {
var refModule = cachedModules[derivedUri]
if (refModule && refModule.realUri &&
refModule.status === STATUS.SAVED) {
cachedModules[derivedUri] = null
}
}
//Storage module information
var module = save(resolvedUri, meta)
// For IE:
// Assigns the first module in package to cachedModules[derivedUrl]
if (derivedUri) {
/ / cachedModules[derivedUri] may be undefined in combo case.
if ((cachedModules[derivedUri] || {}).status === STATUS.FETCHING) {
cachedModules[derivedUri] = module
module .realUri = derivedUri
}
}
else {
// Store the first module into firstModuleInPackage
firstModuleInPackage || (firstModuleInPackage = module)
}
}
// When uri does not exist, module information is stored in the onload callback, where there is a closure
else {
// Saves information for "memoizing" work in the onload event.
/ / Because the uri at this time is not known, the meta information is temporarily stored in anonymousModuleMeta, and the module save operation is performed in the onload callback
anonymousModuleMeta = meta
}
}
// Get the module being compiled
Module._getCompilingModule = function() {
return compileStack[compileStack.length - 1]
}
// Quickly view the sum from seajs.cache Get the loaded module interface, the return value is the module.exports array
// selector supports strings and regular expressions
Module._find = function(selector) {
var matches = []
util.forEach(util.keys(cachedModules), function(uri) {
if (util.isString(selector) && uri.indexOf(selector) > -1 ||
util.isRegExp( selector) && selector.test(uri)) {
var module = cachedModules[uri]
module.exports && matches.push(module.exports)
}
})
return matches
}
// Modify module interface
Module._modify = function(id, modifier) {
var uri = resolve(id)
var module = cachedModules[uri]
// If the module exists and is in the COMPILED state, then perform the interface modification operation
if (module && module.status === STATUS.COMPILED) {
runInModuleContext(modifier, module)
}
// Otherwise, put it into the modified interface cache
else {
cachedModifiers[uri] || (cachedModifiers[uri] = [])
cachedModifiers[uri].push(modifier)
}
return seajs
}
// For plugin developers
Module.STATUS = STATUS
Module._resolve = util.id2Uri
Module._fetch = util.fetch
Module.cache = cachedModules
// Helpers
// -------
// List of modules being downloaded
var fetchingList = {}
//Downloaded module list
var fetchedList = {}
// Callback function list
var callbackList = {}
//Anonymous module meta information
var anonymousModuleMeta = null
var firstModuleInPackage = null
// Circular dependency stack
var circularCheckStack = []
// Path to batch parsed modules
function resolve(ids, refUri) {
if (util.isString(ids)) {
return Module._resolve(ids, refUri)
}
return util.map(ids, function(id) {
return resolve( id, refUri)
})
}
function fetch(uri, callback) {
// When fetching, first convert the uri according to map rules
var requestUri = util. parseMap(uri)
// Search in fethedList (downloaded module list), if any, return directly and execute the callback function
// TODO: Why might this module exist in fetchedList in this step?
if (fetchedList[requestUri]) {
// See test/issues/debug-using-map
cachedModules[uri] = cachedModules[requestUri]
callback()
return
}
// Search in the fetchingList (the list of modules being downloaded). If there is any, just add the callback function to the list and return directly
if (fetchingList[requestUri]) {
callbackList[requestUri].push(callback)
return
}
// If you get to this step, it means that the module is requested for the first time,
// Then insert the module in fetchingList information, indicating that the module is already in the download list, and initializes the callback function list corresponding to the module
fetchingList[requestUri] = true
callbackList[requestUri] = [callback]
// Fetches it
// Get the module, that is, initiate a request
Module._fetch(
requestUri,
function() {
// Insert the module’s information in fetchedList to represent the The module has been downloaded
fetchedList[requestUri] = true
// Updates module status
var module = cachedModules[uri]
// At this time the status may be STATUS.SAVED, previously in It has been said in _define
if (module.status === STATUS.FETCHING) {
module.status = STATUS.FETCHED
}
// Saves anonymous module meta data
// Because it is an anonymous module (the uri is obtained through the closure at this time, and the module information is stored here)
// And set anonymousModuleMeta to empty
if (anonymousModuleMeta) {
save(uri, anonymousModuleMeta)
anonymousModuleMeta = null
}
// Assigns the first module in package to cachedModules[uri]
// See: test/issues/un-correspondence
if ( firstModuleInPackage && module.status === STATUS.FETCHED) {
cachedModules[uri] = firstModuleInPackage
firstModuleInPackage.realUri = uri
}
firstModuleInPackage = null
// Clears
// Clear module information in fetchingList because the module has been fetched and saved
if (fetchingList[requestUri]) {
delete fetchingList[requestUri]
}
// Calls callbackList
// Call the callback functions in sequence and clear the callback function list
if (callbackList[requestUri]) {
util.forEach(callbackList[requestUri], function(fn) {
fn()
})
delete callbackList[requestUri]
}
},
config.charset
)
}
function save( uri, meta) {
var module = cachedModules[uri] || (cachedModules[uri] = new Module(uri))
// Don't override already saved module
// this The status may have two states:
// STATUS.FETCHING, called in define (id specified), stores module information
// STATUS.FETCHED, called in the onload callback function, stores module information
if (module.status < STATUS.SAVED) {
// Lets anonymous module id equal to its uri
// Anonymous module (that is, no id is specified), use its uri as id
module.id = meta.id || uri
// Parse the absolute path from the dependency (array) and store it in the module information
module.dependencies = resolve(
util.filter(meta .dependencies || [], function(dep) {
return !!dep
}), uri)
// Store factory (module code to be executed, which may also be an object or string, etc.)
module.factory = meta.factory
// Updates module status
// The update module status is SAVED, (note that it only has dependencies at this time, not all have been downloaded yet (i.e. Not READY yet))
module.status = STATUS.SAVED
}
return module
}
// Execute module code according to module context
function runInModuleContext (fn, module) {
// Pass in two parameters related to the module and the module itself
// exports is used to expose the interface
// require is used to obtain dependent modules (synchronization) (compilation)
var ret = fn(module.require, module.exports, module)
// Supports return value exposure interface form, such as:
// return {
// fn1 : xx
// ,fn2 : xx
// ...
// }
if (ret !== undefined) {
module.exports = ret
}
}
// Determine whether the module has interface modifications
function hasModifiers(module) {
return !!cachedModifiers[module.realUri || module.uri]
}
// Modify the module interface
function execModifiers(module) {
var uri = module.realUri || module.uri
var modifiers = cachedModifiers[uri]
// The internal variable cachedModifiers is used to store the values defined by the user through the seajs.modify method Modification point
//Check whether the uri has been changed by modify
if (modifiers) {
// Execute factory uniformly on the modification point and return the modified module.exports
util.forEach( modifiers, function(modifier) {
runInModuleContext(modifier, module)
})
// Delete the modification points defined by the modify method to avoid executing it again
delete cachedModifiers[uri]
}
}
//Get pure dependencies and get a dependency array without circular dependencies
function getPureDependencies(module) {
var uri = module.uri
// For each dependency Filter the items, eliminate those that may form circular dependencies, and print out a warning log
return util.filter(module.dependencies, function(dep) {
// First put the uri of the checked module into In the circular dependency check stack, subsequent checks will use
circularCheckStack = [uri]
//Next, check whether the module uri has a circular dependency on its dependent modules
var isCircular = isCircularWaiting(cachedModules[dep ])
if (isCircular) {
// If it is circular, put the uri into the circular dependency check stack
circularCheckStack.push(uri)
// Print out the circular warning log
printCircularLog(circularCheckStack)
}
return !isCircular
})
}
function isCircularWaiting(module) {
// If the dependent module does not exist, then Return false, because the dependencies of the dependent module cannot be obtained at this time, so no judgment can be made here
// Or if the status value of the module is equal to saved, false is also returned, because when the module status is saved, it represents the information of the module. Already have it,
// So even though a circular dependency is formed, when the main module is required, it can be compiled normally and the main module interface is returned (it seems that nodejs will return undefined)
if (!module || module.status !== STATUS.SAVED) {
return false
}
// If it is not the above situation, then put the uri of the dependent module into the circular dependency check stack, and subsequent checks will use
circularCheckStack.push(module.uri)
// Get the dependent module of the dependent module again
var deps = module.dependencies
if (deps.length) {
// Through loop Dependency check stack, check whether there is a circular dependency (here is the first layer of dependency module check, circular dependency with the main module)
if (isOverlap(deps, circularCheckStack)) {
return true
}
// If the above situation does not exist, then further check the dependent modules of the dependent modules to see if they have a circular dependency on the module of the uri in the circular dependency check stack
// In this case, it is recursive and circular The dependency check stack is like a chain. The current module checks the main module, the main module of the main module... until the main module at the top to determine whether there are dependencies
for (var i = 0; i < deps.length; i ) {
if (isCircularWaiting(cachedModules[deps[i]])) {
return true
}
}
}
// if not If there is a circular dependency, then pop out the module uri that has been pushed in before and return false
circularCheckStack.pop()
return false
}
// Print out the circular warning log
function printCircularLog (stack, type) {
util.log('Found circular dependencies:', stack.join(' --> '), type)
}
//Determine whether two arrays are duplicated The value of
function isOverlap(arrA, arrB) {
var arrC = arrA.concat(arrB)
return arrC.length > util.unique(arrC).length
}
/ / Read from the configuration file whether there are modules that need to be loaded in advance
// If there is a preloaded module, first set the preloaded module to empty (to ensure that it does not have to be loaded again next time), and load the preloaded module and execute the callback, if If not, execute sequentially
function preload(callback) {
var preloadMods = config.preload.slice()
config.preload = []
preloadMods.length ? globalModule._use(preloadMods, callback) : callback()
}
// Public API
// API exposed to the outside world
// ----------
// The global module can be considered as the page module. The js and css files in the page are loaded through it
// The initial state of the module is COMPILED, and the uri is the uri of the page
var globalModule = new Module(util .pageUri, STATUS.COMPILED)
// Page js, css file loader
seajs.use = function(ids, callback) {
// Loads preload modules before all other modules.
// Preload module
preload(function() {
globalModule._use(ids, callback)
})
// Chain
return seajs
}
// For normal users
// For normal users to call
seajs.define = Module._define
seajs.cache = Module.cache
seajs.find = Module._find
seajs.modify = Module._modify
// For plugin developers
// For developers
seajs.pluginSDK = {
Module: Module ,
util: util,
config: config
}
})(seajs, seajs._util, seajs._config)