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:
Core exposes a compat object that maps to core exports (components/Modal => export).
Webpack translates imports of core modules to an access to compat.
Core does not import its modules from compat.
Extensions (like tags and mentions), make their exports accessible by mutating compat (import from flarum/tags/..).
Extensions also do not import their own modules from compat.
Besides compat there is extensions object which maps to an extension's main exports.
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.
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.
Exports from core and extensions are auto-loaded into the registry (through webpack).
Webpack translates non-local imports to an access to the registry.
Each export is keyed by an ID (namespace + path + name).
The registry allows listening to the addition of an export. (Useful for lazy loading).
Namespace & ID
Exports will be identified and registered with:
a namespace (the usual extension ID format, we will use core for core and for example flarum-tags or blomstra-realtime for extensions).
an ID, which is the path of the module within the package, for example, common/components/Acme.
Then:
When importing flarum/_path_/module.js, it will be translated to a namespace of core and ID of _path_/module.
For extensions, the import format will be ext:blomstra/realtime/_path_/module.js, interpreted as namespace=blomstra-realtime and ID=_path_/module.
Open challenges
When a module that has not been loaded yet (code splitting) is imported, what should the registry return?
How to allow separation between public and private API?
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
// 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
Importing from flarum/tags or flarum/suspend or flarum/mentions or flarum/flags no longer works, instead use ext:flarum/tags ..etc.
Importing from @flarum/core no longer works.
Webpack config's useExtensions option has been removed, use the import formats explained above instead.
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:
compat
object that maps to core exports (components/Modal
=> export).compat
.compat
(import fromflarum/tags/..
).compat
.compat
there isextensions
object which maps to an extension's main exports.Goals
Brainstormed system
This is the formalized concept brainstormed by @askvortsov1 in early 2021.
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 aflarum.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:
core
for core and for exampleflarum-tags
orblomstra-realtime
for extensions).common/components/Acme
.Then:
flarum/_path_/module.js
, it will be translated to a namespace ofcore
and ID of_path_/module
.ext:blomstra/realtime/_path_/module.js
, interpreted as namespace=blomstra-realtime
and ID=_path_/module
.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:
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
Module with exports only (no default export)
Directory with modules and an
index.js
See core for an example
extenders
.Breaking Changes
flarum/tags
orflarum/suspend
orflarum/mentions
orflarum/flags
no longer works, instead useext:flarum/tags
..etc.@flarum/core
no longer works.useExtensions
option has been removed, use the import formats explained above instead.