Source code learning of requirejs (02) - module loading process

content

foreword

review

Program entry

Execution process of require.config

The process of requiring this API to load modules


foreword

The module loading process discussed here only discusses the form of define definition and require acquisition.

This does not involve data-main and the dependent module loading process in config.

review

The initialization process of requirejs:

1. Initialize various APIs and functions

1)、req=require=requirejs,req.config,req.load,define等。

2. The core of newContext

1), define various data structures used internally

2), the central data structure of context

3), the basic operation unit of module

3. Complete the configuration of require

Program entry

It's the same program entry from the last time, and it's posted here again.

<!--我在这里并不使用data-main方式,具体原因参考官网-->
<script src="require.js" type="text/javascript"></script>
<script>
        require.config({
            baseUrl:"js",
            paths:{
                app:"./app",
            }
        });
        require(["app"],function(){
            console.log("This is index.html,require app success!");
        });
</script>

Execution process of require.config

Skip the initialization process directly and go to require.config.

    req.config = function (config) {
        return req(config);
    };

require.config actually calls req(config), so it goes directly to the inside of req.

 Finally, modify the default configuration items through context.configure(config).

Note: If you add deps-related configuration to config, the process of require.config will include the loading of dependencies in deps. I did not add deps configuration here, so the process is very simple.

The process of requiring this API to load modules

After executing require.config, continue to execute to the part where require loads the app module.

        require(["app"],function(){
            console.log("This is index.html,require app success!");
        });

After entering require, come to context.require (as mentioned in the previous article, this function is the core entry for module loading).

 follow up

Let's talk about takeDefines first. Since this is the first module to be loaded, so far, there are no elements in the globalDefQueue, so it doesn't actually do anything substantial.

Then, a module loading task is created through context.nextTick. This loading task is executed in the next event loop to load the module contained in deps, that is, app.

important point:

Before calling require, in the initialization process, req() is called twice. Since the parameters of these two calls are empty objects, context.configure will not be executed. Therefore, only context.require is called at the end, so finally Will generate two empty module load tasks.

After that, execute require.config, and actually execute req(config). This time config is not an empty object, so context.configure will be called once. Inside configure, since cfg.deps is empty (deps is not configured in config) configuration item), context.require will not be called; finally, context.require is called once at the end of req, resulting in an empty module loading task.

Therefore, before we call require, a total of three empty module tasks are generated, so only the fourth module loading task is the module task corresponding to the app.

This empty call was complained about in my previous article, and there were 3 more useless tasks for no reason.

 At this point, the require execution ends, and then the next event loop is entered, so the module loading task is executed.

 Here we have entered into the internal execution of the module loading task.

Since globalDefQueue is still an empty queue, takeDefines has no effect.

Then execute and enter getModule(makeModuleMap(null,relMap)), which is a very important link, and a lot of work has been done here.

Let's first look at the makeModule function

        function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
            var url, pluginModule, suffix, nameParts,
                prefix = null,
                parentName = parentModuleMap ? parentModuleMap.name : null,
                originalName = name,
                isDefine = true,
                normalizedName = '';

            //If no name, then it means it is a require call, generate an
            //internal name.
            if (!name) {
                isDefine = false;
                name = '_@r' + (requireCounter += 1);
            }

            nameParts = splitPrefix(name);
            prefix = nameParts[0];
            name = nameParts[1];

            if (prefix) {
                prefix = normalize(prefix, parentName, applyMap);
                pluginModule = getOwn(defined, prefix);
            }

            //Account for relative paths if there is a base name.
            if (name) {
                if (prefix) {
                    if (isNormalized) {
                        normalizedName = name;
                    } else if (pluginModule && pluginModule.normalize) {
                        //Plugin is loaded, use its normalize method.
                        normalizedName = pluginModule.normalize(name, function (name) {
                            return normalize(name, parentName, applyMap);
                        });
                    } else {
                        // If nested plugin references, then do not try to
                        // normalize, as it will not normalize correctly. This
                        // places a restriction on resourceIds, and the longer
                        // term solution is not to normalize until plugins are
                        // loaded and all normalizations to allow for async
                        // loading of a loader plugin. But for now, fixes the
                        // common uses. Details in #1131
                        normalizedName = name.indexOf('!') === -1 ?
                                         normalize(name, parentName, applyMap) :
                                         name;
                    }
                } else {
                    //A regular module.
                    normalizedName = normalize(name, parentName, applyMap);

                    //Normalized name may be a plugin ID due to map config
                    //application in normalize. The map config values must
                    //already be normalized, so do not need to redo that part.
                    nameParts = splitPrefix(normalizedName);
                    prefix = nameParts[0];
                    normalizedName = nameParts[1];
                    isNormalized = true;

                    url = context.nameToUrl(normalizedName);
                }
            }

            //If the id is a plugin id that cannot be determined if it needs
            //normalization, stamp it with a unique ID so two matching relative
            //ids that may conflict can be separate.
            suffix = prefix && !pluginModule && !isNormalized ?
                     '_unnormalized' + (unnormalizedCounter += 1) :
                     '';

            return {
                prefix: prefix,
                name: normalizedName,
                parentMap: parentModuleMap,
                unnormalized: !!suffix,
                url: url,
                originalName: originalName,
                isDefine: isDefine,
                id: (prefix ?
                        prefix + '!' + normalizedName :
                        normalizedName) + suffix
            };
        }

