-
Notifications
You must be signed in to change notification settings - Fork 510
Description
Summary
Some initial brainstorming ideas around migrating node-config to ESM and potentially eliminating the singleton entirely.
Given that all of the LTS supported NodeJS versions now have a way to require ESM modules, and we now support strict one or both of these seemed reasonable, even overdue.
Subsequently, I ran into a number of roadblocks, particularly to do with how idiomatic use of require inside of the config directly is very much at odds with how ESM modules want to handle imports. This leads to issues with createRequire even before we get into the tests, which themselves are also actively antagonistic to use of import, createRequire, or any mixture of both.
In the interests of regaining some momentum, I am now cutting scope a bit and altering the strategy. I have a route forward that I believe gives us more choices going forward for the features that are being cut, while giving me a straighter path to a 5.0 release that I can ship.
This work is still informed by #569, with some continued additions, alterations, and substitutions, informed by other feature requests and bug reports.
The Plan
For 4.3 I am working toward the following goals:
[x] reduce reliance on requireUncached in the tests on this function - previously 80 uses.
[x] introduce defer and util to executable config files by supporting export of a callback function to evaluate the file
[x] deprecate async.js
[x] relocate deferConfig's implementation into lib, and deprecate node-config/defer, which doesn't work great with imports anyway
[x] modify defer to accept async functions
[ ] deprecate raw.js
[ ] expose raw through the same callback mechanism as defer
For 5.0 then,
- Utilize a cache busting workaround for the remaining uses of requireUncached
- Remove async.js, defer.js, raw.js
- Remove
config.util.makeImmutable() - Fix immutability bugs in lib/util.js
- Switch to ESM
- use
createRequireto replacerequireinloadFiles()and elsewhere - Create prerelease builds of 5.0.0 for public commentary.
Blocking Issues
Bootstrapping issues
The require() thunking layers don't like exporting a module that is still being loaded.
The most straightforward way to fix this also addresses #740, which is to modify node-config to allow executable config
files to return a function instead of the data. The function can then receive defer and util as arguments instead of having to load them. This is a small but
Imports are Immutable
If you look at the requireCache for createRequire you would think that there's a way to reset ESM modules the same way you reset CJS modules for testing purposes. You would be wrong. So we need a way to completely reset the state of node-config for different test suites.
After the work I did in the spring, there are only a couple of variables that are still file scope. FIRST_LOAD, config, and some error handling variables that really are only used in one spot and so could be tucked into a function hanging off of the init process.
The problem is that the one use of 'FIRST_LOAD' happens inside Config.get, which looks like everything works properly until you try to do a Config.get.get and you find out that this has changed and so you cannot just hang the load object off of it to allow for state reload across multiple calls.
Not that we probably want reload across multiple calls in the production version. The simplest solution might be to set a flag that forces reload on every invocation.
Resolved Issues
- vows is CJS, and seems to be causing issues during loading vows needs to go #807 Remove vows from tests. #854
- Coffeescript only blows up if we set the module type in package.json. It causes all the tests to break if set. There are other ways to delay doing that.
Vows
In particular, since vows plays games with module.exports, if you try to convert the tests to cjs to attempt to continue to make the code work, then the tests end up require()ing the code, and then parser.js freaks out because createRequire doesn't work with an error:
Cannot set properties of undefined (setting 'id')