你真的理解node环境的require的加载逻辑吗?
背景
在一个electron项目中,需要引入一些模板文件,之前处理一直是require引入后使用webpack进行打包.. 但是既然是electron环境,支持node,为什么就不能让node的require直接引入模板文件呢?实际上就是不想打包, 不想在支持node的环境中使用webpack.. so,开始折腾
相关资料
- How
require()
Actually Works
你真的理解require()如何工作的吗?
Almost any Node.js developer can tell you what the require() function does, but how many of us actually know how it works?
We use it every day to load libraries and modules, but its behavior otherwise is a mystery.
几乎所有的nodejs开发者都知道require()是做什么的,但是有多少知道它是如何共走的呢?
我们每天使用它加载库和模块,但是他的工作原理依然很神秘.
Curious, I dug into Node core to find out what was happening under the hood.
But instead of finding a single function, I ended up at the heart of Node’s module system: module.js.
The file contains a surprisingly powerful yet relatively unknown core module that controls the loading, compiling, and caching of every file used.
require(), it turned out, was just the tip of the iceberg.
因为好奇, 我深入node的核心来查找require后到底发生了什么.
本来以为会发现一个单独的函数处理,结果确是node的模块系统: module.js.
这个文件是一个非常强大的核心模块, 它控制着我们所有文件的加载、编译、缓存.
原来, require只是其中的冰山一角.
module.js
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
// …
The Module type found in module.js has two main roles inside of Node.js.
First, it provides a foundation for all Node.js modules to build off of.
Each file is given a new instance of this base module on load, which persists even after the file has run.
This is why we are able attach properties to module.exports and return them later as needed.
module.js的Module在node.js中有两个重要作用.
第一, 它提供了一个模块编译的基础.
每一个文件加载时都将是一个新的module实例, 并且在文件执行完只有仍然存在.
这就是为什么我们能够把属性添加到module.exports并且按需要返回它们.
The module’s second big job is to handle Node’s module loading mechanism.
The stand-alone require function that we use is actually an abstraction over module.require, which is itself just a simple wrapper around Module._load.
This load method handles the actual loading of each file, and is where we’ll begin our journey.
Module的第二个作用是处理node的模块加载机制.
我们使用的require函数实际上是module.require的一个抽象, 是对Module._load的一个简单包装.
load方法处理每个文件如何加载,这就是我们开始旅程的地方.
Module._load
Module._load = function(request, parent, isMain) {
// 1. Check Module._cache for the cached module.
// 2. Create a new Module instance if cache is empty.
// 3. Save it to the cache.
// 4. Call module.load() with your the given filename.
// This will call module.compile() after reading the file contents.
// 5. If there was an error loading/parsing the file,
// delete the bad module from the cache
// 6. return module.exports
};
Module._load is responsible for loading new modules and managing the module cache.
Caching each module on load reduces the number of redundant file reads and can speed up your application significantly.
In addition, sharing module instances allows for singleton-like modules that can keep state across a project.
Module._load负责加载新模块和管理模块缓存.
缓存模块可以减少文件读取并且提高程序的执行速度.
此外, 单独模块的共享实例能够在整个项目中缓存状态.
If a module doesn’t exist in the cache, Module._load will create a new base module for that file.
It will then tell the module to read in the new file’s contents before sending them to module._compile.[1]
如何一个模块在缓存中不存在, Module._laod将为这个文件新建一个基础模块.
读取文件的内容之后才会执行module._compile进行编译.
If you notice step #6 above, you’ll see that module.exports is returned to the user.
This is why you use exports and module.exports when defining your public interface, since that’s exactly what Module._load and then require will return.
I was surprised that there wasn’t more magic going on here, but if anything that’s for the better.
如果你注意到了上面的第#6步, 你将会发现module.exports被返回给用户.
这就是为什么使用exports和module.exports来定义公共接口,因为这就是Module._load加载后将返回他们.
我很惊讶, 这里没有更多魔术般的处理, 但是有什么更好的呢.
module._compile
Module.prototype._compile = function(content, filename) {
// 1. Create the standalone require function that calls module.require.
// 2. Attach other helper methods to require.
// 3. Wraps the JS code in a function that provides our require,
// module, etc. variables locally to the module scope.
// 4. Run that function
};
This is where the real magic happens. First, a special standalone require function is created for that module.
THIS is the require function that we are all familiar with.
While the function itself is just a wrapper around Module.require, it also contains some lesser-known helper properties and methods for us to use:
require(): Loads an external module
require.resolve(): Resolves a module name to its absolute path
require.main: The main module
require.cache: All cached modules
require.extensions: Available compilation methods for each valid file type, based on its extension
Once require is ready, the entire loaded source code is wrapped in a new function, which takes in require, module, exports, and all other exposed variables as arguments. This creates a new functional scope just for that module so that there is no pollution of the rest of the Node.js environment.
(function (exports, require, module, filename, dirname) {
// YOUR CODE INJECTED HERE!
});
Finally, the function wrapping the module is run.
The entire Module._compile method is executed synchronously, so the original call to Module._load just waits for this code to run before finishing up and returning module.exports back to the user.
Conclusion
And so we’ve reached the end of the require code path, and in doing so have come full circle by creating the very require function that we had begun investigating in the first place.
If you’ve made it all this way, then you’re ready for the final secret: require(‘module’).
That’s right, the module system itself can be loaded VIA the module system. INCEPTION. This may sound strange, but it lets userland modules interact with the loading system without digging into Node.js core. Popular modules like mockery and rewire are built off of this.[2]
If you want to learn more, check out the module.js source code for yourself.
There is plenty more there to keep you busy and blow your mind.
Bonus points for the first person who can tell me what ‘NODE_MODULE_CONTEXTS’ is and why it was added.
[1] The module._compile method is only used for running JavaScript files. JSON files are simply parsed and returned via JSON.parse()
[2] However, both of these modules are built on private Module methods, like Module._resolveLookupPaths and Module._findPath. You could argue that this isn’t much better…