This function actually generates a module-related parameter information at the end.

The moduleMap information generated by the last module loading task of the app is as follows:

 Then getModule will be called next, and the parameter information is the moduleMap generated above:

Here _@r5 is the module loading entry, and requirejs will create a module object for all module entries.

hint: 

The registry stores the registered module id. For the registered module, the module object is naturally regenerated.

id: This is the unique identification of the module object inside requirejs.

module: An abstract data structure for each module.

        function getModule(depMap) {
            var id = depMap.id,
                mod = getOwn(registry, id);

            if (!mod) {
                mod = registry[id] = new context.Module(depMap);
            }

            return mod;
        }

 getModule first determines whether the module has been loaded and registered. If it has been registered, it will directly return the corresponding Module object; if it has not been registered, it will generate a new Module object and register it in the registry.

Then call init to start loading the module.

important point:

The module object generated here corresponds to the module that currently calls require. Here we call it in index.html, so this module has no name. requirejs will automatically generate a name and id. The name or id is _@r5.

All initial module loading entries do not have names, and requirejs will treat it as a module and create a module object for it. This initial module object name is generated by requirejs.

deps is the list of dependent modules of _@r5, and a module object will be generated for each module in deps later.

in addition:

enabled: true is very important, indicating that this module can be loaded.

Here, the module object is associated with the list of dependent modules [app] as shown below.

Because the module loading of requirejs allows multiple dependent modules to be loaded at one time, this deps is an array, and this step of association just associates the array of multiple dependent modules that need to be loaded this time with the previously created module object.

Then use this module object to gradually operate on each module in the deps array.

Later, requirejs will traverse this deps array, create a module object for each module, and then start loading separately.

In short, it is quite confusing here. requirejs regards the code that calls require in index.html as a module. Since there is no module name, requirejs automatically generates one.

 Then at the end of init, the this.enable function is called, and follow up to see this function.

The enable function internally creates module objects for all dependent modules of this module, and then loads them separately.

The arrow traverses the deps, creates a module object for each module in the deps, and loads it.

Follow up to the callback function of each at the arrow:

Here a moduleMap object is created for the app module:

 Then through the on function, the getModule function is called inside on to create a module object corresponding to the app and register it in the registry.

Then this callback function is executed to the end, calling context.enable, and in context.enable, it will finally call module.enable.

At this time, this points to the module corresponding to the app, that is to say, the app is loaded through the module object corresponding to the app.

Since the app has not been loaded at this time, the deps of the app is not known yet, so this.check will be called directly to the end here: 

Follow up to this.check to see what's going on:

 

 As you can see in the image above, this.fetch is finally called:

 This.load is called again in this.fetch:

 Call context.load in this.load:

 And context.load will eventually call req.load. req.load has been parsed in the previous article. This function is used to create a script node and actually load the script.

After execution, there is one more line in index.html:

At this point, the app module is loaded. 

Small summary:

1. Enter module loading through require

2. Enter localRequire to create a module loading task

3. Execute the module loading task

     1) Create a module object for the require code part in index.html. The name of the module is generated by requirejs, which is referred to by _@r5 here.

     2) Take the module loaded in require (app here) as the deps of the (_@r5) object in the previous step, and then call (_@r5).init to start the initialization process.

     3) After (_@r5).init, call (_@r5).enable to traverse the dependencies in deps, and then load the dependencies in deps.

4. The dependency loading process in deps (here, because there is only app in the dependencies, it is over after only the app is loaded.):

     1) Execute in enable, come to the each function, traverse the deps through this function, and call the corresponding function for each of the dependencies. Since there is only one app in the deps, there is only one app in the end as the corresponding function. parameters are called.

     2) Enter the corresponding function and execute it. First, a moduleMap object will be created for the app. There are three important properties in this object: name, id, and url, where id is the unique identifier of the module that requirejs will use internally, url Is the src source used to create the script tag last, and name is the module name.

     3), then execute the on function (this on function is the one in the context, not the one in the module, it must be clearly distinguished), inside the on function, return the module object corresponding to the app through the getModule call, and then use the app to refer to this module object.

     4), then call context.enable, and then call (app).enable.

It can be seen that module.enable is the core function of the module loading module, and the loading of the module is implemented in this function.

     5) Finally, call (app).check, then call (app).fetch, then call (app).load, finally call context.load, and call req.load in context.load, and then call The script tag is created here, and the app is downloaded.

Guess you like

Origin blog.csdn.net/lengye7/article/details/123754723