electron-jsx-hotload
Version:
JSX Transformation and ReactComponent Hot-Loading for Electron Apps
161 lines (146 loc) • 5.28 kB
JavaScript
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);
}
}