[Discussion] Simplification of Loader · Issue #147 · whatwg/loader (original) (raw)

Disclaimer: the following is a compilation of many thoughts and discussions about the future of the loader spec, it is not an actual plan, just food for thoughts.

Rationale on why we might want to simplify the Loader API

Examples

Imperative vs Declarative Import

Imperative form of import, equivalent to declarative import statement:

import {something} from "./foo.js"; something(1);

import("./foo.js").then(foo => ({something}) { something(2); });

Even easier when using await:

const {something} = await import("foo"); something(2);

note: the catch is that something may or may not be a live binding depending on how you import it.

Configuring Loader

Extending the current loader via hooks:

import {resolveHook} from "custom-resolver"; import.loader[Reflect.Loader.resolve] = resolveHook; import("foo").then(foo => ({something}) { something(2); });

note: the catch here is that module "custom-resolver" and its dependencies are imported before attempting to configure the loader, while "foo" and its dependencies are imported after the fact, which means they will be subject to the new resolution rules defined by the new hook.

Similarly, you can apply AOP on current resolver hooks:

import {resolveFactory} from "custom-resolver"; const oldResolver = import.loader[Reflect.Loader.resolve]; import.loader[Reflect.Loader.resolve] = resolveFactory(oldResolver); import("foo").then(foo => ({something}) { something(2); });

Controlling the Registry

If we open up the resolve hook, then we will have to open on the registry as well:

const loader = import.loader; loader[Reflect.Loader.resolve] = (name, referrer) => { if (name === "foo" && !loader.registry.has(name)) { const fooNS = new Reflect.Module(...); loader.registry.set(name, fooNS); } return name; }; import("foo").then(foo => { foo; // ... it is just a reference to the exotic object fooNS created in the resolver. });

note: this also show that the @@instantiate hook may not be needed after all.

Default Loader

Not need to have a System.loader initially, instead, you can use <script type="module"> to gain access to the global loader, e.g:

Custom Loader

A module may want to create a loader instance, pre-populate its registry, configure the hook, and start importing other modules using the newly created loader instance, e.g.:

note: "foo" module and its dependencies will have access to l1 via import.loader.

Open questions

Should the current loader be exposed initially?

const loader = import.loader; export {loader};

note: it seems that this is needed to simplify the mental model of the example above.

What about other intermedia states for relative modules?

We may want to expose a resolve and load mechanism for relative modules, e.g.:

// relative vs top level? import.resolve('./foo'); // the referrer (2nd arg) is implicit import.loader.resolve('foo'); // top level resolve operation // and load? import.load('./foo'); // relative import.loader.load('foo'); // top level

note: the resolve() and load() methods are important to apply performance optimizations.

Alternative, if the key of the module in the loader registry is exposed somehow, then we might not need relative resolve and relative load after all. e.g.:

import.loader.resolve('./foo', import.key); // relative import.loader.resolve('foo'); // top level resolve operation import.load('./foo', import.key); // relative import.loader.load('foo'); // top level load operation

note: this second option is probably better because it retains the mental model of dealing with the loader power object.