UNPKG

electron-jsx-hotload

Version:

JSX Transformation and ReactComponent Hot-Loading for Electron Apps

161 lines (146 loc) 5.28 kB
module.exports = exports = enable; const path = require('path'); const Module = module.constructor; const babel = require("babel-core"); const babelOpts = { ast: false, babelrc: false, comments: false, presets: [ require('babel-preset-es2015'), require('babel-preset-react') ], sourceMaps: 'inline' }; const chokidarOpts = { persistent: true, ignoreInitial: true }; const React = require('react'); const COMPONENT = Symbol('COMPONENT'); const MOUNTED = Symbol('MOUNTED'); const SWAP = Symbol('SWAP'); const DEBUG = function() { console.log.apply(console, arguments); }; function enable(extension) { extension = extension || '.jsx'; if (Module._extensions[extension]) { return; } console.log('Enabling require JSX for *' + extension); Module._extensions[extension] = compile; if (process.env.NODE_ENV === 'production') { return; } watch(findRootFolder() + '/**/*' + extension, chokidarOpts, changed); } function compile(module, filename) { DEBUG('Loading: ' + filename); const content = babel.transformFileSync(filename, babelOpts); module._compile(content.code, filename); if (Module._cache[filename] === module) { setupProxies(module); } else { updateProxies(Module._cache[filename], module); } } function changed(modpath) { try { const chgmod = Module._cache[modpath]; if (!chgmod) { return; } var newmod = new Module(chgmod.id, chgmod.parent); newmod.load(modpath); Module._cache[modpath] = newmod; console.log('Hot-Reloaded: ' + modpath); } catch(ex) { console.error(ex.stack); } } function setupProxies(mod) { switch(typeof mod.exports) { case 'object': Object.keys(mod.exports).forEach(setupProxy, mod.exports); break; case 'function': setupProxy.call(this, 'exports'); break; } if (mod.exports.__esModule && ('object' === typeof mod.exports.default)) { Object.keys(mod.exports.default).forEach(setupProxy, mod.exports.default); } } function setupProxy(prop) { const Component = this[prop]; if ('function' !== typeof Component) { return; } if (!(Component.prototype instanceof React.Component)) { return; } if (Component[COMPONENT]) { return; } this[prop] = proxyComponent(Component); } function proxyComponent(Component) { function HotLoadProxy() { HotLoadProxy[MOUNTED].add(this); return HotLoadProxy[COMPONENT].apply(this, arguments) || this; } HotLoadProxy.name = HotLoadProxy.displayName = Component.displayName || Component.name; HotLoadProxy[COMPONENT] = Component; HotLoadProxy[MOUNTED] = new Set(); HotLoadProxy[SWAP] = swapComponent HotLoadProxy.prototype = Object.create(Component.prototype); HotLoadProxy.prototype.componentDidMount = componentDidMount; HotLoadProxy.prototype.componentWillUnmount = componentWillUnmount; DEBUG('Proxied: ' + HotLoadProxy.displayName); return HotLoadProxy; function componentDidMount() { DEBUG('Mounted: ' + HotLoadProxy.displayName); HotLoadProxy[MOUNTED].add(this); if ('function' !== typeof HotLoadProxy[COMPONENT].prototype.componentDidMount) { return; } return HotLoadProxy[COMPONENT].prototype.componentDidMount.apply(this, arguments); } function componentWillUnmount() { DEBUG('Unmounting: ' + HotLoadProxy.displayName); HotLoadProxy[MOUNTED].delete(this); if ('function' !== typeof HotLoadProxy[COMPONENT].prototype.componentWillUnmount) { return; } return HotLoadProxy[COMPONENT].prototype.componentWillUnmount.apply(this, arguments); } } function swapComponent(NewComponent) { this[COMPONENT] = NewComponent; this.prototype = Object.create(NewComponent.prototype); this[MOUNTED].forEach(update, this.prototype); } function update(instance) { DEBUG('Hot-Swapping: ', instance); Object.setPrototypeOf(instance, this); instance.forceUpdate(); } function updateProxies(oldmod, newmod) { switch(typeof oldmod.exports) { case 'object': Object.keys(oldmod.exports).forEach(updateProxy, { old: oldmod.exports, new:newmod.exports }); break; case 'function': updateProxy.call({ old: oldmod.exports, new: newmod.exports }, 'exports'); break; } if (oldmod.exports.__esModule && ('object' === typeof oldmod.exports.default)) { Object.keys(oldmod.exports.default).forEach(updateProxy, { old: oldmod.exports.default, new: newmod.exports.default }); } setupProxies(newmod); } function updateProxy(prop) { const Proxy = this.old[prop]; if (!Proxy || !Proxy[SWAP]) { return; } Proxy[SWAP](this.new[prop]); this.new[prop] = Proxy; } function findRootFolder() { var mod = module; while (mod.id !== '.') { mod = mod.parent; } return path.dirname(mod.filename); } function watch(expr, opts, handler) { try { const chokidar = require('chokidar'); console.log('Hot-Reloading: ' + expr); chokidar.watch(expr, opts).on('change', handler); } catch(ex) { console.log('Hot-Reloading Disabled: ' + expr); } }