Skip to content

fixes #44 #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# http://editorconfig.org

root = true

[*]

# Change these settings to your own preference
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ The asynchronous component factory. Config goes in, an asynchronous component co
- `ErrorComponent` (_Component_, Optional, default: `null`) : A Component that will be displayed if any error occurred whilst trying to resolve your component. All props will be passed to it as well as an `error` prop containing the `Error`.
- `name` (_String_, Optional, default: `'AsyncComponent'`) : Use this if you would like to name the created async Component, which helps when firing up the React Dev Tools for example.
- `autoResolveES2015Default` (_Boolean_, Optional, default: `true`) : Especially useful if you are resolving ES2015 modules. The resolved module will be checked to see if it has a `.default` and if so then the value attached to `.default` will be used. So easy to forget to do that. 😀
- `getModuleId` (_(props) => String_, Optional, default: internal static id) : Function which gathers id, when `resolve` can return different modules. All props will be passed to it.
- `render` (_(module, props) => Component_, Optional, default: `null`) : Function to use resolved module and render something with it. All props and resolved module for current id will be passed to it.
- `env` (_String_, Optional) : Provide either `'node'` or `'browser'` so you can write your own environment detection. Especially useful when using PhantomJS or ElectronJS to prerender the React App.
- `serverMode` (_Boolean_, Optional, default: `'resolve'`) : Only applies for server side rendering applications. Please see the documentation on server side rendering. The following values are allowed.
- __`'resolve'`__ - Your asynchronous component will be resolved and rendered on the server. It's children will
Expand Down Expand Up @@ -133,6 +135,17 @@ export default asyncComponent({
})
```

##### `render`

```jsx
export default asyncComponent({
resolve: (props) => import(`assets/images/icons/${props.iconName}.svg`),
getModuleId: (props) => props.iconName,
render: (svgContent, props) => <GeneralIcon svgContent={svgContent} {...props} />,
name: 'AsyncSvgIcon',
})
```

##### Named chunks

```jsx
Expand Down
53 changes: 36 additions & 17 deletions commonjs/asyncComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function _possibleConstructorReturn(self, call) { if (!self) { throw new Referen
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }

var validSSRModes = ['resolve', 'defer', 'boundary'];
var staticModuleId = Symbol();

function asyncComponent(config) {
var name = config.name,
Expand All @@ -36,7 +37,12 @@ function asyncComponent(config) {
_config$serverMode = config.serverMode,
serverMode = _config$serverMode === undefined ? 'resolve' : _config$serverMode,
LoadingComponent = config.LoadingComponent,
ErrorComponent = config.ErrorComponent;
ErrorComponent = config.ErrorComponent,
_render = config.render,
_config$getModuleId = config.getModuleId,
getModuleId = _config$getModuleId === undefined ? function () {
return staticModuleId;
} : _config$getModuleId;


if (validSSRModes.indexOf(serverMode) === -1) {
Expand All @@ -52,7 +58,7 @@ function asyncComponent(config) {
// This will be use to hold the resolved module allowing sharing across
// instances.
// NOTE: When using React Hot Loader this reference will become null.
module: null,
modules: {},
// If an error occurred during a resolution it will be stored here.
error: null,
// Allows us to share the resolver promise across instances.
Expand All @@ -64,12 +70,15 @@ function asyncComponent(config) {
return autoResolveES2015Default && x != null && (typeof x === 'function' || (typeof x === 'undefined' ? 'undefined' : _typeof(x)) === 'object') && x.default ? x.default : x;
};

var getResolver = function getResolver() {
if (sharedState.resolver == null) {
var getResolver = function getResolver(props) {
// FIXME can we make "sharedState.resover" like "sharedState.modules" to get rid of `getModuleId(props) !== staticModuleId`?
// Because atm it calls `resolver` two times on first render. This is no problem for dynamic `import` thing of webpack,
// but other promise based api.
if (sharedState.resolver == null || getModuleId(props) !== staticModuleId) {
try {
// Wrap whatever the user returns in Promise.resolve to ensure a Promise
// is always returned.
var resolver = resolve();
var resolver = resolve(props);
sharedState.resolver = Promise.resolve(resolver);
} catch (err) {
sharedState.resolver = Promise.reject(err);
Expand Down Expand Up @@ -142,9 +151,7 @@ function asyncComponent(config) {
}, {
key: 'componentWillMount',
value: function componentWillMount() {
this.setState({
module: sharedState.module
});
this.setState({ modules: sharedState.modules });
if (sharedState.error) {
this.registerErrorState(sharedState.error);
}
Expand All @@ -159,27 +166,28 @@ function asyncComponent(config) {
}, {
key: 'shouldResolve',
value: function shouldResolve() {
return sharedState.module == null && sharedState.error == null && !this.resolving && typeof window !== 'undefined';
return sharedState.modules[getModuleId(this.props)] !== null && sharedState.error == null && !this.resolving && typeof window !== 'undefined';
}
}, {
key: 'resolveModule',
value: function resolveModule() {
var _this3 = this;

var props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.props;

this.resolving = true;

return getResolver().then(function (module) {
var moduleId = getModuleId(props);
return getResolver(props).then(function (module) {
if (_this3.unmounted) {
return undefined;
}
if (_this3.context.asyncComponents != null) {
_this3.context.asyncComponents.resolved(sharedState.id);
}
sharedState.module = module;
sharedState.modules[moduleId] = module;
if (env === 'browser') {
_this3.setState({
module: module
});
_this3.setState({ modules: sharedState.modules });
}
_this3.resolving = false;
return module;
Expand All @@ -201,6 +209,16 @@ function asyncComponent(config) {
return undefined;
});
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
var lastModuleId = getModuleId(this.props);
var nextModuleId = getModuleId(nextProps);
if (lastModuleId !== nextModuleId && !sharedState.modules[nextModuleId]) {
// FIXME add LoadingComponent logic to show old module for X ms (to prevent flash of content) and then show a loading component till the new module is loaded, make this configurable as for some cases it makes no sense to show the old content, like for icons
this.resolveModule(nextProps);
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
Expand All @@ -225,7 +243,7 @@ function asyncComponent(config) {
key: 'render',
value: function render() {
var _state = this.state,
module = _state.module,
modules = _state.modules,
error = _state.error;

if (error) {
Expand All @@ -240,8 +258,9 @@ function asyncComponent(config) {
this.resolveModule();
}

var Component = es6Resolve(module);
return Component ? _react2.default.createElement(Component, this.props) : LoadingComponent ? _react2.default.createElement(LoadingComponent, this.props) : null;
var Component = es6Resolve(modules[getModuleId(this.props)]);
// eslint-disable-next-line no-nested-ternary
return Component ? _render ? _render(Component, this.props) : _react2.default.createElement(Component, this.props) : LoadingComponent ? _react2.default.createElement(LoadingComponent, this.props) : null;
}
}]);

Expand Down
Loading