santi
Version:
Isomorphic framework for base on create-react-app and jsdom
805 lines (659 loc) • 22 kB
TypeScript
import React, { useContext, createContext, useMemo, forwardRef, useState as useState$1, useRef, useEffect } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import NodeKey from 'react-node-key';
import ReactDOM from 'react-dom';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
try {
var info = gen[key](arg);
var value = info.value;
} catch (error) {
reject(error);
return;
}
if (info.done) {
resolve(value);
} else {
Promise.resolve(value).then(_next, _throw);
}
}
function _asyncToGenerator(fn) {
return function () {
var self = this,
args = arguments;
return new Promise(function (resolve, reject) {
var gen = fn.apply(self, args);
function _next(value) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
}
function _throw(err) {
asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
}
_next(undefined);
});
};
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _objectWithoutPropertiesLoose(source, excluded) {
if (source == null) return {};
var target = {};
var sourceKeys = Object.keys(source);
var key, i;
for (i = 0; i < sourceKeys.length; i++) {
key = sourceKeys[i];
if (excluded.indexOf(key) >= 0) continue;
target[key] = source[key];
}
return target;
}
function _objectWithoutProperties(source, excluded) {
if (source == null) return {};
var target = _objectWithoutPropertiesLoose(source, excluded);
var key, i;
if (Object.getOwnPropertySymbols) {
var sourceSymbolKeys = Object.getOwnPropertySymbols(source);
for (i = 0; i < sourceSymbolKeys.length; i++) {
key = sourceSymbolKeys[i];
if (excluded.indexOf(key) >= 0) continue;
if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue;
target[key] = source[key];
}
}
return target;
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArrayLimit(arr, i) {
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
if (_i == null) return;
var _arr = [];
var _n = true;
var _d = false;
var _s, _e;
try {
for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
// 值类型判断 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
var isUndefined = function isUndefined(val) {
return typeof val === 'undefined';
};
var isNull = function isNull(val) {
return val === null;
};
var isFunction = function isFunction(val) {
return typeof val === 'function';
};
var isString = function isString(val) {
return typeof val === 'string';
};
var isExist = function isExist(val) {
return !(isUndefined(val) || isNull(val));
};
var isNaN = function isNaN(val) {
return isFunction(Number.isNaN) ? Number.isNaN(val) : val !== val;
}; // eslint-disable-line
var isNumber = function isNumber(val) {
return typeof val === 'number' && !isNaN(val);
};
var isPromiseLike = function isPromiseLike(val) {
return isExist(val) && isFunction(val.then);
}; // 值类型判断 -------------------------------------------------------------
var get = function get(obj) {
var keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var defaultValue = arguments.length > 2 ? arguments[2] : undefined;
try {
if (isNumber(keys)) {
keys = String(keys);
}
var result = (isString(keys) ? keys.split('.') : keys).reduce(function (res, key) {
return res[key];
}, obj);
return isUndefined(result) ? defaultValue : result;
} catch (e) {
return defaultValue;
}
};
var run = function run(obj) {
var keys = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
keys = isString(keys) ? keys.split('.') : keys;
var func = get(obj, keys);
var context = get(obj, keys.slice(0, -1));
for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
args[_key - 2] = arguments[_key];
}
return isFunction(func) ? func.call.apply(func, [context].concat(args)) : func;
};
var delay = function delay(time) {
return new Promise(function (resolve) {
return setTimeout(resolve, time);
});
};
/**
* [防抖]
* @param {Function} func 执行函数
* @param {Number} wait 多少毫秒后运行一次
*/
var debounce = function debounce(func) {
var wait = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 16;
var timeout;
return function () {
var _this = this;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
clearTimeout(timeout);
timeout = setTimeout(function () {
func.apply(_this, args);
}, wait);
return timeout;
};
};
/**
* [用来智能处理Promise类型返回值]
* 当值生成过程为 promise 时,将得到 promise 类型返回值,按约定 resolve 最终值
* 当过程不为 promise 时将直接得到值
* @param {Function} executor 执行过程获取
* @param {Function} valuer 值处理过程
*/
var promiseGuess = function promiseGuess(executor, valuer) {
return function () {
var _this2 = this;
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
var value = executor.apply(this, args);
return isPromiseLike(value) ? new Promise(function (resolve) {
return value.then(function (value) {
return resolve(valuer.call.apply(valuer, [_this2, null, value].concat(args)));
})["catch"](function (err) {
return resolve(valuer.call.apply(valuer, [_this2, err, undefined].concat(args)));
});
}) : valuer.call.apply(valuer, [this, null, value].concat(args));
};
};
var _excluded = ["forwardRef", "__key"];
var _filehash = "nNBh";
var context = /*#__PURE__*/createContext();
var Provider = context.Provider;
function withSanti(Component) {
function WrappedComponent(_ref) {
var forwardRef = _ref.forwardRef,
nodeKey = _ref.__key,
props = _objectWithoutProperties(_ref, _excluded);
var count = 0;
var contextValue = useMemo(function () {
return {
nodeKey: nodeKey,
getCountedKey: function getCountedKey() {
return nodeKey ? "".concat(nodeKey, ":").concat(count++) : undefined;
}
};
}, [nodeKey]);
return /*#__PURE__*/React.createElement(Provider, {
value: contextValue,
_nk: "".concat(_filehash, "11")
}, /*#__PURE__*/React.createElement(Component, _extends({}, props, {
ref: forwardRef,
_nk: "".concat(_filehash, "21")
})));
}
var ForwardedRefHOC = /*#__PURE__*/forwardRef(function (props, ref) {
return /*#__PURE__*/React.createElement(NodeKey, {
_nk: "".concat(_filehash, "31")
}, function (nodeKey) {
return /*#__PURE__*/React.createElement(WrappedComponent, _extends({}, props, {
forwardedRef: ref,
__key: nodeKey,
_nk: "".concat(_filehash, "41")
}));
});
});
return hoistStatics(ForwardedRefHOC, WrappedComponent);
}
function useNodeKey() {
return useContext(context) || {
nodeKey: undefined,
getCountedKey: function getCountedKey() {
return undefined;
}
};
}
function useSID() {
var _useNodeKey = useNodeKey(),
sid = _useNodeKey.nodeKey,
getCountedSID = _useNodeKey.getCountedKey;
return {
sid: sid,
getCountedSID: getCountedSID
};
}
var KEY = '__SSRDATA__';
function inject() {
var script = document.getElementById(KEY) || document.createElement('script');
script.id = KEY;
script.innerHTML = "window.".concat(KEY, "={};");
document.head.appendChild(script);
}
function bootstrap() {
if (window.__SSR__) {
inject();
} else {
if (isExist(document.getElementById(KEY))) {
window.__SSRED__ = true;
}
}
var data = window[KEY] || {};
function remove(key) {
delete data[key];
}
function set(key, value) {
if (!window.__SSR__) {
return;
}
data[key] = value;
document.getElementById(KEY).innerHTML = "window.".concat(KEY, "=").concat(JSON.stringify(data), ";");
}
function get(key) {
var value = data[key];
remove(key); // 初始值只在 html 就绪后使用一次,使用即销毁
return value;
}
return {
set: set,
get: get,
remove: remove
};
}
var store = bootstrap();
var store$1 = {
get: promiseGuess(function (key, builder) {
if (!key && window.__SSR__) {
// 若无 key 则 SSR 阶段不计算
return undefined;
}
var value;
if (!window.__SSR__) {
value = store.get(key);
}
if (isUndefined(value)) {
value = run(builder);
}
return value;
}, function (err, value, key) {
if (!key) {
return [null, value];
}
if (err) {
store.remove(key);
return [err, undefined];
}
if (window.__SSR__) {
store.set(key, value);
}
return [null, value];
}),
set: store.set
};
var warningMissingKey = debounce(function () {
if (window.__SSR__) {
return;
}
console.warn('You shouldn\'t use "santi.useState" outside a "withSanti"');
}, 32);
function useState(initialState, key) {
var _useNodeKey = useNodeKey(),
getCountedKey = _useNodeKey.getCountedKey;
var nodeKey = key || run(getCountedKey);
var _useReactState = useState$1(function () {
if (!nodeKey) {
warningMissingKey();
}
var _store$get = store$1.get(nodeKey, initialState),
_store$get2 = _slicedToArray(_store$get, 2),
err = _store$get2[0],
value = _store$get2[1];
if (err) {
console.error(err);
}
return value;
}),
_useReactState2 = _slicedToArray(_useReactState, 2),
state = _useReactState2[0],
setState = _useReactState2[1];
return [state, function (getNextState) {
var nextState = run(getNextState, undefined, state);
store$1.set(nodeKey, nextState);
return setState(nextState);
}, nodeKey];
}
var _excluded$1 = ["forwardedRef"];
var _filehash$1 = "8NS2";
var getInitialProps = function getInitialProps(fetch) {
var fallback = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
var key = arguments.length > 2 ? arguments[2] : undefined;
return function (Component) {
function WrappedComponent(_ref) {
var forwardedRef = _ref.forwardedRef,
props = _objectWithoutProperties(_ref, _excluded$1);
var _useState = useState$1(false),
_useState2 = _slicedToArray(_useState, 2),
ready = _useState2[0],
setReady = _useState2[1];
var _useState3 = useState$1({}),
_useState4 = _slicedToArray(_useState3, 2),
ssrProps = _useState4[0],
setSsrProps = _useState4[1];
var mounted = useRef(true);
var _useNodeKey = useNodeKey(),
getCountedKey = _useNodeKey.getCountedKey;
var nodeKey = key || run(getCountedKey);
useEffect(function () {
function init() {
return _init.apply(this, arguments);
}
function _init() {
_init = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var _yield$store$get, _yield$store$get2, err, ssrProps;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return store$1.get(nodeKey, function () {
return run(fetch, undefined, props);
});
case 2:
_yield$store$get = _context.sent;
_yield$store$get2 = _slicedToArray(_yield$store$get, 2);
err = _yield$store$get2[0];
ssrProps = _yield$store$get2[1];
if (!err) {
_context.next = 9;
break;
}
console.error('[getInitialProps error]', err);
return _context.abrupt("return");
case 9:
if (mounted.current && ssrProps) {
setSsrProps(ssrProps);
setReady(true);
}
case 10:
case "end":
return _context.stop();
}
}
}, _callee);
}));
return _init.apply(this, arguments);
}
init();
return function () {
mounted.current = false;
};
}, []);
return ready ? /*#__PURE__*/React.createElement(Component, _extends({}, _objectSpread2(_objectSpread2({}, props), ssrProps), {
ref: forwardedRef,
_nk: "".concat(_filehash$1, "11")
})) : run(fallback, undefined, props);
}
var ForwardedRefHOC = /*#__PURE__*/forwardRef(function (props, ref) {
return /*#__PURE__*/React.createElement(WrappedComponent, _extends({}, props, {
forwardedRef: ref,
_nk: "".concat(_filehash$1, "21")
}));
});
return withSanti(hoistStatics(ForwardedRefHOC, WrappedComponent));
};
};
var ready = /*#__PURE__*/function () {
var _ref = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {
var delayMillisecond,
script,
_args = arguments;
return regeneratorRuntime.wrap(function _callee$(_context) {
while (1) {
switch (_context.prev = _context.next) {
case 0:
delayMillisecond = _args.length > 0 && _args[0] !== undefined ? _args[0] : -1;
_context.prev = 1;
if (!(delayMillisecond && Number(delayMillisecond) >= 0)) {
_context.next = 5;
break;
}
_context.next = 5;
return delay(delayMillisecond);
case 5:
if (window.__SSR__ && !window.__SSRREADY__) {
script = document.createElement('script');
script.innerHTML = 'window.__SSRREADY__=true';
document.head.appendChild(script);
} // ssr 阶段:通知 santi 可以采集页面内容
// csr 阶段:通知 santi/render 已完成呈现
document.dispatchEvent(new Event('ssr-ready'));
case 7:
_context.prev = 7;
return _context.finish(7);
case 9:
case "end":
return _context.stop();
}
}
}, _callee, null, [[1,, 7, 9]]);
}));
return function ready() {
return _ref.apply(this, arguments);
};
}();
var Container = withSanti(function (_ref) {
var children = _ref.children;
return children;
});
var _filehash$2 = "4mmK";
var ROOT_KEY = 'ssr-root';
function render(content, container, callback) {
var element = /*#__PURE__*/React.createElement(Container, {
_nk: "".concat(_filehash$2, "11")
}, content); // 支持热加载
// FIXME: Dev 阶段会导致渲染抖动
if (module.hot) {
return ReactDOM.render(element, container, callback);
} // ssr 阶段将内容渲染至动态生成的 ssr-root 节点中
if (window.__SSR__) {
var ssrRoot = document.getElementById(ROOT_KEY);
if (!ssrRoot) {
ssrRoot = document.createElement('div');
ssrRoot.style.cssText = 'width:100%;height:100%;';
ssrRoot.id = ROOT_KEY;
document.body.insertBefore(ssrRoot, container);
}
return ReactDOM.render(element, ssrRoot, callback);
} // csr 阶段若为 ssr 渲染结果,则在 csr 完成后替换 ssr 结果
// 注:不使用水合操作(ReactDOM.hydrate)因为可能造成节点错误问题
if (window.__SSRED__) {
var display = function display() {
// 做一定延时,尽可能保证平滑呈现
setTimeout(function () {
var ssrRoot = document.getElementById(ROOT_KEY);
if (ssrRoot) {
try {
ssrRoot.parentNode.removeChild(ssrRoot);
} finally {// nothing
}
}
container.style.display = '';
}, 56);
}; // 若为快照 ssr 则 csr 阶段将同样收到 ssr-ready 事件,在此事件后平滑呈现真实可用交互
var _ssrRoot = document.getElementById(ROOT_KEY);
var renderCallback = callback;
container.style.display = 'none';
container.innerHTML = _ssrRoot.innerHTML;
if (window.__SSRREADY__) {
var onReady = function onReady() {
display();
document.removeEventListener('ssr-ready', onReady);
};
document.addEventListener('ssr-ready', onReady);
} else {
// 若不为快照 ssr(一般为超时自动快照),则在 render 回调结束后呈现真实结果
renderCallback = function renderCallback() {
// FIXME: 超时快照可能造成呈现不平滑,具体表现为 SSR 切到真实内容过程中有轻微空屏现象,通俗为 “闪一下”,此问题待完美修正
display();
if (isFunction(callback)) {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
callback.apply(this, args);
}
};
}
return ReactDOM.render(element, container, renderCallback);
} // csr 若不为 ssr 渲染结果则正常渲染
return ReactDOM.render(element, container, callback);
}
var _filehash$3 = "B/hE";
function Ready(_ref) {
var children = _ref.children,
delay = _ref.delay,
_ref$onMount = _ref.onMount,
onMount = _ref$onMount === void 0 ? false : _ref$onMount,
_ref$when = _ref.when,
when = _ref$when === void 0 ? false : _ref$when;
useEffect(function () {
if (onMount) {
ready(delay);
}
}, []);
useEffect(function () {
if (when) {
ready(delay);
}
}, [when]);
return children;
}
function ReadyOnMount(_ref2) {
var children = _ref2.children,
delay = _ref2.delay;
var _useState = useState$1(false),
_useState2 = _slicedToArray(_useState, 2),
ready = _useState2[0],
setReady = _useState2[1];
useEffect(function () {
setReady(true);
}, []);
return /*#__PURE__*/React.createElement(Ready, {
delay: delay,
when: ready,
_nk: "".concat(_filehash$3, "11")
}, children);
}
Ready.OnMount = ReadyOnMount;
function NoSSR(_ref) {
var children = _ref.children,
_ref$fallback = _ref.fallback,
fallback = _ref$fallback === void 0 ? null : _ref$fallback;
return window.__SSR__ ? run(fallback) : children;
}
var index = {
render: render,
store: store$1,
useState: useState,
getInitialProps: getInitialProps,
ready: ready,
withSanti: withSanti,
useNodeKey: useNodeKey,
useSID: useSID,
Ready: Ready,
NoSSR: NoSSR
};
export default index;
export { NoSSR, Ready, getInitialProps, ready, render, store$1 as store, useNodeKey, useSID, useState, withSanti };