Skip to content

GHC JS browser introduction blog post/tutorial #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 28 commits into from
Jan 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
e5aef39
WIP blog post & associated example code for GHC JS browser example
JoshMeredith Dec 19, 2022
e8624e2
WIP browser example blog post - building ghc and start of node/browse…
JoshMeredith Dec 20, 2022
ccbd4e7
Browser example blog post
JoshMeredith Dec 21, 2022
031eedc
Browser example blog post intro/conclusion
JoshMeredith Dec 22, 2022
d3d4e51
Browser example: incorporate feedback
JoshMeredith Jan 3, 2023
8027fcd
Browser example: incorporate feedback 2
JoshMeredith Jan 10, 2023
69d0b35
miscellaneous wordsmithing
Jan 12, 2023
34a523e
add code explanation
Jan 12, 2023
2bdd961
more wordsmithing
Jan 12, 2023
edfce60
Update blog/ghc-js-browser-example/browser-example.md
JoshMeredith Jan 18, 2023
35268e9
Update browser-example.md
JoshMeredith Jan 18, 2023
f99717a
Browser example: screenshot and other small changes
JoshMeredith Jan 18, 2023
15f1a0a
Browser example: typo
JoshMeredith Jan 18, 2023
6777696
Update blog/2023-01-18-javascript-browser-tutorial.md
JoshMeredith Jan 19, 2023
01a3554
Update 2023-01-18-javascript-browser-tutorial.md
JoshMeredith Jan 19, 2023
cfcec31
Update 2023-01-18-javascript-browser-tutorial.md
JoshMeredith Jan 19, 2023
1d24d15
Update 2023-01-18-javascript-browser-tutorial.md
JoshMeredith Jan 19, 2023
571344e
Set docasaurus static directory
JoshMeredith Jan 19, 2023
49d1f42
fix en-dashes, remove passive voices
Jan 20, 2023
8d6ac89
Update 2023-01-18-javascript-browser-tutorial.md
JoshMeredith Jan 20, 2023
2a8a8a0
Update blog/2023-01-18-javascript-browser-tutorial.md
hsyl20 Jan 20, 2023
97373b1
Update blog/2023-01-18-javascript-browser-tutorial.md
hsyl20 Jan 20, 2023
524bcb0
Update blog/2023-01-18-javascript-browser-tutorial.md
hsyl20 Jan 20, 2023
444ca9b
Update blog/2023-01-18-javascript-browser-tutorial.md
hsyl20 Jan 20, 2023
f6bcd1e
Update 2023-01-18-javascript-browser-tutorial.md
JoshMeredith Jan 20, 2023
0db79fd
Update 2023-01-18-javascript-browser-tutorial.md
JoshMeredith Jan 23, 2023
1a560c1
Update blog/2023-01-18-javascript-browser-tutorial.md
hsyl20 Jan 24, 2023
8dd58a9
Update and rename 2023-01-18-javascript-browser-tutorial.md to 2023-0…
hsyl20 Jan 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions blog/ghc-js-browser-example/browser-example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@

# GHC JavaScript Browser Example

## Introduction


## Building

TODO: Can we do something with the cabal project file and/or environment variables and the with-compiler/with-hs-pkg fields to optionally specify /path/to/build/stage1/bin in the case that the user is currently building the compiler themselves, since Hadrian can't install stage 1 and prebuilt binaries are generally unavailable.

## Features

### Foreign Import JavaScript

To access JavaScript-specific features in Haskell, we can import them via Haskell's `foreign import` syntax.

In our example (in `app/Main.hs`), we have:
```
foreign import javascript unsafe "((html) => { return setInner(html); })"
setInnerHtml :: CString -> IO ()
```

Here, there are a number of details to take note of:
- we specify the import as a string of JavaScript with _a single function call_ (TODO: Elaborate on syntax)
- _TODO: Explain unsafe vs interruptible_
- we must pass arguments as types from the `base` library's `Foreign.C` module - in this case `CString`
- return types must also be `Foreign.C` types, or unit (`()`)

On the JavaScript side (in `js/example.js`), we have:
```
function setInner(html) {
document.body.innerHTML = "example" + h$decodeUtf8z(html,0);
return;
}
```

In this JavaScript function, we match the inputs specified in the `foreign import` declaration. However, we unfortunately have to currently use internal functions of GHC's JavaScript backend - in order to convert this `CString` (stored as a byte array) into a JavaScript string, we use `h$decodeUtf8z`.

## Current Limitations

### Libraries Using C FFI Imports

If a function is imported via `foreign import ccall`, then that symbol will end up being referenced in the generated JavaScript, with `h$` prepended.

In the example code, the `lucid-svg` library indirectly calls `bytestring`'s `cIsValidUtf8`, which imports the c symbol `bytestring_is_valid_utf8`. To work around this, we can add the missing definition in our own javascript - which we do in `js/example.js`:

```
function h$bytestring_is_valid_utf8(bs) {
return true;
}
```

Although this definiton is far from ideal, it demonstrates that we can link in missing library c functions via our own implementation in our project.

### TODO: Talk about the ghc-options JS source linking workaround?
26 changes: 26 additions & 0 deletions blog/ghc-js-browser-example/browser-example/app/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{-# LANGUAGE OverloadedStrings #-}

module Main where

import Foreign.C.String
import Lucid.Svg


foreign import javascript unsafe "((html) => { return setInner(html); })"
setInnerHtml :: CString -> IO ()


svg :: Svg () -> Svg ()
svg content = do
doctype_
with (svg11_ content) [width_ "300", height_ "200"]


image :: Svg ()
image = rect_ [width_ "100%", height_ "100%", fill_ "red"]


main :: IO ()
main = do
setInnerHtml =<< newCString (show $ svg image)
print $ svg image
13 changes: 13 additions & 0 deletions blog/ghc-js-browser-example/browser-example/browser-example.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
cabal-version: 2.4
name: browser-example
version: 0.1.0.0

executable browser-example
main-is: Main.hs

build-depends: base ^>=4.17.0.0,
lucid-svg,
text,
hs-source-dirs: app
ghc-options: js/example.js
default-language: Haskell2010
9 changes: 9 additions & 0 deletions blog/ghc-js-browser-example/browser-example/js/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

function h$bytestring_is_valid_utf8(bs) {
return true;
}

function setInner(html) {
document.body.innerHTML = "example" + h$decodeUtf8z(html,0);
return;
}