diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..259cf3d --- /dev/null +++ b/.editorconfig @@ -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 diff --git a/README.md b/README.md index d97d0c8..3c81d18 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) => , + name: 'AsyncSvgIcon', +}) +``` + ##### Named chunks ```jsx diff --git a/commonjs/asyncComponent.js b/commonjs/asyncComponent.js index 375eced..8ca3afc 100644 --- a/commonjs/asyncComponent.js +++ b/commonjs/asyncComponent.js @@ -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, @@ -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) { @@ -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. @@ -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); @@ -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); } @@ -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; @@ -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() { @@ -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) { @@ -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; } }]); diff --git a/package-lock.json b/package-lock.json index 14664ea..0885f22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1703,7 +1703,6 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", - "fsevents": "1.1.2", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -3246,905 +3245,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", - "integrity": "sha512-Sn44E5wQW4bTHXvQmvSHwqbuiXtduD6Rrjm2ZtUEGbyrig+nUH3t/QD4M4/ZXViY556TBpRgZkHLDx3JxPwxiw==", - "dev": true, - "optional": true, - "requires": { - "nan": "2.7.0", - "node-pre-gyp": "0.6.36" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "1.0.0", - "readable-stream": "2.2.9" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "0.14.5" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "requires": { - "inherits": "2.0.3" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.16.3" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "0.4.2", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "requires": { - "delayed-stream": "1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.15" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "requires": { - "graceful-fs": "4.1.11", - "inherits": "2.0.3", - "mkdirp": "0.5.1", - "rimraf": "2.6.1" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "1.0.11", - "inherits": "2.0.3", - "minimatch": "3.0.4" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "1.1.1", - "console-control-strings": "1.1.0", - "has-unicode": "2.0.1", - "object-assign": "4.1.1", - "signal-exit": "3.0.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wide-align": "1.1.2" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "4.11.8", - "har-schema": "1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.0", - "sshpk": "1.13.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "0.1.1" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "requires": { - "mime-db": "1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.36", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "mkdirp": "0.5.1", - "nopt": "4.0.1", - "npmlog": "4.1.0", - "rc": "1.2.1", - "request": "2.81.0", - "rimraf": "2.6.1", - "semver": "5.3.0", - "tar": "2.2.1", - "tar-pack": "3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1.1.0", - "osenv": "0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "1.1.4", - "console-control-strings": "1.1.0", - "gauge": "2.7.4", - "set-blocking": "2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1.0.2" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.4", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.1", - "util-deprecate": "1.0.2" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "4.2.1", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.15", - "oauth-sign": "0.8.2", - "performance-now": "0.2.0", - "qs": "6.4.0", - "safe-buffer": "5.0.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.2", - "tunnel-agent": "0.6.0", - "uuid": "3.0.1" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "requires": { - "glob": "7.1.2" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.16.3" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jodid25519": "1.0.2", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "requires": { - "block-stream": "0.0.9", - "fstream": "1.0.11", - "inherits": "2.0.3" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "2.6.8", - "fstream": "1.0.11", - "fstream-ignore": "1.0.5", - "once": "1.4.0", - "readable-stream": "2.2.9", - "rimraf": "2.6.1", - "tar": "2.2.1", - "uid-number": "0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - } - } - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -6433,13 +5533,6 @@ "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", "dev": true }, - "nan": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz", - "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=", - "dev": true, - "optional": true - }, "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", @@ -7699,7 +6792,6 @@ "anymatch": "1.3.2", "exec-sh": "0.2.1", "fb-watchman": "2.0.0", - "fsevents": "1.1.2", "minimatch": "3.0.4", "minimist": "1.2.0", "walker": "1.0.7", diff --git a/src/__tests__/__snapshots__/asyncComponent.test.js.snap b/src/__tests__/__snapshots__/asyncComponent.test.js.snap index e3b0e7b..f249962 100644 --- a/src/__tests__/__snapshots__/asyncComponent.test.js.snap +++ b/src/__tests__/__snapshots__/asyncComponent.test.js.snap @@ -2,4 +2,34 @@ exports[`asyncComponent in a browser environment when an error occurs resolving a component should render the ErrorComponent 1`] = `"
failed to resolve
"`; +exports[`asyncComponent in a browser environment when resolving dynamic imports should resolve module again on module id change 1`] = ` + + +
+ barComponent:  + test +
+
+
+`; + +exports[`asyncComponent in a browser environment when resolving dynamic imports should resolve module again on module id change 2`] = ` + + +
+ fooComponent:  + foo +
+
+
+`; + exports[`asyncComponent in a server environment when an error occurs resolving a component should not render the ErrorComponent 1`] = `null`; diff --git a/src/__tests__/__snapshots__/integration.test.js.snap b/src/__tests__/__snapshots__/integration.test.js.snap index e054e5d..c4c13ef 100644 --- a/src/__tests__/__snapshots__/integration.test.js.snap +++ b/src/__tests__/__snapshots__/integration.test.js.snap @@ -1,5 +1,115 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`integration tests browser rendering can resolve dynamic components - bar 1`] = ` + + + +`; + +exports[`integration tests browser rendering can resolve dynamic components - bar 2`] = ` + + + + +
+ barComponent:  + test +
+
+
+
+
+`; + +exports[`integration tests browser rendering can resolve dynamic components - foo 1`] = ` + + + +`; + +exports[`integration tests browser rendering can resolve dynamic components - foo 2`] = ` + + + + +
+ fooComponent:  + foo +
+
+
+
+
+`; + exports[`integration tests browser rendering renders the ErrorComponent 1`] = ` `; +exports[`integration tests browser rendering renders the LoadingComponent 2`] = ` + + + +
+ foo +
+
+
+
+`; + exports[`integration tests render server and client 1`] = `"
In Render.
DeferredAsyncBob Loading
In Boundary but outside an AsyncComponent, server render me!
"`; exports[`integration tests render server and client 2`] = ` diff --git a/src/__tests__/asyncComponent.test.js b/src/__tests__/asyncComponent.test.js index 3c0cc5d..4a1756d 100644 --- a/src/__tests__/asyncComponent.test.js +++ b/src/__tests__/asyncComponent.test.js @@ -24,7 +24,7 @@ describe('asyncComponent', () => { describe('in a browser environment', () => { describe('when an error occurs resolving a component', () => { - it.only('should render the ErrorComponent', async () => { + it('should render the ErrorComponent', async () => { const Bob = asyncComponent({ resolve: () => Promise.reject(new Error('failed to resolve')), ErrorComponent: ({ error }) =>
{error.message}
, @@ -35,6 +35,41 @@ describe('asyncComponent', () => { expect(renderWrapper.html()).toMatchSnapshot() }) }) + describe('when resolving dynamic imports', () => { + it('should resolve module again on module id change', async () => { + const Dynamic = asyncComponent({ + getModuleId: props => props.name, + resolve: props => { + if (props.name === 'foo') { + return ({ name }) =>
fooComponent: {name}
+ } else { + return ({ name }) =>
barComponent: {name}
+ } + }, + render: (ResolvedComponent, props) => ( + + ), + env: 'browser', + }) + + const renderWrapper = mount() + await new Promise(resolve => setTimeout(resolve, errorResolveDelay)) + renderWrapper.update() + expect(renderWrapper).toMatchSnapshot() + await new Promise(resolve => { + renderWrapper.setProps( + { + name: 'foo', + }, + () => { + setTimeout(resolve, errorResolveDelay) + }, + ) + }) + renderWrapper.update() + expect(renderWrapper).toMatchSnapshot() + }) + }) }) describe('in a server environment', () => { diff --git a/src/__tests__/integration.test.js b/src/__tests__/integration.test.js index 2f9ccc2..5f66150 100644 --- a/src/__tests__/integration.test.js +++ b/src/__tests__/integration.test.js @@ -199,6 +199,58 @@ describe('integration tests', () => { .then(() => expect(resolveCount).toEqual(1)) }) + const Dynamic = asyncComponent({ + getModuleId: props => props.name, + resolve: props => { + if (props.name === 'foo') { + return ({ name }) =>
fooComponent: {name}
+ } else { + return ({ name }) =>
barComponent: {name}
+ } + }, + render: (ResolvedComponent, props) => ( + + + + ), + }) + + it('can resolve dynamic components - bar', () => { + const app = () => ( + + + + ) + + return asyncBootstrapper(app()) + .then(() => mount(app())) + .then(render => { + // async component with props + expect(render).toMatchSnapshot() + // resolve to bar + render.update() + expect(render).toMatchSnapshot() + }) + }) + + it.only('can resolve dynamic components - foo', () => { + const app = () => ( + + + + ) + + return asyncBootstrapper(app()) + .then(() => mount(app())) + .then(render => { + // async component with props + expect(render).toMatchSnapshot() + // resolve to foo + render.update() + expect(render).toMatchSnapshot() + }) + }) + it('renders the LoadingComponent', () => { const Foo = asyncComponent({ resolve: () => @@ -216,7 +268,15 @@ describe('integration tests', () => { return asyncBootstrapper(app) .then(() => mount(app)) - .then(render => expect(render).toMatchSnapshot()) + .then(render => { + expect(render).toMatchSnapshot() + return new Promise(resolve => setTimeout(() => resolve(render), 150)) + }) + .then(render => { + render.update() + // rendered after loading + expect(render).toMatchSnapshot() + }) }) }) diff --git a/src/asyncComponent.js b/src/asyncComponent.js index 75e0bd2..ee216c9 100644 --- a/src/asyncComponent.js +++ b/src/asyncComponent.js @@ -2,6 +2,7 @@ import React from 'react' import PropTypes from 'prop-types' const validSSRModes = ['resolve', 'defer', 'boundary'] +const staticModuleId = Symbol() function asyncComponent(config) { const { @@ -11,6 +12,8 @@ function asyncComponent(config) { serverMode = 'resolve', LoadingComponent, ErrorComponent, + render, + getModuleId = () => staticModuleId, } = config if (validSSRModes.indexOf(serverMode) === -1) { @@ -29,7 +32,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. @@ -46,12 +49,12 @@ function asyncComponent(config) { ? x.default : x - const getResolver = () => { - if (sharedState.resolver == null) { + const getResolver = props => { + if (sharedState.resolver == null || getModuleId(props) !== staticModuleId) { try { // Wrap whatever the user returns in Promise.resolve to ensure a Promise // is always returned. - const resolver = resolve() + const resolver = resolve(props) sharedState.resolver = Promise.resolve(resolver) } catch (err) { sharedState.resolver = Promise.reject(err) @@ -106,9 +109,7 @@ function asyncComponent(config) { } componentWillMount() { - this.setState({ - module: sharedState.module, - }) + this.setState({ modules: sharedState.modules }) if (sharedState.error) { this.registerErrorState(sharedState.error) } @@ -122,17 +123,18 @@ function asyncComponent(config) { shouldResolve() { return ( - sharedState.module == null && + sharedState.modules[getModuleId(this.props)] !== null && sharedState.error == null && !this.resolving && typeof window !== 'undefined' ) } - resolveModule() { + resolveModule(props = this.props) { this.resolving = true - return getResolver() + let moduleId = getModuleId(props) + return getResolver(props) .then(module => { if (this.unmounted) { return undefined @@ -140,11 +142,9 @@ function asyncComponent(config) { if (this.context.asyncComponents != null) { this.context.asyncComponents.resolved(sharedState.id) } - sharedState.module = module + sharedState.modules[moduleId] = module if (env === 'browser') { - this.setState({ - module, - }) + this.setState({ modules: sharedState.modules }) } this.resolving = false return module @@ -168,6 +168,14 @@ function asyncComponent(config) { }) } + componentWillReceiveProps(nextProps) { + let lastModuleId = getModuleId(this.props) + let nextModuleId = getModuleId(nextProps) + if (lastModuleId !== nextModuleId && !sharedState.modules[nextModuleId]) { + this.resolveModule(nextProps) + } + } + componentWillUnmount() { this.unmounted = true } @@ -185,7 +193,7 @@ function asyncComponent(config) { } render() { - const { module, error } = this.state + const { modules, error } = this.state if (error) { return ErrorComponent ? ( @@ -200,9 +208,14 @@ function asyncComponent(config) { this.resolveModule() } - const Component = es6Resolve(module) + const Component = es6Resolve(modules[getModuleId(this.props)]) + // eslint-disable-next-line no-nested-ternary return Component ? ( - + render ? ( + render(Component, this.props) + ) : ( + + ) ) : LoadingComponent ? ( ) : null diff --git a/umd/react-async-component.js b/umd/react-async-component.js index 761fba7..ae041eb 100644 --- a/umd/react-async-component.js +++ b/umd/react-async-component.js @@ -288,6 +288,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, @@ -297,7 +298,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) { @@ -313,7 +319,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. @@ -325,12 +331,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); @@ -403,9 +412,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); } @@ -420,27 +427,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; @@ -462,6 +470,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() { @@ -486,7 +504,7 @@ function asyncComponent(config) { key: 'render', value: function render() { var _state = this.state, - module = _state.module, + modules = _state.modules, error = _state.error; if (error) { @@ -501,8 +519,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; } }]); diff --git a/umd/react-async-component.min.js b/umd/react-async-component.min.js index 2382552..44c0332 100644 --- a/umd/react-async-component.min.js +++ b/umd/react-async-component.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react"),require("prop-types")):"function"==typeof define&&define.amd?define(["react","prop-types"],t):"object"==typeof exports?exports.ReactAsyncComponent=t(require("react"),require("prop-types")):e.ReactAsyncComponent=t(e.React,e.PropTypes)}(this,function(e,t){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=3)}([function(t,n){t.exports=e},function(e,n){e.exports=t},function(e,t,n){"use strict";function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(){var e=0,t={};return{getNextId:function(){return e+=1},resolved:function(e){t[e]=!0},getState:function(){return{resolved:Object.keys(t).reduce(function(e,t){return Object.assign(e,o({},t,!0))},{})}}}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.asyncComponent=t.createAsyncContext=t.AsyncComponentProvider=void 0;var r=n(4),u=o(r),s=n(2),i=o(s),l=n(5),a=o(l);t.AsyncComponentProvider=u.default,t.createAsyncContext=i.default,t.asyncComponent=a.default},function(e,t,n){"use strict";function o(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function s(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n-1?e.env:"undefined"==typeof window?"node":"browser",C={id:null,module:null,error:null,resolver:null},x=function(e){return i&&null!=e&&("function"==typeof e||"object"===(void 0===e?"undefined":c(e)))&&e.default?e.default:e},g=function(){if(null==C.resolver)try{var e=n();C.resolver=Promise.resolve(e)}catch(e){C.resolver=Promise.reject(e)}return C.resolver},w=function(e){function t(e,n){r(this,t);var o=u(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n));return null==o.context.asyncComponents||C.id||(C.id=o.context.asyncComponents.getNextId()),o}return s(t,e),a(t,[{key:"asyncBootstrap",value:function(){var e=this,t=this.context,n=t.asyncComponents,o=t.asyncComponentsAncestor,r=n.shouldRehydrate,u=function(){return e.resolveModule().then(function(e){return void 0!==e})};if("browser"===b)return!!r(C.id)&&u();var s=null!=o&&o.isBoundary;return"defer"!==p&&!s&&u()}},{key:"getChildContext",value:function(){return null==this.context.asyncComponents?{asyncComponentsAncestor:null}:{asyncComponentsAncestor:{isBoundary:"boundary"===p}}}},{key:"componentWillMount",value:function(){this.setState({module:C.module}),C.error&&this.registerErrorState(C.error)}},{key:"componentDidMount",value:function(){this.shouldResolve()&&this.resolveModule()}},{key:"shouldResolve",value:function(){return null==C.module&&null==C.error&&!this.resolving&&"undefined"!=typeof window}},{key:"resolveModule",value:function(){var e=this;return this.resolving=!0,g().then(function(t){if(!e.unmounted)return null!=e.context.asyncComponents&&e.context.asyncComponents.resolved(C.id),C.module=t,"browser"===b&&e.setState({module:t}),e.resolving=!1,t}).catch(function(t){e.unmounted||(("node"===b||"browser"===b&&!m)&&(console.warn("Failed to resolve asyncComponent"),console.warn(t)),C.error=t,e.registerErrorState(t),e.resolving=!1)})}},{key:"componentWillUnmount",value:function(){this.unmounted=!0}},{key:"registerErrorState",value:function(e){var t=this;"browser"===b&&setTimeout(function(){t.unmounted||t.setState({error:e})},16)}},{key:"render",value:function(){var e=this.state,t=e.module,n=e.error;if(n)return m?d.default.createElement(m,l({},this.props,{error:n})):null;this.shouldResolve()&&this.resolveModule();var o=x(t);return o?d.default.createElement(o,this.props):h?d.default.createElement(h,this.props):null}}]),t}(d.default.Component);return w.displayName=t||"AsyncComponent",w.contextTypes={asyncComponentsAncestor:y.default.shape({isBoundary:y.default.bool}),asyncComponents:y.default.shape({getNextId:y.default.func.isRequired,resolved:y.default.func.isRequired,shouldRehydrate:y.default.func.isRequired})},w.childContextTypes={asyncComponentsAncestor:y.default.shape({isBoundary:y.default.bool})},w}Object.defineProperty(t,"__esModule",{value:!0});var l=Object.assign||function(e){for(var t=1;t-1?e.env:"undefined"==typeof window?"node":"browser",_={id:null,modules:{},error:null,resolver:null},O=function(e){return i&&null!=e&&("function"==typeof e||"object"===(void 0===e?"undefined":c(e)))&&e.default?e.default:e},j=function(e){if(null==_.resolver||g(e)!==h)try{var t=n(e);_.resolver=Promise.resolve(t)}catch(e){_.resolver=Promise.reject(e)}return _.resolver},R=function(e){function t(e,n){r(this,t);var o=u(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e,n));return null==o.context.asyncComponents||_.id||(_.id=o.context.asyncComponents.getNextId()),o}return s(t,e),a(t,[{key:"asyncBootstrap",value:function(){var e=this,t=this.context,n=t.asyncComponents,o=t.asyncComponentsAncestor,r=n.shouldRehydrate,u=function(){return e.resolveModule().then(function(e){return void 0!==e})};if("browser"===w)return!!r(_.id)&&u();var s=null!=o&&o.isBoundary;return"defer"!==p&&!s&&u()}},{key:"getChildContext",value:function(){return null==this.context.asyncComponents?{asyncComponentsAncestor:null}:{asyncComponentsAncestor:{isBoundary:"boundary"===p}}}},{key:"componentWillMount",value:function(){this.setState({modules:_.modules}),_.error&&this.registerErrorState(_.error)}},{key:"componentDidMount",value:function(){this.shouldResolve()&&this.resolveModule()}},{key:"shouldResolve",value:function(){return null!==_.modules[g(this.props)]&&null==_.error&&!this.resolving&&"undefined"!=typeof window}},{key:"resolveModule",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.props;this.resolving=!0;var n=g(t);return j(t).then(function(t){if(!e.unmounted)return null!=e.context.asyncComponents&&e.context.asyncComponents.resolved(_.id),_.modules[n]=t,"browser"===w&&e.setState({modules:_.modules}),e.resolving=!1,t}).catch(function(t){e.unmounted||(("node"===w||"browser"===w&&!b)&&(console.warn("Failed to resolve asyncComponent"),console.warn(t)),_.error=t,e.registerErrorState(t),e.resolving=!1)})}},{key:"componentWillReceiveProps",value:function(e){var t=g(this.props),n=g(e);t===n||_.modules[n]||this.resolveModule(e)}},{key:"componentWillUnmount",value:function(){this.unmounted=!0}},{key:"registerErrorState",value:function(e){var t=this;"browser"===w&&setTimeout(function(){t.unmounted||t.setState({error:e})},16)}},{key:"render",value:function(){var e=this.state,t=e.modules,n=e.error;if(n)return b?d.default.createElement(b,l({},this.props,{error:n})):null;this.shouldResolve()&&this.resolveModule();var o=O(t[g(this.props)]);return o?C?C(o,this.props):d.default.createElement(o,this.props):m?d.default.createElement(m,this.props):null}}]),t}(d.default.Component);return R.displayName=t||"AsyncComponent",R.contextTypes={asyncComponentsAncestor:y.default.shape({isBoundary:y.default.bool}),asyncComponents:y.default.shape({getNextId:y.default.func.isRequired,resolved:y.default.func.isRequired,shouldRehydrate:y.default.func.isRequired})},R.childContextTypes={asyncComponentsAncestor:y.default.shape({isBoundary:y.default.bool})},R}Object.defineProperty(t,"__esModule",{value:!0});var l=Object.assign||function(e){for(var t=1;t