@shopify/react-async
Version:
Tools for creating powerful, asynchronously-loaded React components
239 lines (232 loc) • 6.79 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var async = require('@shopify/async');
var reactIntersectionObserver = require('@shopify/react-intersection-observer');
var reactIdle = require('@shopify/react-idle');
var reactHydrate = require('@shopify/react-hydrate');
var hooks = require('./hooks.js');
var types = require('./types.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
function createAsyncComponent({
id,
load,
defer,
deferHydration,
displayName,
renderLoading = noopRender,
renderError = defaultRenderError,
usePreload: useCustomPreload = noopUse,
usePrefetch: useCustomPrefetch = noopUse,
useKeepFresh: useCustomKeepFresh = noopUse
}) {
const resolver = async.createResolver({
id,
load
});
const componentName = displayName || displayNameFromId(resolver.id);
const deferred = defer != null;
const progressivelyHydrated = deferHydration != null;
const scriptTiming = deferred || progressivelyHydrated ? types.AssetTiming.CurrentPage : types.AssetTiming.Immediate;
const stylesTiming = deferred ? types.AssetTiming.CurrentPage : types.AssetTiming.Immediate;
function Async(props) {
const {
resolved: Component,
load,
loading,
error
} = hooks.useAsync(resolver, {
scripts: scriptTiming,
styles: stylesTiming,
immediate: !deferred
});
const {
current: startedHydrated
} = React.useRef(reactHydrate.useHydrationManager().hydrated);
if (error) {
return renderError(error);
}
let loadingMarkup = null;
if (progressivelyHydrated && !startedHydrated) {
loadingMarkup = /*#__PURE__*/React__default["default"].createElement(Loader, {
defer: deferHydration,
load: load,
props: props
});
} else if (loading) {
loadingMarkup = /*#__PURE__*/React__default["default"].createElement(Loader, {
defer: defer,
load: load,
props: props
});
}
let contentMarkup = null;
const rendered = Component ? /*#__PURE__*/React__default["default"].createElement(Component, props) : null;
if (progressivelyHydrated && !startedHydrated) {
contentMarkup = rendered ? /*#__PURE__*/React__default["default"].createElement(reactHydrate.Hydrator, {
id: resolver.id
}, rendered) : /*#__PURE__*/React__default["default"].createElement(reactHydrate.Hydrator, {
id: resolver.id
});
} else if (loading) {
contentMarkup = renderLoading(props);
} else {
contentMarkup = rendered;
}
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, contentMarkup, loadingMarkup);
}
Async.displayName = `Async(${componentName})`;
function usePreload(props) {
const {
load
} = hooks.useAsync(resolver, {
assets: types.AssetTiming.NextPage
});
const customPreload = useCustomPreload(props);
return React.useCallback(() => {
load();
if (customPreload) {
customPreload();
}
}, [load, customPreload]);
}
function usePrefetch(props) {
const {
load
} = hooks.useAsync(resolver, {
assets: types.AssetTiming.NextPage
});
const customPrefetch = useCustomPrefetch(props);
return React.useCallback(() => {
load();
if (customPrefetch) {
customPrefetch();
}
}, [load, customPrefetch]);
}
function useKeepFresh(props) {
const {
load
} = hooks.useAsync(resolver, {
assets: types.AssetTiming.NextPage
});
const customKeepFresh = useCustomKeepFresh(props);
return React.useCallback(() => {
load();
if (customKeepFresh) {
customKeepFresh();
}
}, [load, customKeepFresh]);
}
function Preload(options) {
reactIdle.useIdleCallback(usePreload(options));
return null;
}
Preload.displayName = `Async.Preload(${displayName})`;
function Prefetch(options) {
const prefetch = usePrefetch(options);
React.useEffect(() => {
prefetch();
}, [prefetch]);
return null;
}
Prefetch.displayName = `Async.Prefetch(${displayName})`;
function KeepFresh(options) {
reactIdle.useIdleCallback(useKeepFresh(options));
return null;
}
KeepFresh.displayName = `Async.KeepFresh(${displayName})`;
const FinalComponent = Async;
Reflect.defineProperty(FinalComponent, 'resolver', {
value: resolver,
writable: false
});
Reflect.defineProperty(FinalComponent, 'Preload', {
value: Preload,
writable: false
});
Reflect.defineProperty(FinalComponent, 'Prefetch', {
value: Prefetch,
writable: false
});
Reflect.defineProperty(FinalComponent, 'KeepFresh', {
value: KeepFresh,
writable: false
});
Reflect.defineProperty(FinalComponent, 'usePreload', {
value: usePreload,
writable: false
});
Reflect.defineProperty(FinalComponent, 'usePrefetch', {
value: usePrefetch,
writable: false
});
Reflect.defineProperty(FinalComponent, 'useKeepFresh', {
value: useKeepFresh,
writable: false
});
Reflect.defineProperty(FinalComponent, 'renderLoading', {
value: renderLoading,
writable: false
});
return FinalComponent;
}
function noopUse() {
return () => {};
}
function noopRender() {
return null;
}
const DEFAULT_DISPLAY_NAME = 'Component';
const FILENAME_REGEX = /([^/]*)\.\w+$/;
function displayNameFromId(id) {
if (!id) {
return DEFAULT_DISPLAY_NAME;
}
const match = FILENAME_REGEX.exec(id);
return match ? match[1] : DEFAULT_DISPLAY_NAME;
}
function defaultRenderError(error) {
if (error) {
throw error;
}
return null;
}
function Loader({
defer,
load,
props
}) {
const handleIntersection = React.useCallback(({
isIntersecting = true
}) => {
if (isIntersecting) {
load();
}
}, [load]);
React.useEffect(() => {
if (defer == null || defer === async.DeferTiming.Mount) {
load();
} else if (typeof defer === 'function' && defer(props)) {
load();
}
}, [defer, load, props]);
if (typeof defer === 'function') {
return null;
}
switch (defer) {
case async.DeferTiming.Idle:
return /*#__PURE__*/React__default["default"].createElement(reactIdle.OnIdle, {
perform: load
});
case async.DeferTiming.InViewport:
return /*#__PURE__*/React__default["default"].createElement(reactIntersectionObserver.IntersectionObserver, {
threshold: 0,
onIntersectionChange: handleIntersection
});
default:
return null;
}
}
exports.createAsyncComponent = createAsyncComponent;