- list indention means "I stepped into the code here"
- TKTKTK means "to come", borrowed from isaac, I have no idea what it really means
- I've broken these down into sections, the toplevel list items are "entry points"
into a file
- from there, list items in order mean "this executed in this order"
- another note for background: anytime you see something like "FrobnicatorWrap", that is a strong indication that there's a binding from JS to C++ backing the object. Word to the wise!
node.cc - entry point
config_experimental_modules
config_userland_loader
node_config.cc - InitConfig - configurationconfig_experimental_modules
-> JS-visibleexperimentalModules
config_userland_loader
-> JS-visibleuserLoader
- following
experimentalModules
:- lib/internal/bootstrap_node.js L103: only used in bootstrap to issue a warning about how truly experimental it is
- lib/module.js
- L451, Module._load -- if we don't have an ESMLoader yet, create one
- Loader comes from
internal/loader/Loader.js
(oh lord capcase)- see Loader
- We use loader to load the userland loader hooks
- see "hooks" in Loader
- then we continue to load using
getURLFromFilePath
- this uses the WHATWG URL to create a
file://
url
- this uses the WHATWG URL to create a
- we call Loader#import
- see Loader
- ∅ _load exits
exports a Loader class, which inherits from null (a very bradley thing to do)
Loader entry points:
constructor
: covered in the type defhook({resolve, dynamicInstantiate})
: sets a customresolver
anddynamicInstantiate
import(specifier, parentURL = this.base)
: this seems load-bearing, if you'll excuse the pun!await this.getModuleJob(specifier, parentURL)
returns aModuleJob
- calls
this.resolve
(usuallyModuleRequest.resolve
) L101- see
resolve
inmodule request
- see
- checks to see if we've got an outstanding
ModuleJob
in ourmoduleMap
- if so, return it
- ∅
getModuleJob
exits (great job all)
resolve
returns a url and a format- if the format is mundane (i.e., "not dynamic") pick the loader instance off of
ModuleRequest.loaders
- see
loaders
inmodule request
- see
- otherwise create a dynamic loader instance
- pass the loader instance to
ModuleJob
- see
constructor
inmodule job
- see
- END result, returns a
ModuleJob
- calls
- gets a module from
job.run()
- see
run
inmodule job
- see
- returns
module.namespace()
entry points:
static loaders
: loaders are provided for:esm
-- usesnew ModuleWrap
(seemodule wrap
)cjs
-- usescreateDynamicModule
(from moduleWrap) (seedynamic loads
)builtin
-- dittoaddon
-- dittojson
-- surprisingly, ditto
static resolve
: aResolver
- checks for builtins,
- searches for the file,
- checks the extension if it exists,
- throws an error if it doesn't
Entry points
constructor
- constructing a
ModuleJob
immediately attempts to link all dependend modules (viaModuleWrap#link
(seelink
inmodule wrap
))
- constructing a
run
- calls
this.instantiate()
- visits all modules in dependency graph, adding them to a set
- once all modules have been visited, calls
ModuleWrap#instantiate
(seeinstantiate
inmodule wrap
)- This instantiates all modules in the graph!
- then we do some bookkeeping by setting all of our dependency's
instantiated
props to a resolved promise
- calls
evaluate
inmodule wrap
- calls
The C++ binding to V8.
new ModuleWrap(source, url)
- compile a moduleModuleWrap#link(resolver : Function)
- Iterate over all direct dependencies of the current module
- for each dependency, call anon async fn on lib/internal/loader/ModuleJob.js#L33
- this recursively creates
ModuleJob
objects, linking them - internally we populate a map of strings to promises for modules (
resolve_cache_
)
- this recursively creates
ModuleWrap#instantiate()
- calls V8's InstantiateModule with
ResolveCallback
- resolve callback is called by v8, and it expects a module to be returned
- this is how we look up modules!
- calls V8's InstantiateModule with
ModuleWrap#evaluate()
- This actually executes the module!
ModuleWrap#namespace()
- This returns the exported namespace of the module.
Anything that's not ESM is dynamic. There are specializations for cjs
, builtin
, json
, and addon
modules (ref static loaders
in module request
). These specializations use dynamic modules!
See createDynamicModule
in lib/internal/loader/ModuleWrap.js
.
- We perform a magic trick.
- First we create a module that exports an executor variable
- The module's completion value (that is, the last value evaluted) is a function returning an object with a setter for the executor variable, and a reflect object with getters and setters for individual exported names.
- We immediately instantiate this facade module.
- Then we evaluate it, returning the function exposing the executor and exports setters.
- THEN we create another module that imports "executor" and other exports from
""
- it also exports those variables
- and if "executor" is a function, we call it
- calling executor will mutate those variables so we end up getting the right values
- We link this fronting module with a resolver that always returns our facade.
- We instantiate it and send it out into the world.
Optionally takes base
.
moduleMap : ModuleMap
: aMap
of strings toModuleJob
sbase : String
: defaults to thefile://
url for the CWDresolver : Resolver
: a Resolver returning a promise for a url & format- if
userLoader
is not set, this will beModuleRequest.resolve
- if
dynamicInstantiate : Function
: for "dynamic" loads
Takes a loader
, url
, and moduleProvider
(a ModuleProvider
).
loader : Loader
: The loader that created the job viagetModuleJob
error : null
:hadError : Boolean
:modulePromise : Promise<{module : ModuleWrap, reflect : null | ???}>
:module : undefined | ModuleWrap
: Populated with aModuleWrap
once linked.reflect : undefined | ????
: Populated once linked. Unclear what reflect is for.linked : Promise<Array<ModuleJob>>
: a promise for the graph of modules depended upon by the current moduleinstantiated : undefined | Promise<>
:
ModuleProvider ::= (
url : String
) -> Promise<{
module : ModuleWrap,
reflect : null | ???
}>
Resolver ::= (
specifier : String,
parentURL : String,
defaultResolve : Resolver = ModuleRequest.resolve
) -> Promise<{
url: String,
format: ValidFormt
}>
- one major cutting point in
Module._load
- provide support for
.coffee
et al by adding a custom loader- no support for doing this at runtime, which seems a little prescriptive
- dynamic imports support
cjs
,json
,addons
, etc - in ESM as implemented,
require
is already undefined- we can, in fact, reserve
nodejs
as a special module name - we should be able to "import {require} from nodejs" using the facade pattern
- we can, in fact, reserve
This is by design due to limitations around conflicts of mutating the loader system at runtime. It also mimics problems with replacing service workers etc. in the browser.