Merge pull request #138 from bhauman/master
Create better more durable interactions with Figwheel and more.
This commit is contained in:
commit
cd8f250372
|
@ -31,3 +31,7 @@ com_crashlytics_export_strings.xml
|
||||||
|
|
||||||
# npm
|
# npm
|
||||||
node_modules
|
node_modules
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# Emacs
|
||||||
|
\.\#*
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
(enable-console-print!)
|
(enable-console-print!)
|
||||||
|
|
||||||
|
(assert (exists? core/init) "Fatal Error - Your core.cljs file doesn't define an 'init' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
(assert (exists? core/app-root) "Fatal Error - Your core.cljs file doesn't define an 'app-root' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
|
||||||
(figwheel/watch-and-reload
|
(figwheel/watch-and-reload
|
||||||
:websocket-url "ws://localhost:3449/figwheel-ws"
|
:websocket-url "ws://localhost:3449/figwheel-ws"
|
||||||
:heads-up-display false
|
:heads-up-display false
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
|
|
||||||
(enable-console-print!)
|
(enable-console-print!)
|
||||||
|
|
||||||
|
(assert (exists? core/init) "Fatal Error - Your core.cljs file doesn't define an 'init' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
(assert (exists? core/app-root) "Fatal Error - Your core.cljs file doesn't define an 'app-root' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
|
||||||
(def cnt (r/atom 0))
|
(def cnt (r/atom 0))
|
||||||
(defn reloader [] @cnt [core/app-root])
|
(defn reloader [] @cnt [core/app-root])
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,9 @@
|
||||||
|
|
||||||
(enable-console-print!)
|
(enable-console-print!)
|
||||||
|
|
||||||
|
(assert (exists? core/init) "Fatal Error - Your core.cljs file doesn't define an 'init' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
(assert (exists? core/app-root) "Fatal Error - Your core.cljs file doesn't define an 'app-root' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
|
||||||
(def cnt (r/atom 0))
|
(def cnt (r/atom 0))
|
||||||
(defn reloader [] @cnt [core/app-root])
|
(defn reloader [] @cnt [core/app-root])
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
(:require [$PROJECT_NAME_HYPHENATED$.$PLATFORM$.core :as core]
|
(:require [$PROJECT_NAME_HYPHENATED$.$PLATFORM$.core :as core]
|
||||||
[figwheel.client :as figwheel :include-macros true]))
|
[figwheel.client :as figwheel :include-macros true]))
|
||||||
|
|
||||||
|
(assert (exists? core/init) "Fatal Error - Your core.cljs file doesn't define an 'init' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
(assert (exists? core/app-root) "Fatal Error - Your core.cljs file doesn't define an 'app-root' function!!! - Perhaps there was a compilation failure?")
|
||||||
|
|
||||||
(enable-console-print!)
|
(enable-console-print!)
|
||||||
|
|
||||||
(figwheel/watch-and-reload
|
(figwheel/watch-and-reload
|
||||||
|
|
|
@ -18,22 +18,51 @@ var createReactClass = require('create-react-class');
|
||||||
var ReactNative = require('react-native');
|
var ReactNative = require('react-native');
|
||||||
var WebSocket = require('WebSocket');
|
var WebSocket = require('WebSocket');
|
||||||
var self;
|
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 evaluate = eval; // This is needed, direct calls to eval does not work (RN packager???)
|
||||||
var externalModules = {};
|
var externalModules = {};
|
||||||
var evalListeners = [ // Functions to be called after each js file is loaded and evaluated
|
var evalListeners = {};
|
||||||
function (url) {
|
var asyncImportChain = new Promise(function (succ,fail) {succ(true);});
|
||||||
if (url.indexOf('jsloader') > -1) {
|
|
||||||
shimJsLoader();
|
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']) {
|
||||||
function (url) {
|
errorStr += "in file " + data['file'] + " ";
|
||||||
if (url.indexOf('/figwheel/client/socket') > -1) {
|
|
||||||
setCorrectWebSocketImpl();
|
|
||||||
}
|
}
|
||||||
}];
|
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) {
|
var figwheelApp = function (platform, devHost) {
|
||||||
return createReactClass({
|
return createReactClass({
|
||||||
|
@ -51,11 +80,13 @@ var figwheelApp = function (platform, devHost) {
|
||||||
}
|
}
|
||||||
return this.state.root;
|
return this.state.root;
|
||||||
},
|
},
|
||||||
|
|
||||||
componentDidMount: function () {
|
componentDidMount: function () {
|
||||||
var app = this;
|
var app = this;
|
||||||
if (typeof goog === "undefined") {
|
if (typeof goog === "undefined") {
|
||||||
loadApp(platform, devHost, function (appRoot) {
|
loadApp(platform, devHost, function (appRoot) {
|
||||||
app.setState({root: appRoot, loaded: true})
|
app.setState({root: appRoot, loaded: true});
|
||||||
|
listenToFigwheelMessages();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,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 () {
|
var isChrome = function () {
|
||||||
return typeof importScripts === "function"
|
return typeof importScripts === "function"
|
||||||
};
|
};
|
||||||
|
|
||||||
function asyncImportScripts(url, success, error) {
|
function asyncImportScripts(url, success, error) {
|
||||||
logDebug('(asyncImportScripts) Importing: ' + url);
|
logDebug('(asyncImportScripts) Importing: ' + url);
|
||||||
scriptQueue.push(url);
|
asyncImportChain =
|
||||||
fetch(url)
|
asyncImportChain
|
||||||
|
.then(function (v) {return fetch(url);})
|
||||||
.then(function (response) {
|
.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) {
|
.then(function (responseText) {
|
||||||
return customEval(url, responseText, success, error);
|
evaluate(responseText);
|
||||||
|
fireEvalListenters(url);
|
||||||
|
success();
|
||||||
|
return true;
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (e) {
|
||||||
console.error(error);
|
console.error(e);
|
||||||
return error();
|
error();
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,9 +130,7 @@ function syncImportScripts(url, success, error) {
|
||||||
try {
|
try {
|
||||||
importScripts(url);
|
importScripts(url);
|
||||||
logDebug('Evaluated: ' + url);
|
logDebug('Evaluated: ' + url);
|
||||||
evalListeners.forEach(function (listener) {
|
fireEvalListenters(url);
|
||||||
listener(url)
|
|
||||||
});
|
|
||||||
success();
|
success();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -131,22 +140,14 @@ function syncImportScripts(url, success, error) {
|
||||||
|
|
||||||
// Loads js file sync if possible or async.
|
// Loads js file sync if possible or async.
|
||||||
function importJs(src, success, error) {
|
function importJs(src, success, error) {
|
||||||
if (typeof success !== 'function') {
|
var noop = function(){};
|
||||||
success = function () {
|
success = (typeof success == 'function') ? success : noop;
|
||||||
};
|
error = (typeof error == 'function') ? error : noop;
|
||||||
}
|
logDebug('(importJs) Importing: ' + src);
|
||||||
if (typeof error !== 'function') {
|
|
||||||
error = function () {
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var file = fileBasePath + '/' + src;
|
|
||||||
|
|
||||||
logDebug('(importJs) Importing: ' + file);
|
|
||||||
if (isChrome()) {
|
if (isChrome()) {
|
||||||
syncImportScripts(serverBaseUrl("localhost") + '/' + file, success, error);
|
syncImportScripts(src, success, error);
|
||||||
} else {
|
} else {
|
||||||
asyncImportScripts(serverBaseUrl(serverHost) + '/' + file, success, error);
|
asyncImportScripts(src, success, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,61 +163,51 @@ 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) {
|
function serverBaseUrl(host) {
|
||||||
return "http://" + host + ":" + config.serverPort
|
return "http://" + host + ":" + config.serverPort
|
||||||
}
|
}
|
||||||
|
|
||||||
function setCorrectWebSocketImpl() {
|
function isUnDefined(x) {
|
||||||
figwheel.client.socket.get_websocket_imp = function () {
|
return typeof x == "undefined";
|
||||||
return WebSocket;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) {
|
function loadApp(platform, devHost, onLoadCb) {
|
||||||
serverHost = devHost;
|
var fileBasePath = serverBaseUrl((isChrome() ? "localhost" : devHost)) + "/" + config.basePath + platform;
|
||||||
fileBasePath = config.basePath + platform;
|
|
||||||
|
|
||||||
// callback when app is ready to get the reloadable component
|
// callback when app is ready to get the reloadable component
|
||||||
var mainJs = '/env/' + platform + '/main.js';
|
var mainJs = '/figwheel/connect/build_' + platform + '.js';
|
||||||
evalListeners.push(function (url) {
|
evalListeners.waitForFinalEval = function (url) {
|
||||||
if (url.indexOf(mainJs) > -1) {
|
if (url.indexOf(mainJs) > -1) {
|
||||||
|
assertRootElExists(platform);
|
||||||
onLoadCb(env[platform].main.root_el);
|
onLoadCb(env[platform].main.root_el);
|
||||||
console.info('Done loading Clojure app');
|
console.info('Done loading Clojure app');
|
||||||
|
delete evalListeners.waitForFinalEval;
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
if (typeof goog === "undefined") {
|
if (typeof goog === "undefined") {
|
||||||
console.info('Loading Closure base.');
|
console.info('Loading Closure base.');
|
||||||
interceptRequire();
|
interceptRequire();
|
||||||
compileWarningsToYellowBox();
|
// need to know base path here
|
||||||
importJs('goog/base.js', function () {
|
importJs(fileBasePath + '/goog/base.js', function () {
|
||||||
shimBaseGoog();
|
shimBaseGoog(fileBasePath);
|
||||||
importJs('cljs_deps.js');
|
importJs(fileBasePath + '/cljs_deps.js');
|
||||||
importJs('goog/deps.js', function () {
|
importJs(fileBasePath + '/goog/deps.js', function () {
|
||||||
// This is needed because of RN packager
|
// This is needed because of RN packager
|
||||||
// seriously React packager? why.
|
// seriously React packager? why.
|
||||||
var googreq = goog.require;
|
var googreq = goog.require;
|
||||||
|
@ -237,10 +228,18 @@ function withModules(moduleById) {
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function figwheelImportScript(uri, callback) {
|
||||||
|
importJs(uri.toString(),
|
||||||
|
function () {callback(true);},
|
||||||
|
function () {callback(false);})
|
||||||
|
}
|
||||||
|
|
||||||
// Goog fixes
|
// Goog fixes
|
||||||
function shimBaseGoog() {
|
function shimBaseGoog(basePath) {
|
||||||
console.info('Shimming goog functions.');
|
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.writeScriptSrcNode = importJs;
|
||||||
goog.writeScriptTag_ = function (src, optSourceText) {
|
goog.writeScriptTag_ = function (src, optSourceText) {
|
||||||
importJs(src);
|
importJs(src);
|
||||||
|
@ -248,44 +247,6 @@ 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 = {
|
self = {
|
||||||
withModules: withModules,
|
withModules: withModules,
|
||||||
start: startApp
|
start: startApp
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
[org.clojure/clojurescript "1.9.542"]
|
[org.clojure/clojurescript "1.9.542"]
|
||||||
$INTERFACE_DEPS$]
|
$INTERFACE_DEPS$]
|
||||||
:plugins [[lein-cljsbuild "1.1.4"]
|
:plugins [[lein-cljsbuild "1.1.4"]
|
||||||
[lein-figwheel "0.5.10"]]
|
[lein-figwheel "0.5.14-SNAPSHOT"]]
|
||||||
:clean-targets ["target/" #_($PLATFORM_CLEAN$)]
|
:clean-targets ["target/" #_($PLATFORM_CLEAN$)]
|
||||||
:aliases {"prod-build" ^{:doc "Recompile code with prod profile."}
|
:aliases {"prod-build" ^{:doc "Recompile code with prod profile."}
|
||||||
["do" "clean"
|
["do" "clean"
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
"advanced-build" ^{:doc "Recompile code for production using :advanced compilation."}
|
"advanced-build" ^{:doc "Recompile code for production using :advanced compilation."}
|
||||||
["do" "clean"
|
["do" "clean"
|
||||||
["with-profile" "advanced" "cljsbuild" "once"]]}
|
["with-profile" "advanced" "cljsbuild" "once"]]}
|
||||||
:profiles {:dev {:dependencies [[figwheel-sidecar "0.5.10"]
|
:profiles {:dev {:dependencies [[figwheel-sidecar "0.5.14-SNAPSHOT"]
|
||||||
[com.cemerick/piggieback "0.2.1"]]
|
[com.cemerick/piggieback "0.2.1"]]
|
||||||
:source-paths ["src" "env/dev"]
|
:source-paths ["src" "env/dev"]
|
||||||
:cljsbuild {:builds [
|
:cljsbuild {:builds [
|
||||||
|
|
Loading…
Reference in New Issue