Skip to content

Instantly share code, notes, and snippets.

@fizruk
Last active November 21, 2017 05:23
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save fizruk/fb5bad8a08a4b788764851c11ebbadf8 to your computer and use it in GitHub Desktop.
Run GHCJSi for a project with static files.
  1. Put irunner.js into your repository at ghcjs/lib/etc/irunner.js.
  2. Put static content (CSS, images, etc.) for executable at <executable name>/static.
  3. Put ghcjsiClient.html to <executable name>/static and add any headers you want.
  4. Run run.sh <package name> <executable name>.

Caveats

npm packages

In order for everything to work you need to install a few packages via npm.

npm install socket.io finalhandler serve-static

If you want those avaible in multiple GHCJS projects (which is probable) I recommend installing those globally:

npm install socket.io finalhandler serve-static --global

NODE_PATH

You might have to set up NODE_PATH environment variable, here's a nice way to do that:

export NODE_PATH=$(npm root):$(npm root --global)

Clear <body>

It is useful to clear <body> before reloading an app.

You can define clearBody and reload helpers like this:

foreign import javascript unsafe
  "document.body.innerHTML = '';"
  clearBody :: IO ()

-- | Clear everything and reload an app in the browser.
-- Useful to reload in GHCJSi.
reload :: IO a -> IO a
reload app = clearBody >> app

With these you can reload your main with a simple command:

>>> reload main

GHCJSi is experimental

