Skip to content
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

Exposed Load method to invoke synchronous loading of component #42

Open
wants to merge 6 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
6 changes: 6 additions & 0 deletions capacities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"use strict";

exports.__esModule = true;
exports.IntersectionObserver = void 0;
var IntersectionObserver = typeof window !== 'undefined' && window.IntersectionObserver;
exports.IntersectionObserver = IntersectionObserver;
132 changes: 132 additions & 0 deletions createLoadableVisibilityComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"use strict";

exports.__esModule = true;
exports["default"] = void 0;

var _react = _interopRequireWildcard(require("react"));

var _capacities = require("./capacities");

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

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); }

var intersectionObserver;
var trackedElements = new Map();

if (_capacities.IntersectionObserver) {
intersectionObserver = new window.IntersectionObserver(function (entries, observer) {
entries.forEach(function (entry) {
var visibilityHandler = trackedElements.get(entry.target);

if (visibilityHandler && (entry.isIntersecting || entry.intersectionRatio > 0)) {
visibilityHandler();
}
});
});
}

function createLoadableVisibilityComponent(args, _ref) {
var Loadable = _ref.Loadable,
preloadFunc = _ref.preloadFunc,
loadFunc = _ref.loadFunc,
LoadingComponent = _ref.LoadingComponent;
var preloaded = false,
loaded = false;
var visibilityHandlers = [];
var LoadableComponent = Loadable.apply(void 0, args);

function LoadableVisibilityComponent(props) {
var visibilityElementRef = (0, _react.useRef)();

var _useState = (0, _react.useState)(preloaded),
isVisible = _useState[0],
setVisible = _useState[1];

function visibilityHandler() {
if (visibilityElementRef.current) {
intersectionObserver.unobserve(visibilityElementRef.current);
trackedElements["delete"](visibilityElementRef.current);
}

setVisible(true);
}

(0, _react.useEffect)(function () {
var element = visibilityElementRef.current;

if (!isVisible && element) {
visibilityHandlers.push(visibilityHandler);
trackedElements.set(element, visibilityHandler);
intersectionObserver.observe(element);
return function () {
var handlerIndex = visibilityHandlers.indexOf(visibilityHandler);

if (handlerIndex >= 0) {
visibilityHandlers.splice(handlerIndex, 1);
}

intersectionObserver.unobserve(element);
trackedElements["delete"](element);
};
}
}, [isVisible, visibilityElementRef.current]);

if (isVisible) {
return _react["default"].createElement(LoadableComponent, props);
}

if (LoadingComponent || props.fallback) {
return _react["default"].createElement("div", _extends({
style: {
display: "inline-block",
minHeight: "1px",
minWidth: "1px"
}
}, props, {
ref: visibilityElementRef
}), LoadingComponent ? _react["default"].createElement(LoadingComponent, _extends({
isLoading: true
}, props)) : props.fallback);
}

return _react["default"].createElement("div", _extends({
style: {
display: "inline-block",
minHeight: "1px",
minWidth: "1px"
}
}, props, {
ref: visibilityElementRef
}));
}

LoadableVisibilityComponent[preloadFunc] = function () {
if (!preloaded) {
preloaded = true;
visibilityHandlers.forEach(function (handler) {
return handler();
});
}

return LoadableComponent[preloadFunc]();
};

LoadableVisibilityComponent[loadFunc] = function () {
if (!loaded) {
loaded = true;
visibilityHandlers.forEach(function (handler) {
return handler();
});
}

return LoadableComponent[loadFunc]();
};

return LoadableVisibilityComponent;
}

var _default = createLoadableVisibilityComponent;
exports["default"] = _default;
3 changes: 3 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"use strict";

module.exports = require("./loadable-components");
36 changes: 36 additions & 0 deletions loadable-components.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"use strict";

var _react = _interopRequireWildcard(require("react"));

var _component = _interopRequireDefault(require("@loadable/component"));

var _createLoadableVisibilityComponent = _interopRequireDefault(require("./createLoadableVisibilityComponent"));

var _capacities = require("./capacities");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function loadableVisiblity(load, opts) {
if (opts === void 0) {
opts = {};
}

if (_capacities.IntersectionObserver) {
return (0, _createLoadableVisibilityComponent["default"])([load, opts], {
Loadable: _component["default"],
preloadFunc: "preload",
loadFunc: "load",
LoadingComponent: opts.fallback ? function () {
return opts.fallback;
} : null
});
} else {
return (0, _component["default"])(load, opts);
}
}

module.exports = loadableVisiblity;
46 changes: 46 additions & 0 deletions react-loadable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use strict";

var _react = _interopRequireWildcard(require("react"));

var _reactLoadable = _interopRequireDefault(require("react-loadable"));

var _createLoadableVisibilityComponent = _interopRequireDefault(require("./createLoadableVisibilityComponent"));

