UNPKG

@shopify/react-async

Version:

Tools for creating powerful, asynchronously-loaded React components.

191 lines (190 loc) 7.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var react_1 = tslib_1.__importStar(require("react")); var async_1 = require("@shopify/async"); var react_idle_1 = require("@shopify/react-idle"); var react_intersection_observer_1 = require("@shopify/react-intersection-observer"); var react_hydrate_1 = require("@shopify/react-hydrate"); var hooks_1 = require("./hooks"); var types_1 = require("./types"); function createAsyncComponent(_a) { var id = _a.id, load = _a.load, defer = _a.defer, deferHydration = _a.deferHydration, displayName = _a.displayName, _b = _a.renderLoading, renderLoading = _b === void 0 ? noopRender : _b, _c = _a.renderError, renderError = _c === void 0 ? defaultRenderError : _c, _d = _a.usePreload, useCustomPreload = _d === void 0 ? noopUse : _d, _e = _a.usePrefetch, useCustomPrefetch = _e === void 0 ? noopUse : _e, _f = _a.useKeepFresh, useCustomKeepFresh = _f === void 0 ? noopUse : _f; var resolver = async_1.createResolver({ id: id, load: load }); var componentName = displayName || displayNameFromId(resolver.id); var deferred = defer != null; var progressivelyHydrated = deferHydration != null; var scriptTiming = deferred || progressivelyHydrated ? types_1.AssetTiming.CurrentPage : types_1.AssetTiming.Immediate; var stylesTiming = deferred ? types_1.AssetTiming.CurrentPage : types_1.AssetTiming.Immediate; function Async(props) { var _a = hooks_1.useAsync(resolver, { scripts: scriptTiming, styles: stylesTiming, immediate: !deferred, }), Component = _a.resolved, load = _a.load, loading = _a.loading, error = _a.error; var startedHydrated = react_1.useRef(react_hydrate_1.useHydrationManager().hydrated).current; if (error) { return renderError(error); } var loadingMarkup = null; if (progressivelyHydrated && !startedHydrated) { loadingMarkup = (react_1.default.createElement(Loader, { defer: deferHydration, load: load, props: props })); } else if (loading) { loadingMarkup = react_1.default.createElement(Loader, { defer: defer, load: load, props: props }); } var contentMarkup = null; var rendered = Component ? react_1.default.createElement(Component, tslib_1.__assign({}, props)) : null; if (progressivelyHydrated && !startedHydrated) { contentMarkup = rendered ? (react_1.default.createElement(react_hydrate_1.Hydrator, { id: resolver.id }, rendered)) : (react_1.default.createElement(react_hydrate_1.Hydrator, { id: resolver.id })); } else if (loading) { contentMarkup = renderLoading(props); } else { contentMarkup = rendered; } return (react_1.default.createElement(react_1.default.Fragment, null, contentMarkup, loadingMarkup)); } Async.displayName = "Async(" + componentName + ")"; function usePreload(props) { var load = hooks_1.useAsync(resolver, { assets: types_1.AssetTiming.NextPage, }).load; var customPreload = useCustomPreload(props); return react_1.useCallback(function () { load(); if (customPreload) { customPreload(); } }, [load, customPreload]); } function usePrefetch(props) { var load = hooks_1.useAsync(resolver, { assets: types_1.AssetTiming.NextPage, }).load; var customPrefetch = useCustomPrefetch(props); return react_1.useCallback(function () { load(); if (customPrefetch) { customPrefetch(); } }, [load, customPrefetch]); } function useKeepFresh(props) { var load = hooks_1.useAsync(resolver, { assets: types_1.AssetTiming.NextPage, }).load; var customKeepFresh = useCustomKeepFresh(props); return react_1.useCallback(function () { load(); if (customKeepFresh) { customKeepFresh(); } }, [load, customKeepFresh]); } function Preload(options) { react_idle_1.useIdleCallback(usePreload(options)); return null; } Preload.displayName = "Async.Preload(" + displayName + ")"; function Prefetch(options) { var prefetch = usePrefetch(options); react_1.useEffect(function () { prefetch(); }, [prefetch]); return null; } Prefetch.displayName = "Async.Prefetch(" + displayName + ")"; function KeepFresh(options) { react_idle_1.useIdleCallback(useKeepFresh(options)); return null; } KeepFresh.displayName = "Async.KeepFresh(" + displayName + ")"; var 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, }); return FinalComponent; } exports.createAsyncComponent = createAsyncComponent; function noopUse() { return function () { }; } function noopRender() { return null; } var DEFAULT_DISPLAY_NAME = 'Component'; var FILENAME_REGEX = /([^/]*)\.\w+$/; function displayNameFromId(id) { if (!id) { return DEFAULT_DISPLAY_NAME; } var match = id.match(FILENAME_REGEX); return match ? match[1] : DEFAULT_DISPLAY_NAME; } function defaultRenderError(error) { if (error) { throw error; } return null; } function Loader(_a) { var defer = _a.defer, load = _a.load, props = _a.props; var handleIntersection = react_1.useCallback(function (_a) { var _b = _a.isIntersecting, isIntersecting = _b === void 0 ? true : _b; if (isIntersecting) { load(); } }, [load]); react_1.useEffect(function () { if (defer == null || defer === async_1.DeferTiming.Mount) { load(); } else if (typeof defer === 'function' && defer(props)) { load(); } }, [defer, load, props]); if (typeof defer === 'function') { return null; } switch (defer) { case async_1.DeferTiming.Idle: return react_1.default.createElement(react_idle_1.OnIdle, { perform: load }); case async_1.DeferTiming.InViewport: return (react_1.default.createElement(react_intersection_observer_1.IntersectionObserver, { threshold: 0, onIntersectionChange: handleIntersection })); default: return null; } }