Code Splitting
Introduction
Code splitting is a technique used to reduce the size of your bundle by splitting your code into various bundles which can then be loaded on demand or in parallel. This results in smaller bundles which leads to faster load time. Flarum instances can have a lot of extensions installed, and when each extension lazy loads the modules it does not immediately or frequently need, the initial load time of the forum can be significantly reduced. The opposite leads to a bloated bundle and a slow initial load time.
How to Split Your Code
If you wish to split (lazy load) a module, you can use the asynchronous import()
function. This function returns a promise which resolves to the module you are importing. Webpack will automatically split the module into a separate chunk file which will be loaded on demand.
import('acme/forum/components/CustomPage').then(({ default: CustomPage }) => {
// do something with CustomPage
});
This will create a chunk file under js/dist/forum/components/CustomPage.js
. This chunk file will be loaded when the import is called. But before that can happen, the backend needs to be made aware of this chunk file. You do that by adding the js/dist/forum
path as a source for the forum
frontend. (If the chunk was under js/dist/admin
, you would add it as a source for the admin
frontend, same for js/dist/common
and common
.)
In extend.php
:
use Flarum\Extend;
return [
(new Extend\Frontend('forum'))
->jsDirectory(__DIR__.'/js/dist/forum'),
];
Importing split modules from core or other extensions
Flarum by default lazy loads certain modules of its own, such as the LogInModal
component. If you need to import one of these modules, you can do so by just asynchronously importing it as you would any other module.
import('flarum/forum/components/LogInModal').then(({ default: LogInModal }) => {
// do something with LogInModal
});
For modules from other extensions, you can import them using the ext:
syntax.
import('ext:flarum/tags/common/components/TagSelectionModal').then(({ default: TagSelectionModal }) => {
// do something with CustomPage
});
Extending/Overriding/Adding split modules methods
If you wish to extend, override or add a method to a split module, rather than directly accessing the module prototype Component.prototype
or passing the prototype to extend
or override
, you have to pass the import path as a first argument to either extend
or override
utilities. The callback will be executed when the module is loaded. Checkout Changing The UI Part 3 for more details.
Code APIs that support lazy loading
The following code APIs support lazy loading:
Async Modals
You can pass a callback that returns a promise to app.modal.show
. The modal will be shown when the promise resolves.
app.modal.show(() => import('flarum/forum/components/LogInModal'));
Async Pages
You can pass a callback that returns a promise when declaring the page component.
import Extend from 'flarum/common/extenders';
export default [
new Extend.Routes()
.add('acme', '/acme', () => import('./components/CustomPage')),
];
Async Composers
If you are using a custom composer like the DiscussionComposer
, you can pass a callback that returns a promise to the composer
method.
app.composer.load(() => import('flarum/forum/components/DiscussionComposer'), { user: app.session.user }).then(() => app.composer.show());
Flarum Lazy Loaded Modules
You can see a list of all the modules that are lazy loaded by Flarum in the GitHub repository.
Extending a split component class
Often, you may want to create a component that extends a split component class. Here is a common example, the fof/byobu
extension has a PrivateDiscussionComposer
component which extends flarum/forum/components/DiscussionComposer
.
The DiscussionComposer
along with other modules related to the composer, are lazy loaded. So this line of code will not work:
import PrivateDiscussionComposer from './discussions/PrivateDiscussionComposer';
app.composer.load(PrivateDiscussionComposer, {
user: app.session.user,
recipients: recipients,
recipientUsers: recipients,
});
app.composer.show();
Because flarum/forum/components/DiscussionComposer
is not loaded yet, the frontend will throw an error, complaining of not being able to find that module.
What we need to do in this case, is to first ensure that flarum/forum/components/DiscussionComposer
has been loaded, then we can load the custom component, that means we have to lazy load the custom component:
const PrivateDiscussionComposer = await app.composer
.load(() => import('flarum/forum/components/DiscussionComposer').then(async () => {
return await import('./discussions/PrivateDiscussionComposer');
}), {
user: app.session.user,
recipients: recipients,
recipientUsers: recipients,
});
app.composer.show();