preface

Hi, I’m the composer of the Require package. I’ve been debugging requir source code on WebStorm countless times to understand the require package logic and debug source code

1. My process of interpreting source code

More subjective, not necessarily suitable for you, can refer to

  1. In the documentation, play with the module’s API to learn the general rules of use
  2. Find a source code interpretation of the article, not all understand remember insideA function or field with high frequencyThere are such asThe main logicThe location of the
  3. In WebStorm interrupt point debugging, encountered keywords into view internal logic
  4. Compatibility skips branch logic such as parameter verification or caching and only looks at the trunk logic
  5. Organize trunk logicWhat did you doLeave the details aside
  6. Now that you have some understanding of the trunk, look back at some important branch logic of interest
  7. Once you have some idea of the process, try to draw a flow chart
  8. Be sure to debug a few times, don’t think about it all at once
  9. Try to visualize your own trunk logic, or some minor function

2. Interruption point: Sorting out the logic of Requie backbone

Use WebStorm to break the point

1.Call mod. Require (path) \ into makeRequireFunction2.Module.prototype.require parameter check, load Module (requireDepth record load depth) \3.Module._load(id, this./* isMain */ false); Loading module; The cache done earlier, \3.1Cache module, with cache directly return \3.2 constfilename = Module._resolveFilename(request, parent, isMain); Get the absolute path to the file \3.3Check if it is an internal module, and return \ if it is3.4generatenew moduleObject (module) \
  3.5The cache modulemodule \
  3.4 return module.exports; \
4.module.load Gets the file name extension \5.Module._compile corresponds to different parsing methods \, depending on the suffix.js.json5.1.module. _extensions Reads the js file from fs.readfilesync (filename,'utf8'), and finally returnsmodule._compile \
  5.2.module._compile uses wrapSafe to wrap a function around the file, and executes, tomoduleExports assignment \6.In the endrequireThe method will getmodule.exports returns a resultCopy the code

3. My understanding of require loading logic

Draw a flow chart of the trunk logic:

Ps :1. WarpSafe uses vm. RunInThisContext to convert character templates to compileFunction

/ / compileFunction function
function fn(module.exports,required,__dirname,__filename){
	module.exports = hello
       return module.exports 

}
Copy the code

4. Implement a require

Only the trunk logic is implemented and can be executed directly

Use the vm

// Implement a package loading logic yourself
 
const path = require("path")
const fs = require("fs")
const vm = require("vm")

// Return the absolute path
Module._resolveFilename = function (filename) {
    const filePath = path.resolve(__dirname, filename)
    let exists = fs.existsSync(filePath)
    if (exists) return filePath   // Return it if it exists
    // Try adding.js and json suffixes if file does not exist
    let keys = Reflect.ownKeys(Module._extensions)  // Returns all method names for module._extensions
    for (let i = 0; i < keys.length; i++) {
        let newPath = filePath + keys[i]
        if (fs.existsSync(newPath)) return newPath  // add the suffix, return if it exists
    }
    throw  new Error("module not found")}// More different files load different policies
Module._extensions = {
    '.js'(module) {
        // Read the file
        let script = fs.readFileSync(module.id, 'utf8')
        // Generate a character template wrapped in a function
        let template = `(function(exports,module,require,__filename,__dirname){${script}}) `;
        // Generate a function based on the template and the current JS context
        let compileFunction = vm.runInThisContext(template);
        let exports = module.exports; // To implement a shorthand
        let thisValue = exports; // this = exports = module.exports = {}
        let filename = module.id; // Remove the file path
        let dirname = path.dirname(filename) // Remove the current folder
        // Execute the template function and assign a value to module.export
        compileFunction.call(thisValue, exports.module, myRequire, filename, dirname)

    },
    '.json'(module) {
        let script = fs.readFileSync(module.id, 'utf8');
        module.exports = JSON.parse(script); // Mount JSON directly to the exports object so that the user can require a JSON file and get the json content}}// Load the module
Module.prototype.load = function () {
    let extension = path.extname(this.id); // Retrieve the file extension (suffix)
    // Implement different loading logic according to the suffix name
    Module._extensions[extension] && Module._extensions[extension](this) //Module exists the file name extension method and executes it
}


function Module(id) {
    this.id = id   // Absolute path
    this.exports = {} // The export result of the module
}

Module._cache = {}  // for caching


function myRequire(filename) {
    let filePath = Module._resolveFilename(filename)
    let exists = Module._cache[filePath]
    if (exists) {
        return exists.exports
    }
    // create a module
    let module = new Module(filePath)
    // 3. Cache module
    Module._cache[filePath] = module
    // Get the contents of the module, wrap the function, and make the function execute. The user's logic assigns values to module,export
    module.load()
    console.log(module)
    return module.exports // The final result
}
const r = myRequire('./main.js');
console.log(r,123);

Copy the code

Ps: The simplified version does not check the parameters, and the path splicing simplifies the attributes of the Module object. In fact, there are many attributes

The last

This article is only my understanding of require, and cannot guarantee the authority of the strict, but can help you to guide the logic of the require package, hope you break the point of debugging more, transform your knowledge ~

If it works for you, give it a thumbs up