Discussion: Module Reflection - import.reflect

# Randy Buchholz (5 months ago)

Would it be worthwhile to add reflection to modules as a distinct feature?

import.meta.url is a basic form of reflection, but it seems (?) meta is trying to be a fairly loose specification. Being able query a module can be helpful. For example, if I can see the static imports, I can conditionally do dynamic imports. Or (thinking out loud here), can "generic" modules be built where default is the generic?

Some of the possibilities are import.reflect.default - returns default in its native form (function, class, value) import.reflect.imports - provides a list of static imports. Not sure what this should be (value or key-value (e.g., [importName, module]), but should be enumerable. import.reflect.functions - provides a list of exported functions. Can be use, for instance, to validate the shape of a module.

This can be exposed to an importing module.

// myModule has default export of `class xxx`

Import * as m from myModule
const instance = Reflect.construct(m.import.reflect.default, []);

# Jordan Harband (5 months ago)

In import * as m from myModule, m.default is already the default export; Object.keys(m) is already the list of export names, and Object.entries(m).filter(([k, v]) => typeof v === 'function')) is the

list of functions.

# Randy Buchholz (2 months ago)

Maybe my example was misleading by using an import. What I'm talking about is inspecting the module from within itself. Similar to this or self, AFAIK, I can't get a reference to the module from within the module - e.g., module. Say, I'm in a module that has a default export of class "Foo". In a function in that module (or class), I want to create an instance of that default export. I can do it by name new Foo(). But, in more generic code, I need to do it by "role" - default. So my generic version wants something that represents new module.default(). This is because I may not have access to the name - say the function is in a base class. Take for example a "type" module, that is a wrapper for a single class, and the filename = class name. One way I can implement instanceof is a string approach:

    // Foo.mjs
    export default class Foo {
        static type = import.meta.url;

        meta = { type: import.meta.url };

        // instanceof
        static [Symbol.hasInstance](instance) {
            return instance.meta.type === Foo.type;
        }
    }

Some weaknesses are it's strings, and I need to know the name of the class (for Foo.type). So I can't move this to a base class.

With module reflection, I could do this with objects:

    // Foo.mjs
    export default class Foo {
        meta = { type: import.reflect.default };

        // instanceof
        static [Symbol.hasInstance](instance) {
            return instance.meta.type === import.reflect.default;
        }
    }

A similar idea would be to define a Module Scope - module. Within a module, using module would be like a "static self" - it works on the structure (e.g., definition) of the module. Then import.reflect.default could be module.default.

# Jordan Harband (2 months ago)

import * as Self from '.' should work in every implementation of ESM that's shipped so far that I'm aware of.

# Randy Buchholz (2 months ago)

Interesting approach. Covers "public" (exported) members, which is about 90% of my use cases. Works great! Thanks.

# Randy Buchholz (2 months ago)

It seems the recursion has problems with Symbol, at least in Chrome.

    import * as module from "/foo.mjs";
    export default class Foo{ }
    const bar = Symbol("Bar");

Load page, enter debug mode, and reload. The debugger disconnects.