Description
Current napi_addon_register_func
callback is quite obviously designed around CommonJS (for understandable reasons), but given that N-API is a new API, it might be easy enough to provide something that will be module-system-agnostic and work well with CommonJS while also being aligned better with ES6 import
statements that are upcoming in Node.js.
One of the main incompatibilities from aligning current CommonJS ecosystem with ES6 comes from the fact that in CommonJS you can either export arbitrary named declarations, or you can completely redefine module.exports
object with any JS value.
ES6 module system, on the other hand, does not allow such conflicts and presents all the exports on a single object with explicit names (or default
as a key), so there are arguments about how to import module.exports
case - should it be imported as default one and then effectively each CommonJS module can be used only as import fs from "fs"
and not import { readFile } from "fs"
, or should it do some magic and destructure itself into ES6 names exports as well.
To avoid similar incompatibilies, my suggestion is to make loader system opaque to the registration callback and either:
1. Allow only named exports by having something like:
typedef struct {
const char* name; // NULL for default export
napi_value value;
} napi_export_descriptor;
NAPI_EXTERN napi_status napi_export(napi_env env, size_t export_count, const napi_export_descriptor* descriptors);
Which would do equivalent of exports.name = value
/ exports.default = value
in CommonJS and export const name = value
/ export default value
in ES6 module system.
This might be incompatible with some existing modules though that effectively used module.exports = ...
and such change would require them updating require(...)
part too.
2. Allow only "default" export. Effectively this means changing a callback to:
typedef void (*napi_addon_register_func)(napi_env env, napi_value *result);
For modules that just defined properties on given exports
empty object, the only difference would be that they will have to call napi_create_object
theirselves and return it in the result
.
For modules that used to override module.exports
, this only simplifies things as they don't need to use property modification APIs to change "exports"
property on a given module
and can just return the value directly.
For CommonJS users nothing will change and require(...)
will work exactly like before.
For ES6, this will allow to import such modules with import nativeModule from "..."
and using its properties - this won't allow having named imports unfortunately, but that's a minor inconvenience, and it will allow module loader to load native module without having to simulate CommonJS' module
and exports
wrapper objects.
Personally, I'm leaning towards option #2 as that's a pretty simple change that aligns well with both CommonJS and ES6 loaders without breaking compatibility with existing native modules in the ecosystem, but would love to hear your thoughts.