Skip to content

[Discussion] Simplification of Loader #147

@caridy

Description

@caridy
Contributor

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

  • Importing relative and global modules on-demand is a primary use-case to level-up with node/CJS capabilities (import() imperative form is needed).
  • A module may want to create a new loader instance via new Reflect.Loader(), useful for frameworks and sandboxes.
  • A module should never attempt to use System.loader because it makes the module less portable.
  • The default global loader instance (e.g.: System.loader) is rarely needed.
  • <script type="module"> or node's --module (or it equivalent) are far better options to kick in your app than the global loader.
  • fetch and translate hooks can be implemented in user-land via service workers or Realm's evaluation hook.
  • instantiate hook can be removed as well if the registry is exposed via the current loader reference.

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:

<script type="module">
const loader = import.loader;
</script>

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.:

<script type="module">
const l1 = new Reflect.loader();
// configure the loader in any way you want
l1.import('foo').then(...);
</script>

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.

Activity

caridy

caridy commented on Jun 30, 2016

@caridy
ContributorAuthor
caridy

caridy commented on Jun 30, 2016

@caridy
ContributorAuthor

Removing the fetch hook will solve #132

caridy

caridy commented on Jun 30, 2016

@caridy
ContributorAuthor

related to #121, #69 and #130

caridy

caridy commented on Jun 30, 2016

@caridy
ContributorAuthor

this will also answer the question from #72, since there will be no global loader to be mutated.

caridy

caridy commented on Jun 30, 2016

@caridy
ContributorAuthor

it also touch on the imperative form of import defined in #36

caridy

caridy commented on Jun 30, 2016

@caridy
ContributorAuthor

this might entirely solve #89

domenic

domenic commented on Jun 30, 2016

@domenic
Member

This seems like a great direction to me.

Further simplification may be achievable by not allowing mutation of import.loader. instead you load scripts with custom loaders using thatLoader.import (which also applies to their dependencies).

That way you can't affect the way other code loads, unless you yourself load it.

matthewp

matthewp commented on Jul 1, 2016

@matthewp

In regards to the fetch hook and using service workers, would there be a way to know, in the service worker fetch event, that the FetchEvent is coming from a loader request (vs. an XHR request, a <link> request, etc.)? That would be important to know in order to do the types of things that you are likely to do in a loader fetch or translate hook.

guybedford

guybedford commented on Jul 1, 2016

@guybedford

Nice to see the simplification effort here! Is 'import.loader.load' here
just a normal import function?
On Fri, 01 Jul 2016 at 02:11, Matthew Phillips notifications@github.com
wrote:

In regards to the fetch hook and using service workers, would there be a
way to know, in the service worker fetch event, that the FetchEvent is
coming from a loader request (vs. an XHR request, a request, etc.)? That
would be important to know in order to do the types of things that you are
likely to do in a loader fetch or translate hook.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#147 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/AAkiyvg05xg2E8pB-Uj0bXQgWZZiLIVPks5qRFtDgaJpZM4JCrlP
.

caridy

caridy commented on Jul 1, 2016

@caridy
ContributorAuthor

@guybedford no, import.loader.load is just loader.load(), which is available today to fetch a module and its dependencies, instantiate all of them, and get them ready to be evaluated.

guybedford

guybedford commented on Jul 1, 2016

@guybedford

Thanks @caridy that makes sense. Will loader.define still be available for
population of modular source text? Would be necessary to allow translation
workflows to esm I think?
On Fri, 01 Jul 2016 at 15:49, Caridy Patiño notifications@github.com
wrote:

@guybedford https://github.com/guybedford no, import.loader.load is
just loader.load(), which is available today to fetch a module and its
dependencies, instantiate all of them, and get them ready to be evaluated.


You are receiving this because you were mentioned.

Reply to this email directly, view it on GitHub
#147 (comment), or mute
the thread
https://github.com/notifications/unsubscribe/AAkiysV34houNttd0IycMJ9MFrQuT81Gks5qRRrogaJpZM4JCrlP
.

caridy

caridy commented on Jul 1, 2016

@caridy
ContributorAuthor

@guybedford no, you can push module records into the registry (check the example "Controlling the Registry"), but as today, there is no way to process a source text module record from the loader. This might or might not be a problem, we have some ideas. Alternative, you can always rely on the service worker to produce the string to be evaluated for a particular key/url, in which case the loader will evaluate it.

44 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @Constellation@caridy@shicks@probins@matthewp

        Issue actions

          [Discussion] Simplification of Loader · Issue #147 · whatwg/loader