mirror of https://github.com/status-im/clj-rn.git
* Fix #2 Do not require local figwheel-bridge.js
This commit is contained in:
parent
e3fe471c60
commit
8e01bcfc72
25
README.md
25
README.md
|
@ -27,8 +27,33 @@ clj -R:repl -m clj-rn.main rebuild-index --help
|
|||
|
||||
```
|
||||
$ clj -R:repl -m clj-rn.main rebuild-index -p android,ios -a genymotion -i real --figwheel-port 3456
|
||||
|
||||
```
|
||||
|
||||
### Upgrading from an existing project
|
||||
|
||||
- If your existing project has a `figwheel-bridge.js` in the root directory, it can be deleted now as `figwheel` task of `clj-rn.main` would create it for you and place it in `target` directory, it is being required by `index.*.js` every time when starting the app with figwheel. Alternatively, you can choose to use your own JS bridge by setting a variable named `:figwheel-bridge` in `clj-rn.conf.edn`, the value should be that of the JS module name. e.g. `./resources/bridge`
|
||||
- If you are using the default `figwheel-bridge.js` provided by `clj-rn` (originally from `re-natal`), make sure that `cljsbuild` compiler options for `dev` environment look like this below:
|
||||
```
|
||||
{:ios
|
||||
{:source-paths ["src"]
|
||||
:compiler {:output-to "target/ios/index.js"
|
||||
:main "env.ios.main"
|
||||
:output-dir "target/ios"
|
||||
:optimizations :none
|
||||
:target :nodejs}}
|
||||
:android
|
||||
{:source-paths ["src"]
|
||||
:compiler {:output-to "target/android/index.js"
|
||||
:main "env.android.main"
|
||||
:output-dir "target/android"
|
||||
:optimizations :none
|
||||
:target :nodejs}}}
|
||||
```
|
||||
Note: it also supports `:preloads` and `:closure-defines` options now thanks to the work done by `re-natal`.
|
||||
|
||||
- Supported `figwheel-sidecar` version => `0.5.14`.
|
||||
|
||||
## Thanks
|
||||
|
||||
Special thanks to @psdp for the initial implementation. Awesome job!
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
/*
|
||||
* Originally taken from https://github.com/decker405/figwheel-react-native
|
||||
*
|
||||
* @providesModule figwheel-bridge
|
||||
*/
|
||||
|
||||
var debugEnabled = false;
|
||||
|
||||
var config = {
|
||||
basePath: "target/",
|
||||
googBasePath: 'goog/',
|
||||
serverPort: 8081
|
||||
};
|
||||
|
||||
var React = require('react');
|
||||
var createReactClass = require('create-react-class');
|
||||
var ReactNative = require('react-native');
|
||||
var WebSocket = require('WebSocket');
|
||||
var self;
|
||||
var evaluate = eval; // This is needed, direct calls to eval does not work (RN packager???)
|
||||
var externalModules = {};
|
||||
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({
|
||||
getInitialState: function () {
|
||||
return {loaded: false}
|
||||
},
|
||||
render: function () {
|
||||
if (!this.state.loaded) {
|
||||
var plainStyle = {flex: 1, alignItems: 'center', justifyContent: 'center'};
|
||||
return (
|
||||
<ReactNative.View style={plainStyle}>
|
||||
<ReactNative.Text>Waiting for Figwheel to load files.</ReactNative.Text>
|
||||
</ReactNative.View>
|
||||
);
|
||||
}
|
||||
return this.state.root;
|
||||
},
|
||||
|
||||
componentDidMount: function () {
|
||||
var app = this;
|
||||
if (typeof goog === "undefined") {
|
||||
loadApp(platform, devHost, function (appRoot) {
|
||||
app.setState({root: appRoot, loaded: true});
|
||||
listenToFigwheelMessages();
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
function logDebug(msg) {
|
||||
if (debugEnabled) {
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
|
||||
var isChrome = function () {
|
||||
return typeof importScripts === "function"
|
||||
};
|
||||
|
||||
function asyncImportScripts(url, transform, success, error) {
|
||||
logDebug('(asyncImportScripts) Importing: ' + url);
|
||||
asyncImportChain =
|
||||
asyncImportChain
|
||||
.then(function (v) {return fetch(url);})
|
||||
.then(function (response) {
|
||||
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) {
|
||||
evaluate(transform(responseText));
|
||||
fireEvalListenters(url);
|
||||
success();
|
||||
return true;
|
||||
})
|
||||
.catch(function (e) {
|
||||
console.error(e);
|
||||
error();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function syncImportScripts(url, success, error) {
|
||||
try {
|
||||
importScripts(url);
|
||||
logDebug('Evaluated: ' + url);
|
||||
fireEvalListenters(url);
|
||||
success();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error()
|
||||
}
|
||||
}
|
||||
|
||||
// Loads js file sync if possible or async.
|
||||
function importJs(src, success, error) {
|
||||
var noop = function(){};
|
||||
var identity = function (arg){return arg};
|
||||
var successCb = (typeof success == 'function') ? success : noop;
|
||||
var errorCb = (typeof error == 'function') ? error : noop;
|
||||
logDebug('(importJs) Importing: ' + src);
|
||||
if (isChrome()) {
|
||||
syncImportScripts(src, successCb, errorCb);
|
||||
} else {
|
||||
asyncImportScripts(src, identity, successCb, errorCb);
|
||||
}
|
||||
}
|
||||
|
||||
function interceptRequire() {
|
||||
var oldRequire = window.require;
|
||||
console.info("Shimming require");
|
||||
window.require = function (id) {
|
||||
console.info("Requiring: " + id);
|
||||
if (externalModules[id]) {
|
||||
return externalModules[id];
|
||||
}
|
||||
return oldRequire(id);
|
||||
};
|
||||
}
|
||||
|
||||
function serverBaseUrl(host) {
|
||||
return "http://" + host + ":" + config.serverPort
|
||||
}
|
||||
|
||||
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 importIndexJs(fileBasePath) {
|
||||
var src = fileBasePath + '/index.js';
|
||||
var transformFn = function(code) {
|
||||
var defines = code.match(new RegExp ("goog.global.CLOSURE_UNCOMPILED_DEFINES.*?;"));
|
||||
var deps = code.match(/goog.require\(.*?\);/g);
|
||||
var transformedCode = defines.concat(deps).join('');
|
||||
logDebug('transformed index.js: ', transformedCode);
|
||||
return transformedCode;
|
||||
};
|
||||
logDebug('(importIndexJs) Importing: ' + src);
|
||||
asyncImportScripts(src, transformFn, function(){}, function(){});
|
||||
}
|
||||
|
||||
function loadApp(platform, devHost, onLoadCb) {
|
||||
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.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();
|
||||
|
||||
// need to know base path here
|
||||
importJs(fileBasePath + '/goog/base.js', function () {
|
||||
shimBaseGoog(fileBasePath);
|
||||
importJs(fileBasePath + '/cljs_deps.js', function () {
|
||||
importJs(fileBasePath + '/goog/deps.js', function () {
|
||||
importIndexJs(fileBasePath);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function startApp(appName, platform, devHost) {
|
||||
ReactNative.AppRegistry.registerComponent(
|
||||
appName, () => figwheelApp(platform, devHost));
|
||||
}
|
||||
|
||||
function withModules(moduleById) {
|
||||
externalModules = moduleById;
|
||||
return self;
|
||||
}
|
||||
|
||||
function figwheelImportScript(uri, callback) {
|
||||
importJs(uri.toString(),
|
||||
function () {callback(true);},
|
||||
function () {callback(false);})
|
||||
}
|
||||
|
||||
// Goog fixes
|
||||
function shimBaseGoog(basePath) {
|
||||
console.info('Shimming goog functions.');
|
||||
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);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
self = {
|
||||
withModules: withModules,
|
||||
start: startApp
|
||||
};
|
||||
|
||||
module.exports = self;
|
|
@ -6,6 +6,12 @@
|
|||
|
||||
(def debug-host-rx #"host]\s+\?:\s+@\".*\";")
|
||||
|
||||
(defn copy-resource-file! [resource-path target-path]
|
||||
(let [resource-file (io/resource resource-path)
|
||||
target-file (io/file target-path)]
|
||||
(with-open [in (io/input-stream resource-file)]
|
||||
(io/copy in target-file))))
|
||||
|
||||
(defn edit-file-contents! [path replacements-map]
|
||||
(as-> (slurp path) $
|
||||
(reduce (fn [contents [match replacement]]
|
||||
|
@ -76,7 +82,7 @@
|
|||
with-out-str))
|
||||
((partial spit "env/dev/env/config.cljs"))))
|
||||
|
||||
(defn rebuild-index-js [platform {:keys [app-name host-ip js-modules resource-dirs]}]
|
||||
(defn rebuild-index-js [platform {:keys [app-name host-ip js-modules resource-dirs figwheel-bridge]}]
|
||||
(let [modules (->> (for [dir resource-dirs]
|
||||
(->> (file-seq (io/file dir))
|
||||
(filter #(and (not (re-find #"DS_Store" (str %)))
|
||||
|
@ -92,12 +98,13 @@
|
|||
modules-map (zipmap modules modules)
|
||||
target-file (str "index." (if (= :ios platform) "ios" "android") ".js")]
|
||||
(try
|
||||
(-> "var modules={};%s;\nrequire('figwheel-bridge').withModules(modules).start('%s','%s','%s');"
|
||||
(-> "var modules={};%s;\nrequire('%s').withModules(modules).start('%s','%s','%s');"
|
||||
(format
|
||||
(->> modules-map
|
||||
(map (fn [[module path]]
|
||||
(str "modules['" module "']=require('" path "')")))
|
||||
(str/join ";"))
|
||||
(or figwheel-bridge "./target/figwheel-bridge.js")
|
||||
app-name
|
||||
(name platform)
|
||||
host-ip)
|
||||
|
@ -109,3 +116,8 @@
|
|||
(defn update-ios-rct-web-socket-executor [host]
|
||||
(edit-file-contents! "node_modules/react-native/Libraries/WebSocket/RCTWebSocketExecutor.m"
|
||||
{debug-host-rx (str "host] ?: @\"" host "\";")}))
|
||||
|
||||
(defn copy-figwheel-bridge []
|
||||
(io/make-parents "target/.")
|
||||
(copy-resource-file! "figwheel-bridge.js" "target/figwheel-bridge.js")
|
||||
(utils/log "Copied figwheel-bridge.js"))
|
||||
|
|
|
@ -94,24 +94,28 @@
|
|||
(let [{:keys [build-ids
|
||||
android-device
|
||||
ios-device
|
||||
figwheel-port]} (parse-cli-opts args rebuild-index-task-opts)
|
||||
hosts-map {:android (core/resolve-dev-host :android android-device)
|
||||
:ios (core/resolve-dev-host :ios ios-device)}]
|
||||
figwheel-port]} (parse-cli-opts args rebuild-index-task-opts)
|
||||
hosts-map {:android (core/resolve-dev-host :android android-device)
|
||||
:ios (core/resolve-dev-host :ios ios-device)}
|
||||
{:keys [name
|
||||
js-modules
|
||||
resource-dirs
|
||||
figwheel-bridge]} (get-main-config)]
|
||||
(when-not figwheel-bridge
|
||||
(core/copy-figwheel-bridge))
|
||||
(core/write-env-dev hosts-map figwheel-port)
|
||||
(doseq [build-id build-ids
|
||||
:let [host-ip (get hosts-map build-id)
|
||||
platform-name (if (= build-id :ios) "iOS" "Android")
|
||||
{:keys [name
|
||||
js-modules
|
||||
resource-dirs]} (get-main-config)]]
|
||||
(core/rebuild-index-js build-id {:app-name name
|
||||
:host-ip host-ip
|
||||
:js-modules js-modules
|
||||
:resource-dirs resource-dirs})
|
||||
:let [host-ip (get hosts-map build-id)
|
||||
platform-name (if (= build-id :ios) "iOS" "Android")]]
|
||||
(core/rebuild-index-js build-id {:app-name name
|
||||
:host-ip host-ip
|
||||
:js-modules js-modules
|
||||
:resource-dirs resource-dirs
|
||||
:figwheel-bridge figwheel-bridge})
|
||||
(when (= build-id :ios)
|
||||
(core/update-ios-rct-web-socket-executor host-ip)
|
||||
(utils/log "Host in RCTWebSocketExecutor.m was updated"))
|
||||
(utils/log (format "Dev server host for %s: %s" platform-name host-ip)))))
|
||||
(utils/log (format "Dev server host for %s: %s" platform-name host-ip)))))
|
||||
|
||||
;;; Help
|
||||
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
;;; config
|
||||
|
||||
(s/def :config/main (s/merge (s/keys :req-un [:config/name]
|
||||
:opt-un [:config/js-modules :config/resource-dirs])
|
||||
(s/map-of #{:name :js-modules :resource-dirs} any?)))
|
||||
:opt-un [:config/js-modules
|
||||
:config/resource-dirs
|
||||
:config/figwheel-bridge])
|
||||
(s/map-of #{:name :js-modules :resource-dirs :figwheel-bridge} any?)))
|
||||
|
||||
(s/def :config/name (and not-empty-string? #(re-matches #"^[A-Z][A-Za-z0-9]+$" %)))
|
||||
(s/def :config/js-modules (s/coll-of not-empty-string?))
|
||||
(s/def :config/resource-dirs (s/coll-of not-empty-string?))
|
||||
(s/def :config/figwheel-bridge not-empty-string?)
|
||||
|
|
Loading…
Reference in New Issue