Frontend Extension Overrides
NOTE: The new frontend system is in alpha and is only supported by a small number of plugins.
Introduction
An important customization point in the frontend system is the ability to override existing extensions. It can be used for anything from slight tweaks to the extension logic, to completely replacing an extension with a custom implementation. While extensions are encouraged to make themselves configurable, there are many situations where you need to override an extension to achieve the desired behavior. The ability to override extensions should be kept in mind when building plugins, and can be a powerful tool to allow for deeper customizations without the need to re-implement large parts of the plugin.
In general, most features should have a good level of customization built into them, so that users do not have to leverage extension overrides to achieve common goals. A well written feature often has configuration settings, or uses extension inputs for extensibility where applicable. An example of this is the search plugin, which allows you to provide result renderers as inputs rather than replacing the result page wholesale just to tweak how results are shown. Adopters should take advantage of those when possible in order to reduce the need and size of extension overrides.
Overriding an extension
Every extension created with createExtension
comes with an override
method, including those created from an extension blueprint. The override
method creates a new extension, it does not mutate the existing extension. This new extension in created in such a way that if it is installed adjacent to the existing extension, it will take precedence and override the existing extension. While the override
method does create new extension instances, it is not intended to be used as a way to create multiple new extensions from a base template, for that use-case you will want to use an extension blueprint instead.
The following is an example of calling the .override(...)
method on an extension:
const myOverrideExtension = myExtension.override({
factory(originalFactory) {
return originalFactory();
},
});
This override is a no-op, it does not change the behavior of the extension, but simply forwards the outputs from the original extension factory. If you are familiar with extension blueprints, you will recognize this factory override pattern where we get access to the original factory function. In fact the only difference is that we do not need to pass any parameters to the original factory. The first parameter is now instead the optional factory context overrides, more on that as we dive into each override pattern in the following sections.
Overriding original factory outputs
When overriding an extension you can choose to forward the existing outputs, or replace them with your own. The override factory has an exception to the rule that extension factories can only return a single value for each declared output. It will instead always use the last value provided for each extension data reference. This makes it possible to forward the outputs from the original factory, but also provide your own, for example:
const myOverrideExtension = myExtension.override({
factory(originalFactory) {
return [
...originalFactory(),
coreExtensionData.reactElement(<h1>Hello Override</h1>),
];
},
});
You can also access individual data values from the original factory, in order to decorate the output:
const myOverrideExtension = myExtension.override({
factory(originalFactory) {
const originalOutput = originalFactory();
const originalElement = originalOutput.get(coreExtensionData.reactElement);
return [
...originalOutput,
coreExtensionData.reactElement(
<details>
<summary>Show original element</summary>
{originalElement}
</details>,
),
];
},
});
Just as extension factories can be declared as a generator function, so can the override factory. Using a generator function, the first example above can be written as follows:
const myOverrideExtension = myExtension.override({
*factory(originalFactory) {
yield* originalFactory();
yield coreExtensionData.reactElement(<h1>Hello Override</h1>);
},
});
Note the yield*
expression, which forwards all values from the provided iterable to the generator, in this case the original factory output.
Overriding blueprint parameters
If you are overriding an extension that was originally created from an extension blueprint, you are able to override the parameters that were originally provided for the blueprint. This can be done directly as an option to .override
, or when calling the original factory in the override factory. The provided parameter overrides will be merged with the existing parameters that where provided when creating the extension from the blueprint.
For example, consider the following extension created from the PageBlueprint
:
const exampleExtension = PageBlueprint.make({
params: {
loader: () =>
import('./components/ExamplePage').then(m => <m.ExamplePage />),
defaultPath: '/example',
},
});
You can immediately override parameters through the params
option:
const overrideExtension = exampleExtension.override({
params: {
loader: () =>
import('./components/OverridePage').then(m => <m.OverridePage />),
},
});
It is also possible to pass parameter overrides when calling the original factory in the override factory:
const overrideExtension = exampleExtension.override({
factory(originalFactory) {
return originalFactory({
params: {
loader: () =>
import('./components/OverridePage').then(m => <m.OverridePage />),
},
});
},
});