diff --git a/.gitignore b/.gitignore index 3e7ec31..07fa63a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,7 @@ com_crashlytics_export_strings.xml # npm node_modules +package-lock.json + +# Emacs +\.\#* \ No newline at end of file diff --git a/resources/cljs-om-next/main_dev.cljs b/resources/cljs-om-next/main_dev.cljs index ed95294..a72016a 100644 --- a/resources/cljs-om-next/main_dev.cljs +++ b/resources/cljs-om-next/main_dev.cljs @@ -6,6 +6,9 @@ (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 :websocket-url "ws://localhost:3449/figwheel-ws" :heads-up-display false @@ -14,4 +17,4 @@ (core/init) ;; Do not delete, root-el is used by the figwheel-bridge.js -(def root-el (core/app-root)) \ No newline at end of file +(def root-el (core/app-root)) diff --git a/resources/cljs-reagent/main_dev.cljs b/resources/cljs-reagent/main_dev.cljs index e73ac95..59b9738 100644 --- a/resources/cljs-reagent/main_dev.cljs +++ b/resources/cljs-reagent/main_dev.cljs @@ -5,6 +5,9 @@ (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)) (defn reloader [] @cnt [core/app-root]) @@ -16,4 +19,4 @@ :heads-up-display false :jsload-callback #(swap! cnt inc)) -(core/init) \ No newline at end of file +(core/init) diff --git a/resources/cljs-reagent6/main_dev.cljs b/resources/cljs-reagent6/main_dev.cljs index 5fa2a56..0161078 100644 --- a/resources/cljs-reagent6/main_dev.cljs +++ b/resources/cljs-reagent6/main_dev.cljs @@ -6,6 +6,9 @@ (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)) (defn reloader [] @cnt [core/app-root]) @@ -21,4 +24,4 @@ :heads-up-display false :jsload-callback force-reload!) -(core/init) \ No newline at end of file +(core/init) diff --git a/resources/cljs-rum/main_dev.cljs b/resources/cljs-rum/main_dev.cljs index a3ba7b0..a11f7b2 100644 --- a/resources/cljs-rum/main_dev.cljs +++ b/resources/cljs-rum/main_dev.cljs @@ -2,6 +2,9 @@ (:require [$PROJECT_NAME_HYPHENATED$.$PLATFORM$.core :as core] [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!) (figwheel/watch-and-reload diff --git a/resources/figwheel-bridge.js b/resources/figwheel-bridge.js index 03b25df..bda6713 100644 --- a/resources/figwheel-bridge.js +++ b/resources/figwheel-bridge.js @@ -18,22 +18,51 @@ 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 createReactClass({ @@ -51,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(); }); } } @@ -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 () { 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; }); } @@ -119,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); @@ -131,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); } } @@ -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) { 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 = '/figwheel/connect/build_' + platform + '.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; @@ -237,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); @@ -248,47 +247,9 @@ function shimBaseGoog() { }; } -// Figwheel fixes -// Used by figwheel - uses importScript to load JS rather than