Since I have been using it, I learned about the source code of seajs. Here is my understanding of the following questions:
1. How does seajs’ require(XXX) method implement module loading?
2. Why do you need to preload?
3. Why do you need build tools?
4. What are the differences between the code before and after building, and why do you do this?
Question 1: How does the require(XXX) method of seajs implement module loading?
The code logic is rather convoluted. The understanding of the source code is placed at the end of the article. Here we briefly review the logic of module loading:
1. From the seajs.use method entry, start loading the module used.
2. The module used must not exist in the mod cache at this time. seajs creates a new mod and gives it some initial status.
3. Execute the mod.load method
4. After a bunch of logic, go to the seajs.request method and request the module file. After the module is loaded, the define method is executed.
5. The define method analyzes and extracts the dependent modules of the module and saves them. The factory is cached but not executed.
6. The dependent modules of the module are loaded again. If there are still dependent modules, the loading continues. Until all dependent modules are loaded.
7. After all modules are loaded, execute the callback of the use method.
8. The internal logic of the module is executed from callback. The require method is only executed during this process.
Question 2: Why is preloading needed?
We see that the seajs.use method actually executes the callback after all dependent modules have been loaded. It can be understood that before the business logic code is executed, all dependent module codes must be preloaded. So why is there such a logic that must be preloaded first?
The answer lies in the execution method of the require method that references other module methods in the logic code:
var mod = require(id);
This syntax determines that the acquisition of the mod is a synchronous execution process. If the module code has not been preloaded before, it can only be implemented using the asynchronous loading callback method, then the entire seajs execution logic will be completely It's something else. Because of asynchrony, you will not understand the execution order of the modules, and the logic will become difficult to control.
Question 3: Why do you need build tools?
You can see that each dependent module is loaded separately before building. This will generate too many module requests, which is detrimental to page loading performance. Build tools are essentially designed to solve the problem of combined module loading.
Question 4: What are the differences between the code before and after building, and why do we do this?
What exactly does the build tool do. We say that it is essentially to solve the problem of code merge loading, so what it does is simply merge various module files into one file?
Of course not. Test it. If you simply merge several module files into one file, you will find that this file cannot be executed normally.
The reason lies in the implementation of define method.
Seajs recommends only passing in the factory parameter in the define method when defining a module. Looking back at the define method, when no id (temporarily equivalent to the url of the module) is passed in, the getCurrentScript() method will be used to obtain the url path of the module file currently being executed, and then this path will be used as a key value together with the module itself. Cache to cachedMods. The key point here is that the module caching mechanism within the entire seajs actually relies on the URL of each module as the cache key. The require(id) method ultimately comes through the url key value. The require(id) method ultimately uses the URL key value to find the corresponding module in cachedMods. This key value cannot be repeated and cannot make mistakes, otherwise the corresponding relationship between the modules will be confused. If several module files a, b, and c are simply merged into a target file x, getCurrentScript() can only obtain the path of x, and the key values of the three modules cannot be distinguished, and the execution will definitely go wrong.
So if you want to merge several module files together, you must specify the uri for each module. That is, the define method must pass in the id parameter. When the id is passed in, seajs will convert the id into a url and use it as a cache key.
If only id and factory are passed, that is, define(id, factory), then deps = undefined, the define method will execute the parseDependencies(factory.toString()) method to extract the dependent modules in the factory, and then it will go to Parse the module path and load the logic of each module separately online. At this time, the meaning of combined loading is lost.
So for merge loading, the define method must correctly pass in the three parameters of id, deps, and factory to execute correctly.
Seajs’ so-called CMD module definition method encourages everyone to pass only one parameter, factory, during the module writing phase, and the other two parameters are generated in the later code construction phase. The above explains why these two parameters are necessary after construction.
As for why it is advocated to only pass the factory when defining a module, I think it is mainly because the id and deps parameters passed in manually are extremely error-prone and inconvenient to maintain. Tools can improve efficiency and ensure correct parameters.
Attachment: Understanding of the main code logic of seajs.
Note: The source code version is Sea.js 2.3.0
1. Let’s first look at what the define method does
Module.define = function (id, deps, factory)
The define method supports three parameters. Among them, id and deps are optional. factory required. The code is controlled by the following logic:
But deps is actually necessary, because seajs must know which modules each module depends on, otherwise it cannot be loaded.
So, when the factory is a function and deps is not actively passed in, you need to use the parseDependencies method to analyze the dependent modules in the factory.
The main thing the parseDependencies method does is to use a regular expression to extract the XXX in all require(XXX) in the function body. This is all the modules that this function depends on.
The method itself is not complicated, but this regular expression is not simple:
After analyzing the deps, store the module definition in the cache:
Note that we will find that the define method only analyzes modules and stores modules, and does not execute modules.
2. The actual execution of the module is in the require method. Let’s look at require next.
In short, the require method is to find the corresponding module in the module cache where the define definition is stored based on the id, and execute it to obtain the method returned by the module definition:
In this whole big step, there is a very critical step that needs to be explained in detail:
Module.get(require.resolve(id)).
When requiring a module, you must first find the module. The Module.get method plays this role.
If there is no cachedMods, create a new Module and cache it in cachedMods:
The define and rquire methods don’t seem complicated. The main reason for seajs is that the module loading logic is a bit complicated.
3. The real entry point for seajs execution is the use method:
Through the use method, the loading and execution of the module are triggered starting from the ids here.
You can see that the key point of loading is in the mod.load method.
The code of the load method is a bit long. The main logic is to determine whether the current status of the mod is loaded or loading.
In Module’s comfort function, we can see that the default value of status is 0.
So new modules that have not been loaded are here: mod.status = STATUS.LOADING The status is set to loading, and subsequent loading logic is executed.
The next step is to obtain the dependency urls of the module
mod.resolve method:
The Module.resolve method essentially converts relative paths, configured paths, aliases, etc. into an absolute path. No more code posted.
Update module loading status.
Logic for loading modules:
Mainly the m.fetch method, other logic in it is skipped here.
You can see that seajs.request will eventually load the module file:
After all dependent modules are loaded, execute the mod’s onload method
Here is the mod.onload() method
At this point, you have almost seen the core logic of seajs. For reference, if there is any unclear understanding or inaccurate expression, please feel free to discuss it together.
The above is the entire content of this article, I hope you all like it.