Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-async-script

Package Overview
Dependencies
Maintainers
1
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-async-script - npm Package Compare versions

Comparing version 0.11.1 to 1.0.0-rc.1

13

CHANGELOG.md

@@ -0,1 +1,13 @@

v1.0.0-rc.1 - Sun 12 Aug 2018 17:47:00 PST
--------------------------------------
- React forward ref [(#37)](https://github.com/dozoisch/react-async-script/pull/37)
- Update to react 16.4.1 [(#37)](https://github.com/dozoisch/react-async-script/pull/37)
- Hoist non react statics [(#35)](https://github.com/dozoisch/react-async-script/pull/35)
- Updated Travis Node versions [(#36)](https://github.com/dozoisch/react-async-script/pull/36)
- Refactor to new HOC pattern [(#34)](https://github.com/dozoisch/react-async-script/pull/34)
- Remove old broken IE support [(#34)](https://github.com/dozoisch/react-async-script/pull/34)
v0.11.1 - Sat, 4 Aug 2018 12:46:00 PST

@@ -8,2 +20,3 @@ --------------------------------------

v0.11.0 - Sun, 29 Jul 2018 11:58:00 PST

@@ -10,0 +23,0 @@ --------------------------------------

328

lib/async-script-loader.js

@@ -11,4 +11,2 @@ "use strict";

var _react2 = _interopRequireDefault(_react);
var _propTypes = require("prop-types");

@@ -18,2 +16,6 @@

var _hoistNonReactStatics = require("hoist-non-react-statics");
var _hoistNonReactStatics2 = _interopRequireDefault(_hoistNonReactStatics);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

@@ -34,209 +36,205 @@

function makeAsyncScript(Component, getScriptURL, options) {
function makeAsyncScript(getScriptURL, options) {
options = options || {};
var wrappedComponentName = Component.displayName || Component.name || "Component";
return function wrapWithAsyncScript(WrappedComponent) {
var wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || "Component";
var AsyncScriptLoader = function (_React$Component) {
_inherits(AsyncScriptLoader, _React$Component);
var AsyncScriptLoader = function (_Component) {
_inherits(AsyncScriptLoader, _Component);
function AsyncScriptLoader() {
_classCallCheck(this, AsyncScriptLoader);
function AsyncScriptLoader(props, context) {
_classCallCheck(this, AsyncScriptLoader);
var _this = _possibleConstructorReturn(this, _React$Component.call(this));
var _this = _possibleConstructorReturn(this, _Component.call(this, props, context));
_this.state = {};
_this.__scriptURL = "";
return _this;
}
AsyncScriptLoader.prototype.asyncScriptLoaderGetScriptLoaderID = function asyncScriptLoaderGetScriptLoaderID() {
if (!this.__scriptLoaderID) {
this.__scriptLoaderID = "async-script-loader-" + idCount++;
_this.state = {};
_this.__scriptURL = "";
return _this;
}
return this.__scriptLoaderID;
};
AsyncScriptLoader.prototype.setupScriptURL = function setupScriptURL() {
this.__scriptURL = typeof getScriptURL === "function" ? getScriptURL() : getScriptURL;
return this.__scriptURL;
};
AsyncScriptLoader.prototype.asyncScriptLoaderGetScriptLoaderID = function asyncScriptLoaderGetScriptLoaderID() {
if (!this.__scriptLoaderID) {
this.__scriptLoaderID = "async-script-loader-" + idCount++;
}
return this.__scriptLoaderID;
};
AsyncScriptLoader.prototype.getComponent = function getComponent() {
return this.__childComponent;
};
AsyncScriptLoader.prototype.setupScriptURL = function setupScriptURL() {
this.__scriptURL = typeof getScriptURL === "function" ? getScriptURL() : getScriptURL;
return this.__scriptURL;
};
AsyncScriptLoader.prototype.asyncScriptLoaderHandleLoad = function asyncScriptLoaderHandleLoad(state) {
this.setState(state, this.props.asyncScriptOnLoad);
};
AsyncScriptLoader.prototype.asyncScriptLoaderHandleLoad = function asyncScriptLoaderHandleLoad(state) {
var _this2 = this;
AsyncScriptLoader.prototype.asyncScriptLoaderTriggerOnScriptLoaded = function asyncScriptLoaderTriggerOnScriptLoaded() {
var mapEntry = SCRIPT_MAP[this.__scriptURL];
if (!mapEntry || !mapEntry.loaded) {
throw new Error("Script is not loaded.");
}
for (var obsKey in mapEntry.observers) {
mapEntry.observers[obsKey](mapEntry);
}
delete window[options.callbackName];
};
// use reacts setState callback to fire props.asyncScriptOnLoad with new state/entry
this.setState(state, function () {
return _this2.props.asyncScriptOnLoad && _this2.props.asyncScriptOnLoad(_this2.state);
});
};
AsyncScriptLoader.prototype.componentDidMount = function componentDidMount() {
var _this2 = this;
AsyncScriptLoader.prototype.asyncScriptLoaderTriggerOnScriptLoaded = function asyncScriptLoaderTriggerOnScriptLoaded() {
var mapEntry = SCRIPT_MAP[this.__scriptURL];
if (!mapEntry || !mapEntry.loaded) {
throw new Error("Script is not loaded.");
}
for (var obsKey in mapEntry.observers) {
mapEntry.observers[obsKey](mapEntry);
}
delete window[options.callbackName];
};
var scriptURL = this.setupScriptURL();
var key = this.asyncScriptLoaderGetScriptLoaderID();
var _options = options,
globalName = _options.globalName,
callbackName = _options.callbackName;
AsyncScriptLoader.prototype.componentDidMount = function componentDidMount() {
var _this3 = this;
if (globalName && typeof window[globalName] !== "undefined") {
SCRIPT_MAP[scriptURL] = { loaded: true, observers: {} };
}
var scriptURL = this.setupScriptURL();
var key = this.asyncScriptLoaderGetScriptLoaderID();
var _options = options,
globalName = _options.globalName,
callbackName = _options.callbackName;
if (SCRIPT_MAP[scriptURL]) {
var entry = SCRIPT_MAP[scriptURL];
if (entry && (entry.loaded || entry.errored)) {
this.asyncScriptLoaderHandleLoad(entry);
// check if global object already attached to window
if (globalName && typeof window[globalName] !== "undefined") {
SCRIPT_MAP[scriptURL] = { loaded: true, observers: {} };
}
// check if script loading already
if (SCRIPT_MAP[scriptURL]) {
var entry = SCRIPT_MAP[scriptURL];
// if loaded or errored then "finish"
if (entry && (entry.loaded || entry.errored)) {
this.asyncScriptLoaderHandleLoad(entry);
return;
}
// if still loading then callback to observer queue
entry.observers[key] = function (entry) {
return _this3.asyncScriptLoaderHandleLoad(entry);
};
return;
}
entry.observers[key] = function (entry) {
return _this2.asyncScriptLoaderHandleLoad(entry);
/*
* hasn't started loading
* start the "magic"
* setup script to load and observers
*/
var observers = {};
observers[key] = function (entry) {
return _this3.asyncScriptLoaderHandleLoad(entry);
};
return;
}
SCRIPT_MAP[scriptURL] = {
loaded: false,
observers: observers
};
var observers = {};
observers[key] = function (entry) {
return _this2.asyncScriptLoaderHandleLoad(entry);
};
SCRIPT_MAP[scriptURL] = {
loaded: false,
observers: observers
};
var script = document.createElement("script");
var script = document.createElement("script");
script.src = scriptURL;
script.async = true;
script.src = scriptURL;
script.async = true;
var callObserverFuncAndRemoveObserver = function callObserverFuncAndRemoveObserver(func) {
if (SCRIPT_MAP[scriptURL]) {
var mapEntry = SCRIPT_MAP[scriptURL];
var observersMap = mapEntry.observers;
var callObserverFuncAndRemoveObserver = function callObserverFuncAndRemoveObserver(func) {
if (SCRIPT_MAP[scriptURL]) {
var mapEntry = SCRIPT_MAP[scriptURL];
var observersMap = mapEntry.observers;
for (var obsKey in observersMap) {
if (func(observersMap[obsKey])) {
delete observersMap[obsKey];
for (var obsKey in observersMap) {
if (func(observersMap[obsKey])) {
delete observersMap[obsKey];
}
}
}
};
if (callbackName && typeof window !== "undefined") {
window[callbackName] = function () {
return _this3.asyncScriptLoaderTriggerOnScriptLoaded();
};
}
};
if (callbackName && typeof window !== "undefined") {
window[callbackName] = function () {
return _this2.asyncScriptLoaderTriggerOnScriptLoaded();
script.onload = function () {
var mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.loaded = true;
callObserverFuncAndRemoveObserver(function (observer) {
if (callbackName) {
return false;
}
observer(mapEntry);
return true;
});
}
};
}
script.onerror = function (event) {
var mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.errored = true;
callObserverFuncAndRemoveObserver(function (observer) {
observer(mapEntry);
return true;
});
}
};
script.onload = function () {
var mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.loaded = true;
callObserverFuncAndRemoveObserver(function (observer) {
if (callbackName) {
return false;
document.body.appendChild(script);
};
AsyncScriptLoader.prototype.componentWillUnmount = function componentWillUnmount() {
// Remove tag script
var scriptURL = this.__scriptURL;
if (options.removeOnUnmount === true) {
var allScripts = document.getElementsByTagName("script");
for (var i = 0; i < allScripts.length; i += 1) {
if (allScripts[i].src.indexOf(scriptURL) > -1) {
if (allScripts[i].parentNode) {
allScripts[i].parentNode.removeChild(allScripts[i]);
}
}
observer(mapEntry);
return true;
});
}
}
};
script.onerror = function (event) {
// Clean the observer entry
var mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.errored = true;
callObserverFuncAndRemoveObserver(function (observer) {
observer(mapEntry);
return true;
});
delete mapEntry.observers[this.asyncScriptLoaderGetScriptLoaderID()];
if (options.removeOnUnmount === true) {
delete SCRIPT_MAP[scriptURL];
}
}
};
// (old) MSIE browsers may call "onreadystatechange" instead of "onload"
script.onreadystatechange = function () {
if (_this2.readyState === "loaded") {
// wait for other events, then call onload if default onload hadn't been called
window.setTimeout(function () {
var mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry && mapEntry.loaded !== true) {
script.onload();
}
}, 0);
}
};
AsyncScriptLoader.prototype.render = function render() {
var globalName = options.globalName;
// remove asyncScriptOnLoad from childProps
document.body.appendChild(script);
};
var _props = this.props,
asyncScriptOnLoad = _props.asyncScriptOnLoad,
forwardedRef = _props.forwardedRef,
childProps = _objectWithoutProperties(_props, ["asyncScriptOnLoad", "forwardedRef"]); // eslint-disable-line no-unused-vars
AsyncScriptLoader.prototype.componentWillUnmount = function componentWillUnmount() {
// Remove tag script
var scriptURL = this.__scriptURL;
if (options.removeOnUnmount === true) {
var allScripts = document.getElementsByTagName("script");
for (var i = 0; i < allScripts.length; i += 1) {
if (allScripts[i].src.indexOf(scriptURL) > -1) {
if (allScripts[i].parentNode) {
allScripts[i].parentNode.removeChild(allScripts[i]);
}
}
if (globalName && typeof window !== "undefined") {
childProps[globalName] = typeof window[globalName] !== "undefined" ? window[globalName] : undefined;
}
}
// Clean the observer entry
var mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
delete mapEntry.observers[this.asyncScriptLoaderGetScriptLoaderID()];
if (options.removeOnUnmount === true) {
delete SCRIPT_MAP[scriptURL];
}
}
};
childProps.ref = forwardedRef;
return (0, _react.createElement)(WrappedComponent, childProps);
};
AsyncScriptLoader.prototype.render = function render() {
var _this3 = this;
return AsyncScriptLoader;
}(_react.Component);
var globalName = options.globalName;
// remove asyncScriptOnLoad from childprops
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to AsyncScriptLoader as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
var _props = this.props,
asyncScriptOnLoad = _props.asyncScriptOnLoad,
childProps = _objectWithoutProperties(_props, ["asyncScriptOnLoad"]);
if (globalName && typeof window !== "undefined") {
childProps[globalName] = typeof window[globalName] !== "undefined" ? window[globalName] : undefined;
}
return _react2.default.createElement(Component, _extends({
ref: function ref(comp) {
_this3.__childComponent = comp;
}
}, childProps));
var ForwardedComponent = (0, _react.forwardRef)(function (props, ref) {
return (0, _react.createElement)(AsyncScriptLoader, _extends({}, props, { forwardedRef: ref }));
});
ForwardedComponent.displayName = "AsyncScriptLoader(" + wrappedComponentName + ")";
ForwardedComponent.propTypes = {
asyncScriptOnLoad: _propTypes2.default.func
};
return AsyncScriptLoader;
}(_react2.default.Component);
AsyncScriptLoader.displayName = "AsyncScriptLoader(" + wrappedComponentName + ")";
AsyncScriptLoader.propTypes = {
asyncScriptOnLoad: _propTypes2.default.func
return (0, _hoistNonReactStatics2.default)(ForwardedComponent, WrappedComponent);
};
if (options.exposeFuncs) {
options.exposeFuncs.forEach(function (funcToExpose) {
AsyncScriptLoader.prototype[funcToExpose] = function () {
var _getComponent;
return (_getComponent = this.getComponent())[funcToExpose].apply(_getComponent, arguments);
};
});
}
return AsyncScriptLoader;
}
{
"name": "react-async-script",
"version": "0.11.1",
"version": "1.0.0-rc.1",
"description": "A composition mixin for loading scripts asynchronously for React",

@@ -41,2 +41,3 @@ "main": "lib/async-script-loader.js",

"es5-shim": "~4.1.3",
"es6-shim": "^0.35.3",
"eslint": "~1.6.0",

@@ -55,13 +56,15 @@ "eslint-config-defaults": "~7.0.1",

"mocha": "~2.3.3",
"phantomjs": "^1.9.18",
"react": "^15.5.0",
"react-dom": "^15.5.0",
"phantomjs": "^2.0.0",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-is": "^16.4.2",
"webpack": "~1.14.0"
},
"peerDependencies": {
"react": ">=15.5.0"
"react": ">=16.4.1"
},
"dependencies": {
"prop-types": ">=15.5.0"
"hoist-non-react-statics": "^3.0.1",
"prop-types": "^15.5.0"
}
}

@@ -5,19 +5,53 @@ # React Async Script Loader

A React composition mixin for loading 3rd party scripts asynchronously. This component allows you to wrap component
that needs 3rd party resources, like reCAPTCHA or Google Maps, and have them load the script asynchronously.
A React HOC for loading 3rd party scripts asynchronously. This HOC allows you to wrap a component that needs 3rd party resources, like reCAPTCHA or Google Maps, and have them load the script asynchronously.
## Usage
The api is very simple `makeAsyncScriptLoader(Component, getScriptUrl, options)`. Where options can contain exposeFuncs, callbackName and globalName.
#### Async Script HOC api
- `Component`: The component to wrap.
- `getScriptUrl`: a string or function that returns the full URL of the script tag.
- options *(optional)*:
- `exposeFuncs`: Array of Strings. It'll create a function that will call the child component with the same name. It passes arguments and return value.
- `callbackName`: If the scripts calls a global function when loaded, provide the callback name here. It'll be autoregistered on the window.
- `globalName`: If wanted, provide the globalName of the loaded script. It'll be injected on the component with the same name *(ex: "grecaptcha")*
- `removeOnUnmount`: Boolean **default=false**: If set to true removes the script tag on the component unmount
`makeAsyncScriptLoader(getScriptUrl, options)(Component)`
You can retrieve the child component using the function called `getComponent()`.
- `Component`: The *Component* to wrap.
- `getScriptUrl`: *string* or *function* that returns the full URL of the script tag.
- `options` *(optional)*:
- `callbackName`: *string* : If the script needs to call a global function when finished loading *(for example: `recaptcha/api.js?onload=callbackName`)*. Please provide the callback name here and it will be autoregistered on `window` for you.
- `globalName`: *string* : Can provide the name of the global that the script attaches to `window`. Async-script will pass this as a prop to the wrapped component. *(`props[globalName] = window[globalName]`)*
- `removeOnUnmount`: *boolean* **default=false** : If set to `true` removes the script tag when component unmounts.
#### HOC Component props
```
const AsyncScriptComponent = makeAsyncScriptLoader(URL)(Component);
---
<AsyncScriptComponent asyncScriptOnLoad={callAfterScriptLoads} />
```
- `asyncScriptOnLoad`: *function* : called after script finishes loading. *using `script.onload`*
#### Ref and forwardRef
`react-async-script` uses react's `forwardRef` method to pass along the `ref` applied to the wrapped component.
If you pass a `ref` prop you'll have access to your wrapped components instance. See the tests for detailed example.
Simple Example:
```
const AsyncHoc = makeAsyncScriptLoader(URL)(ComponentNeedsScript);
class DisplayComponent extends React.Component {
constructor(props) {
super(props);
this._internalRef = React.createRef();
}
componentDidMount() {
console.log("ComponentNeedsScript's Instance -", this._internalRef.current);
}
render() { return (<AsyncHoc ref={this._internalRef} />)}
}
```
##### Notes on Requirements
At least `React@16.4.1` is required due to `forwardRef` usage internally.
### Example

@@ -28,34 +62,36 @@

```js
// recaptcha.js
export class ReCAPTCHA extends React.Component {
componentDidUpdate(prevProps) {
// recaptcha has loaded via async script
if (!prevProps.grecaptcha && this.props.grecaptcha) {
this.props.grecaptcha.render(this._container)
}
}
render() { return (
<div ref={(r) => this._container = r} />)
}
}
// recaptcha-wrapper.js
import React from "react";
import makeAsyncScriptLoader from "react-async-script";
import { ReCAPTCHA } from "./recaptcha";
import ReCAPTCHA from "./recaptcha";
import makeAsyncScriptLoader from "./react-async-script";
const callbackName = "onloadcallback";
const URL = `https://www.google.com/recaptcha/api.js?onload=${callbackName}&render=explicit`;
// the name of the global that recaptcha/api.js sets on window ie: window.grecaptcha
const globalName = "grecaptcha";
export default makeAsyncScriptLoader(ReCAPTCHA, URL, {
export default makeAsyncScriptLoader(URL, {
callbackName: callbackName,
globalName: globalName,
});
})(ReCAPTCHA);
// main.js
import React from "react";
import ReCAPTCHAWrapper from "./recaptcha-wrapper.js"
function onLoad() {
console.log("script loaded");
}
const onLoad = () => console.log("script loaded")
let reCAPTCHAprops = {
siteKey: "xxxxxxx",
//...
};
React.render(
<ReCAPTCHAWrapper asyncScriptOnLoad={onLoad} {...reCAPTCHAprops} />,
<ReCAPTCHAWrapper asyncScriptOnLoad={onLoad} />,
document.body

@@ -65,52 +101,8 @@ );

## Expose Functions
This is really useful if the child component has some utility functions (like `getValue`) that you would like the wrapper to expose.
You can still retrieve the child component using `getComponent()`.
### Example
```js
const MockedComponent = React.createClass({
displayName: "MockedComponent",
callsACallback(fn) {
fn();
},
render() {
return <span/>;
}
});
let ComponentWrapper = makeAsyncScriptLoader(MockedComponent, "http://example.com", {
exposeFuncs: ["callsACallback"]
});
let instance = ReactTestUtils.renderIntoDocument(
<ComponentWrapper />
);
instance.callsACallback(function () { console.log("Called from child", this.constructor.displayName); });
```
## Notes
### History
Pre `1.0.0` and - `React < 15.5.*` support details in [0.11.1](https://github.com/dozoisch/react-async-script/tree/v0.11.1).
With React 0.13, mixins are getting deprecated in favor of composition.
After reading this article, [Mixins Are Dead. Long Live Composition][dan_abramov],
I decided push react-script-loader a bit further and make a composition function that wraps component.
### Version to use
- __React < 15.5__: v0.8.0
- __React >= 15.5__: >= v0.9.0
---
*Inspired by [react-script-loader][sl]*
*The build tools are highly inspired by [react-bootstrap][rb]*
[travis.img]: https://travis-ci.org/dozoisch/react-async-script.svg?branch=master

@@ -124,5 +116,1 @@ [travis.url]: https://travis-ci.org/dozoisch/react-async-script

[deps.url]: https://david-dm.org/dozoisch/react-async-script
[dan_abramov]: https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750
[sl]: https://github.com/yariv/ReactScriptLoader
[rb]: https://github.com/react-bootstrap/react-bootstrap/

@@ -1,3 +0,4 @@

import React from "react";
import { Component, createElement, forwardRef } from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";

@@ -9,190 +10,182 @@ let SCRIPT_MAP = {};

export default function makeAsyncScript(Component, getScriptURL, options) {
export default function makeAsyncScript(getScriptURL, options) {
options = options || {};
const wrappedComponentName =
Component.displayName || Component.name || "Component";
return function wrapWithAsyncScript(WrappedComponent) {
const wrappedComponentName =
WrappedComponent.displayName || WrappedComponent.name || "Component";
class AsyncScriptLoader extends React.Component {
constructor() {
super();
this.state = {};
this.__scriptURL = "";
}
class AsyncScriptLoader extends Component {
constructor(props, context) {
super(props, context)
this.state = {};
this.__scriptURL = "";
}
asyncScriptLoaderGetScriptLoaderID() {
if (!this.__scriptLoaderID) {
this.__scriptLoaderID = "async-script-loader-" + idCount++;
asyncScriptLoaderGetScriptLoaderID() {
if (!this.__scriptLoaderID) {
this.__scriptLoaderID = "async-script-loader-" + idCount++;
}
return this.__scriptLoaderID;
}
return this.__scriptLoaderID;
}
setupScriptURL() {
this.__scriptURL =
typeof getScriptURL === "function" ? getScriptURL() : getScriptURL;
return this.__scriptURL;
}
setupScriptURL() {
this.__scriptURL =
typeof getScriptURL === "function" ? getScriptURL() : getScriptURL;
return this.__scriptURL;
}
getComponent() {
return this.__childComponent;
}
asyncScriptLoaderHandleLoad(state) {
this.setState(state, this.props.asyncScriptOnLoad);
}
asyncScriptLoaderTriggerOnScriptLoaded() {
let mapEntry = SCRIPT_MAP[this.__scriptURL];
if (!mapEntry || !mapEntry.loaded) {
throw new Error("Script is not loaded.");
asyncScriptLoaderHandleLoad(state) {
// use reacts setState callback to fire props.asyncScriptOnLoad with new state/entry
this.setState(state,
() => this.props.asyncScriptOnLoad && this.props.asyncScriptOnLoad(this.state)
);
}
for (let obsKey in mapEntry.observers) {
mapEntry.observers[obsKey](mapEntry);
}
delete window[options.callbackName];
}
componentDidMount() {
const scriptURL = this.setupScriptURL();
const key = this.asyncScriptLoaderGetScriptLoaderID();
const { globalName, callbackName } = options;
if (globalName && typeof window[globalName] !== "undefined") {
SCRIPT_MAP[scriptURL] = { loaded: true, observers: {} };
asyncScriptLoaderTriggerOnScriptLoaded() {
let mapEntry = SCRIPT_MAP[this.__scriptURL];
if (!mapEntry || !mapEntry.loaded) {
throw new Error("Script is not loaded.");
}
for (let obsKey in mapEntry.observers) {
mapEntry.observers[obsKey](mapEntry);
}
delete window[options.callbackName];
}
if (SCRIPT_MAP[scriptURL]) {
let entry = SCRIPT_MAP[scriptURL];
if (entry && (entry.loaded || entry.errored)) {
this.asyncScriptLoaderHandleLoad(entry);
componentDidMount() {
const scriptURL = this.setupScriptURL();
const key = this.asyncScriptLoaderGetScriptLoaderID();
const { globalName, callbackName } = options;
// check if global object already attached to window
if (globalName && typeof window[globalName] !== "undefined") {
SCRIPT_MAP[scriptURL] = { loaded: true, observers: {} };
}
// check if script loading already
if (SCRIPT_MAP[scriptURL]) {
let entry = SCRIPT_MAP[scriptURL];
// if loaded or errored then "finish"
if (entry && (entry.loaded || entry.errored)) {
this.asyncScriptLoaderHandleLoad(entry);
return;
}
// if still loading then callback to observer queue
entry.observers[key] = entry => this.asyncScriptLoaderHandleLoad(entry);
return;
}
entry.observers[key] = entry => this.asyncScriptLoaderHandleLoad(entry);
return;
}
let observers = {};
observers[key] = entry => this.asyncScriptLoaderHandleLoad(entry);
SCRIPT_MAP[scriptURL] = {
loaded: false,
observers,
};
/*
* hasn't started loading
* start the "magic"
* setup script to load and observers
*/
let observers = {};
observers[key] = entry => this.asyncScriptLoaderHandleLoad(entry);
SCRIPT_MAP[scriptURL] = {
loaded: false,
observers,
};
let script = document.createElement("script");
let script = document.createElement("script");
script.src = scriptURL;
script.async = true;
script.src = scriptURL;
script.async = true;
let callObserverFuncAndRemoveObserver = func => {
if (SCRIPT_MAP[scriptURL]) {
let mapEntry = SCRIPT_MAP[scriptURL];
let observersMap = mapEntry.observers;
let callObserverFuncAndRemoveObserver = func => {
if (SCRIPT_MAP[scriptURL]) {
let mapEntry = SCRIPT_MAP[scriptURL];
let observersMap = mapEntry.observers;
for (let obsKey in observersMap) {
if (func(observersMap[obsKey])) {
delete observersMap[obsKey];
for (let obsKey in observersMap) {
if (func(observersMap[obsKey])) {
delete observersMap[obsKey];
}
}
}
};
if (callbackName && typeof window !== "undefined") {
window[callbackName] = () =>
this.asyncScriptLoaderTriggerOnScriptLoaded();
}
};
if (callbackName && typeof window !== "undefined") {
window[callbackName] = () =>
this.asyncScriptLoaderTriggerOnScriptLoaded();
script.onload = () => {
let mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.loaded = true;
callObserverFuncAndRemoveObserver(observer => {
if (callbackName) {
return false;
}
observer(mapEntry);
return true;
});
}
};
script.onerror = event => {
let mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.errored = true;
callObserverFuncAndRemoveObserver(observer => {
observer(mapEntry);
return true;
});
}
};
document.body.appendChild(script);
}
script.onload = () => {
let mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.loaded = true;
callObserverFuncAndRemoveObserver(observer => {
if (callbackName) {
return false;
componentWillUnmount() {
// Remove tag script
const scriptURL = this.__scriptURL;
if (options.removeOnUnmount === true) {
const allScripts = document.getElementsByTagName("script");
for (let i = 0; i < allScripts.length; i += 1) {
if (allScripts[i].src.indexOf(scriptURL) > -1) {
if (allScripts[i].parentNode) {
allScripts[i].parentNode.removeChild(allScripts[i]);
}
}
observer(mapEntry);
return true;
});
}
}
};
script.onerror = event => {
// Clean the observer entry
let mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
mapEntry.errored = true;
callObserverFuncAndRemoveObserver(observer => {
observer(mapEntry);
return true;
});
}
};
// (old) MSIE browsers may call "onreadystatechange" instead of "onload"
script.onreadystatechange = () => {
if (this.readyState === "loaded") {
// wait for other events, then call onload if default onload hadn't been called
window.setTimeout(() => {
const mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry && mapEntry.loaded !== true) {
script.onload();
}
}, 0);
}
};
document.body.appendChild(script);
}
componentWillUnmount() {
// Remove tag script
const scriptURL = this.__scriptURL;
if (options.removeOnUnmount === true) {
const allScripts = document.getElementsByTagName("script");
for (let i = 0; i < allScripts.length; i += 1) {
if (allScripts[i].src.indexOf(scriptURL) > -1) {
if (allScripts[i].parentNode) {
allScripts[i].parentNode.removeChild(allScripts[i]);
}
delete mapEntry.observers[this.asyncScriptLoaderGetScriptLoaderID()];
if (options.removeOnUnmount === true) {
delete SCRIPT_MAP[scriptURL];
}
}
}
// Clean the observer entry
let mapEntry = SCRIPT_MAP[scriptURL];
if (mapEntry) {
delete mapEntry.observers[this.asyncScriptLoaderGetScriptLoaderID()];
if (options.removeOnUnmount === true) {
delete SCRIPT_MAP[scriptURL];
render() {
const globalName = options.globalName;
// remove asyncScriptOnLoad from childProps
let { asyncScriptOnLoad, forwardedRef, ...childProps } = this.props; // eslint-disable-line no-unused-vars
if (globalName && typeof window !== "undefined") {
childProps[globalName] =
typeof window[globalName] !== "undefined"
? window[globalName]
: undefined;
}
childProps.ref = forwardedRef;
return createElement(WrappedComponent, childProps);
}
}
render() {
const globalName = options.globalName;
// remove asyncScriptOnLoad from childprops
let { asyncScriptOnLoad, ...childProps } = this.props;
if (globalName && typeof window !== "undefined") {
childProps[globalName] =
typeof window[globalName] !== "undefined"
? window[globalName]
: undefined;
}
return (
<Component
ref={comp => {
this.__childComponent = comp;
}}
{...childProps}
/>
);
}
}
AsyncScriptLoader.displayName = `AsyncScriptLoader(${wrappedComponentName})`;
AsyncScriptLoader.propTypes = {
asyncScriptOnLoad: PropTypes.func,
};
// Note the second param "ref" provided by React.forwardRef.
// We can pass it along to AsyncScriptLoader as a regular prop, e.g. "forwardedRef"
// And it can then be attached to the Component.
const ForwardedComponent = forwardRef((props, ref) => {
return createElement(AsyncScriptLoader, {...props, forwardedRef: ref });
});
ForwardedComponent.displayName = `AsyncScriptLoader(${wrappedComponentName})`;
ForwardedComponent.propTypes = {
asyncScriptOnLoad: PropTypes.func,
};
if (options.exposeFuncs) {
options.exposeFuncs.forEach(funcToExpose => {
AsyncScriptLoader.prototype[funcToExpose] = function() {
return this.getComponent()[funcToExpose](...arguments);
};
});
return hoistStatics(ForwardedComponent, WrappedComponent);
}
return AsyncScriptLoader;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc