UNPKG

react-universal-component

Version:

A higher order component for loading components with promises

217 lines (182 loc) 5.26 kB
// @flow import type { Tools, ModuleOptions, Ids, Config, ConfigFunc, Props, Load } from './flowTypes' import { isWebpack, tryRequire, resolveExport, callForString, loadFromCache, loadFromPromiseCache, cacheProm, isServer, isTest } from './utils' declare var __webpack_require__: Function declare var __webpack_modules__: Object export const CHUNK_NAMES = new Set() export const MODULE_IDS = new Set() export default function requireUniversalModule<Props: Props>( universalConfig: Config | ConfigFunc, options: ModuleOptions, props: Props, prevProps?: Props ): Tools { const { key, timeout = 15000, onLoad, onError, isDynamic, modCache, promCache, usesBabelPlugin } = options const config = getConfig(isDynamic, universalConfig, options, props) const { chunkName, path, resolve, load } = config const asyncOnly = (!path && !resolve) || typeof chunkName === 'function' const requireSync = (props: Object): ?any => { let exp = loadFromCache(chunkName, props, modCache) if (!exp) { let mod if (!isWebpack() && path) { const modulePath = callForString(path, props) || '' mod = tryRequire(modulePath) } else if (isWebpack() && resolve) { const weakId = callForString(resolve, props) if (__webpack_modules__[weakId]) { mod = tryRequire(weakId) } } if (mod) { exp = resolveExport(mod, key, onLoad, chunkName, props, modCache, true) } } return exp } const requireAsync = (props: Object): Promise<?any> => { const exp = loadFromCache(chunkName, props, modCache) if (exp) return Promise.resolve(exp) const cachedPromise = loadFromPromiseCache(chunkName, props, promCache) if (cachedPromise) return cachedPromise const prom = new Promise((res, rej) => { const reject = error => { error = error || new Error('timeout exceeded') clearTimeout(timer) if (onError) { const isServer = typeof window === 'undefined' const info = { isServer } onError(error, info) } rej(error) } // const timer = timeout && setTimeout(reject, timeout) const timer = timeout && setTimeout(reject, timeout) const resolve = mod => { clearTimeout(timer) const exp = resolveExport(mod, key, onLoad, chunkName, props, modCache) if (exp) return res(exp) reject(new Error('export not found')) } const request = load(props, { resolve, reject }) // if load doesn't return a promise, it must call resolveImport // itself. Most common is the promise implementation below. if (!request || typeof request.then !== 'function') return request.then(resolve).catch(reject) }) cacheProm(prom, chunkName, props, promCache) return prom } const addModule = (props: Object): ?string => { if (isServer || isTest) { if (chunkName) { let name = callForString(chunkName, props) if (usesBabelPlugin) { // if ignoreBabelRename is true, don't apply regex const shouldKeepName = options && !!options.ignoreBabelRename if (!shouldKeepName) { name = name.replace(/\//g, '-') } } if (name) CHUNK_NAMES.add(name) if (!isTest) return name // makes tests way smaller to run both kinds } if (isWebpack()) { const weakId = callForString(resolve, props) if (weakId) MODULE_IDS.add(weakId) return weakId } if (!isWebpack()) { const modulePath = callForString(path, props) if (modulePath) MODULE_IDS.add(modulePath) return modulePath } } } const shouldUpdate = (next, prev): boolean => { const cacheKey = callForString(chunkName, next) const config = getConfig(isDynamic, universalConfig, options, prev) const prevCacheKey = callForString(config.chunkName, prev) return cacheKey !== prevCacheKey } return { requireSync, requireAsync, addModule, shouldUpdate, asyncOnly } } export const flushChunkNames = (): Ids => { const chunks = Array.from(CHUNK_NAMES) CHUNK_NAMES.clear() return chunks } export const flushModuleIds = (): Ids => { const ids = Array.from(MODULE_IDS) MODULE_IDS.clear() return ids } export const clearChunks = (): void => { CHUNK_NAMES.clear() MODULE_IDS.clear() } const getConfig = ( isDynamic: ?boolean, universalConfig: Config | ConfigFunc, options: ModuleOptions, props: Props ): Config => { if (isDynamic) { let resultingConfig = typeof universalConfig === 'function' ? universalConfig(props) : universalConfig if (options) { resultingConfig = { ...resultingConfig, ...options } } return resultingConfig } const load: Load = typeof universalConfig === 'function' ? universalConfig : // $FlowIssue () => universalConfig return { file: 'default', id: options.id || 'default', chunkName: options.chunkName || 'default', resolve: options.resolve || '', path: options.path || '', load, ignoreBabelRename: true } }