UNPKG

ud

Version:

Utilities for updating code live with hot module replacement

114 lines (107 loc) 3.57 kB
/* @flow */ const range = require('array-range'); const zipObject = require('zip-object'); const moduleUsedUdKeys: WeakMap<typeof module, Set<string>> = new WeakMap(); export function markReloadable(module: typeof module) { if ((module:any).hot) { (module:any).hot.accept(); } } export function defonce<T>(module: typeof module, fn: ()=>T, key?:string=''): T { markReloadable(module); let usedKeys = moduleUsedUdKeys.get(module); if (!usedKeys) { usedKeys = new Set(); moduleUsedUdKeys.set(module, usedKeys); } if (usedKeys.has(key)) { throw new Error('ud functions can only be used once per module with a given key'); } usedKeys.add(key); let valueWasSet = false; let value: any = undefined; if ((module:any).hot) { if ( (module:any).hot.data && (module:any).hot.data.__ud__ && Object.prototype.hasOwnProperty.call((module:any).hot.data.__ud__, key) ) { value = (module:any).hot.data.__ud__[key]; valueWasSet = true; } (module:any).hot.dispose(data => { if (!data.__ud__) data.__ud__ = {}; data.__ud__[key] = value; }); } if (!valueWasSet) value = fn(); return value; } export function defobj<T: Object>(module: typeof module, object: T, key?:string=''): T { const sharedObject = defonce(module, ()=>object, '--defobj-'+key); if (sharedObject !== object) { cloneOntoTarget(sharedObject, object); } return sharedObject; } // Assigns all properties of object onto target, and deletes any properties // from target that don't exist on object. The optional blacklist argument // specifies properties to not assign on target. function cloneOntoTarget<T: Object>(target: T, object: Object): T { Object.getOwnPropertyNames(target) .filter(name => !Object.prototype.hasOwnProperty.call(object, name)) .forEach(name => { delete target[name]; }); const newPropsChain = Object.getOwnPropertyNames(object); Object.defineProperties( target, zipObject(newPropsChain, newPropsChain .map(name => Object.getOwnPropertyDescriptor(object, name)) .filter(Boolean) .map(({value,enumerable}) => ({value,enumerable,writable:true,configurable:true}) ) ) ); return target; } export function defn<T: Function>(module: typeof module, fn: T, key?:string=''): T { const shared = defonce(module, ()=>{ if (!(module:any).hot) { return {fn: (null: ?T), wrapper: fn}; } const shared: Object = {fn: null, wrapper: null}; const paramsList = range(fn.length).map(x => 'a'+x).join(','); shared.wrapper = (new Function( 'shared', ` 'use strict'; return function ${fn.name}__ud_wrapper(${paramsList}) { if (new.target) { return Reflect.construct(shared.fn, arguments, new.target); } else { return shared.fn.apply(this, arguments); } }; ` ): any)(shared); if (fn.prototype) { (shared.wrapper:any).prototype = Object.create(fn.prototype); (shared.wrapper:any).prototype.constructor = shared.wrapper; } else { (shared.wrapper:any).prototype = fn.prototype; } return shared; }, '--defn-shared-'+key); shared.fn = fn; if ((module:any).hot) { if (fn.prototype && (shared.wrapper:any).prototype && Object.getPrototypeOf((shared.wrapper:any).prototype) !== fn.prototype) { (Object: any).setPrototypeOf((shared.wrapper:any).prototype, fn.prototype); } (Object: any).setPrototypeOf(shared.wrapper, fn); } return shared.wrapper; }