Remember that GHCJSi is experimental and you might have to restart GHCJSi occasionally.

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Custom GHCJSi session</title>
<!-- Do not remove this script! -->
<!-- It is needed for proper GHCJSi integration -->
<script src="/socket.io/socket.io.js" language="javascript"></script>
<!-- ------------------------------------------------------------ -->
</head>
<body>
</body>
<!-- ----------------------------------------------- -->
<!-- Do not remove this script! -->
<!-- It is needed for proper GHCJSi integration -->
<script src="/client.js" language="javascript"></script>
<!-- ----------------------------------------------- -->
</html>
/*
GHCJSi communication
reads messages from stdin, sends over stderr
*/
var h$GHCJSiRecord = // true ||
!!process.env['GHCJS_RECORD_GHCJSI'];
var h$GHCJSiPort = process.env['GHCJSI_PORT'] || 6400;
/* An optional static directory to serve at root */
var h$GHCJSiStaticDir = process.env['GHCJSI_STATIC_DIR'];
var h$GHCJSiReplay = process.argv.length > 0 &&
process.argv[process.argv.length-1] === 'replay';
var h$GHCJSi = { data: null
, loadedSymbols: {}
, current: null
, sendMessage: h$sendMessage
, done: h$GHCJSiDone
, clientHtml: ''
, clientJS: ''
, socket: null
};
global.h$GHCJSi = h$GHCJSi;
global.require = require;
global.module = module;
var fs = require('fs');
var server = require('http').createServer(h$handleHTTP);
var url = require('url');
var io = null;
try {
io = require('socket.io')(server);
} catch (e) { }
if (h$GHCJSiStaticDir) {
try {
/* Try to set up a static file server for GHCJSi */
var finalhandler = require('finalhandler');
var serveStatic = require('serve-static');
var serveStaticDir = serveStatic(h$GHCJSiStaticDir);
} catch (e) {
console.log("\ncan't set up static file server for " + h$GHCJSiStaticDir);
console.log("\nmake sure you have finalhandler and serve-static installed:");
console.log("\n\n npm install finalhandler serve-static\n");
}
}
// start listening
function h$initGHCJSi() {
process.stdin.setEncoding('utf8');
process.stderr.setEncoding('binary');
process.on('uncaughtException', function(err) {
console.log(err);
console.log(err.stack);
});
if(h$GHCJSiReplay) {
h$replayMessage(1);
} else {
h$startHTTPServer();
process.stdin.on('readable', function() {
while(true) {
var str = process.stdin.read();
if(str) {
var buf = new Buffer(str, 'hex');
h$GHCJSi.data = h$GHCJSi.data ? Buffer.concat([h$GHCJSi.data, buf]) : buf;
h$processInput();
} else {
return;
}
}
});
process.stdin.on('close', function() { process.exit(0); });
}
}
function h$replayMessage(n) {
try {
var buffer = fs.readFileSync("ghcjsimessage." + n + ".dat");
var msgType = buffer.readUInt32BE(0);
h$processMessage(msgType, buffer.slice(4));
setTimeout(function() { h$replayMessage(n+1); }, 1500);
} catch(e) { }
}
function h$startHTTPServer() {
if(!io) {
console.log("\nsocket.io not found, browser session not available");
return;
} else {
console.log("\nsocket.io found, browser session available at http://localhost:" + h$GHCJSiPort);
if (typeof serveStaticDir !== 'undefined') {
console.log("\nserving static files from " + h$GHCJSiStaticDir);
}
}
io.on('connection', function(socket) {
console.log("\nbrowser connected, code runs in browser from now on");
h$GHCJSi.socket = io;
socket.on('msg', function(msg) {
h$GHCJSi.sendMessage(msg.type, msg.payload);
});
socket.on('out', function(data) {
process.stdout.write(data);
});
socket.on('disconnect', function() {
// console.log('browser disconnected');
});
});
h$GHCJSi.clientHtml = fs.readFileSync(__dirname + '/ghcjsiClient.html');
h$GHCJSi.clientJs = fs.readFileSync(__dirname + '/ghcjsiClient.js');
server.listen(h$GHCJSiPort);
}
function h$handleHTTP(req, res) {
var u = url.parse(req.url);
if(u.pathname === '/' || u.pathname === '/index.html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(h$GHCJSi.clientHtml);
} else if(u.pathname === '/client.js') {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(h$GHCJSi.clientJs);
} else if (typeof serveStaticDir !== 'undefined') {
serveStaticDir(req, res, finalhandler(req, res));
} else {
res.statusCode = 404;
res.statusMessage = 'not found';
res.end();
}
}
var h$GHCJSiMessageN = 0;
function h$processInput() {
while(h$GHCJSi.data && h$GHCJSi.data.length >= 8) {
var msgLength = h$GHCJSi.data.readUInt32BE(0);
var msgType = h$GHCJSi.data.readUInt32BE(4);
if(h$GHCJSi.data.length >= msgLength + 8) {
if(h$GHCJSiRecord && !h$GHCJSiReplay) {
fs.writeFileSync("ghcjsimessage." + (++h$GHCJSiMessageN) + ".dat"
,h$GHCJSi.data.slice(4, msgLength+8));
}
var msgPayload = h$GHCJSi.data.slice(8, msgLength + 8);
h$GHCJSi.data = h$GHCJSi.data.slice(msgLength + 8);
if(h$GHCJSi.socket) {
h$GHCJSi.socket.emit('msg', { type: msgType, payload: msgPayload });
} else {
h$processMessage(msgType, msgPayload);
}
} else return;
}
}
function h$processMessage(msgType, msgPayload) {
switch(msgType) {
case 0: // load initial code/rts and init
h$loadInitialCode(msgPayload.toString('utf8'));
h$sendMessage(0);
break;
case 1: // load code
h$loadCodeStr(msgPayload.toString('utf8'));
h$sendMessage(0);
break;
case 2: // run action
var symb = msgPayload.toString('utf8');
h$GHCJSi.current = h$main(h$GHCJSi.loadedSymbols[msgPayload.toString('utf8')]);
break;
case 3: // abort
if(h$GHCJSi.current)
h$killThread( h$GHCJSi.current
, h$baseZCControlziExceptionziBasezinonTermination);
break;
default:
throw new Error("unknown message type: " + msgType);
}
}
function h$GHCJSiDone(thread) {
h$sendMessage(0);
h$GHCJSi.current = null;
}
function h$sendMessage(msgType, msg, c) {
var hdr = new Buffer(8);
hdr.writeUInt32BE(msg ? msg.length : 0, 0);
hdr.writeUInt32BE(msgType, 4);
process.stderr.write( msg ? Buffer.concat([hdr, msg]) : hdr, 'binary'
, function() { if(c) c(); });
}
// load the RTS and set up the standard streams
function h$loadInitialCode(code) {
h$loadCodeStr(code, true);
// don't allow Haskell to read from stdin (fixme!)
h$base_stdin_fd.read = function(fd, fdo, buf, buf_offset, n, c) { c(0); }
// redirect Haskell's stderr to stdout since we use stderr to communicate (fixme!)
h$base_stderr_fd.write = h$base_stdout_fd.write;
}
function h$loadCodeStr(str) {
eval.call(null, str);
}
h$initGHCJSi();
#!/bin/sh
set -e
set -x
# cabal package name
package=$1
# cabal component name to run GHCJSi for
name=$2
# always run from a directory this script is in
cd $(dirname $0)
# find out where GHCJS /lib/etc files are located
GHCJS_LIB_ETC_PATH=$(dirname $(stack path --global-pkg-db))
# replace irunner.js to enable static file server
cp ghcjs/lib/etc/irunner.js ${GHCJS_LIB_ETC_PATH}/irunner.js
# specify a directory to serve static files from
export GHCJSI_STATIC_DIR=$(pwd)/$name/static
# run GHCJSi
# include :lib to reload library code too using :r in GHCJSi
stack ghci $package:lib $package:exe:$name
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment