1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var CLOSURE_UNCOMPILED_DEFINES = null;
|
8 | var debugEnabled = false;
|
9 |
|
10 | var config = {
|
11 | basePath: "target/",
|
12 | googBasePath: 'goog/',
|
13 | serverPort: 8081
|
14 | };
|
15 |
|
16 | var React = require('react');
|
17 | var createReactClass = require('create-react-class');
|
18 | var ReactNative = require('react-native');
|
19 | var WebSocket = require('WebSocket');
|
20 | var self;
|
21 | var evaluate = eval;
|
22 | var externalModules = {};
|
23 | var evalListeners = {};
|
24 | var asyncImportChain = new Promise(function (succ,fail) {succ(true);});
|
25 |
|
26 | function fireEvalListenters(url) {
|
27 | Object.values(evalListeners).forEach(function (listener) {
|
28 | listener(url)
|
29 | });
|
30 | }
|
31 |
|
32 | function 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 |
|
51 |
|
52 |
|
53 |
|
54 | function figwheelMessageHandler(msg) {
|
55 | if(msg["msg-name"] == "compile-failed") {
|
56 | console.warn(formatCompileError(msg));
|
57 | }
|
58 | }
|
59 |
|
60 | function listenToFigwheelMessages() {
|
61 | if(figwheel.client.add_json_message_watch) {
|
62 | figwheel.client.add_json_message_watch("ReactNativeMessageIntercept",
|
63 | figwheelMessageHandler);
|
64 | }
|
65 | }
|
66 |
|
67 | var 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 |
|
96 | function logDebug(msg) {
|
97 | if (debugEnabled) {
|
98 | console.log(msg);
|
99 | }
|
100 | }
|
101 |
|
102 | var isChrome = function () {
|
103 | return typeof importScripts === "function"
|
104 | };
|
105 |
|
106 | function 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 |
|
129 | function 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 |
|
142 | function 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 |
|
154 | function 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 |
|
166 | function serverBaseUrl(host) {
|
167 | return "http://" + host + ":" + config.serverPort
|
168 | }
|
169 |
|
170 | function isUnDefined(x) {
|
171 | return typeof x == "undefined";
|
172 | }
|
173 |
|
174 |
|
175 | function 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 |
|
189 | function loadApp(platform, devHost, onLoadCb) {
|
190 | var fileBasePath = serverBaseUrl((isChrome() ? "localhost" : devHost)) + "/" + config.basePath + platform;
|
191 |
|
192 |
|
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 |
|
207 | importJs(fileBasePath + '/goog/base.js', function () {
|
208 | shimBaseGoog(fileBasePath);
|
209 | importJs(fileBasePath + '/cljs_deps.js');
|
210 | importJs(fileBasePath + '/goog/deps.js', function () {
|
211 |
|
212 |
|
213 | var googreq = goog.require;
|
214 |
|
215 | googreq(`env.${platform}.main`);
|
216 | });
|
217 | });
|
218 | }
|
219 | }
|
220 |
|
221 | function startApp(appName, platform, devHost) {
|
222 | ReactNative.AppRegistry.registerComponent(
|
223 | appName, () => figwheelApp(platform, devHost));
|
224 | }
|
225 |
|
226 | function withModules(moduleById) {
|
227 | externalModules = moduleById;
|
228 | return self;
|
229 | }
|
230 |
|
231 | function figwheelImportScript(uri, callback) {
|
232 | importJs(uri.toString(),
|
233 | function () {callback(true);},
|
234 | function () {callback(false);})
|
235 | }
|
236 |
|
237 |
|
238 | function 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 |
|
250 | self = {
|
251 | withModules: withModules,
|
252 | start: startApp
|
253 | };
|
254 |
|
255 | module.exports = self;
|