nodejs / node

Node.js JavaScript runtime ✨🐢🚀✨
https://nodejs.org
Other
107.62k stars 29.6k forks source link

REQ: custom configurability of the node module resolution algorithm #36750

Closed monoclex closed 3 years ago

monoclex commented 3 years ago

Is your feature request related to a problem? Please describe. In the JavaScript ecosystem, there are many problems that could, in theory, be fixed with custom user-defined module resolution configuration.

Describe the solution you'd like My suggestion to this problem is to have a .resolve.js file inside of node_modules that will execute to resolve the path for a module, that yields a single export with a function that, given the current script path and module name to be found, will yield a promise to the path to another path that is resolvable. For example, consider the following structure:

/node_modules/is-number/index.js
/node_modules/.resolve.js
/index.js
// /node_modules/is-number/index.js
module.exports = (num) => {
    return typeof num === "number"; // simple implementation for this example
}
// .resolve.js
module.exports = async function(moduleName, scriptPath) {
    return `./${moduleName}/`
};
const isNumber = require("is-number");

console.log(isNumber(1));
console.log(isNumber("not a number"));

Running node /index.js would roughly lead to the following code being executed

require("is-number");
-> /node_modules/.resolve.js
-> require("./is-number/")
-> -> require("./is-number/index.js")

This solution offers 100% customizability of the code being required.

Describe alternatives you've considered Ideally, a solution that is easily statically analyzable and thus easily implementable in languages without requiring a NodeJS runtime would be most ideal. However, due to the possible unforeseen innovations that may be created and require custom functionality AND requirement of other vendors implementing the changes Node has (which we cannot expect vendors to do), it would be the simplest and most ecosystem-wide compatible have a JS file that executes to automatically resolve the paths of modules.

The asynchronous nature of the require code is so that asynchronous calls can be made rather than synchornous ones (i.e. fs.readFile vs fs.readFileSync), but given that require is synchronous this may have to be altered.

The reasoning for .resolve.js to be inside node_modules is that it's a file that shouldn't be committed - rather, package manages such as yarn or npm could auto generate this file. Perhaps it might be better to include it next to package.json so it does get committed however, as the current tooling environment doesn't seem very co-existant and other projects would likely require their own boilerplate to be inserted.

The path to the current script being an argument to the function is suggested so that implementing custom node_modules algorithms can be context aware about the caller. The usecase in mind that this would be useful for, is proper versioning support of modules. Consider the following scenario, where a project looks as such:

/node_modules/is-number/7.0.0/index.js
/node_modules/is-number/6.0.0/index.js
/node_modules/depends-on-is-number-6/index.js
/node_modules/.resolve.js
/index.js

The dependency depends-on-is-number-6 could make a call to require("is-number"), and .resolve.js could yield ./is-number/6.0.0/index.js to NodeJS and thus maintain a flat node_modules structure. A more realistic example, would be a package manager that stores several versions of their packages in a cache, and redirecting imports to the correct location.

Overall, there's a lot of remaining bike-shedding to be done since this is something especially important to get right for ecosystem unity, however it'd all be for naught if this idea is deemed unworthy.

monoclex commented 3 years ago

Closing this in favor of import maps, which seem like a much more viable alternative to this.