Name: inline-js
Owner: Tweag I/O
Description: Execute Node.js scripts in Haskell.
Created: 2017-09-23 10:25:25.0
Updated: 2018-04-24 10:13:05.0
Pushed: 2018-04-07 09:28:29.0
Homepage: null
Size: 13098
Language: Haskell
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
NOTE: this is alpha quality software. Please don't publicize widely.
First, add inline-js
to the custom-setup
stanza of your package's .cabal
config. The Setup.hs
script looks like:
rt Language.JavaScript.Inline.Configure
:: IO ()
= defaultMainWithInlineJS defaultConfigureOptions
This will execute some additional post-configure actions when building your package, for example running npm install
to initialize the eval server. The initialization will be performed only once if your package is built without re-configuring. The ConfigureOptions
type in Language.JavaScript.Inline.Configure
provide several fields to customize the initialization, for example, specifying a minimum node
version, supplying additional environment variables or running custom shell commands.
If you need to add npm
dependencies, you can add them like this:
LANGUAGE OverloadedStrings #-}
rt Language.JavaScript.Inline.Configure
:: IO ()
= defaultMainWithInlineJS $ defaultConfigureOptions { commands = ["npm install left-pad"] }
Then, add inline-js
as a regular dependency for your build target. The above Setup.hs
scripts will produce a .buildinfo
file in the project directory, so you may want to add it to the .gitignore
.
LANGUAGE OverloadedStrings #-}
LANGUAGE QuasiQuotes #-}
LANGUAGE TemplateHaskell #-}
rt Control.Exception
rt Data.Aeson
rt Language.JavaScript.Inline.Configure
rt Language.JavaScript.Inline.Session
:: IO ()
= do
<- newSession $(configureOptionsQ)
ip finally (closeSession s) $ do
nul <- eval s "let answer = 6 * 7"
print (nul :: Value)
answer <- eval s "answer"
print (answer :: Double)
tmpdir <- eval s "require('os').tmpdir()"
print (tmpdir :: String)
async <-
eval
s
[js|new Promise((resolve, reject) => resolve('the answer is: ' + $(answer)))|]
print (async :: String)
Executing newSession
will start the eval server. An eval server has a single V8 execution context, and bindings are shared across multiple eval
calls. require
is available. newSession
requires a ConfigureOptions
value which should not be written by hand, but supplied by using the $(configureOptionsQ)
splice, which fetches the value from .buildinfo
generated by our custom Setup.hs
script.
eval
accepts Text
as a JavaScript source code, performs evaluation and returns the result. The result is marshalled to the Haskell world with a FromJSON
instance, so you may need to annotate the result type if it can't be inferred from the context. Evaluation failure will raise an exception.
You can use the js
quasi-quoter to embed inline JavaScript in Haskell. The inline JavaScript snippet can contain interpolations like $(var)
. var
is a Haskell binding in scope, and it will be marshalled from the Haskell world with a ToJSON
instance.
closeSession
will terminate the eval server. Remember to use a bracket
-like function to make sure the finalizer is invoked even in case of exception, to prevent dangling node
processes.
foreign import javascript
There is another higher-level wrapper for Session
and its operations. Language.JavaScript.Inline.MonadJS
provides a MonadJS
class which supports eval
, along with a JST
monad transformer.
Combined with Language.JavaScript.Inline.Import.js
, we can actually use foreign import javascript
in our code like this:
LANGUAGE QuasiQuotes #-}
LANGUAGE TemplateHaskell #-}
rt Control.Monad.IO.Class
rt Data.Aeson
rt Language.JavaScript.Inline.Configure
rt Language.JavaScript.Inline.Import
rt Language.JavaScript.Inline.MonadJS
[d|
reign import javascript "$(0) * $(1)" mul :: MonadJS m => Double -> Double -> m Double
reign import javascript "require('os').tmpdir()" getTmpDir :: MonadJS m => m FilePath
reign import javascript "new Promise(resolve => resolve('The answer is: ' + $(0)))" oracle :: (MonadJS m, ToJSON a) => a -> m String
)
:: IO ()
=
nJST $(configureOptionsQ) $ do
answer <- mul 6 7
liftIO $ print answer
tmpdir <- getTmpDir
liftIO $ print tmpdir
s <- oracle answer
liftIO $ print s
The above demo is included in examples
. The haddock documentation of the master
branch is available here.
inline-js is maintained by Tweag I/O.
Have questions? Need help? Tweet at @tweagio.