diff --git a/package.json b/package.json
index d2eacdffe..3ddced5dd 100644
--- a/package.json
+++ b/package.json
@@ -6,11 +6,11 @@
"bundlesize": [
{
"path": "./packages/component/dist/loadable.min.js",
- "maxSize": "2.5 kB"
+ "maxSize": "3.5 kB"
},
{
"path": "./packages/component/dist/loadable.esm.js",
- "maxSize": "3.5 kB"
+ "maxSize": "4.5 kB"
}
],
"scripts": {
diff --git a/packages/component/package.json b/packages/component/package.json
index 6b0f3db92..9b16bcfa4 100644
--- a/packages/component/package.json
+++ b/packages/component/package.json
@@ -38,6 +38,7 @@
},
"dependencies": {
"@babel/runtime": "^7.7.7",
- "hoist-non-react-statics": "^3.3.1"
+ "hoist-non-react-statics": "^3.3.1",
+ "react-is": "^16.12.0"
}
}
diff --git a/packages/component/src/createLoadable.js b/packages/component/src/createLoadable.js
index 261a09acf..1d00ee10a 100644
--- a/packages/component/src/createLoadable.js
+++ b/packages/component/src/createLoadable.js
@@ -1,5 +1,7 @@
/* eslint-disable no-use-before-define, react/no-multi-comp, no-underscore-dangle */
import React from 'react'
+import * as ReactIs from 'react-is'
+import hoistNonReactStatics from 'hoist-non-react-statics'
import { invariant } from './util'
import Context from './Context'
@@ -19,7 +21,7 @@ const withChunkExtractor = Component => props => (
const identity = v => v
-function createLoadable({ resolve = identity, render, onLoad }) {
+function createLoadable({ defaultResolveComponent = identity, render, onLoad }) {
function loadable(loadableConstructor, options = {}) {
const ctor = resolveConstructor(loadableConstructor)
const cache = {}
@@ -33,6 +35,19 @@ function createLoadable({ resolve = identity, render, onLoad }) {
return null
}
+ function resolve(module, props, Loadable) {
+ const Component = options.resolveComponent
+ ? options.resolveComponent(module, props)
+ : defaultResolveComponent(module)
+ if (options.resolveComponent && !ReactIs.isValidElementType(Component)) {
+ throw new Error(`resolveComponent returned something that is not a React component!`)
+ }
+ hoistNonReactStatics(Loadable, Component, {
+ preload: true,
+ })
+ return Component;
+ }
+
class InnerLoadable extends React.Component {
static getDerivedStateFromProps(props, state) {
const cacheKey = getCacheKey(props)
@@ -128,7 +143,7 @@ function createLoadable({ resolve = identity, render, onLoad }) {
try {
const loadedModule = ctor.requireSync(this.props)
- const result = resolve(loadedModule, { Loadable })
+ const result = resolve(loadedModule, this.props, Loadable)
this.state.result = result
this.state.loading = false
} catch (error) {
@@ -154,13 +169,13 @@ function createLoadable({ resolve = identity, render, onLoad }) {
this.promise = ctor
.requireAsync(props)
.then(loadedModule => {
- const result = resolve(loadedModule, { Loadable })
+ const result = resolve(loadedModule, this.props, Loadable)
if (options.suspense) {
this.setCache(result)
}
this.safeSetState(
{
- result: resolve(loadedModule, { Loadable }),
+ result: resolve(loadedModule, this.props, Loadable),
loading: false,
},
() => this.triggerOnLoad(),
diff --git a/packages/component/src/loadable.js b/packages/component/src/loadable.js
index 8a407a6d7..6944bf14a 100644
--- a/packages/component/src/loadable.js
+++ b/packages/component/src/loadable.js
@@ -1,10 +1,10 @@
/* eslint-disable no-use-before-define, react/no-multi-comp */
import React from 'react'
import createLoadable from './createLoadable'
-import { resolveComponent } from './resolvers'
+import { defaultResolveComponent } from './resolvers'
export const { loadable, lazy } = createLoadable({
- resolve: resolveComponent,
+ defaultResolveComponent,
render({ result: Component, props }) {
return
},
diff --git a/packages/component/src/loadable.test.js b/packages/component/src/loadable.test.js
index a48083e84..ac3376f1d 100644
--- a/packages/component/src/loadable.test.js
+++ b/packages/component/src/loadable.test.js
@@ -90,7 +90,7 @@ describe('#loadable', () => {
expect(load).toHaveBeenCalledTimes(2)
})
- it('supports non-default export', async () => {
+ it('supports commonjs default export', async () => {
const load = createLoadFunction()
const Component = loadable(load)
const { container } = render()
@@ -98,6 +98,22 @@ describe('#loadable', () => {
await wait(() => expect(container).toHaveTextContent('loaded'))
})
+ it('supports non-default export via resolveComponent', async () => {
+ const load = createLoadFunction()
+ const importedModule = { exported: () => 'loaded'};
+ const resolveComponent = jest.fn(({ exported: component }) => component);
+ const Component = loadable(load, {
+ resolveComponent,
+ })
+ const { container } = render()
+ load.resolve(importedModule)
+ await wait(() => expect(container).toHaveTextContent('loaded'))
+ expect(resolveComponent).toHaveBeenCalledWith(
+ importedModule,
+ { someProp: '123', __chunkExtractor: undefined, forwardedRef: null },
+ )
+ })
+
it('forwards props', async () => {
const load = createLoadFunction()
const Component = loadable(load)
diff --git a/packages/component/src/resolvers.js b/packages/component/src/resolvers.js
index e64a5e24a..cd2728fe1 100644
--- a/packages/component/src/resolvers.js
+++ b/packages/component/src/resolvers.js
@@ -1,12 +1,6 @@
-import hoistNonReactStatics from 'hoist-non-react-statics'
-
-export function resolveComponent(loadedModule, { Loadable }) {
+export function defaultResolveComponent(loadedModule) {
// eslint-disable-next-line no-underscore-dangle
- const Component = loadedModule.__esModule
- ? loadedModule.default
- : loadedModule.default || loadedModule
- hoistNonReactStatics(Loadable, Component, {
- preload: true,
- })
- return Component
+ return loadedModule.__esModule
+ ? loadedModule.default
+ : loadedModule.default || loadedModule
}
diff --git a/website/src/pages/docs/api-loadable-component.mdx b/website/src/pages/docs/api-loadable-component.mdx
index 4412bbc25..906268a7e 100644
--- a/website/src/pages/docs/api-loadable-component.mdx
+++ b/website/src/pages/docs/api-loadable-component.mdx
@@ -10,13 +10,14 @@ order: 10
Create a loadable component.
-| Arguments | Description |
-| ------------------ | -------------------------------------------------------------------- |
-| `loadFn` | The function call to load the component. |
-| `options` | Optional options. |
-| `options.fallback` | Fallback displayed during the loading. |
-| `options.ssr` | If `false`, it will not be processed server-side. Default to `true`. |
-| `options.cacheKey` | Cache key function (see [dynamic import](/docs/dynamic-import/)) |
+| Arguments | Description |
+| -------------------------- | -------------------------------------------------------------------- |
+| `loadFn` | The function call to load the component. |
+| `options` | Optional options. |
+| `options.resolveComponent` | Function to resolve the imported component from the imported module. |
+| `options.fallback` | Fallback displayed during the loading. |
+| `options.ssr` | If `false`, it will not be processed server-side. Default to `true`. |
+| `options.cacheKey` | Cache key function (see [dynamic import](/docs/dynamic-import/)) |
```js
import loadable from '@loadable/component'
@@ -24,6 +25,41 @@ import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
```
+### `options.resolveComponent`
+This is a function that receives the imported module (what the `import()` call resolves to) and the props, and returns the component.
+
+The default value assumes that the component is exported as a default export.
+It can be customized to make a loadable component where the imported component is not the default export, or even where a different export is chosen depending on the props.
+For example:
+
+```js
+// components.js
+
+export const Apple = () => 'Apple!'
+export const Orange = () => 'Orange!'
+```
+
+```js
+// loadable.js
+
+const LoadableApple = loadable(() => import('./components'), {
+ resolveComponent: (components) => components.Apple,
+})
+
+const LoadableOrange = loadable(() => import('./components'), {
+ resolveComponent: (components) => components.Orange,
+})
+
+const LoadableFruit = loadable(() => import('./components'), {
+ resolveComponent: (components, props) => components[props.fruit],
+})
+
+```
+
+**Note:** The default `resolveComponent` breaks Typescript type inference due to CommonJS compatibility.
+To avoid this, you can specify `resolveComponent` as `(imported) => imported.default`.
+This requires that the imported components have ES6/Harmony exports.
+
## lazy
Create a loadable component "Suspense" ready.
@@ -89,13 +125,14 @@ OtherComponent.load().then(() => {
Create a loadable library.
-| Arguments | Description |
-| ------------------ | -------------------------------------------------------------------- |
-| `loadFn` | The function call to load the component. |
-| `options` | Optional options. |
-| `options.fallback` | Fallback displayed during the loading. |
-| `options.ssr` | If `false`, it will not be processed server-side. Default to `true`. |
-| `options.cacheKey` | Cache key function (see [dynamic import](/docs/dynamic-import)) |
+| Arguments | Description |
+| -------------------------- | -------------------------------------------------------------------- |
+| `loadFn` | The function call to load the component. |
+| `options` | Optional options. |
+| `options.resolveComponent` | Function to resolve the imported component from the imported module. |
+| `options.fallback` | Fallback displayed during the loading. |
+| `options.ssr` | If `false`, it will not be processed server-side. Default to `true`. |
+| `options.cacheKey` | Cache key function (see [dynamic import](/docs/dynamic-import)) |
```js
import loadable from '@loadable/component'
diff --git a/yarn.lock b/yarn.lock
index 866727dcb..02369ebbe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7938,7 +7938,7 @@ react-dom@^16.12.0:
prop-types "^15.6.2"
scheduler "^0.18.0"
-react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
+react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==