var _capacities = require("./capacities");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function _getRequireWildcardCache() { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

function LoadableVisibility(opts) {
if (_capacities.IntersectionObserver) {
return (0, _createLoadableVisibilityComponent["default"])([opts], {
Loadable: _reactLoadable["default"],
preloadFunc: 'preload',

/* Preload helps in synchronously loading a component and returns a promise */
loadFunc: 'preload',
LoadingComponent: opts.loading
});
} else {
return (0, _reactLoadable["default"])(opts);
}
}

function LoadableVisibilityMap(opts) {
if (_capacities.IntersectionObserver) {
return (0, _createLoadableVisibilityComponent["default"])([opts], {
Loadable: _reactLoadable["default"].Map,
preloadFunc: 'preload',
loadFunc: 'preload',
LoadingComponent: opts.loading
});
} else {
return _reactLoadable["default"].Map(opts);
}
}

LoadableVisibility.Map = LoadableVisibilityMap;
module.exports = LoadableVisibility;
1 change: 1 addition & 0 deletions src/__mocks__/@loadable/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const loadableObject = props => {
};

loadableObject.preload = jest.fn();
loadableObject.load = jest.fn();

function loadable(opts) {
return loadableObject;
Expand Down
28 changes: 28 additions & 0 deletions src/__tests__/loadable-components/intersection-observer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,34 @@ describe("Loadable", () => {
expect(queryByTestId("loaded-component")).toBeTruthy();
});

test("load calls loadable load", () => {
// Mock @loadable/component to get a stable `load` function
jest.doMock("@loadable/component");

const loadable = require("@loadable/component");
const loadableVisiblity = require("../../loadable-components"); // Require our tested module with the above mock applied

loadableVisiblity(loader).load();

expect(loadable().load).toHaveBeenCalled();
});

test("load will cause the loadable component to be displayed", async () => {
const Loader = loadableVisiblity(loader);
let returnedValue;

const { queryByTestId } = render(<Loader {...props} />);
expect(queryByTestId("loaded-component")).toBeNull();

act(() => {
returnedValue = Loader.load()
expect(returnedValue).toBeInstanceOf(Promise);
});
returnedValue.then(() => {
expect(queryByTestId("loaded-component")).toBeTruthy();
})
});

test("it displays the loadable component when it becomes visible", async () => {
const Loader = loadableVisiblity(loader);

Expand Down
13 changes: 7 additions & 6 deletions src/__tests__/react-loadable/intersection-observer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,18 +99,19 @@ describe("Loadable", () => {

test("preload will cause the loadable component to be displayed", async () => {
const Loader = LoadableVisibility(opts);
let returnedValue;

const { queryByTestId } = render(<Loader {...props} />);

expect(queryByTestId("loaded-component")).toBeNull();

act(() => {
Loader.preload();
returnedValue = Loader.preload()
expect(returnedValue).toBeInstanceOf(Promise);
});

await waitForElement(() => queryByTestId("loaded-component"));

expect(queryByTestId("loaded-component")).toBeTruthy();
returnedValue.then(() => {
expect(queryByTestId("loaded-component")).toBeTruthy();
})
});

test("it displays the loadable component when it becomes visible", async () => {
Expand Down Expand Up @@ -210,4 +211,4 @@ describe("Loadable.Map", () => {

expect(Loadable.Map().preload).toHaveBeenCalled();
});
});
})
12 changes: 10 additions & 2 deletions src/createLoadableVisibilityComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ if (IntersectionObserver) {

function createLoadableVisibilityComponent(
args,
{ Loadable, preloadFunc, LoadingComponent }
{ Loadable, preloadFunc, loadFunc, LoadingComponent }
) {
let preloaded = false;
let preloaded = false, loaded = false;
const visibilityHandlers = [];

const LoadableComponent = Loadable(...args);
Expand Down Expand Up @@ -108,6 +108,14 @@ function createLoadableVisibilityComponent(
return LoadableComponent[preloadFunc]();
};

LoadableVisibilityComponent[loadFunc] = () => {
if (!loaded) {
loaded = true;
visibilityHandlers.forEach(handler => handler());
}
return LoadableComponent[loadFunc]();
};

return LoadableVisibilityComponent;
}

Expand Down
1 change: 1 addition & 0 deletions src/loadable-components.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ function loadableVisiblity(load, opts = {}) {
return createLoadableVisibilityComponent([load, opts], {
Loadable: loadable,
preloadFunc: "preload",
loadFunc: "load",
LoadingComponent: opts.fallback ? () => opts.fallback : null
});
} else {
Expand Down
3 changes: 3 additions & 0 deletions src/react-loadable.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ function LoadableVisibility (opts) {
return createLoadableVisibilityComponent([opts], {
Loadable,
preloadFunc: 'preload',
/* Preload helps in synchronously loading a component and returns a promise */
loadFunc: 'preload',
LoadingComponent: opts.loading,
})
} else {
Expand All @@ -20,6 +22,7 @@ function LoadableVisibilityMap (opts) {
return createLoadableVisibilityComponent([opts], {
Loadable: Loadable.Map,
preloadFunc: 'preload',
loadFunc: 'preload',
LoadingComponent: opts.loading,
})
} else {
Expand Down
Loading