React Loadable is great! And it works just perfectly fine with Meteor as long as you only use it on the client.
When using React Loadable with server side rendering things get tricky. This package tries to solve that.
meteor add nemms:meteor-react-loadable
Make sure you also have the necessary NPM modules installed:
meteor npm install --save react-loadable prop-types
Of course, React needs to be installed as well, but I guess since you are looking for this package you already have it ;)
The usage is pretty similar to react-loadable.
Always import the package like so:
import MeteorLoadable from 'meteor/nemms:meteor-react-loadable'
Instead of the original Loadable
component you have to use MeteorLoadable
.
const MyComponent = MeteorLoadable({
loader: () => import('./components/MyComponent'),
moduleId: require.resolve('./components/MyComponent'),
})
Additionally to the loader
you need to provide the moduleId
. This is the same as the
absolute path of the module (including the file extension). Alternatively to require.resolve()
your module you can also just pass the absolute path: /imports/components/MyComponent.js
If you ommit the moduleId
server side rendering won't work properly.
Note: You don't need to add the react-loadable babel plugin.
Oh, but don't forget to pass your loading component.
All components which might be rendered on the server must be loaded beforehand. Since they will be imported asynchronously they otherwise wouldn't be available on render.
onPageLoad(async (sink) => {
await MeteorLoadable.preloadComponents()
const html = renderToString(
<MyApp />
)
// …
})
Now we are done for the app to be rendered properly on the server. But there is still the client which also needs the same components preloaded before rendering.
For preparation we need to wrap our app with MeteorLoadable.Capture
.
This will receive the information which components got actually rendered.
After rendering we need to pass this information to the client. MeteorLoadable takes care of it, you only need to append the generated script tag to the body. This contains all module ids the client can finally load.
Meteor.startup(async () => {
await MeteorLoadable.preloadComponents()
onPageLoad((sink) => {
const html = renderToString(
<MeteorLoadable.Capture>
<MyApp />
</MeteorLoadable.Capture>
)
sink.appendToBody(MeteorLoadable.getLoadedModulesScriptTag())
// …
})
})
Similar to how we preloaded the components on the server we need to preload them on the client before we hydrate the app.
onPageLoad(async () => {
await MeteorLoadable.preloadComponents()
hydrate(<MyApp />, document.getElementById('root'))
})
That's it. Now it should work as expected.
- This is the very first version. We already use it in production. But it might not be working for others. Feel free to contribute to it.
- Currently only one level of dynamic imports is working. If you define a
MeteorLoadable
in a component which is defined as aMeteorLoadable
itself they won't be preloaded properly and React will throw errors. - Other than with Webpack it's currently not possible to integrate the dynamic components into the bundle so the client would have it immediately. The client needs to import the components before the app can be hydrated. But I would argue the benefit is still high enough in contrast to integrate everything into one large bundle.