flarum / framework

Simple forum software for building great communities.
http://flarum.org/
6.36k stars 835 forks source link

Export Registry #3833

Closed SychO9 closed 1 year ago

SychO9 commented 1 year ago

What & Why

The export registry is a concept created to give us more control and flexibility over how modules are imported. The idea is to store modules in a registry, from which external exports are translated to access to that registry. This is an important step towards lazy loading modules.

Current system of imports/exports

This is the gist of the current import/export logic in Flarum:

export_registry

Goals

Essential Nice to have
- Formalise into an exports registry API - Overriding imports, it would require core and extension local imports to rely on the registry.
- Allow extending/overriding components asynchronously (for lazy loading later on).
- Auto load all exports into the registry (making compat redundant).
- Allow determining public vs private API.

Brainstormed system

This is the formalized concept brainstormed by @askvortsov1 in early 2021.

export_registry_after

interface ExportRegistry {
    /**
     * Add an instance to the registry.
     * This serves as the equivalent of `flarum.core.compat[id] = object`
     */
    add(namespace: string, id: string, object: any);

    /**
     * Add a function to run when object of id "id" is added (or overriden).
     * If such an object is already registered, the handler will be applied immediately.
     */
    onLoad(namespace: string, id: string, handler: Function);

    /**
     * Retrieve an object of type `id` from the registry.
     */
    get(namespace: string, id: string): any;
}

Every module source will be appended by a custom Webpack loader, with flarum.reg.add($namespace, $path, $module) and every import from another package will be translated to a flarum.reg.get($name, $path) Giving us full control over loading modules and exposing them. One of the added benefits of this is no longer needing to explicitly add things to a compact API to be used externally.

Namespace & ID

Exports will be identified and registered with:

Then:

Open challenges

Export/Import formats

The following scenarios are not supported:

Exports that will not be added to the registry

When using an unnamed export, the module cannot be added to the registry, unless that is a wanted behavior on your part, make sure to have names on exports. Examples:

// Will not be added to the registry
export default function () {
    return 'anonymous';
};

// Will be added
export default function acme() {
    return 'anonymous';
};
// Will not be added to the registry
import Model from './Model';
import PostTypes from './PostTypes';
import Routes from './Routes';
import Store from './Store';

export default {
  Model,
  PostTypes,
  Routes,
  Store,
};

// Will be added
import Model from './Model';
import PostTypes from './PostTypes';
import Routes from './Routes';
import Store from './Store';

const extenders = {
  Model,
  PostTypes,
  Routes,
  Store,
};

export default extenders;

Supported Exports/Imports across extensions

There are 3 ways you can export modules to be imported from other extensions

Module file with a default export only
// Extension A: vendor/extension-a
// Filename: moduleA.ts
// Export
export default ...;

// Extension B: vendor/extension-b
// Import
import moduleA from 'ext:vendor/extension-a/.../moduleA';
Module with exports only (no default export)
// Extension A: vendor/extension-a
// Filename: moduleA.ts
// Export
export function acme() {}
export class Test {}
const foo = 'foo';
export { foo };

// Extension B: vendor/extension-b
// Import
import { acme, Test, foo } from 'ext:vendor/extension-a/.../moduleA';
Directory with modules and an index.js

See core for an example extenders.

// Can import as
import Routes from 'flarum/common/extenders/Routes';
// Or
import { Model, PostTypes } from 'flarum/common/extenders';
// Or
import Extend from 'flarum/common/extenders';

Breaking Changes