UNPKG

7.61 kBJavaScriptView Raw
1/*
2 * Originally taken from https://github.com/decker405/figwheel-react-native
3 *
4 * @providesModule figwheel-bridge
5 */
6
7var CLOSURE_UNCOMPILED_DEFINES = null;
8var debugEnabled = false;
9
10var config = {
11 basePath: "target/",
12 googBasePath: 'goog/',
13 serverPort: 8081
14};
15
16var React = require('react');
17var createReactClass = require('create-react-class');
18var ReactNative = require('react-native');
19var WebSocket = require('WebSocket');
20var self;
21var evaluate = eval; // This is needed, direct calls to eval does not work (RN packager???)
22var externalModules = {};
23var evalListeners = {};
24var asyncImportChain = new Promise(function (succ,fail) {succ(true);});
25
26function fireEvalListenters(url) {
27 Object.values(evalListeners).forEach(function (listener) {
28 listener(url)
29 });
30}
31
32function formatCompileError(msg) {
33 var errorStr = "Figwheel Compile Exception: "
34 var data = msg['exception-data'];
35 if(data['message']) {
36 errorStr += data['message'] + " ";
37 }
38 if(data['file']) {
39 errorStr += "in file " + data['file'] + " ";
40 }
41 if(data['line']) {
42 errorStr += "at line " + data['line'];
43 }
44 if(data['column']) {
45 errorStr += ", column " + data['column'];
46 }
47 return errorStr;
48}
49
50/* This is simply demonstrating that we can receive and react to
51 * arbitrary messages from Figwheel this will enable creating a nicer
52 * feedback system in the Figwheel top level React component.
53 */
54function figwheelMessageHandler(msg) {
55 if(msg["msg-name"] == "compile-failed") {
56 console.warn(formatCompileError(msg));
57 }
58}
59
60function listenToFigwheelMessages() {
61 if(figwheel.client.add_json_message_watch) {
62 figwheel.client.add_json_message_watch("ReactNativeMessageIntercept",
63 figwheelMessageHandler);
64 }
65}
66
67var figwheelApp = function (platform, devHost) {
68 return createReactClass({
69 getInitialState: function () {
70 return {loaded: false}
71 },
72 render: function () {
73 if (!this.state.loaded) {
74 var plainStyle = {flex: 1, alignItems: 'center', justifyContent: 'center'};
75 return (
76 <ReactNative.View style={plainStyle}>
77 <ReactNative.Text>Waiting for Figwheel to load files.</ReactNative.Text>
78 </ReactNative.View>
79 );
80 }
81 return this.state.root;
82 },
83
84 componentDidMount: function () {
85 var app = this;
86 if (typeof goog === "undefined") {
87 loadApp(platform, devHost, function (appRoot) {
88 app.setState({root: appRoot, loaded: true});
89 listenToFigwheelMessages();
90 });
91 }
92 }
93 })
94};
95
96function logDebug(msg) {
97 if (debugEnabled) {
98 console.log(msg);
99 }
100}
101
102var isChrome = function () {
103 return typeof importScripts === "function"
104};
105
106function asyncImportScripts(url, success, error) {
107 logDebug('(asyncImportScripts) Importing: ' + url);
108 asyncImportChain =
109 asyncImportChain
110 .then(function (v) {return fetch(url);})
111 .then(function (response) {
112 if(response.ok)
113 return response.text();
114 throw new Error("Failed to Fetch: " + url + " - Perhaps your project was cleaned and you haven't recompiled?")
115 })
116 .then(function (responseText) {
117 evaluate(responseText);
118 fireEvalListenters(url);
119 success();
120 return true;
121 })
122 .catch(function (e) {
123 console.error(e);
124 error();
125 return true;
126 });
127}
128
129function syncImportScripts(url, success, error) {
130 try {
131 importScripts(url);
132 logDebug('Evaluated: ' + url);
133 fireEvalListenters(url);
134 success();
135 } catch (e) {
136 console.error(e);
137 error()
138 }
139}
140
141// Loads js file sync if possible or async.
142function importJs(src, success, error) {
143 var noop = function(){};
144 success = (typeof success == 'function') ? success : noop;
145 error = (typeof error == 'function') ? error : noop;
146 logDebug('(importJs) Importing: ' + src);
147 if (isChrome()) {
148 syncImportScripts(src, success, error);
149 } else {
150 asyncImportScripts(src, success, error);
151 }
152}
153
154function interceptRequire() {
155 var oldRequire = window.require;
156 console.info("Shimming require");
157 window.require = function (id) {
158 console.info("Requiring: " + id);
159 if (externalModules[id]) {
160 return externalModules[id];
161 }
162 return oldRequire(id);
163 };
164}
165
166function serverBaseUrl(host) {
167 return "http://" + host + ":" + config.serverPort
168}
169
170function isUnDefined(x) {
171 return typeof x == "undefined";
172}
173
174// unlikely to happen but it happened to me a couple of times so ...
175function assertRootElExists(platform) {
176 var basicMessage = "ClojureScript project didn't compile, or didn't load correctly.";
177 if(isUnDefined(env)) {
178 throw new Error("Critical Error: env namespace not defined - " + basicMessage);
179 } else if(isUnDefined(env[platform])) {
180 throw new Error("Critical Error: env." + platform + " namespace not defined - " + basicMessage);
181 } else if(isUnDefined(env[platform].main)) {
182 throw new Error("Critical Error: env." + platform + ".main namespace not defined - " + basicMessage);
183 } else if(isUnDefined(env[platform].main.root_el)) {
184 throw new Error("Critical Error: env." +
185 platform + ".main namespace doesn't define a root-el which should hold the root react node of your app.");
186 }
187 }
188
189function loadApp(platform, devHost, onLoadCb) {
190 var fileBasePath = serverBaseUrl((isChrome() ? "localhost" : devHost)) + "/" + config.basePath + platform;
191
192 // callback when app is ready to get the reloadable component
193 var mainJs = `/env/${platform}/main.js`;
194 evalListeners.waitForFinalEval = function (url) {
195 if (url.indexOf(mainJs) > -1) {
196 assertRootElExists(platform);
197 onLoadCb(env[platform].main.root_el);
198 console.info('Done loading Clojure app');
199 delete evalListeners.waitForFinalEval;
200 }
201 };
202
203 if (typeof goog === "undefined") {
204 console.info('Loading Closure base.');
205 interceptRequire();
206 // need to know base path here
207 importJs(fileBasePath + '/goog/base.js', function () {
208 shimBaseGoog(fileBasePath);
209 importJs(fileBasePath + '/cljs_deps.js');
210 importJs(fileBasePath + '/goog/deps.js', function () {
211 // This is needed because of RN packager
212 // seriously React packager? why.
213 var googreq = goog.require;
214
215 googreq(`env.${platform}.main`);
216 });
217 });
218 }
219}
220
221function startApp(appName, platform, devHost) {
222 ReactNative.AppRegistry.registerComponent(
223 appName, () => figwheelApp(platform, devHost));
224}
225
226function withModules(moduleById) {
227 externalModules = moduleById;
228 return self;
229}
230
231function figwheelImportScript(uri, callback) {
232 importJs(uri.toString(),
233 function () {callback(true);},
234 function () {callback(false);})
235}
236
237// Goog fixes
238function shimBaseGoog(basePath) {
239 console.info('Shimming goog functions.');
240 goog.basePath = basePath + '/' + config.googBasePath;
241 goog.global.FIGWHEEL_WEBSOCKET_CLASS = WebSocket;
242 goog.global.FIGWHEEL_IMPORT_SCRIPT = figwheelImportScript;
243 goog.writeScriptSrcNode = importJs;
244 goog.writeScriptTag_ = function (src, optSourceText) {
245 importJs(src);
246 return true;
247 };
248}
249
250self = {
251 withModules: withModules,
252 start: startApp
253};
254
255module.exports = self;