Skip to content

Instantly share code, notes, and snippets.

@jeaye
Created October 10, 2017 17:20
Show Gist options
  • Save jeaye/ba9513abcbe1d0ca0014af8e2a878fb4 to your computer and use it in GitHub Desktop.
Save jeaye/ba9513abcbe1d0ca0014af8e2a878fb4 to your computer and use it in GitHub Desktop.
diff --git a/figwheel-bridge.js b/figwheel-bridge.js
index e5d9a9c..6f4e285 100644
--- a/figwheel-bridge.js
+++ b/figwheel-bridge.js
@@ -14,28 +14,58 @@ var config = {
};
var React = require('react');
+var createReactClass = require('create-react-class');
var ReactNative = require('react-native');
var WebSocket = require('WebSocket');
var self;
-var scriptQueue = [];
-var serverHost = null; // will be set dynamically
-var fileBasePath = null; // will be set dynamically
var evaluate = eval; // This is needed, direct calls to eval does not work (RN packager???)
var externalModules = {};
-var evalListeners = [ // Functions to be called after each js file is loaded and evaluated
- function (url) {
- if (url.indexOf('jsloader') > -1) {
- shimJsLoader();
- }
- },
- function (url) {
- if (url.indexOf('/figwheel/client/socket') > -1) {
- setCorrectWebSocketImpl();
- }
- }];
+var evalListeners = {};
+var asyncImportChain = new Promise(function (succ,fail) {succ(true);});
+
+function fireEvalListenters(url) {
+ Object.values(evalListeners).forEach(function (listener) {
+ listener(url)
+ });
+}
+
+function formatCompileError(msg) {
+ var errorStr = "Figwheel Compile Exception: "
+ var data = msg['exception-data'];
+ if(data['message']) {
+ errorStr += data['message'] + " ";
+ }
+ if(data['file']) {
+ errorStr += "in file " + data['file'] + " ";
+ }
+ if(data['line']) {
+ errorStr += "at line " + data['line'];
+ }
+ if(data['column']) {
+ errorStr += ", column " + data['column'];
+ }
+ return errorStr;
+}
+
+/* This is simply demonstrating that we can receive and react to
+ * arbitrary messages from Figwheel this will enable creating a nicer
+ * feedback system in the Figwheel top level React component.
+ */
+function figwheelMessageHandler(msg) {
+ if(msg["msg-name"] == "compile-failed") {
+ console.warn(formatCompileError(msg));
+ }
+}
+
+function listenToFigwheelMessages() {
+ if(figwheel.client.add_json_message_watch) {
+ figwheel.client.add_json_message_watch("ReactNativeMessageIntercept",
+ figwheelMessageHandler);
+ }
+}
var figwheelApp = function (platform, devHost) {
- return React.createClass({
+ return createReactClass({
getInitialState: function () {
return {loaded: false}
},
@@ -50,11 +80,13 @@ var figwheelApp = function (platform, devHost) {
}
return this.state.root;
},
+
componentDidMount: function () {
var app = this;
if (typeof goog === "undefined") {
loadApp(platform, devHost, function (appRoot) {
- app.setState({root: appRoot, loaded: true})
+ app.setState({root: appRoot, loaded: true});
+ listenToFigwheelMessages();
});
}
}
@@ -67,50 +99,30 @@ function logDebug(msg) {
}
}
-// evaluates js code ensuring proper ordering
-function customEval(url, javascript, success, error) {
- if (scriptQueue.length > 0) {
- if (scriptQueue[0] === url) {
- try {
- evaluate(javascript);
- logDebug('Evaluated: ' + url);
- scriptQueue.shift();
- evalListeners.forEach(function (listener) {
- listener(url)
- });
- success();
- } catch (e) {
- console.error(e);
- error();
- }
- } else {
- setTimeout(function () {
- customEval(url, javascript, success, error)
- }, 5);
- }
- } else {
- console.error('Something bad happened...');
- error()
- }
-}
-
var isChrome = function () {
return typeof importScripts === "function"
};
function asyncImportScripts(url, success, error) {
logDebug('(asyncImportScripts) Importing: ' + url);
- scriptQueue.push(url);
- fetch(url)
+ asyncImportChain =
+ asyncImportChain
+ .then(function (v) {return fetch(url);})
.then(function (response) {
- return response.text()
+ if(response.ok)
+ return response.text();
+ throw new Error("Failed to Fetch: " + url + " - Perhaps your project was cleaned and you haven't recompiled?")
})
.then(function (responseText) {
- return customEval(url, responseText, success, error);
+ evaluate(responseText);
+ fireEvalListenters(url);
+ success();
+ return true;
})
- .catch(function (error) {
- console.error(error);
- return error();
+ .catch(function (e) {
+ console.error(e);
+ error();
+ return true;
});
}
@@ -118,9 +130,7 @@ function syncImportScripts(url, success, error) {
try {
importScripts(url);
logDebug('Evaluated: ' + url);
- evalListeners.forEach(function (listener) {
- listener(url)
- });
+ fireEvalListenters(url);
success();
} catch (e) {
console.error(e);
@@ -130,22 +140,14 @@ function syncImportScripts(url, success, error) {
// Loads js file sync if possible or async.
function importJs(src, success, error) {
- if (typeof success !== 'function') {
- success = function () {
- };
- }
- if (typeof error !== 'function') {
- error = function () {
- };
- }
-
- var file = fileBasePath + '/' + src;
-
- logDebug('(importJs) Importing: ' + file);
+ var noop = function(){};
+ success = (typeof success == 'function') ? success : noop;
+ error = (typeof error == 'function') ? error : noop;
+ logDebug('(importJs) Importing: ' + src);
if (isChrome()) {
- syncImportScripts(serverBaseUrl("localhost") + '/' + file, success, error);
+ syncImportScripts(src, success, error);
} else {
- asyncImportScripts(serverBaseUrl(serverHost) + '/' + file, success, error);
+ asyncImportScripts(src, success, error);
}
}
@@ -161,66 +163,56 @@ function interceptRequire() {
};
}
-function compileWarningsToYellowBox() {
- var log = window.console.log;
- var compileWarningRx = /Figwheel: Compile/;
- var compileExceptionRx = /Figwheel: Compile Exception/;
- var errorInFileRx = /Error on file/;
- var isBuffering = false;
- var compileExceptionBuffer = "";
- window.console.log = function (msg) {
- log.apply(window.console, arguments);
- if (compileExceptionRx.test(msg)) { // enter buffering mode to get all the messages for exception
- isBuffering = true;
- compileExceptionBuffer = msg + "\n";
- } else if (errorInFileRx.test(msg) && isBuffering) { // exit buffering mode and log buffered messages to YellowBox
- isBuffering = false;
- console.warn(compileExceptionBuffer + msg);
- compileExceptionBuffer = "";
- } else if (isBuffering) { //log messages buffering mode
- compileExceptionBuffer += msg + "\n";
- } else if (compileWarningRx.test(msg)) {
- console.warn(msg);
- }
- };
-}
-
function serverBaseUrl(host) {
return "http://" + host + ":" + config.serverPort
}
-function setCorrectWebSocketImpl() {
- figwheel.client.socket.get_websocket_imp = function () {
- return WebSocket;
- };
+function isUnDefined(x) {
+ return typeof x == "undefined";
}
+// unlikely to happen but it happened to me a couple of times so ...
+function assertRootElExists(platform) {
+ var basicMessage = "ClojureScript project didn't compile, or didn't load correctly.";
+ if(isUnDefined(env)) {
+ throw new Error("Critical Error: env namespace not defined - " + basicMessage);
+ } else if(isUnDefined(env[platform])) {
+ throw new Error("Critical Error: env." + platform + " namespace not defined - " + basicMessage);
+ } else if(isUnDefined(env[platform].main)) {
+ throw new Error("Critical Error: env." + platform + ".main namespace not defined - " + basicMessage);
+ } else if(isUnDefined(env[platform].main.root_el)) {
+ throw new Error("Critical Error: env." +
+ platform + ".main namespace doesn't define a root-el which should hold the root react node of your app.");
+ }
+ }
+
function loadApp(platform, devHost, onLoadCb) {
- serverHost = devHost;
- fileBasePath = config.basePath + platform;
+ var fileBasePath = serverBaseUrl((isChrome() ? "localhost" : devHost)) + "/" + config.basePath + platform;
// callback when app is ready to get the reloadable component
- var mainJs = '/env/' + platform + '/main.js';
- evalListeners.push(function (url) {
+ var mainJs = `/env/${platform}/main.js`;
+ evalListeners.waitForFinalEval = function (url) {
if (url.indexOf(mainJs) > -1) {
+ assertRootElExists(platform);
onLoadCb(env[platform].main.root_el);
console.info('Done loading Clojure app');
+ delete evalListeners.waitForFinalEval;
}
- });
+ };
if (typeof goog === "undefined") {
console.info('Loading Closure base.');
interceptRequire();
- compileWarningsToYellowBox();
- importJs('goog/base.js', function () {
- shimBaseGoog();
- importJs('cljs_deps.js');
- importJs('goog/deps.js', function () {
+ // need to know base path here
+ importJs(fileBasePath + '/goog/base.js', function () {
+ shimBaseGoog(fileBasePath);
+ importJs(fileBasePath + '/cljs_deps.js');
+ importJs(fileBasePath + '/goog/deps.js', function () {
// This is needed because of RN packager
// seriously React packager? why.
var googreq = goog.require;
- googreq('figwheel.connect.build_' + platform);
+ googreq(`env.${platform}.main`);
});
});
}
@@ -236,10 +228,18 @@ function withModules(moduleById) {
return self;
}
+function figwheelImportScript(uri, callback) {
+ importJs(uri.toString(),
+ function () {callback(true);},
+ function () {callback(false);})
+}
+
// Goog fixes
-function shimBaseGoog() {
+function shimBaseGoog(basePath) {
console.info('Shimming goog functions.');
- goog.basePath = 'goog/';
+ goog.basePath = basePath + '/' + config.googBasePath;
+ goog.global.FIGWHEEL_WEBSOCKET_CLASS = WebSocket;
+ goog.global.FIGWHEEL_IMPORT_SCRIPT = figwheelImportScript;
goog.writeScriptSrcNode = importJs;
goog.writeScriptTag_ = function (src, optSourceText) {
importJs(src);
@@ -247,47 +247,9 @@ function shimBaseGoog() {
};
}
-// Figwheel fixes
-// Used by figwheel - uses importScript to load JS rather than <script>'s
-function shimJsLoader() {
- console.info('==== Shimming jsloader ====');
- goog.net.jsloader.load = function (uri, options) {
- var deferred = {
- callbacks: [],
- errbacks: [],
- addCallback: function (cb) {
- deferred.callbacks.push(cb);
- },
- addErrback: function (cb) {
- deferred.errbacks.push(cb);
- },
- callAllCallbacks: function () {
- while (deferred.callbacks.length > 0) {
- deferred.callbacks.shift()();
- }
- },
- callAllErrbacks: function () {
- while (deferred.errbacks.length > 0) {
- deferred.errbacks.shift()();
- }
- }
- };
-
- // Figwheel needs this to be an async call,
- // so that it can add callbacks to deferred
- setTimeout(function () {
- importJs(uri.getPath(),
- deferred.callAllCallbacks,
- deferred.callAllErrbacks);
- }, 1);
-
- return deferred;
- };
-}
-
self = {
withModules: withModules,
start: startApp
};
-module.exports = self;
\ No newline at end of file
+module.exports = self;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment