UNPKG

rax

Version:

A universal React-compatible render engine.

1,493 lines (1,409 loc) 91.1 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.Rax = {})); })(this, (function (exports) { /* * Stateful things in runtime */ var Host = { __mountID: 1, __isUpdating: false, // Inject driver: null, // Roots rootComponents: {}, rootInstances: {}, // Current owner component owner: null }; /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var ReactPropTypesSecret$1 = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'; var ReactPropTypesSecret_1 = ReactPropTypesSecret$1; var has$1 = Function.call.bind(Object.prototype.hasOwnProperty); /** * Copyright (c) 2013-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ var printWarning = function() {}; { var ReactPropTypesSecret = ReactPropTypesSecret_1; var loggedTypeFailures = {}; var has = has$1; printWarning = function(text) { var message = 'Warning: ' + text; if (typeof console !== 'undefined') { console.error(message); } try { // --- Welcome to debugging React --- // This error was thrown as a convenience so that you can use this stack // to find the callsite that caused this warning to fire. throw new Error(message); } catch (x) { /**/ } }; } /** * Assert that the values match with the type specs. * Error messages are memorized and will only be shown once. * * @param {object} typeSpecs Map of name to a ReactPropType * @param {object} values Runtime values that need to be type-checked * @param {string} location e.g. "prop", "context", "child context" * @param {string} componentName Name of the component for error messages. * @param {?Function} getStack Returns the component stack. * @private */ function checkPropTypes(typeSpecs, values, location, componentName, getStack) { { for (var typeSpecName in typeSpecs) { if (has(typeSpecs, typeSpecName)) { var error; // Prop type validation may throw. In case they do, we don't want to // fail the render phase where it didn't fail before. So we log it. // After these have been cleaned up, we'll let them throw. try { // This is intentionally an invariant that gets caught. It's the same // behavior as without this statement except with a better message. if (typeof typeSpecs[typeSpecName] !== 'function') { var err = Error( (componentName || 'React class') + ': ' + location + ' type `' + typeSpecName + '` is invalid; ' + 'it must be a function, usually from the `prop-types` package, but received `' + typeof typeSpecs[typeSpecName] + '`.' + 'This often happens because of typos such as `PropTypes.function` instead of `PropTypes.func`.' ); err.name = 'Invariant Violation'; throw err; } error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret); } catch (ex) { error = ex; } if (error && !(error instanceof Error)) { printWarning( (componentName || 'React class') + ': type specification of ' + location + ' `' + typeSpecName + '` is invalid; the type checker ' + 'function must return `null` or an `Error` but returned a ' + typeof error + '. ' + 'You may have forgotten to pass an argument to the type checker ' + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + 'shape all require an argument).' ); } if (error instanceof Error && !(error.message in loggedTypeFailures)) { // Only monitor this failure once because there tends to be a lot of the // same error. loggedTypeFailures[error.message] = true; var stack = getStack ? getStack() : ''; printWarning( 'Failed ' + location + ' type: ' + error.message + (stack != null ? stack : '') ); } } } } } /** * Resets warning cache when testing. * * @private */ checkPropTypes.resetWarningCache = function() { { loggedTypeFailures = {}; } }; var checkPropTypes_1 = checkPropTypes; var checkPropTypes$1 = checkPropTypes_1; function Element(type, key, ref, props, owner) { var element = { // Built-in properties that belong on the element type: type, key: key, ref: ref, props: props, // Record the component responsible for creating this element. _owner: owner }; { var propTypes = type.propTypes; // Validate its props provided by the propTypes definition if (propTypes) { var displayName = type.displayName || type.name; checkPropTypes$1(propTypes, props, 'prop', displayName); } // We make validation flag non-enumerable, so the test framework could ignore it Object.defineProperty(element, '__validated', { configurable: false, enumerable: false, writable: true, value: false }); // Props is immutable if (Object.freeze) { Object.freeze(props); } } return element; } function isNull(obj) { return obj === null; } function isFunction(obj) { return typeof obj === 'function'; } function isObject(obj) { return typeof obj === 'object'; } function isPlainObject(obj) { return EMPTY_OBJECT.toString.call(obj) === '[object Object]'; } function isArray(array) { return Array.isArray(array); } function isString(string) { return typeof string === 'string'; } function isNumber(string) { return typeof string === 'number'; } function isFalsy(val) { return !Boolean(val); } var NOOP = function NOOP() {}; var EMPTY_OBJECT = {}; function traverseChildren(children, result) { if (isArray(children)) { for (var i = 0, l = children.length; i < l; i++) { traverseChildren(children[i], result); } } else { result.push(children); } } function flattenChildren(children) { if (children == null) { return children; } var result = []; traverseChildren(children, result); // If length equal 1, return the only one. return result.length - 1 ? result : result[0]; } var updateCallbacks = []; var effectCallbacks = []; var layoutCallbacks = []; var scheduler = setTimeout; { // Wrapper timer for hijack timers in jest scheduler = function scheduler(callback) { setTimeout(callback); }; } function invokeFunctionsWithClear(callbacks) { var callback; while (callback = callbacks.shift()) { callback(); } } // Schedule before next render function schedule(callback) { if (updateCallbacks.length === 0) { scheduler(flush); } updateCallbacks.push(callback); } // Flush before next render function flush() { invokeFunctionsWithClear(updateCallbacks); } function scheduleEffect(callback) { if (effectCallbacks.length === 0) { scheduler(flushEffect); } effectCallbacks.push(callback); } function flushEffect() { invokeFunctionsWithClear(effectCallbacks); } function scheduleLayout(callback) { layoutCallbacks.push(callback); } function flushLayout() { invokeFunctionsWithClear(layoutCallbacks); } function createMinifiedError(type, code, obj) { var typeInfo = obj === undefined ? '' : ' got: ' + getTypeInfo(obj); return new Error(type + ": #" + code + ", " + getRenderErrorInfo() + "." + typeInfo); } function getTypeInfo(obj) { return isPlainObject(obj) ? Object.keys(obj) : obj; } function getRenderErrorInfo() { var ownerComponent = Host.owner; return ownerComponent ? "check <" + ownerComponent.__getName() + ">" : 'no owner'; } /** * Minified code: * 1: Hooks called outside a component, or multiple version of Rax are used. * 6: Invalid component type, expected a class or function component. * 4: Too many re-renders, the number of renders is limited to prevent an infinite loop. * 5: Rax driver not found. * @param code {Number} * @param obj {Object} */ function throwMinifiedError(code, obj) { throw createMinifiedError('Error', code, obj); } function throwError(message, obj) { var typeInfo = obj === undefined ? '' : '(found: ' + (isPlainObject(obj) ? "object with keys {" + Object.keys(obj) + "}" : obj) + ')'; throw Error(message + " " + typeInfo); } var warning = NOOP; { warning = function warning(template) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } if (typeof console !== 'undefined') { var argsWithFormat = args.map(function (item) { return '' + item; }); argsWithFormat.unshift('Warning: ' + template); // Don't use spread (or .apply) directly because it breaks IE9 Function.prototype.apply.call(console.error, console, argsWithFormat); } // For works in DevTools when enable `Pause on caught exceptions` // that can find the component where caused this warning try { var argIndex = 0; var message = 'Warning: ' + template.replace(/%s/g, function () { return args[argIndex++]; }); throw new Error(message); } catch (e) {} }; } /** * Warn if there's no key explicitly set on dynamic arrays of children or * object keys are not valid. This allows us to keep track of children between * updates. */ var ownerHasKeyUseWarning = {}; function getCurrentComponentErrorInfo(parentType) { var info = ''; var ownerComponent = Host.owner; if (ownerComponent) { var name = ownerComponent.__getName(); if (name) { info = " Check the render method of <" + name + ">."; } } if (!info) { var parentName = typeof parentType === 'string' ? parentType : parentType.displayName || parentType.name; if (parentName) { info = " Check the top-level render call using <" + parentName + ">."; } } return info; } function isValidElement(object) { return typeof object === 'object' && object !== null && object.type && !!object.props; } function validateExplicitKey(element, parentType) { if (element.__validated || element.key != null) { return; } element.__validated = true; var currentComponentErrorInfo = getCurrentComponentErrorInfo(parentType); if (ownerHasKeyUseWarning[currentComponentErrorInfo]) { return; } ownerHasKeyUseWarning[currentComponentErrorInfo] = true; // Usually the current owner is the offender, but if it accepts children as a // property, it may be the creator of the child that's responsible for // assigning it a key. var childOwner = ''; if (element && element._owner && element._owner !== Host.owner) { // Give the component that originally created this child. childOwner = " It was passed a child from <" + element._owner.__getName() + ">."; } warning("Each child in a list should have a unique \"key\" prop." + currentComponentErrorInfo + childOwner); } function validateChildKeys(node, parentType) { // Only array or element object is valid child if (typeof node !== 'object') { return; } if (isArray(node)) { for (var i = 0; i < node.length; i++) { var child = node[i]; if (isValidElement(child)) { validateExplicitKey(child, parentType); } } } else if (isValidElement(node)) { node.__validated = true; } // Rax don't support iterator object as element children // TODO: add validate when rax support iterator object as element. } var RESERVED_PROPS = { key: true, ref: true }; function createElement(type, config, children) { // Reserved names are extracted var props = {}; var propName; var key = null; var ref = null; if (config != null) { ref = config.ref === undefined ? null : config.ref; key = config.key === undefined ? null : '' + config.key; // Remaining properties are added to a new props object for (propName in config) { if (!RESERVED_PROPS[propName]) { props[propName] = config[propName]; } } } // Children arguments can be more than one var childrenLength = arguments.length - 2; if (childrenLength > 0) { if (childrenLength === 1 && !isArray(children)) { props.children = children; } else { var childArray = children; if (childrenLength > 1) { childArray = new Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } } props.children = flattenChildren(childArray); } } // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } if (type == null) { { throwError("Invalid element type, expected a string or a class/function component but got \"" + type + "\"."); } } { if (isString(ref) && !Host.owner) { warning("Adding a string ref \"" + ref + "\" that was not created inside render method, or multiple copies of Rax are used."); } for (var _i = 2; _i < arguments.length; _i++) { validateChildKeys(arguments[_i], type); } } return new Element(type, key, ref, props, Host.owner); } function invokeFunctionsWithContext(fns, context, value) { for (var i = 0, l = fns && fns.length; i < l; i++) { fns[i].call(context, value); } } var hasOwnProperty = EMPTY_OBJECT.hasOwnProperty; /** * inlined Object.is polyfill to avoid requiring consumers ship their own * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is */ function is(x, y) { // SameValue algorithm if (x === y) { // Steps 1-5, 7-10 // Steps 6.b-6.e: +0 != -0 return x !== 0 || 1 / x === 1 / y; } else { // Step 6.a: NaN == NaN return x !== x && y !== y; // eslint-disable-line no-self-compare } } /** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. * Returns true when the values of all keys are strictly equal. */ function shallowEqual(objA, objB) { if (is(objA, objB)) { return true; } if (!isObject(objA) || isNull(objA) || !isObject(objB) || isNull(objB)) { return false; } var keysA = Object.keys(objA); var keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (var i = 0; i < keysA.length; i++) { if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } /* Common constant variables for rax */ var INTERNAL = '_internal'; var INSTANCE = '_instance'; var NATIVE_NODE = '_nativeNode'; var RENDERED_COMPONENT = '_renderedComponent'; function getCurrentInstance() { return Host.owner && Host.owner[INSTANCE]; } function getCurrentRenderingInstance() { var currentInstance = getCurrentInstance(); if (currentInstance) { return currentInstance; } else { { throwError('Hooks called outside a component, or multiple version of Rax are used.'); } } } function areInputsEqual(inputs, prevInputs) { if (isNull(prevInputs) || inputs.length !== prevInputs.length) { return false; } for (var i = 0; i < inputs.length; i++) { if (is(inputs[i], prevInputs[i])) { continue; } return false; } return true; } function useState(initialState) { var currentInstance = getCurrentRenderingInstance(); var hookID = currentInstance.getHookID(); var hooks = currentInstance.getHooks(); if (!hooks[hookID]) { // If the initial state is the result of an expensive computation, // you may provide a function instead for lazy initial state. if (isFunction(initialState)) { initialState = initialState(); } var setState = function setState(newState) { // Flush all effects first before update state if (!Host.__isUpdating) { flushEffect(); } var hook = hooks[hookID]; var eagerState = hook[2]; // function updater if (isFunction(newState)) { newState = newState(eagerState); } if (!is(newState, eagerState)) { // Current instance is in render update phase. // After this one render finish, will continue run. hook[2] = newState; if (getCurrentInstance() === currentInstance) { // Marked as is scheduled that could finish hooks. currentInstance.__isScheduled = true; } else { currentInstance.__update(); } } }; hooks[hookID] = [initialState, setState, initialState]; } var hook = hooks[hookID]; if (!is(hook[0], hook[2])) { hook[0] = hook[2]; currentInstance.__shouldUpdate = true; } return hook; } function useContext(context) { var currentInstance = getCurrentRenderingInstance(); return currentInstance.useContext(context); } function useEffect(effect, inputs) { useEffectImpl(effect, inputs, true); } function useLayoutEffect(effect, inputs) { useEffectImpl(effect, inputs); } function useEffectImpl(effect, inputs, defered) { var currentInstance = getCurrentRenderingInstance(); var hookID = currentInstance.getHookID(); var hooks = currentInstance.getHooks(); inputs = inputs === undefined ? null : inputs; if (!hooks[hookID]) { var __create = function __create(immediately) { if (!immediately && defered) return scheduleEffect(function () { return __create(true); }); var current = __create.current; if (current) { __destory.current = current(); __create.current = null; { var currentDestory = __destory.current; if (currentDestory !== undefined && typeof currentDestory !== 'function') { var msg; if (currentDestory === null) { msg = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).'; } else if (typeof currentDestory.then === 'function') { msg = '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' + 'Instead, write the async function inside your effect ' + 'and call it immediately:\n\n' + 'useEffect(() => {\n' + ' async function fetchData() {\n' + ' // You can await here\n' + ' const response = await MyAPI.getData(someId);\n' + ' // ...\n' + ' }\n' + ' fetchData();\n' + '}, [someId]); // Or [] if effect doesn\'t need props or state.'; } else { msg = ' You returned: ' + currentDestory; } warning('An effect function must not return anything besides a function, ' + 'which is used for clean-up.' + msg); } } } }; var __destory = function __destory(immediately) { if (!immediately && defered) return scheduleEffect(function () { return __destory(true); }); var current = __destory.current; if (current) { current(); __destory.current = null; } }; __create.current = effect; hooks[hookID] = { __create: __create, __destory: __destory, __prevInputs: inputs, __inputs: inputs }; currentInstance.didMount.push(__create); currentInstance.willUnmount.push(function () { return __destory(true); }); currentInstance.didUpdate.push(function () { var _hooks$hookID = hooks[hookID], __prevInputs = _hooks$hookID.__prevInputs, __inputs = _hooks$hookID.__inputs, __create = _hooks$hookID.__create; if (__inputs == null || !areInputsEqual(__inputs, __prevInputs)) { __destory(); __create(); } }); } else { var hook = hooks[hookID]; var _create = hook.__create, prevInputs = hook.__inputs; hook.__inputs = inputs; hook.__prevInputs = prevInputs; _create.current = effect; } } function useImperativeHandle(ref, create, inputs) { var nextInputs = isArray(inputs) ? inputs.concat([ref]) : null; useLayoutEffect(function () { if (isFunction(ref)) { ref(create()); return function () { return ref(null); }; } else if (ref != null) { ref.current = create(); return function () { ref.current = null; }; } }, nextInputs); } function useRef(initialValue) { var currentInstance = getCurrentRenderingInstance(); var hookID = currentInstance.getHookID(); var hooks = currentInstance.getHooks(); if (!hooks[hookID]) { hooks[hookID] = { current: initialValue }; } return hooks[hookID]; } function useCallback(callback, inputs) { return useMemo(function () { return callback; }, inputs); } function useMemo(create, inputs) { var currentInstance = getCurrentRenderingInstance(); var hookID = currentInstance.getHookID(); var hooks = currentInstance.getHooks(); inputs = inputs === undefined ? null : inputs; if (!hooks[hookID]) { hooks[hookID] = [create(), inputs]; } else { var prevInputs = hooks[hookID][1]; if (isNull(inputs) || !areInputsEqual(inputs, prevInputs)) { hooks[hookID] = [create(), inputs]; } } return hooks[hookID][0]; } function useReducer(reducer, initialArg, init) { var currentInstance = getCurrentRenderingInstance(); var hookID = currentInstance.getHookID(); var hooks = currentInstance.getHooks(); var hook = hooks[hookID]; if (!hook) { var initialState = isFunction(init) ? init(initialArg) : initialArg; var dispatch = function dispatch(action) { // Flush all effects first before update state if (!Host.__isUpdating) { flushEffect(); } var hook = hooks[hookID]; // Reducer will update in the next render, before that we add all // actions to the queue var queue = hook[2]; if (getCurrentInstance() === currentInstance) { queue.__actions.push(action); currentInstance.__isScheduled = true; } else { var currentState = queue.__eagerState; var eagerReducer = queue.__eagerReducer; var eagerState = eagerReducer(currentState, action); if (is(eagerState, currentState)) { return; } queue.__eagerState = eagerState; queue.__actions.push(action); currentInstance.__update(); } }; return hooks[hookID] = [initialState, dispatch, { __actions: [], __eagerReducer: reducer, __eagerState: initialState }]; } var queue = hook[2]; var next = hook[0]; if (currentInstance.__reRenders > 0) { for (var i = 0; i < queue.__actions.length; i++) { next = reducer(next, queue.__actions[i]); } } else { next = queue.__eagerState; } if (!is(next, hook[0])) { hook[0] = next; currentInstance.__shouldUpdate = true; } queue.__eagerReducer = reducer; queue.__eagerState = next; queue.__actions.length = 0; return hooks[hookID]; } function toArray(obj) { return isArray(obj) ? obj : [obj]; } function getNearestParent(instance, matcher) { var parent; while (instance && instance[INTERNAL]) { if (matcher(instance)) { parent = instance; break; } instance = instance[INTERNAL].__parentInstance; } return parent; } var id = 0; function createContext(defaultValue) { var contextID = '_c' + id++; // Provider Component var Provider = /*#__PURE__*/function () { function Provider() { this.__contextID = contextID; this.__handlers = []; } var _proto = Provider.prototype; _proto.__on = function __on(handler) { this.__handlers.push(handler); }; _proto.__off = function __off(handler) { this.__handlers = this.__handlers.filter(function (h) { return h !== handler; }); } // Like getChildContext but called in SSR ; _proto._getChildContext = function _getChildContext() { var _ref; return _ref = {}, _ref[contextID] = this, _ref; } // `getValue()` called in rax-server-renderer ; _proto.getValue = function getValue() { return this.props.value !== undefined ? this.props.value : defaultValue; }; _proto.componentDidUpdate = function componentDidUpdate(prevProps) { if (this.props.value !== prevProps.value) { invokeFunctionsWithContext(this.__handlers, null, this.getValue()); } }; _proto.render = function render() { return this.props.children; }; return Provider; }(); function getNearestParentProvider(instance) { return getNearestParent(instance, function (parent) { return parent.__contextID === contextID; }); } // Consumer Component function Consumer(props, context) { var _this = this; // Current `context[contextID]` only works in SSR var _useState = useState(function () { return context[contextID] || getNearestParentProvider(_this); }), provider = _useState[0]; var value = provider ? provider.getValue() : defaultValue; var _useState2 = useState(value), prevValue = _useState2[0], setValue = _useState2[1]; if (value !== prevValue) { setValue(value); return; // Interrupt execution of consumer. } useLayoutEffect(function () { if (provider) { provider.__on(setValue); return function () { provider.__off(setValue); }; } }, []); // Consumer requires a function as a child. // The function receives the current context value. var consumer = toArray(props.children)[0]; if (isFunction(consumer)) { return consumer(value); } } return { Provider: Provider, Consumer: Consumer, // `_contextID` and `_defaultValue` accessed in rax-server-renderer _contextID: contextID, _defaultValue: defaultValue, __getNearestParentProvider: getNearestParentProvider }; } function createRef() { return { current: null }; } function forwardRef (render) { // _forwardRef is also use in rax server renderer render._forwardRef = true; return render; } function memo(type, compare) { compare = compare || shallowEqual; // Memo could composed if (type.__compares) { type.__compares.push(compare); } else { type.__compares = [compare]; } return type; } function Fragment(props) { return props.children; } function injectRenderOptions(_ref) { var driver = _ref.driver, measurer = _ref.measurer; // Inject render driver if (!(Host.driver = driver || Host.driver)) { { throwError('Rax driver not found.'); } } { // Inject performance measurer Host.measurer = measurer; } } function instantiateComponent(element) { var instance; if (isPlainObject(element) && element !== null && element.type) { // Special case string values if (isString(element.type)) { instance = new Host.__Native(element); } else { instance = new Host.__Composite(element); } } else if (isString(element) || isNumber(element)) { instance = new Host.__Text(String(element)); } else if (isArray(element)) { instance = new Host.__Fragment(element); } else { if (!(element === undefined || isNull(element) || element === false || element === true)) { { throwError('Invalid child type, expected types: Element instance, string, boolean, array, null, undefined.', element); } } instance = new Host.__Empty(element); } return instance; } function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } /** * Base component class. */ var Component = /*#__PURE__*/function () { function Component(props, context) { this.props = props; this.context = context; this.refs = {}; } var _proto = Component.prototype; _proto.setState = function setState(partialState, callback) { // The updater property is injected when composite component mounting this.updater.setState(this, partialState, callback); }; _proto.forceUpdate = function forceUpdate(callback) { this.updater.forceUpdate(this, callback); }; return Component; }(); var PureComponent = /*#__PURE__*/function (_Component) { _inheritsLoose(PureComponent, _Component); function PureComponent(props, context) { var _this; _this = _Component.call(this, props, context) || this; _this.__isPureComponent = true; return _this; } return PureComponent; }(Component); var rootID = 1; var Root = /*#__PURE__*/function (_Component) { _inheritsLoose(Root, _Component); function Root() { var _this; _this = _Component.call(this) || this; // Using fragment instead of null for avoid create a comment node when init mount _this.__element = []; _this.__rootID = rootID++; return _this; } var _proto = Root.prototype; _proto.__getPublicInstance = function __getPublicInstance() { return this.__getRenderedComponent().__getPublicInstance(); }; _proto.__getRenderedComponent = function __getRenderedComponent() { return this[INTERNAL][RENDERED_COMPONENT]; }; _proto.__update = function __update(element) { this.__element = element; this.forceUpdate(); }; _proto.render = function render() { return this.__element; }; return Root; }(Component); /** * Instance manager * @NOTE Key should not be compressed, for that will be added to native node and cause DOM Exception. */ var KEY = '_r'; var Instance = { set: function set(node, instance) { if (!node[KEY]) { node[KEY] = instance; // Record root instance to roots map if (instance.__rootID) { Host.rootInstances[instance.__rootID] = instance; Host.rootComponents[instance.__rootID] = instance[INTERNAL]; } } }, get: function get(node) { return node[KEY]; }, remove: function remove(node) { var instance = this.get(node); if (instance) { node[KEY] = null; if (instance.__rootID) { delete Host.rootComponents[instance.__rootID]; delete Host.rootInstances[instance.__rootID]; } } }, mount: function mount(element, container, _ref) { var parent = _ref.parent, hydrate = _ref.hydrate; { Host.measurer && Host.measurer.beforeRender(); } var driver = Host.driver; // Real native root node is body if (container == null) { container = driver.createBody(); } var renderOptions = { element: element, container: container, hydrate: hydrate }; // Before render callback driver.beforeRender && driver.beforeRender(renderOptions); // Get the context from the conceptual parent component. var parentContext; if (parent) { var parentInternal = parent[INTERNAL]; parentContext = parentInternal.__processChildContext(parentInternal._context); } // Update root component var prevRootInstance = this.get(container); if (prevRootInstance && prevRootInstance.__rootID) { if (parentContext) { // Using __penddingContext to pass new context prevRootInstance[INTERNAL].__penddingContext = parentContext; } prevRootInstance.__update(element); // After render callback driver.afterRender && driver.afterRender(renderOptions); return prevRootInstance; } // Init root component with empty children var renderedComponent = instantiateComponent(createElement(Root)); var defaultContext = parentContext || {}; var rootInstance = renderedComponent.__mountComponent(container, parent, defaultContext); this.set(container, rootInstance); // Mount new element through update queue avoid when there is in rendering phase rootInstance.__update(element); // After render callback driver.afterRender && driver.afterRender(renderOptions); { // Devtool render new root hook Host.reconciler.renderNewRootComponent(rootInstance[INTERNAL][RENDERED_COMPONENT]); Host.measurer && Host.measurer.afterRender(); } return rootInstance; } }; /* * Ref manager */ function updateRef(prevElement, nextElement, component) { var prevRef = prevElement ? prevElement.ref : null; var nextRef = nextElement ? nextElement.ref : null; // Update refs in owner component if (prevRef !== nextRef) { // Detach prev RenderedElement's ref prevRef && detachRef(prevElement._owner, prevRef, component); // Attach next RenderedElement's ref nextRef && attachRef(nextElement._owner, nextRef, component); } } function attachRef(ownerComponent, ref, component) { if (!ownerComponent) { { warning('Ref can not attach because multiple copies of Rax are used.'); } return; } var instance = component.__getPublicInstance(); { if (instance == null) { warning('Do not attach ref to function component because they don’t have instances.'); } } if (isFunction(ref)) { ref(instance); } else if (isObject(ref)) { ref.current = instance; } else { ownerComponent[INSTANCE].refs[ref] = instance; } } function detachRef(ownerComponent, ref, component) { if (isFunction(ref)) { // When the referenced component is unmounted and whenever the ref changes, the old ref will be called with null as an argument. ref(null); } else { // Must match component and ref could detach the ref on owner when A's before ref is B's current ref var instance = component.__getPublicInstance(); if (isObject(ref) && ref.current === instance) { ref.current = null; } else if (ownerComponent[INSTANCE].refs[ref] === instance) { delete ownerComponent[INSTANCE].refs[ref]; } } } function shouldUpdateComponent(prevElement, nextElement) { var prevFalsy = isFalsy(prevElement); var nextFalsy = isFalsy(nextElement); if (prevFalsy || nextFalsy) { return prevFalsy === nextFalsy; } if (isArray(prevElement) && isArray(nextElement)) { return true; } var isPrevStringOrNumber = isString(prevElement) || isNumber(prevElement); if (isPrevStringOrNumber) { return isString(nextElement) || isNumber(nextElement); } else { // prevElement and nextElement could be array, typeof [] is "object" return isObject(prevElement) && isObject(nextElement) && prevElement.type === nextElement.type && prevElement.key === nextElement.key; } } function getElementKeyName(children, element, index) { // `element && element.key` will cause elementKey receive "" when element is "" var elementKey = element ? element.key : void 0; var defaultName = '.' + index.toString(36); // Inner child name default format fallback // Key should must be string type if (isString(elementKey)) { var keyName = '$' + elementKey; // Child keys must be unique. var keyUnique = children[keyName] === undefined; { if (!keyUnique) { // Only the first child will be used when encountered two children with the same key warning("Encountered two children with the same key \"" + elementKey + "\"."); } } return keyUnique ? keyName : defaultName; } else { return defaultName; } } /** * This function is usually been used to find the closet previous sibling native node of FragmentComponent. * FragmentComponent does not have a native node in the DOM tree, so when it is replaced, the new node has no corresponding location to insert. * So we need to look forward from the current mount position of the FragmentComponent to the nearest component which have the native node. * @param component * @return nativeNode */ function getPrevSiblingNativeNode(component) { var parent = component; while (parent = component.__parentInstance && component.__parentInstance[INTERNAL]) { if (parent instanceof Host.__Composite) { component = parent; continue; } var keys = Object.keys(parent.__renderedChildren); // Find previous sibling native node from current mount index for (var i = component.__mountIndex - 1; i >= 0; i--) { var nativeNode = parent.__renderedChildren[keys[i]].__getNativeNode(); // Fragment component always return array if (isArray(nativeNode)) { if (nativeNode.length > 0) { // Get the last one return nativeNode[nativeNode.length - 1]; } } else { // Others maybe native node or empty node return nativeNode; } } // Find parent over parent if (parent instanceof Host.__Fragment) { component = parent; } else { return null; } } } /** * Base Component */ var BaseComponent = /*#__PURE__*/function () { function BaseComponent(element) { this.__currentElement = element; } var _proto = BaseComponent.prototype; _proto.__initComponent = function __initComponent(parent, parentInstance, context) { this._parent = parent; this.__parentInstance = parentInstance; this._context = context; this._mountID = Host.__mountID++; }; _proto.__destoryComponent = function __destoryComponent() { { Host.reconciler.unmountComponent(this); } this.__currentElement = this[NATIVE_NODE] = this._parent = this.__parentInstance = this._context = null; if (this[INSTANCE]) { this[INSTANCE] = this[INSTANCE][INTERNAL] = null; } }; _proto.__mountComponent = function __mountComponent(parent, parentInstance, context, nativeNodeMounter) { this.__initComponent(parent, parentInstance, context); this.__mountNativeNode(nativeNodeMounter); { Host.reconciler.mountComponent(this); } var instance = {}; instance[INTERNAL] = this; return instance; }; _proto.unmountComponent = function unmountComponent(shouldNotRemoveChild) { if (this[NATIVE_NODE] && !shouldNotRemoveChild) { Host.driver.removeChild(this[NATIVE_NODE], this._parent); } this.__destoryComponent(); }; _proto.__getName = function __getName() { var currentElement = this.__currentElement; var type = currentElement && currentElement.type; return type && type.displayName || type && type.name || type || // Native component's name is type currentElement; }; _proto.__mountNativeNode = function __mountNativeNode(nativeNodeMounter) { var nativeNode = this.__getNativeNode(); var parent = this._parent; if (nativeNodeMounter) { nativeNodeMounter(nativeNode, parent); } else { Host.driver.appendChild(nativeNode, parent); } }; _proto.__getNativeNode = function __getNativeNode() { return this[NATIVE_NODE] == null ? this[NATIVE_NODE] = this.__createNativeNode() : this[NATIVE_NODE]; }; _proto.__getPublicInstance = function __getPublicInstance() { return this.__getNativeNode(); }; return BaseComponent; }(); var assign = Object.assign; var STYLE = 'style'; var CHILDREN = 'children'; var TREE = 'tree'; var EVENT_PREFIX_REGEXP = /^on[A-Z]/; /** * Native Component */ var NativeComponent = /*#__PURE__*/function (_BaseComponent) { _inheritsLoose(NativeComponent, _BaseComponent); function NativeComponent() { return _BaseComponent.apply(this, arguments) || this; } var _proto = NativeComponent.prototype; _proto.__mountComponent = function __mountComponent(parent, parentInstance, context, nativeNodeMounter) { this.__initComponent(parent, parentInstance, context); var currentElement = this.__currentElement; var props = currentElement.props; var type = currentElement.type; var children = props[CHILDREN]; var appendType = props.append || TREE; // Default is tree // Clone a copy for style diff this.__prevStyleCopy = assign({}, props[STYLE]); var instance = { type: type, props: props }; instance[INTERNAL] = this; this[INSTANCE] = instance; if (appendType === TREE) { // Should after process children when mount by tree mode this.__mountChildren(children, context); this.__mountNativeNode(nativeNodeMounter); } else { // Should before process children when mount by node mode this.__mountNativeNode(nativeNodeMounter); this.__mountChildren(children, context); } // Ref acttach if (currentElement && currentElement.ref) { attachRef(currentElement._owner, currentElement.ref, this); } { Host.reconciler.mountComponent(this); } return instance; }; _proto.__mountChildren = function __mountChildren(children, context) { if (children == null) return children; var nativeNode = this.__getNativeNode(); return this.__mountChildrenImpl(nativeNode, toArray(children), context); }; _proto.__mountChildrenImpl = function __mountChildrenImpl(parent, children, context, nativeNodeMounter) { var renderedChildren = this.__renderedChildren = {}; var renderedChildrenImage = []; for (var i = 0, l = children.length; i < l; i++) { var element = children[i]; var renderedChild = instantiateComponent(element); var name = getElementKeyName(renderedChildren, element, i); renderedChildren[name] = renderedChild; renderedChild.__mountIndex = i; // Mount children var mountImage = renderedChild.__mountComponent(parent, this[INSTANCE], context, nativeNodeMounter); renderedChildrenImage.push(mountImage); } return renderedChildrenImage; }; _proto.__unmountChildren = function __unmountChildren(shouldNotRemoveChild) { var renderedChildren = this.__renderedChildren; if (renderedChildren) { for (var name in renderedChildren) { var renderedChild = renderedChildren[name]; renderedChild.unmountComponent(shouldNotRemoveChild); } this.__renderedChildren = null; } }; _proto.unmountComponent = function unmountComponent(shouldNotRemoveChild) { if (this[NATIVE_NODE]) { var ref = this.__currentElement.ref; if (ref) { detachRef(this.__currentElement._owner, ref, this); } Instance.remove(this[NATIVE_NODE]); if (!shouldNotRemoveChild) { Host.driver.removeChild(this[NATIVE_NODE], this._parent); } } this.__unmountChildren(true); this.__prevStyleCopy = null; this.__destoryComponent(); }; _proto.__updateComponent = function __updateComponent(prevElement, nextElement, prevContext, nextContext) { // Replace current element this.__currentElement = nextElement; updateRef(prevElement, nextElement, this); var prevProps = prevElement.props; var nextProps = nextElement.props; this.__updateProperties(prevProps, nextProps); // If the prevElement has no child, mount children directly if (prevProps[CHILDREN] == null || isArray(prevProps[CHILDREN]) && prevProps[CHILDREN].length === 0) { this.__mountChildren(nextProps[CHILDREN], nextContext); } else { this.__updateChildren(nextProps[CHILDREN], nextContext); } { Host.reconciler.receiveComponent(this); } }; _proto.__updateProperties = function __updateProperties(prevProps, nextProps) { var propKey; var styleName; var styleUpdates; var driver = Host.driver; var nativeNode = this.__getNativeNode(); for (propKey in prevProps) { // Continue children and null value prop or nextProps has some propKey that do noting if (propKey === CHILDREN || prevProps[propKey] == null || // Use hasOwnProperty here for avoid propKey name is some with method name in object proptotype nextProps.hasOwnProperty(propKey)) { continue; } if (propKey === STYLE) { // Remove all style var lastStyle = this.__prevStyleCopy; for (styleName in lastStyle) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = ''; } this.__prevStyleCopy = null; } else if (EVENT_PREFIX_REGEXP.test(propKey)) { // Remove event var eventListener = prevProps[propKey]; if (isFunction(eventListener)) { driver.removeEventListener(nativeNode, propKey.slice(2).toLowerCase(), eventListener); } } else { // Remove attribute driver.removeAttribute(nativeNode, propKey, prevProps[propKey]); } } for (propKey in nextProps) { var nextProp = nextProps[propKey]; var prevProp = propKey === STYLE ? this.__prevStyleCopy : prevProps != null ? prevProps[propKey] : undefined; // Continue children or prevProp equal nextProp if (propKey === CHILDREN || prevProp === nextProp || nextProp == null && prevProp == null) { continue; } // Update style if (propKey === STYLE) { if (nextProp) { // Clone property nextProp = this.__prevStyleCopy = assign({}, nextProp); } else { this.__prevStyleCopy = null; } if (prevProp != null) { // Unset styles on `prevProp` but not on `nextProp`. for (styleName in prevProp) { if (!nextProp || !nextProp[styleName] && nextProp[styleName] !== 0) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = ''; } } // Update styles that changed since `prevProp`. for (styleName in nextProp) { if (prevProp[styleName] !== nextProp[styleName]) { styleUpdates = styleUpdates || {}; styleUpdates[styleName] = nextProp[styleName]; } } } else { // Assign next prop when prev style is null styleUpdates = nextProp; } } else if (EVENT_PREFIX_REGEXP.test(propKey)) { // Update event binding var eventName = propKey.slice(2).toLowerCase(); if (isFunction(prevProp)) { driver.removeEventListener(nativeNode, eventName, prevProp, nextProps); } if (isFunction(nextProp)) { driver.addEventListener(nativeNode, eventName, nextProp, nextProps); } } else { // Update other property if (nextProp != null) { driver.setAttribute(nativeNode, propKey, nextProp); } else { driver.removeAttribute(nativeNode, propKey, prevProps[propKey]); } { var _payload; Host.measurer && Host.measurer.recordOperation({ instanceID: this._mountID, type: 'update attribute', payload: (_payload = {}, _payload[propKey] = nextProp, _payload) }); } }