@loadable/component
Version:
React code splitting made easy.
620 lines (508 loc) • 19.1 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('hoist-non-react-statics')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'hoist-non-react-statics'], factory) :
(global = global || self, factory(global.loadable = {}, global.React, global.hoistNonReactStatics));
}(this, (function (exports, React, hoistNonReactStatics) { 'use strict';
React = React && React.hasOwnProperty('default') ? React['default'] : React;
hoistNonReactStatics = hoistNonReactStatics && hoistNonReactStatics.hasOwnProperty('default') ? hoistNonReactStatics['default'] : hoistNonReactStatics;
/* eslint-disable import/prefer-default-export */
function invariant(condition, message) {
if (condition) return;
var error = new Error("loadable: " + message);
error.framesToPop = 1;
error.name = 'Invariant Violation';
throw error;
}
function warn(message) {
// eslint-disable-next-line no-console
console.warn("loadable: " + message);
}
var Context = /*#__PURE__*/
React.createContext();
var LOADABLE_REQUIRED_CHUNKS_KEY = '__LOADABLE_REQUIRED_CHUNKS__';
function getRequiredChunkKey(namespace) {
return "" + namespace + LOADABLE_REQUIRED_CHUNKS_KEY;
}
var sharedInternals = /*#__PURE__*/Object.freeze({
__proto__: null,
getRequiredChunkKey: getRequiredChunkKey,
invariant: invariant,
Context: Context
});
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
subClass.__proto__ = superClass;
}
var LOADABLE_SHARED = {
initialChunks: {}
};
var STATUS_PENDING = 'PENDING';
var STATUS_RESOLVED = 'RESOLVED';
var STATUS_REJECTED = 'REJECTED';
function resolveConstructor(ctor) {
if (typeof ctor === 'function') {
return {
requireAsync: ctor,
resolve: function resolve() {
return undefined;
},
chunkName: function chunkName() {
return undefined;
}
};
}
return ctor;
}
var withChunkExtractor = function withChunkExtractor(Component) {
var LoadableWithChunkExtractor = function LoadableWithChunkExtractor(props) {
return React.createElement(Context.Consumer, null, function (extractor) {
return React.createElement(Component, Object.assign({
__chunkExtractor: extractor
}, props));
});
};
if (Component.displayName) {
LoadableWithChunkExtractor.displayName = Component.displayName + "WithChunkExtractor";
}
return LoadableWithChunkExtractor;
};
var identity = function identity(v) {
return v;
};
function createLoadable(_ref) {
var _ref$defaultResolveCo = _ref.defaultResolveComponent,
defaultResolveComponent = _ref$defaultResolveCo === void 0 ? identity : _ref$defaultResolveCo,
_render = _ref.render,
onLoad = _ref.onLoad;
function loadable(loadableConstructor, options) {
if (options === void 0) {
options = {};
}
var ctor = resolveConstructor(loadableConstructor);
var cache = {};
/**
* Cachekey represents the component to be loaded
* if key changes - component has to be reloaded
* @param props
* @returns {null|Component}
*/
function _getCacheKey(props) {
if (options.cacheKey) {
return options.cacheKey(props);
}
if (ctor.resolve) {
return ctor.resolve(props);
}
return 'static';
}
/**
* Resolves loaded `module` to a specific `Component
* @param module
* @param props
* @param Loadable
* @returns Component
*/
function resolve(module, props, Loadable) {
var Component = options.resolveComponent ? options.resolveComponent(module, props) : defaultResolveComponent(module); // FIXME: suppressed due to https://github.com/gregberge/loadable-components/issues/990
// 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;
}
var cachedLoad = function cachedLoad(props) {
var cacheKey = _getCacheKey(props);
var promise = cache[cacheKey];
if (!promise || promise.status === STATUS_REJECTED) {
promise = ctor.requireAsync(props);
promise.status = STATUS_PENDING;
cache[cacheKey] = promise;
promise.then(function () {
promise.status = STATUS_RESOLVED;
}, function (error) {
console.error('loadable-components: failed to asynchronously load component', {
fileName: ctor.resolve(props),
chunkName: ctor.chunkName(props),
error: error ? error.message : error
});
promise.status = STATUS_REJECTED;
});
}
return promise;
};
var InnerLoadable =
/*#__PURE__*/
function (_React$Component) {
_inheritsLoose(InnerLoadable, _React$Component);
InnerLoadable.getDerivedStateFromProps = function getDerivedStateFromProps(props, state) {
var cacheKey = _getCacheKey(props);
return _extends({}, state, {
cacheKey: cacheKey,
// change of a key triggers loading state automatically
loading: state.loading || state.cacheKey !== cacheKey
});
};
function InnerLoadable(props) {
var _this;
_this = _React$Component.call(this, props) || this;
_this.state = {
result: null,
error: null,
loading: true,
cacheKey: _getCacheKey(props)
};
invariant(!props.__chunkExtractor || ctor.requireSync, 'SSR requires `@loadable/babel-plugin`, please install it'); // Server-side
if (props.__chunkExtractor) {
// This module has been marked with no SSR
if (options.ssr === false) {
return _assertThisInitialized(_this);
} // We run load function, we assume that it won't fail and that it
// triggers a synchronous loading of the module
ctor.requireAsync(props)["catch"](function () {
return null;
}); // So we can require now the module synchronously
_this.loadSync();
props.__chunkExtractor.addChunk(ctor.chunkName(props));
return _assertThisInitialized(_this);
} // Client-side with `isReady` method present (SSR probably)
// If module is already loaded, we use a synchronous loading
// Only perform this synchronous loading if the component has not
// been marked with no SSR, else we risk hydration mismatches
if (options.ssr !== false && ( // is ready - was loaded in this session
ctor.isReady && ctor.isReady(props) || // is ready - was loaded during SSR process
ctor.chunkName && LOADABLE_SHARED.initialChunks[ctor.chunkName(props)])) {
_this.loadSync();
}
return _this;
}
var _proto = InnerLoadable.prototype;
_proto.componentDidMount = function componentDidMount() {
this.mounted = true; // retrieve loading promise from a global cache
var cachedPromise = this.getCache(); // if promise exists, but rejected - clear cache
if (cachedPromise && cachedPromise.status === STATUS_REJECTED) {
this.setCache();
} // component might be resolved synchronously in the constructor
if (this.state.loading) {
this.loadAsync();
}
};
_proto.componentDidUpdate = function componentDidUpdate(prevProps, prevState) {
// Component has to be reloaded on cacheKey change
if (prevState.cacheKey !== this.state.cacheKey) {
this.loadAsync();
}
};
_proto.componentWillUnmount = function componentWillUnmount() {
this.mounted = false;
};
_proto.safeSetState = function safeSetState(nextState, callback) {
if (this.mounted) {
this.setState(nextState, callback);
}
}
/**
* returns a cache key for the current props
* @returns {Component|string}
*/
;
_proto.getCacheKey = function getCacheKey() {
return _getCacheKey(this.props);
}
/**
* access the persistent cache
*/
;
_proto.getCache = function getCache() {
return cache[this.getCacheKey()];
}
/**
* sets the cache value. If called without value sets it as undefined
*/
;
_proto.setCache = function setCache(value) {
if (value === void 0) {
value = undefined;
}
cache[this.getCacheKey()] = value;
};
_proto.triggerOnLoad = function triggerOnLoad() {
var _this2 = this;
if (onLoad) {
setTimeout(function () {
onLoad(_this2.state.result, _this2.props);
});
}
}
/**
* Synchronously loads component
* target module is expected to already exists in the module cache
* or be capable to resolve synchronously (webpack target=node)
*/
;
_proto.loadSync = function loadSync() {
// load sync is expecting component to be in the "loading" state already
// sounds weird, but loading=true is the initial state of InnerLoadable
if (!this.state.loading) return;
try {
var loadedModule = ctor.requireSync(this.props);
var result = resolve(loadedModule, this.props, Loadable);
this.state.result = result;
this.state.loading = false;
} catch (error) {
console.error('loadable-components: failed to synchronously load component, which expected to be available', {
fileName: ctor.resolve(this.props),
chunkName: ctor.chunkName(this.props),
error: error ? error.message : error
});
this.state.error = error;
}
}
/**
* Asynchronously loads a component.
*/
;
_proto.loadAsync = function loadAsync() {
var _this3 = this;
var promise = this.resolveAsync();
promise.then(function (loadedModule) {
var result = resolve(loadedModule, _this3.props, Loadable);
_this3.safeSetState({
result: result,
loading: false
}, function () {
return _this3.triggerOnLoad();
});
})["catch"](function (error) {
return _this3.safeSetState({
error: error,
loading: false
});
});
return promise;
}
/**
* Asynchronously resolves(not loads) a component.
* Note - this function does not change the state
*/
;
_proto.resolveAsync = function resolveAsync() {
var _this$props = this.props,
__chunkExtractor = _this$props.__chunkExtractor,
forwardedRef = _this$props.forwardedRef,
props = _objectWithoutPropertiesLoose(_this$props, ["__chunkExtractor", "forwardedRef"]);
return cachedLoad(props);
};
_proto.render = function render() {
var _this$props2 = this.props,
forwardedRef = _this$props2.forwardedRef,
propFallback = _this$props2.fallback,
__chunkExtractor = _this$props2.__chunkExtractor,
props = _objectWithoutPropertiesLoose(_this$props2, ["forwardedRef", "fallback", "__chunkExtractor"]);
var _this$state = this.state,
error = _this$state.error,
loading = _this$state.loading,
result = _this$state.result;
if (options.suspense) {
var cachedPromise = this.getCache() || this.loadAsync();
if (cachedPromise.status === STATUS_PENDING) {
throw this.loadAsync();
}
}
if (error) {
throw error;
}
var fallback = propFallback || options.fallback || null;
if (loading) {
return fallback;
}
return _render({
fallback: fallback,
result: result,
options: options,
props: _extends({}, props, {
ref: forwardedRef
})
});
};
return InnerLoadable;
}(React.Component);
var EnhancedInnerLoadable = withChunkExtractor(InnerLoadable);
var Loadable = React.forwardRef(function (props, ref) {
return React.createElement(EnhancedInnerLoadable, Object.assign({
forwardedRef: ref
}, props));
});
Loadable.displayName = 'Loadable'; // In future, preload could use `<link rel="preload">`
Loadable.preload = function (props) {
Loadable.load(props);
};
Loadable.load = function (props) {
return cachedLoad(props);
};
return Loadable;
}
function lazy(ctor, options) {
return loadable(ctor, _extends({}, options, {
suspense: true
}));
}
return {
loadable: loadable,
lazy: lazy
};
}
function defaultResolveComponent(loadedModule) {
// eslint-disable-next-line no-underscore-dangle
return loadedModule.__esModule ? loadedModule["default"] : loadedModule["default"] || loadedModule;
}
/* eslint-disable no-use-before-define, react/no-multi-comp */
var _createLoadable =
/*#__PURE__*/
createLoadable({
defaultResolveComponent: defaultResolveComponent,
render: function render(_ref) {
var Component = _ref.result,
props = _ref.props;
return React.createElement(Component, props);
}
}),
loadable = _createLoadable.loadable,
lazy = _createLoadable.lazy;
/* eslint-disable no-use-before-define, react/no-multi-comp */
var _createLoadable$1 =
/*#__PURE__*/
createLoadable({
onLoad: function onLoad(result, props) {
if (result && props.forwardedRef) {
if (typeof props.forwardedRef === 'function') {
props.forwardedRef(result);
} else {
props.forwardedRef.current = result;
}
}
},
render: function render(_ref) {
var result = _ref.result,
props = _ref.props;
if (props.children) {
return props.children(result);
}
return null;
}
}),
loadable$1 = _createLoadable$1.loadable,
lazy$1 = _createLoadable$1.lazy;
/* eslint-disable no-underscore-dangle, camelcase */
var BROWSER = typeof window !== 'undefined';
function loadableReady(done, _temp) {
if (done === void 0) {
done = function done() {};
}
var _ref = _temp === void 0 ? {} : _temp,
_ref$namespace = _ref.namespace,
namespace = _ref$namespace === void 0 ? '' : _ref$namespace,
_ref$chunkLoadingGlob = _ref.chunkLoadingGlobal,
chunkLoadingGlobal = _ref$chunkLoadingGlob === void 0 ? '__LOADABLE_LOADED_CHUNKS__' : _ref$chunkLoadingGlob;
if (!BROWSER) {
warn('`loadableReady()` must be called in browser only');
done();
return Promise.resolve();
}
var requiredChunks = null;
if (BROWSER) {
var id = getRequiredChunkKey(namespace);
var dataElement = document.getElementById(id);
if (dataElement) {
requiredChunks = JSON.parse(dataElement.textContent);
var extElement = document.getElementById(id + "_ext");
if (extElement) {
var _JSON$parse = JSON.parse(extElement.textContent),
namedChunks = _JSON$parse.namedChunks;
namedChunks.forEach(function (chunkName) {
LOADABLE_SHARED.initialChunks[chunkName] = true;
});
} else {
// version mismatch
throw new Error('loadable-component: @loadable/server does not match @loadable/component');
}
}
}
if (!requiredChunks) {
warn('`loadableReady()` requires state, please use `getScriptTags` or `getScriptElements` server-side');
done();
return Promise.resolve();
}
var resolved = false;
return new Promise(function (resolve) {
window[chunkLoadingGlobal] = window[chunkLoadingGlobal] || [];
var loadedChunks = window[chunkLoadingGlobal];
var originalPush = loadedChunks.push.bind(loadedChunks);
function checkReadyState() {
if (requiredChunks.every(function (chunk) {
return loadedChunks.some(function (_ref2) {
var chunks = _ref2[0];
return chunks.indexOf(chunk) > -1;
});
})) {
if (!resolved) {
resolved = true;
resolve();
}
}
}
loadedChunks.push = function () {
originalPush.apply(void 0, arguments);
checkReadyState();
};
checkReadyState();
}).then(done);
}
/* eslint-disable no-underscore-dangle */
var loadable$2 = loadable;
loadable$2.lib = loadable$1;
var lazy$2 = lazy;
lazy$2.lib = lazy$1;
var __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = sharedInternals;
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
exports.default = loadable$2;
exports.lazy = lazy$2;
exports.loadableReady = loadableReady;
Object.defineProperty(exports, '__esModule', { value: true });
})));