Comparing version 0.5.0 to 0.7.0
## Pre-v1.0.0 changes | ||
[v0.7.0] | ||
- Significant refactor, and support MutationObserver fallback. This means IE9 support (hopefully)! :tada: | ||
[v0.7.0]: https://github.com/rstacruz/remount/compare/v0.6.0...v0.7.0 | ||
[v0.6.0] | ||
- Fix `remount/esm` module. | ||
[v0.6.0]: https://github.com/rstacruz/remount/compare/v0.5.0...v0.6.0 | ||
[v0.5.0] | ||
@@ -4,0 +16,0 @@ |
@@ -9,6 +9,29 @@ (function (global, factory) { | ||
/* global HTMLElement */ | ||
var injected = void 0; | ||
/* | ||
* Adapted from https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs@2.0.4/custom-elements-es5-adapter.js | ||
* Rolling this in so we don't need another polyfill. | ||
*/ | ||
function inject() { | ||
if (injected || void 0 === window.Reflect || void 0 === window.customElements || window.customElements.hasOwnProperty('polyfillWrapFlushCallback')) { | ||
return; | ||
} | ||
var a = HTMLElement; | ||
window.HTMLElement = function () { | ||
return Reflect.construct(a, [], this.constructor); | ||
}; | ||
HTMLElement.prototype = a.prototype; | ||
HTMLElement.prototype.constructor = HTMLElement; | ||
Object.setPrototypeOf(HTMLElement, a); | ||
injected = true; | ||
} | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } | ||
@@ -23,52 +46,39 @@ | ||
/*:: | ||
export type Component = React.ComponentType<{}> | ||
export type PropertyMap = { | ||
[string]: ?string | ||
} | ||
export type ElementMap = { | ||
[string]: ElementSpec | Component | ||
} | ||
export type Defaults = { | ||
attributes?: Array<string>, | ||
quiet?: boolean, | ||
shadow?: boolean | ||
} | ||
export type ElementSpec = { | ||
component: Component, | ||
attributes?: Array<string>, | ||
quiet?: boolean, | ||
shadow?: boolean | ||
} | ||
import type { | ||
Component, | ||
PropertyMap, | ||
ElementMap, | ||
Defaults, | ||
ElementSpec, | ||
ReactAdapter, | ||
ElementEvents | ||
} from './types' | ||
*/ | ||
/** | ||
* Registers elements. | ||
* Registers a custom element. | ||
* | ||
* This creates a custom element (ie, a subclass of `window.HTMLElement`) and | ||
* registers it (ie, `window.customElements.define`). | ||
* | ||
* Events will be triggered when something interesting happens. | ||
* | ||
* @example | ||
* defineElement( | ||
* { component: Tooltip }, | ||
* 'x-tooltip', | ||
* { onUpdate, onUnmount } | ||
* ) | ||
* | ||
* @private | ||
*/ | ||
function define(components /*: ElementMap */ | ||
, defaults /*: ?Defaults */ | ||
function defineElement(elSpec /*: ElementSpec */ | ||
, name /*: string */ | ||
, _ref /*: ElementEvents */ | ||
) { | ||
Object.keys(components).forEach(function (name /*: string */) { | ||
var elSpec /*: ElementSpec */ = toElementSpec(components[name]); | ||
defineOne(Object.assign({}, defaults, elSpec), name); | ||
}); | ||
} | ||
var onUpdate = _ref.onUpdate, | ||
onUnmount = _ref.onUnmount; | ||
function toElementSpec(thing /*: ElementSpec | Component */ | ||
) /*: ElementSpec */{ | ||
// $FlowFixMe$ | ||
if ((typeof thing === 'undefined' ? 'undefined' : _typeof(thing)) === 'object' && thing.component) return thing; | ||
return { component: thing }; | ||
} | ||
/** | ||
* Registers one element. | ||
* @private | ||
*/ | ||
function defineOne(elSpec /*: ElementSpec */, name /*: string */) { | ||
inject(); | ||
var attributes = elSpec.attributes || []; | ||
@@ -89,3 +99,3 @@ | ||
this._mountPoint = createMountPoint(this, elSpec); | ||
update(this, elSpec, this._mountPoint); | ||
onUpdate(this, this._mountPoint); | ||
} | ||
@@ -96,3 +106,3 @@ }, { | ||
if (!this._mountPoint) return; | ||
ReactDOM.unmountComponentAtNode(this._mountPoint); | ||
onUnmount(this, this._mountPoint); | ||
} | ||
@@ -103,3 +113,3 @@ }, { | ||
if (!this._mountPoint) return; | ||
update(this, elSpec, this._mountPoint); | ||
onUpdate(this, this._mountPoint); | ||
} | ||
@@ -116,5 +126,5 @@ }], [{ | ||
if (!ensureSupported()) return; | ||
// Supress warning when quiet mode is on | ||
// Supress warning when quiet mode is on | ||
if (elSpec.quiet && window.customElements.get(name)) { | ||
@@ -127,14 +137,4 @@ return; | ||
/** | ||
* Ensures that custom elements are supported | ||
* @private | ||
*/ | ||
function ensureSupported() { | ||
if (!window.customElements || !window.customElements.define) { | ||
console.error("remount: Custom elements aren't support in this browser. " + 'Remount will not work. ' + 'Including polyfills will likely fix this. ' + 'See Remount documentation for more info: ' + 'https://github.com/rstacruz/remount'); | ||
return false; | ||
} | ||
return true; | ||
function isSupported() { | ||
return window.customElements && window.customElements.define; | ||
} | ||
@@ -144,3 +144,3 @@ | ||
* Creates a `<span>` element that serves as the mounting point for React | ||
* components. | ||
* components. If `shadow: true` is requested, it'll attach a shadow node. | ||
* @private | ||
@@ -150,5 +150,5 @@ */ | ||
function createMountPoint(element /*: Element */ | ||
, _ref /*: ElementSpec */ | ||
, _ref2 /*: ElementSpec */ | ||
) { | ||
var shadow = _ref.shadow; | ||
var shadow = _ref2.shadow; | ||
@@ -164,2 +164,55 @@ if (shadow) { | ||
var name = 'CustomElements'; | ||
var ElementsAdapter = /*#__PURE__*/Object.freeze({ | ||
defineElement: defineElement, | ||
isSupported: isSupported, | ||
name: name | ||
}); | ||
function isSupported$1() { | ||
return !!window.MutationObserver; | ||
} | ||
function defineElement$1(elSpec, name, _ref) { | ||
var onUpdate = _ref.onUpdate, | ||
onUnmount = _ref.onUnmount; | ||
name = name.toLowerCase(); | ||
var observer = new window.MutationObserver(function (mutations) { | ||
mutations.forEach(function (mutation) { | ||
mutation.addedNodes.forEach(function (node) { | ||
if (node.nodeName.toLowerCase() !== name) return; | ||
onUpdate(node, node); | ||
}); | ||
// todo handle update | ||
mutation.removedNodes.forEach(function (node) { | ||
if (node.nodeName.toLowerCase() !== name) return; | ||
onUnmount(node, node); | ||
}); | ||
}); | ||
}); | ||
observer.observe(document.body, { | ||
attributes: true, | ||
childList: true, | ||
subtree: true | ||
}); | ||
} | ||
var name$1 = 'MutationObserver'; | ||
var MutationAdapter = /*#__PURE__*/Object.freeze({ | ||
isSupported: isSupported$1, | ||
defineElement: defineElement$1, | ||
name: name$1 | ||
}); | ||
/*:: | ||
import type { ElementSpec } from './types' | ||
*/ | ||
/** | ||
@@ -170,13 +223,10 @@ * Updates a custom element by calling `ReactDOM.render()`. | ||
function update(element /* Element */ | ||
, _ref2 /*: ElementSpec */ | ||
, mountPoint /* Element */ | ||
function update(_ref /*: ElementSpec */ | ||
, mountPoint /*: Element */ | ||
, props /*: {} */ | ||
) { | ||
var component = _ref2.component, | ||
attributes = _ref2.attributes; | ||
var component = _ref.component, | ||
attributes = _ref.attributes; | ||
var props = element.hasAttribute('props-json') ? JSON.parse(element.getAttribute('props-json')) : getProps(element, attributes); | ||
var reactElement = React.createElement(component, props); | ||
ReactDOM.render(reactElement, mountPoint); | ||
@@ -186,7 +236,93 @@ } | ||
/** | ||
* Unmounts a component. | ||
* @private | ||
*/ | ||
function unmount(_ /*: any */, mountPoint /*: Element */) { | ||
ReactDOM.unmountComponentAtNode(mountPoint); | ||
} | ||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; | ||
/*:: | ||
import type { | ||
Component, | ||
PropertyMap, | ||
ElementMap, | ||
Defaults, | ||
ElementSpec | ||
} from './lib/types' | ||
*/ | ||
var Adapter = isSupported() ? ElementsAdapter : isSupported$1() ? MutationAdapter : null; | ||
if (!Adapter) { | ||
throw new Error('Unsupported platform'); | ||
} else { | ||
console.log('Remount: using adapter', Adapter.name); | ||
} | ||
/** | ||
* Inspect `Remount.adapterName` to see what adapter's being used. | ||
*/ | ||
var adapterName = Adapter.name; | ||
/** | ||
* Registers elements. | ||
*/ | ||
function define(components /*: ElementMap */ | ||
, defaults /*: ?Defaults */ | ||
) { | ||
Object.keys(components).forEach(function (name$$1 /*: string */) { | ||
// Construct the specs for the element. | ||
// (eg, { component: Tooltip, attributes: ['title'] }) | ||
var elSpec /*: ElementSpec */ = Object.assign({}, defaults, toElementSpec(components[name$$1])); | ||
// Define a custom element. | ||
Adapter.defineElement(elSpec, name$$1, { | ||
onUpdate: function onUpdate(element /*: Element */, mountPoint /*: Element */) { | ||
var props = getProps(element, elSpec.attributes); | ||
update(elSpec, mountPoint, props); | ||
}, | ||
onUnmount: function onUnmount(element /*: Element */, mountPoint /*: Element */) { | ||
unmount(elSpec, mountPoint); | ||
} | ||
}); | ||
}); | ||
} | ||
/** | ||
* Coerces something into an `ElementSpec` type. | ||
* @private | ||
* | ||
* @example | ||
* toElementSpec(Tooltip) | ||
* // => { component: Tooltip } | ||
* | ||
* toElementSpec({ component: Tooltip }) | ||
* // => { component: Tooltip } | ||
*/ | ||
function toElementSpec(thing /*: ElementSpec | Component */ | ||
) /*: ElementSpec */{ | ||
// $FlowFixMe$ | ||
if ((typeof thing === 'undefined' ? 'undefined' : _typeof(thing)) === 'object' && thing.component) return thing; | ||
return { component: thing }; | ||
} | ||
/** | ||
* Returns properties for a given HTML element. | ||
* @private | ||
* | ||
* @example | ||
* getProps(div, ['name']) | ||
* // => { name: 'Romeo' } | ||
*/ | ||
function getProps(element /*: Element */, attributes /*: ?Array<string> */) { | ||
var rawJson = element.getAttribute('props-json'); | ||
if (rawJson) return JSON.parse(rawJson); | ||
var names /*: Array<string> */ = attributes || []; | ||
@@ -197,7 +333,5 @@ return names.reduce(function (result /*: PropertyMap */, attribute /*: string */) { | ||
}, {}); | ||
// By the way, did you know el.getAttributeNames() | ||
// will not work in IE11? Now you do. | ||
} | ||
exports.adapterName = adapterName; | ||
exports.define = define; | ||
@@ -204,0 +338,0 @@ |
@@ -1,1 +0,1 @@ | ||
(function(a,b){'object'==typeof exports&&'undefined'!=typeof module?b(exports,require('react'),require('react-dom')):'function'==typeof define&&define.amd?define(['exports','react','react-dom'],b):b(a.Remount={},a.React,a.ReactDOM)})(this,function(a,b,c){'use strict';function d(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b<a.length;b++)c[b]=a[b];return c}return Array.from(a)}function e(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')}function f(a,b){if(!a)throw new ReferenceError('this hasn\'t been initialised - super() hasn\'t been called');return b&&('object'==typeof b||'function'==typeof b)?b:a}function g(a,b){if('function'!=typeof b&&null!==b)throw new TypeError('Super expression must either be null or a function, not '+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}}),b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}function h(a){return'object'===('undefined'==typeof a?'undefined':o(a))&&a.component?a:{component:a}}function i(a,b){var h=a.attributes||[],i=function(b){function i(){return e(this,i),f(this,(i.__proto__||Object.getPrototypeOf(i)).apply(this,arguments))}return g(i,b),n(i,[{key:'connectedCallback',value:function(){this._mountPoint=k(this,a),l(this,a,this._mountPoint)}},{key:'disconnectedCallback',value:function(){this._mountPoint&&c.unmountComponentAtNode(this._mountPoint)}},{key:'attributeChangedCallback',value:function(){this._mountPoint&&l(this,a,this._mountPoint)}}],[{key:'observedAttributes',get:function(){return['props-json'].concat(d(h))}}]),i}(window.HTMLElement);!j()||a.quiet&&window.customElements.get(b)||window.customElements.define(b,i)}function j(){return!!(window.customElements&&window.customElements.define)||(console.error('remount: Custom elements aren\'t support in this browser. Remount will not work. Including polyfills will likely fix this. See Remount documentation for more info: https://github.com/rstacruz/remount'),!1)}function k(a,b){var c=b.shadow;if(c){var d=document.createElement('span');return a.attachShadow({mode:'open'}).appendChild(d),d}return a}function l(a,d,e){var f=d.component,g=d.attributes,h=a.hasAttribute('props-json')?JSON.parse(a.getAttribute('props-json')):m(a,g),i=b.createElement(f,h);c.render(i,e)}function m(a,b){return(b||[]).reduce(function(b,c){return b[c]=a.getAttribute(c),b},{})}c=c&&c.hasOwnProperty('default')?c['default']:c;var n=function(){function a(a,b){for(var c,d=0;d<b.length;d++)c=b[d],c.enumerable=c.enumerable||!1,c.configurable=!0,'value'in c&&(c.writable=!0),Object.defineProperty(a,c.key,c)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}(),o='function'==typeof Symbol&&'symbol'==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&'function'==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?'symbol':typeof a};a.define=function(a,b){Object.keys(a).forEach(function(c){var d=h(a[c]);i(Object.assign({},b,d),c)})},Object.defineProperty(a,'__esModule',{value:!0})}); | ||
(function(a,b){'object'==typeof exports&&'undefined'!=typeof module?b(exports,require('react'),require('react-dom')):'function'==typeof define&&define.amd?define(['exports','react','react-dom'],b):b(a.Remount={},a.React,a.ReactDOM)})(this,function(a,b,c){'use strict';function d(){if(!(p||void 0===window.Reflect||void 0===window.customElements||window.customElements.hasOwnProperty('polyfillWrapFlushCallback'))){var b=HTMLElement;window.HTMLElement=function(){return Reflect.construct(b,[],this.constructor)},HTMLElement.prototype=b.prototype,HTMLElement.prototype.constructor=HTMLElement,Object.setPrototypeOf(HTMLElement,b),p=!0}}function e(a){if(Array.isArray(a)){for(var b=0,c=Array(a.length);b<a.length;b++)c[b]=a[b];return c}return Array.from(a)}function f(a,b){if(!(a instanceof b))throw new TypeError('Cannot call a class as a function')}function g(a,b){if(!a)throw new ReferenceError('this hasn\'t been initialised - super() hasn\'t been called');return b&&('object'==typeof b||'function'==typeof b)?b:a}function h(a,b){if('function'!=typeof b&&null!==b)throw new TypeError('Super expression must either be null or a function, not '+typeof b);a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,enumerable:!1,writable:!0,configurable:!0}}),b&&(Object.setPrototypeOf?Object.setPrototypeOf(a,b):a.__proto__=b)}function i(){return window.customElements&&window.customElements.define}function j(a,b){var c=b.shadow;if(c){var d=document.createElement('span');return a.attachShadow({mode:'open'}).appendChild(d),d}return a}function k(){return!!window.MutationObserver}function l(a,d,e){var f=a.component,g=a.attributes,h=b.createElement(f,e);c.render(h,d)}function m(a,b){c.unmountComponentAtNode(b)}function n(a){return'object'===('undefined'==typeof a?'undefined':t(a))&&a.component?a:{component:a}}function o(a,b){var c=a.getAttribute('props-json');if(c)return JSON.parse(c);return(b||[]).reduce(function(b,c){return b[c]=a.getAttribute(c),b},{})}c=c&&c.hasOwnProperty('default')?c['default']:c;var p=void 0,q=function(){function a(a,b){for(var c,d=0;d<b.length;d++)c=b[d],c.enumerable=c.enumerable||!1,c.configurable=!0,'value'in c&&(c.writable=!0),Object.defineProperty(a,c.key,c)}return function(b,c,d){return c&&a(b.prototype,c),d&&a(b,d),b}}(),r=Object.freeze({defineElement:function(a,b,c){var i=c.onUpdate,k=c.onUnmount;d();var l=a.attributes||[],m=function(b){function c(){return f(this,c),g(this,(c.__proto__||Object.getPrototypeOf(c)).apply(this,arguments))}return h(c,b),q(c,[{key:'connectedCallback',value:function(){this._mountPoint=j(this,a),i(this,this._mountPoint)}},{key:'disconnectedCallback',value:function(){this._mountPoint&&k(this,this._mountPoint)}},{key:'attributeChangedCallback',value:function(){this._mountPoint&&i(this,this._mountPoint)}}],[{key:'observedAttributes',get:function(){return['props-json'].concat(e(l))}}]),c}(window.HTMLElement);a.quiet&&window.customElements.get(b)||window.customElements.define(b,m)},isSupported:i,name:'CustomElements'}),s=Object.freeze({isSupported:k,defineElement:function(a,b,c){var d=c.onUpdate,e=c.onUnmount;b=b.toLowerCase();var f=new window.MutationObserver(function(a){a.forEach(function(a){a.addedNodes.forEach(function(a){a.nodeName.toLowerCase()!==b||d(a,a)}),a.removedNodes.forEach(function(a){a.nodeName.toLowerCase()!==b||e(a,a)})})});f.observe(document.body,{attributes:!0,childList:!0,subtree:!0})},name:'MutationObserver'}),t='function'==typeof Symbol&&'symbol'==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&'function'==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?'symbol':typeof a},u=i()?r:k()?s:null;if(!u)throw new Error('Unsupported platform');else console.log('Remount: using adapter',u.name);var v=u.name;a.adapterName=v,a.define=function(a,b){Object.keys(a).forEach(function(c){var d=Object.assign({},b,n(a[c]));u.defineElement(d,c,{onUpdate:function(a,b){var c=o(a,d.attributes);l(d,b,c)},onUnmount:function(a,b){m(d,b)}})})},Object.defineProperty(a,'__esModule',{value:!0})}); |
@@ -9,57 +9,70 @@ (function (global, factory) { | ||
// @flow | ||
/* global HTMLElement */ | ||
/*:: | ||
export type Component = React.ComponentType<{}> | ||
let injected; | ||
export type PropertyMap = { | ||
[string]: ?string | ||
} | ||
/* | ||
* Adapted from https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs@2.0.4/custom-elements-es5-adapter.js | ||
* Rolling this in so we don't need another polyfill. | ||
*/ | ||
export type ElementMap = { | ||
[string]: ElementSpec | Component | ||
} | ||
function inject () { | ||
if ( | ||
injected || | ||
void 0 === window.Reflect || | ||
void 0 === window.customElements || | ||
window.customElements.hasOwnProperty('polyfillWrapFlushCallback') | ||
) { | ||
return | ||
} | ||
const a = HTMLElement; | ||
export type Defaults = { | ||
attributes?: Array<string>, | ||
quiet?: boolean, | ||
shadow?: boolean | ||
} | ||
window.HTMLElement = function () { | ||
return Reflect.construct(a, [], this.constructor) | ||
}; | ||
export type ElementSpec = { | ||
component: Component, | ||
attributes?: Array<string>, | ||
quiet?: boolean, | ||
shadow?: boolean | ||
HTMLElement.prototype = a.prototype; | ||
HTMLElement.prototype.constructor = HTMLElement; | ||
Object.setPrototypeOf(HTMLElement, a); | ||
injected = true; | ||
} | ||
*/ | ||
/** | ||
* Registers elements. | ||
*/ | ||
// @flow | ||
function define ( | ||
components /*: ElementMap */, | ||
defaults /*: ?Defaults */ | ||
) { | ||
Object.keys(components).forEach((name /*: string */) => { | ||
const elSpec /*: ElementSpec */ = toElementSpec(components[name]); | ||
defineOne(Object.assign({}, defaults, elSpec), name); | ||
}); | ||
} | ||
/*:: | ||
import type { | ||
Component, | ||
PropertyMap, | ||
ElementMap, | ||
Defaults, | ||
ElementSpec, | ||
ReactAdapter, | ||
ElementEvents | ||
} from './types' | ||
*/ | ||
function toElementSpec ( | ||
thing /*: ElementSpec | Component */ | ||
) /*: ElementSpec */ { | ||
// $FlowFixMe$ | ||
if (typeof thing === 'object' && thing.component) return thing | ||
return { component: thing } | ||
} | ||
/** | ||
* Registers one element. | ||
* Registers a custom element. | ||
* | ||
* This creates a custom element (ie, a subclass of `window.HTMLElement`) and | ||
* registers it (ie, `window.customElements.define`). | ||
* | ||
* Events will be triggered when something interesting happens. | ||
* | ||
* @example | ||
* defineElement( | ||
* { component: Tooltip }, | ||
* 'x-tooltip', | ||
* { onUpdate, onUnmount } | ||
* ) | ||
* | ||
* @private | ||
*/ | ||
function defineOne (elSpec /*: ElementSpec */, name /*: string */) { | ||
function defineElement ( | ||
elSpec /*: ElementSpec */, | ||
name /*: string */, | ||
{ onUpdate, onUnmount } /*: ElementEvents */ | ||
) { | ||
inject(); | ||
const attributes = elSpec.attributes || []; | ||
@@ -74,3 +87,3 @@ | ||
this._mountPoint = createMountPoint(this, elSpec); | ||
update(this, elSpec, this._mountPoint); | ||
onUpdate(this, this._mountPoint); | ||
} | ||
@@ -80,3 +93,3 @@ | ||
if (!this._mountPoint) return | ||
ReactDOM.unmountComponentAtNode(this._mountPoint); | ||
onUnmount(this, this._mountPoint); | ||
} | ||
@@ -86,8 +99,6 @@ | ||
if (!this._mountPoint) return | ||
update(this, elSpec, this._mountPoint); | ||
onUpdate(this, this._mountPoint); | ||
} | ||
} | ||
if (!ensureSupported()) return | ||
// Supress warning when quiet mode is on | ||
@@ -101,20 +112,4 @@ if (elSpec.quiet && window.customElements.get(name)) { | ||
/** | ||
* Ensures that custom elements are supported | ||
* @private | ||
*/ | ||
function ensureSupported () { | ||
if (!window.customElements || !window.customElements.define) { | ||
console.error( | ||
"remount: Custom elements aren't support in this browser. " + | ||
'Remount will not work. ' + | ||
'Including polyfills will likely fix this. ' + | ||
'See Remount documentation for more info: ' + | ||
'https://github.com/rstacruz/remount' | ||
); | ||
return false | ||
} | ||
return true | ||
function isSupported () { | ||
return window.customElements && window.customElements.define | ||
} | ||
@@ -124,3 +119,3 @@ | ||
* Creates a `<span>` element that serves as the mounting point for React | ||
* components. | ||
* components. If `shadow: true` is requested, it'll attach a shadow node. | ||
* @private | ||
@@ -142,2 +137,54 @@ */ | ||
const name = 'CustomElements'; | ||
var ElementsAdapter = /*#__PURE__*/Object.freeze({ | ||
defineElement: defineElement, | ||
isSupported: isSupported, | ||
name: name | ||
}); | ||
function isSupported$1 () { | ||
return !!window.MutationObserver | ||
} | ||
function defineElement$1 (elSpec, name, { onUpdate, onUnmount }) { | ||
name = name.toLowerCase(); | ||
const observer = new window.MutationObserver(mutations => { | ||
mutations.forEach(mutation => { | ||
mutation.addedNodes.forEach(node => { | ||
if (node.nodeName.toLowerCase() !== name) return | ||
onUpdate(node, node); | ||
}); | ||
// todo handle update | ||
mutation.removedNodes.forEach(node => { | ||
if (node.nodeName.toLowerCase() !== name) return | ||
onUnmount(node, node); | ||
}); | ||
}); | ||
}); | ||
observer.observe(document.body, { | ||
attributes: true, | ||
childList: true, | ||
subtree: true | ||
}); | ||
} | ||
const name$1 = 'MutationObserver'; | ||
var MutationAdapter = /*#__PURE__*/Object.freeze({ | ||
isSupported: isSupported$1, | ||
defineElement: defineElement$1, | ||
name: name$1 | ||
}); | ||
// @flow | ||
/*:: | ||
import type { ElementSpec } from './types' | ||
*/ | ||
/** | ||
@@ -149,12 +196,7 @@ * Updates a custom element by calling `ReactDOM.render()`. | ||
function update ( | ||
element /* Element */, | ||
{ component, attributes } /*: ElementSpec */, | ||
mountPoint /* Element */ | ||
mountPoint /*: Element */, | ||
props /*: {} */ | ||
) { | ||
const props = element.hasAttribute('props-json') | ||
? JSON.parse(element.getAttribute('props-json')) | ||
: getProps(element, attributes); | ||
const reactElement = React.createElement(component, props); | ||
ReactDOM.render(reactElement, mountPoint); | ||
@@ -164,7 +206,104 @@ } | ||
/** | ||
* Unmounts a component. | ||
* @private | ||
*/ | ||
function unmount (_ /*: any */, mountPoint /*: Element */) { | ||
ReactDOM.unmountComponentAtNode(mountPoint); | ||
} | ||
// @flow | ||
/*:: | ||
import type { | ||
Component, | ||
PropertyMap, | ||
ElementMap, | ||
Defaults, | ||
ElementSpec | ||
} from './lib/types' | ||
*/ | ||
const Adapter = isSupported() | ||
? ElementsAdapter | ||
: isSupported$1() | ||
? MutationAdapter | ||
: null; | ||
if (!Adapter) { | ||
throw new Error('Unsupported platform') | ||
} else { | ||
console.log('Remount: using adapter', Adapter.name); | ||
} | ||
/** | ||
* Inspect `Remount.adapterName` to see what adapter's being used. | ||
*/ | ||
const adapterName = Adapter.name; | ||
/** | ||
* Registers elements. | ||
*/ | ||
function define ( | ||
components /*: ElementMap */, | ||
defaults /*: ?Defaults */ | ||
) { | ||
Object.keys(components).forEach((name$$1 /*: string */) => { | ||
// Construct the specs for the element. | ||
// (eg, { component: Tooltip, attributes: ['title'] }) | ||
const elSpec /*: ElementSpec */ = Object.assign( | ||
{}, | ||
defaults, | ||
toElementSpec(components[name$$1]) | ||
); | ||
// Define a custom element. | ||
Adapter.defineElement(elSpec, name$$1, { | ||
onUpdate (element /*: Element */, mountPoint /*: Element */) { | ||
const props = getProps(element, elSpec.attributes); | ||
update(elSpec, mountPoint, props); | ||
}, | ||
onUnmount (element /*: Element */, mountPoint /*: Element */) { | ||
unmount(elSpec, mountPoint); | ||
} | ||
}); | ||
}); | ||
} | ||
/** | ||
* Coerces something into an `ElementSpec` type. | ||
* @private | ||
* | ||
* @example | ||
* toElementSpec(Tooltip) | ||
* // => { component: Tooltip } | ||
* | ||
* toElementSpec({ component: Tooltip }) | ||
* // => { component: Tooltip } | ||
*/ | ||
function toElementSpec ( | ||
thing /*: ElementSpec | Component */ | ||
) /*: ElementSpec */ { | ||
// $FlowFixMe$ | ||
if (typeof thing === 'object' && thing.component) return thing | ||
return { component: thing } | ||
} | ||
/** | ||
* Returns properties for a given HTML element. | ||
* @private | ||
* | ||
* @example | ||
* getProps(div, ['name']) | ||
* // => { name: 'Romeo' } | ||
*/ | ||
function getProps (element /*: Element */, attributes /*: ?Array<string> */) { | ||
const rawJson = element.getAttribute('props-json'); | ||
if (rawJson) return JSON.parse(rawJson) | ||
const names /*: Array<string> */ = attributes || []; | ||
@@ -175,7 +314,5 @@ return names.reduce((result /*: PropertyMap */, attribute /*: string */) => { | ||
}, {}) | ||
// By the way, did you know el.getAttributeNames() | ||
// will not work in IE11? Now you do. | ||
} | ||
exports.adapterName = adapterName; | ||
exports.define = define; | ||
@@ -182,0 +319,0 @@ |
@@ -1,1 +0,1 @@ | ||
(function(a,b){'object'==typeof exports&&'undefined'!=typeof module?b(exports,require('react'),require('react-dom')):'function'==typeof define&&define.amd?define(['exports','react','react-dom'],b):b(a.Remount={},a.React,a.ReactDOM)})(this,function(a,b,c){'use strict';function d(a){return'object'==typeof a&&a.component?a:{component:a}}function e(a,b){const d=a.attributes||[];class e extends window.HTMLElement{static get observedAttributes(){return['props-json',...d]}connectedCallback(){this._mountPoint=g(this,a),h(this,a,this._mountPoint)}disconnectedCallback(){this._mountPoint&&c.unmountComponentAtNode(this._mountPoint)}attributeChangedCallback(){this._mountPoint&&h(this,a,this._mountPoint)}}!f()||a.quiet&&window.customElements.get(b)||window.customElements.define(b,e)}function f(){return!!(window.customElements&&window.customElements.define)||(console.error('remount: Custom elements aren\'t support in this browser. Remount will not work. Including polyfills will likely fix this. See Remount documentation for more info: https://github.com/rstacruz/remount'),!1)}function g(a,{shadow:b}){if(b){const b=document.createElement('span');return a.attachShadow({mode:'open'}).appendChild(b),b}return a}function h(a,{component:d,attributes:e},f){const g=a.hasAttribute('props-json')?JSON.parse(a.getAttribute('props-json')):i(a,e),h=b.createElement(d,g);c.render(h,f)}function i(a,b){return(b||[]).reduce((b,c)=>(b[c]=a.getAttribute(c),b),{})}c=c&&c.hasOwnProperty('default')?c['default']:c,a.define=function(a,b){Object.keys(a).forEach((c)=>{const f=d(a[c]);e(Object.assign({},b,f),c)})},Object.defineProperty(a,'__esModule',{value:!0})}); | ||
(function(a,b){'object'==typeof exports&&'undefined'!=typeof module?b(exports,require('react'),require('react-dom')):'function'==typeof define&&define.amd?define(['exports','react','react-dom'],b):b(a.Remount={},a.React,a.ReactDOM)})(this,function(a,b,c){'use strict';function d(){if(l||void 0===window.Reflect||void 0===window.customElements||window.customElements.hasOwnProperty('polyfillWrapFlushCallback'))return;const b=HTMLElement;window.HTMLElement=function(){return Reflect.construct(b,[],this.constructor)},HTMLElement.prototype=b.prototype,HTMLElement.prototype.constructor=HTMLElement,Object.setPrototypeOf(HTMLElement,b),l=!0}function e(){return window.customElements&&window.customElements.define}function f(a,{shadow:b}){if(b){const b=document.createElement('span');return a.attachShadow({mode:'open'}).appendChild(b),b}return a}function g(){return!!window.MutationObserver}function h({component:a,attributes:d},e,f){const g=b.createElement(a,f);c.render(g,e)}function i(a,b){c.unmountComponentAtNode(b)}function j(a){return'object'==typeof a&&a.component?a:{component:a}}function k(a,b){const c=a.getAttribute('props-json');if(c)return JSON.parse(c);return(b||[]).reduce((b,c)=>(b[c]=a.getAttribute(c),b),{})}c=c&&c.hasOwnProperty('default')?c['default']:c;let l;var m=Object.freeze({defineElement:function(a,b,{onUpdate:c,onUnmount:e}){d();const g=a.attributes||[];class h extends window.HTMLElement{static get observedAttributes(){return['props-json',...g]}connectedCallback(){this._mountPoint=f(this,a),c(this,this._mountPoint)}disconnectedCallback(){this._mountPoint&&e(this,this._mountPoint)}attributeChangedCallback(){this._mountPoint&&c(this,this._mountPoint)}}a.quiet&&window.customElements.get(b)||window.customElements.define(b,h)},isSupported:e,name:'CustomElements'});var n=Object.freeze({isSupported:g,defineElement:function(a,b,{onUpdate:c,onUnmount:d}){b=b.toLowerCase();const e=new window.MutationObserver(a=>{a.forEach(a=>{a.addedNodes.forEach(a=>{a.nodeName.toLowerCase()!==b||c(a,a)}),a.removedNodes.forEach(a=>{a.nodeName.toLowerCase()!==b||d(a,a)})})});e.observe(document.body,{attributes:!0,childList:!0,subtree:!0})},name:'MutationObserver'});const o=e()?m:g()?n:null;if(!o)throw new Error('Unsupported platform');else console.log('Remount: using adapter',o.name);const p=o.name;a.adapterName=p,a.define=function(a,b){Object.keys(a).forEach((c)=>{const d=Object.assign({},b,j(a[c]));o.defineElement(d,c,{onUpdate(a,b){const c=k(a,d.attributes);h(d,b,c)},onUnmount(a,b){i(d,b)}})})},Object.defineProperty(a,'__esModule',{value:!0})}); |
184
index.js
// @flow | ||
import * as React from 'react' | ||
import ReactDOM from 'react-dom' | ||
import * as ElementsAdapter from './lib/custom_elements' | ||
import * as MutationAdapter from './lib/mutation_observer' | ||
import * as ReactAdapter from './lib/react' | ||
/*:: | ||
export type Component = React.ComponentType<{}> | ||
import type { | ||
Component, | ||
PropertyMap, | ||
ElementMap, | ||
Defaults, | ||
ElementSpec | ||
} from './lib/types' | ||
*/ | ||
export type PropertyMap = { | ||
[string]: ?string | ||
} | ||
const Adapter = ElementsAdapter.isSupported() | ||
? ElementsAdapter | ||
: MutationAdapter.isSupported() | ||
? MutationAdapter | ||
: null | ||
export type ElementMap = { | ||
[string]: ElementSpec | Component | ||
if (!Adapter) { | ||
throw new Error('Unsupported platform') | ||
} else { | ||
console.log('Remount: using adapter', Adapter.name) | ||
} | ||
export type Defaults = { | ||
attributes?: Array<string>, | ||
quiet?: boolean, | ||
shadow?: boolean | ||
} | ||
/** | ||
* Inspect `Remount.adapterName` to see what adapter's being used. | ||
*/ | ||
export type ElementSpec = { | ||
component: Component, | ||
attributes?: Array<string>, | ||
quiet?: boolean, | ||
shadow?: boolean | ||
} | ||
*/ | ||
export const adapterName = Adapter.name | ||
@@ -39,7 +43,36 @@ /** | ||
Object.keys(components).forEach((name /*: string */) => { | ||
const elSpec /*: ElementSpec */ = toElementSpec(components[name]) | ||
defineOne(Object.assign({}, defaults, elSpec), name) | ||
// Construct the specs for the element. | ||
// (eg, { component: Tooltip, attributes: ['title'] }) | ||
const elSpec /*: ElementSpec */ = Object.assign( | ||
{}, | ||
defaults, | ||
toElementSpec(components[name]) | ||
) | ||
// Define a custom element. | ||
Adapter.defineElement(elSpec, name, { | ||
onUpdate (element /*: Element */, mountPoint /*: Element */) { | ||
const props = getProps(element, elSpec.attributes) | ||
ReactAdapter.update(elSpec, mountPoint, props) | ||
}, | ||
onUnmount (element /*: Element */, mountPoint /*: Element */) { | ||
ReactAdapter.unmount(elSpec, mountPoint) | ||
} | ||
}) | ||
}) | ||
} | ||
/** | ||
* Coerces something into an `ElementSpec` type. | ||
* @private | ||
* | ||
* @example | ||
* toElementSpec(Tooltip) | ||
* // => { component: Tooltip } | ||
* | ||
* toElementSpec({ component: Tooltip }) | ||
* // => { component: Tooltip } | ||
*/ | ||
function toElementSpec ( | ||
@@ -54,104 +87,14 @@ thing /*: ElementSpec | Component */ | ||
/** | ||
* Registers one element. | ||
* @private | ||
*/ | ||
function defineOne (elSpec /*: ElementSpec */, name /*: string */) { | ||
const attributes = elSpec.attributes || [] | ||
class ComponentElement extends window.HTMLElement { | ||
static get observedAttributes () { | ||
return ['props-json', ...attributes] | ||
} | ||
connectedCallback () { | ||
this._mountPoint = createMountPoint(this, elSpec) | ||
update(this, elSpec, this._mountPoint) | ||
} | ||
disconnectedCallback () { | ||
if (!this._mountPoint) return | ||
ReactDOM.unmountComponentAtNode(this._mountPoint) | ||
} | ||
attributeChangedCallback () { | ||
if (!this._mountPoint) return | ||
update(this, elSpec, this._mountPoint) | ||
} | ||
} | ||
if (!ensureSupported()) return | ||
// Supress warning when quiet mode is on | ||
if (elSpec.quiet && window.customElements.get(name)) { | ||
return | ||
} | ||
window.customElements.define(name, ComponentElement) | ||
} | ||
/** | ||
* Ensures that custom elements are supported | ||
* @private | ||
*/ | ||
function ensureSupported () { | ||
if (!window.customElements || !window.customElements.define) { | ||
console.error( | ||
"remount: Custom elements aren't support in this browser. " + | ||
'Remount will not work. ' + | ||
'Including polyfills will likely fix this. ' + | ||
'See Remount documentation for more info: ' + | ||
'https://github.com/rstacruz/remount' | ||
) | ||
return false | ||
} | ||
return true | ||
} | ||
/** | ||
* Creates a `<span>` element that serves as the mounting point for React | ||
* components. | ||
* @private | ||
*/ | ||
function createMountPoint ( | ||
element /*: Element */, | ||
{ shadow } /*: ElementSpec */ | ||
) { | ||
if (shadow) { | ||
const mountPoint = document.createElement('span') | ||
element.attachShadow({ mode: 'open' }).appendChild(mountPoint) | ||
return mountPoint | ||
} else { | ||
return element | ||
} | ||
} | ||
/** | ||
* Updates a custom element by calling `ReactDOM.render()`. | ||
* @private | ||
*/ | ||
function update ( | ||
element /* Element */, | ||
{ component, attributes } /*: ElementSpec */, | ||
mountPoint /* Element */ | ||
) { | ||
const props = element.hasAttribute('props-json') | ||
? JSON.parse(element.getAttribute('props-json')) | ||
: getProps(element, attributes) | ||
const reactElement = React.createElement(component, props) | ||
ReactDOM.render(reactElement, mountPoint) | ||
} | ||
/** | ||
* Returns properties for a given HTML element. | ||
* @private | ||
* | ||
* @example | ||
* getProps(div, ['name']) | ||
* // => { name: 'Romeo' } | ||
*/ | ||
function getProps (element /*: Element */, attributes /*: ?Array<string> */) { | ||
const rawJson = element.getAttribute('props-json') | ||
if (rawJson) return JSON.parse(rawJson) | ||
const names /*: Array<string> */ = attributes || [] | ||
@@ -162,5 +105,2 @@ return names.reduce((result /*: PropertyMap */, attribute /*: string */) => { | ||
}, {}) | ||
// By the way, did you know el.getAttributeNames() | ||
// will not work in IE11? Now you do. | ||
} |
{ | ||
"name": "remount", | ||
"description": "Mount React components to the DOM using custom elements", | ||
"version": "0.5.0", | ||
"version": "0.7.0", | ||
"author": "Rico Sta. Cruz <rstacruz@users.noreply.github.com>", | ||
@@ -16,2 +16,3 @@ "bugs": { | ||
"flow-bin": "^0.79.1", | ||
"gh-pages": "^1.2.0", | ||
"jest": "^23.5.0", | ||
@@ -69,2 +70,3 @@ "npm-run-all": "^4.1.3", | ||
"eslint": "eslint '*.js'", | ||
"deploy": "gh-pages -d . -s 'dist/**/*'", | ||
"flow": "flow", | ||
@@ -71,0 +73,0 @@ "jest": "jest", |
100
README.md
<br> | ||
<p align='center'><img src='docs/images/remount.png' width='500'></p> | ||
<p align='center'><a href='https://github.com/rstacruz/remount'><img src='docs/images/remount.png' width='450'></a></p> | ||
<br> | ||
<h1 align='center'>Remount</h1> | ||
<p align='center'> | ||
⚡ <a href='https://codepen.io/rstacruz/pen/EpBZRv?editors=1010'><b>Demo</b></a> ⚡ | ||
<a href='https://travis-ci.org/rstacruz/remount'><img src='https://api.travis-ci.org/rstacruz/remount.svg?branch=master' alt='Travis CI' /></a> | ||
</p> | ||
<p align='center'>Use your React components anywhere in your <br> HTML as web components (custom elements).</p> | ||
<p align='center'> | ||
<a href='https://codepen.io/rstacruz/pen/EpBZRv?editors=1010'>Demo</a> ⚡ <a href='https://github.com/rstacruz/remount#remount'>Docs</a> | ||
<br> | ||
1kb gzip'd · No dependencies · IE9 support | ||
<em>1kb gzip'd · No dependencies · IE support</em> | ||
</p> | ||
@@ -13,13 +23,7 @@ | ||
# Remount | ||
<br> | ||
> 🔌 Mount React components to the DOM using custom elements | ||
_Experimental_ - Remount lets you use your React components anywhere in the page as a web component (custom element). | ||
[![Status](https://travis-ci.org/rstacruz/remount.svg?branch=master)](https://travis-ci.org/rstacruz/remount 'See test builds') | ||
## Installation | ||
Remount is available through the [npm package repository](https://yarnpkg.com/en/package/remount). | ||
Remount is available through the npm package repository. | ||
@@ -29,7 +33,7 @@ - Via yarn: `yarn add remount` | ||
Be sure to use the [recommended polyfills](#polyfills) below as well! | ||
Be sure to use the recommended polyfills below as well. [#](#polyfills) | ||
## Usage | ||
Given any React component, such as this: | ||
Let's start with any React component. Here's one: | ||
@@ -42,3 +46,3 @@ ```js | ||
You can use _define()_ to define custom elements. Let's define `<x-greeter>` like so: | ||
Use _define()_ to define custom elements. Let's define a `<x-greeter>` element: | ||
@@ -48,8 +52,6 @@ ```js | ||
define({ | ||
'x-greeter': Greeter | ||
}) | ||
define({ 'x-greeter': Greeter }) | ||
``` | ||
You can then use it in your HTML, or even in your other React components! | ||
You can now use it anywhere in your HTML! :boom: | ||
@@ -60,3 +62,3 @@ ```html | ||
➡️ More at **[API documentation](docs/api.md)** | ||
⚡ **[API documentation →](docs/api.md)** | ||
@@ -69,61 +71,27 @@ ## Use cases | ||
| -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| <img src='http://source.unsplash.com/350x250?sea'> | ✨ **Adding React to non-SPA apps** <br> You can use React components on any page of a "regular" HTML site. Great for adding React to apps built on Rails or Phoenix. | | ||
| <br><img src='./docs/images/non-spa.png' width='400'><br><br> | ✨ **Adding React to non-SPA apps** <br> You can use React components on any page of a "regular" HTML site. Great for adding React to apps built on Rails or Phoenix. | | ||
| | | | ||
| <img src='http://source.unsplash.com/350x250?sun'> | 💞 **Interop with other frameworks** <br> Remount lets you use your React components just like any other HTML element. This means you can use React with Vue, Angular, or any other DOM library/framework. | | ||
| | | | ||
| <br><img src='./docs/images/interop.png' width='400'><br><br> | 💞 **Interop with other frameworks** <br> Remount lets you use your React components just like any other HTML element. This means you can use React with Vue, Angular, or any other DOM library/framework. | | ||
## Custom properties | ||
## More features | ||
> `<x-greeter name="John"></x-greeter>` | ||
- JSON props (eg, `<x-greeter props-json="{...}">`) ([docs](./docs/api.md)) | ||
- Named attributes (eg, `<x-greeter name="John">`) ([docs](./docs/api.md)) | ||
- Shadow DOM ([docs](./docs/api.md)) | ||
Only the `props-json` attribute is supported by default. To support custom properties like above, pass the names of attributes you want Remount to use. | ||
## Browser support | ||
```js | ||
import { define } from 'remount' | ||
Remount supports all modern browsers, including IE11 (Internet Explorer's oldest supported version as of 2016). Remember to use the polyfills below to ensure the best compatibility. | ||
define({ | ||
'x-greeter': { | ||
component: Greeter, | ||
attributes: ['name'] | ||
} | ||
}) | ||
``` | ||
⚡ [Browser support docs →](./docs/polyfills.md) | ||
## Limitations | ||
Remount relies on the [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) HTML API, so all limitations of the Custom Elements API apply. Keep these in mind: | ||
- Custom components names require a hyphen, and are case insensitive. | ||
- Attributes are case insensitive. | ||
## Browser support | ||
Remount supports all browsers [supported by React](https://reactjs.org/docs/react-dom.html#browser-support). Use the polyfills below to ensure the best compatibility. | ||
## Polyfills | ||
We recommend these two polyfills provided by the [@webcomponents/webcomponentsjs][@webcomponents/webcomponentsjs] package. Load it via JavaScript in your app's entry point: | ||
More info on this on the [Polyfill docs](./docs/polyfills.md). | ||
```js | ||
// Add the package via: yarn add @webcomponents/webcomponentsjs | ||
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js' | ||
import '@webcomponents/webcomponentsjs/webcomponents-loader.js' | ||
``` | ||
## Documentation | ||
Or you can load it via CDN: | ||
```html | ||
<script crossorigin src='https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs@2.0.4/custom-elements-es5-adapter.js'></script> | ||
<script crossorigin src='https://cdn.jsdelivr.net/npm/@webcomponents/webcomponentsjs@2.0.4/webcomponents-loader.js'></script> | ||
``` | ||
[@webcomponents/webcomponentsjs]: https://yarn.pm/@webcomponents/webcomponentsjs | ||
More info at the [Polyfills documentation](./docs/polyfills.md). | ||
## More info | ||
- [API documentation](./docs/api.md) | ||
- [Builds](./docs/builds.md) — ES2015+ and ES Module builds are also provided. | ||
- [API documentation](./docs/api.md) | ||
- [Frequently-asked questions](./docs/faq.md) | ||
- [FAQ and Troubleshooting](./docs/faq.md) — Start here if you find any issues. | ||
- [Comparison with alternatives](./docs/comparison.md) | ||
@@ -130,0 +98,0 @@ - [Polyfills](./docs/polyfills.md) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
905
3
44994
19
14
110
1