TemplateHaskell and GHCJS

Tomas Carnecky
2 min readJan 5, 2017

This is another post outlining some of my struggles when writing Nauva. This time it’s about getting TemplateHaskell to work in GHCJS.

TLDR: In your JS FFI files, make sure all top-level expressions only depend on symbols defined in the JavaScript language (ES5, ES2015 etc), avoid use of symbols which are specific to a platform (W3C DOM APIs, nodejs APIs etc).

I use TemplateHaskell to convert strings (inline in the source code, through the use of quasiquotation or loaded from external files) into Catalog pages at compile time. There isn’t any need to do this at runtime since it’s a pure transformation and by doing that at compile time I can avoid having to include the whole markdown parser in the executable.

GHCJS implements TH by compiling the Haskell code into a JavaScript string, passing that string into a forked nodejs process which eval()'s it and sends the result back.

The nauva-client package includes a JS FFI file which implements bindings to React and other supporting code which requires the presence of W3C DOM APIs. Even though that FFI code is not actually needed by the TH splices, GHCJS still includes it in the JavaScript code which is sent to nodejs for execution. That means all top-level expressions in the FFI file are evaluated, and if they reference anything that is not available in the nodejs runtime, compiling the TH splice will fail! I had a few of such expressions:

class ControlledInput extends React.Component { ... }const style = document.createElement("style");
document.head.appendChild(style);

I tried to wrap the relevant code in typeof checks (eg. if (typeof React !== 'undefined') { ... }, but that didn’t work, the TH code was still failing.

The solution was to wrap all code which depends on React, document and other global symbols in a function. A thunk, essentially. If the function is never executed — is is not by the TH code — then it will not crash.

function ControlledInput = (() => {
let v = undefined;
return () => {
if (v === undefined) {
v = class extends React.Component { ... };
}
return v;
};
});

Remember, GHCJS smashes all the compiled code and your JS FFI files into a single, big blob of JavaScript code. Everything is global, you can access any function from anywhere. There is no concept of namespaces or modules.

--

--