on 6a87bd4 help sign in
9 files changed+115−31
since splitsnapshots
unifiedsplit
default.nix+27−2 dep/reflex-platform/github.json+5−5 dep/webabi/default.nix+25 lib/backend/src/Obelisk/Backend.hs+38−7 lib/executable-config/lookup/obelisk-executable-config-lookup.cabal+1−1 lib/executable-config/lookup/src-ghcjs/Obelisk/ExecutableConfig/Lookup.hs+4−2 lib/frontend/src/Obelisk/Frontend.hs+11−12 lib/run/src/Obelisk/Run.hs+2−1 skeleton/frontend/frontend.cabal+2−1
default.nix+27−2
@@ -2,6 +2,7 @@
22, profiling ? false
33, iosSdkVersion ? "10.2"
44, config ? {}
5+, enableWasm ? true
56, reflex-platform-func ? import ./dep/reflex-platform
67}:
78let
@@ -85,6 +86,28 @@ in rec {
8586 echo "//# sourceMappingURL=all.js.map" >> all.js
8687 ''}
8788 '';
89+ webabi = pkgs.callPackage ./dep/webabi {};
90+ # wasm-opt seems to have no effect
91+ stripWasm = frontend: pkgs.runCommand "stripWasm" {} ''
92+ mkdir $out
93+ cd $out
94+ ln -s "${frontend}/bin/frontend" frontend.original.wasm
95+ cp "${frontend}/bin/frontend" frontend.wasm
96+ chmod u+rw frontend.wasm
97+ chmod a-x frontend.wasm
98+ ${pkgs.wabt}/bin/wasm-strip frontend.wasm
99+ '';
100+
101+ combinedJsWasmAssets = frontendJs: optimizationLevel: frontendWasm: pkgs.runCommand "combinedJsWasmAssets" {} ''
102+ mkdir $out
103+ cd $out
104+ ln -s ${compressedJs frontendJs optimizationLevel}/* .
105+ ${if frontendWasm != null then ''
106+ ln -s ${stripWasm frontendWasm}/* .
107+ ln -s ${webabi}/lib/node_modules/webabi/jsaddleJS/* .
108+ ln -s ${webabi}/lib/node_modules/webabi/build/worker_runner.js .
109+ '' else '' ''}
110+ '';
88111
89112 serverModules = {
90113 mkBaseEc2 = { nixosPkgs, ... }: {...}: {
@@ -167,13 +190,13 @@ in rec {
167190
168191 inherit mkAssets;
169192
170− serverExe = backend: frontend: assets: optimizationLevel: version:
193+ serverExe = backend: frontendJs: assets: optimizationLevel: frontendWasm: version:
171194 pkgs.runCommand "serverExe" {} ''
172195 mkdir $out
173196 set -eux
174197 ln -s "${if profiling then backend else haskellLib.justStaticExecutables backend}"/bin/* $out/
175198 ln -s "${mkAssets assets}" $out/static.assets
176− ln -s ${mkAssets (compressedJs frontend optimizationLevel)} $out/frontend.jsexe.assets
199+ ln -s ${mkAssets (combinedJsWasmAssets frontendJs optimizationLevel frontendWasm)} $out/frontend.jsexe.assets
177200 echo ${version} > $out/version
178201 '';
179202
@@ -280,6 +303,7 @@ in rec {
280303 lib.filter (x: lib.hasAttr x self.combinedPackages) self.shells-ghcSavedSplices;
281304 ghc = lib.filter (x: lib.hasAttr x self.combinedPackages) self.shells-ghc;
282305 ghcjs = lib.filter (x: lib.hasAttr x self.combinedPackages) self.shells-ghcjs;
306+ wasm = lib.filter (x: lib.hasAttr x self.combinedPackages) self.shells-ghcjs;
283307 };
284308 android = self.__androidWithConfig (self.base + "/config");
285309 ios = self.__iosWithConfig (self.base + "/config");
@@ -305,6 +329,7 @@ in rec {
305329 mainProjectOut.ghcjs.frontend
306330 projectInst.passthru.staticFiles
307331 projectInst.passthru.__closureCompilerOptimizationLevel
332+ (if enableWasm then mainProjectOut.wasm.frontend else null)
308333 version;
309334 linuxExe = serverOn (projectOut { system = "x86_64-linux"; });
310335 dummyVersion = "Version number is only available for deployments";
dep/reflex-platform/github.json+5−5
@@ -1,8 +1,8 @@
11{
2− "owner": "reflex-frp",
2+ "owner": "dfordivam",
33 "repo": "reflex-platform",
4− "branch": "master",
4+ "branch": "ob-1",
55 "private": false,
6− "rev": "41be4d952b75515a037318aa344dd6b13ad29cfe",
7− "sha256": "132yqzyzd3c5fjy7wwnwa5d6pjxv5ap2xz0swwbv3h5pwi8jwv0a"
8−}
6+ "rev": "d3db8e8a104788f8546b812180c5c20e2430cf98",
7+ "sha256": "08zkn0whgvb0grw571r6bdawbi90c9jpii7hak7lmq9i29wxl3bv"
8+}
dep/webabi/default.nixnew file+25
@@ -0,0 +1,25 @@
1+{ nodePackages, git, pkgs, system }:
2+
3+let
4+ runNode2nix = name: src: pkgs.runCommand "${name}-nix" { nativeBuildInputs = [nodePackages.node2nix]; } ''
5+ mkdir -p $out
6+ cd $out
7+ node2nix -i ${src}/package.json -l ${src}/package-lock.json --include-peer-dependencies --nodejs-10 -d -o ./node-packages.nix -e ./node-env.nix -c ./default.nix
8+ '';
9+
10+ callNode2nix = name: src: import (runNode2nix name src) { inherit pkgs system; };
11+
12+ webabiSrc = pkgs.fetchFromGitHub {
13+ owner = "WebGHC";
14+ repo = "webabi";
15+ rev = "7ffeff005dbdce60c96d830a5f67c4c461c0f5a1";
16+ sha256 = "0znwbhgpgf6kk0zwbnhvnx26rgvqql6isnblh87q6wd0dnc57h2x";
17+ };
18+
19+in (callNode2nix "webabi" webabiSrc).package.overrideAttrs (old: {
20+ postInstall = ''
21+ ${old.postInstall or ""}
22+ patchShebangs .
23+ npm run build
24+ '';
25+})
lib/backend/src/Obelisk/Backend.hs+38−7
@@ -20,6 +20,7 @@ module Obelisk.Backend
2020 , runBackendWith
2121 -- * Configuration of backend
2222 , GhcjsWidgets(..)
23+ , GhcjsWasmAssets(..)
2324 , defaultGhcjsWidgets
2425 -- * all.js script loading functions
2526 , deferredGhcjsScript
@@ -73,7 +74,7 @@ data Backend backendRoute frontendRoute = Backend
7374data BackendConfig frontendRoute = BackendConfig
7475 { _backendConfig_runSnap :: !(Snap () -> IO ()) -- ^ Function to run the snap server
7576 , _backendConfig_staticAssets :: !StaticAssets -- ^ Static assets
76− , _backendConfig_ghcjsWidgets :: !(GhcjsWidgets (Text -> FrontendWidgetT (R frontendRoute) ()))
77+ , _backendConfig_ghcjsWidgets :: !(GhcjsWidgets (GhcjsWasmAssets -> FrontendWidgetT (R frontendRoute) ()))
7778 -- ^ Given the URL of all.js, return the widgets which are responsible for
7879 -- loading the script.
7980 } deriving (Generic)
@@ -92,10 +93,14 @@ data GhcjsWidgets a = GhcjsWidgets
9293 -- ^ A script widget, placed in the document body
9394 } deriving (Functor, Generic)
9495
96+data GhcjsWasmAssets = GhcjsWasmAssets
97+ { _ghcjsWasmAssets_allJs :: Text
98+ , _ghcjsWasmAssets_wasm :: Maybe (Text, Text)
99+ }
95100
96101-- | Given the URL of all.js, return the widgets which are responsible for
97102-- loading the script. Defaults to 'preloadGhcjs' and 'deferredGhcjsScript'.
98−defaultGhcjsWidgets :: GhcjsWidgets (Text -> FrontendWidgetT r ())
103+defaultGhcjsWidgets :: GhcjsWidgets (GhcjsWasmAssets -> FrontendWidgetT r ())
99104defaultGhcjsWidgets = GhcjsWidgets
100105 { _ghcjsWidgets_preload = preloadGhcjs
101106 , _ghcjsWidgets_script = deferredGhcjsScript
@@ -167,6 +172,11 @@ renderAllJsPath :: Encoder Identity Identity (R (FullRoute a b)) PageName -> Tex
167172renderAllJsPath validFullEncoder =
168173 renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_Ghcjs) :/ ["all.js"]
169174
175+renderWasmPaths :: Encoder Identity Identity (R (FullRoute a b)) PageName -> (Text, Text)
176+renderWasmPaths validFullEncoder = (,)
177+ (renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_Ghcjs) :/ [])
178+ (renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_Ghcjs) :/ ["frontend.wasm"])
179+
170180serveObeliskApp
171181 :: (MonadSnap m, HasCookies m, MonadFail m)
172182 => (R appRoute -> Text)
@@ -243,11 +253,12 @@ runBackendWith (BackendConfig runSnap staticAssets ghcjsWidgets) backend fronten
243253 Identity r -> case r of
244254 FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
245255 FullRoute_Frontend obeliskRoute :/ a ->
246− serveDefaultObeliskApp routeToUrl (($ allJsUrl) <$> ghcjsWidgets) (serveStaticAssets staticAssets) frontend publicConfigs $
256+ serveDefaultObeliskApp routeToUrl (($ GhcjsWasmAssets allJsUrl (Just wasmUrls)) <$> ghcjsWidgets) (serveStaticAssets staticAssets) frontend publicConfigs $
247257 obeliskRoute :/ a
248258 where
249259 routeToUrl (k :/ v) = renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_App k) :/ v
250260 allJsUrl = renderAllJsPath validFullEncoder
261+ wasmUrls = renderWasmPaths validFullEncoder
251262
252263renderGhcjsFrontend
253264 :: (MonadSnap m, HasCookies m)
@@ -263,13 +274,33 @@ renderGhcjsFrontend urlEnc ghcjsWidgets route configs f = do
263274
264275-- | Preload all.js in a link tag.
265276-- This is the default preload method.
266−preloadGhcjs :: Text -> FrontendWidgetT r ()
267−preloadGhcjs allJsUrl = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: allJsUrl) blank
277+preloadGhcjs :: GhcjsWasmAssets -> FrontendWidgetT r ()
278+preloadGhcjs (GhcjsWasmAssets allJsUrl mWasm) = case mWasm of
279+ Nothing -> elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: allJsUrl) blank
280+ Just _ -> pure ()
268281
269282-- | Load the script from the given URL in a deferred script tag.
270283-- This is the default method.
271−deferredGhcjsScript :: Text -> FrontendWidgetT r ()
272−deferredGhcjsScript allJsUrl = elAttr "script" ("type" =: "text/javascript" <> "src" =: allJsUrl <> "defer" =: "defer") blank
284+deferredGhcjsScript :: GhcjsWasmAssets -> FrontendWidgetT r ()
285+deferredGhcjsScript (GhcjsWasmAssets allJsUrl mWasm) = case mWasm of
286+ Nothing -> elAttr "script" ("type" =: "text/javascript" <> "src" =: allJsUrl <> "defer" =: "defer") blank
287+ Just v -> wasmScript v
288+
289+wasmScript :: (Text, Text) -> FrontendWidgetT r ()
290+wasmScript (jsaddleAssets, wasmUrl) = do
291+ let sendMsgWorkerJs = jsaddleAssets <> "/jsaddle_sendMsgWorker.js"
292+ jsaddleJs = jsaddleAssets <> "/jsaddle.js"
293+ workerRunnerJs = jsaddleAssets <> "/worker_runner.js"
294+ elAttr "script" ("type" =: "text/javascript" ) $ text $
295+ "var jsaddle_sendMsgWorkerPath = '" <> sendMsgWorkerJs <> "';"
296+ elAttr "script" ("type" =: "text/javascript" <> "src" =: jsaddleJs) blank
297+ elAttr "script" ("type" =: "text/javascript") $ text $ mconcat $
298+ [ "var jsaddleVals = jsaddleJsInit();"
299+ , "const worker = new Worker(\"" <> workerRunnerJs <> "\");"
300+ , "worker.postMessage({ url: \"" <> wasmUrl <> "\""
301+ , " , jsaddleVals: jsaddleVals }"
302+ , " , [jsaddleVals.jsaddleListener]);"
303+ ]
273304
274305-- | An all.js script which is loaded after waiting for some time to pass. This
275306-- is useful to ensure any CSS animations on the page can play smoothly before
lib/executable-config/lookup/obelisk-executable-config-lookup.cabal+1−1
@@ -18,7 +18,7 @@ library
1818 build-depends: android-activity
1919 ld-options: -landroid
2020 else
21− if impl(ghcjs)
21+ if impl(ghcjs) || arch(wasm32)
2222 hs-source-dirs: src-ghcjs, src
2323 build-depends: ghcjs-dom
2424 else
lib/executable-config/lookup/src-ghcjs/Obelisk/ExecutableConfig/Lookup.hs+4−2
@@ -2,11 +2,13 @@
22{-# LANGUAGE OverloadedStrings #-}
33module Obelisk.ExecutableConfig.Lookup where
44
5+import Control.Monad.IO.Class
6+import Control.Monad.Fail (MonadFail)
57import Data.ByteString (ByteString)
68import qualified Data.ByteString.Base64 as B64
79import Data.Map (Map)
810import qualified Data.Map as Map
9−import Data.Maybe (catMaybes)
11+import Data.Maybe (catMaybes, isJust)
1012import Data.Text (Text)
1113import qualified Data.Text.Encoding as T
1214import Data.Traversable (for)
@@ -19,7 +21,7 @@ import GHCJS.DOM.NodeList (IsNodeList, item, getLength)
1921import GHCJS.DOM.ParentNode (querySelectorAll)
2022import GHCJS.DOM.Types (MonadJSM, Node(Node), castTo)
2123
22−getConfigs :: IO (Map Text ByteString)
24+getConfigs :: (MonadJSM m, MonadFail m) => m (Map Text ByteString)
2325getConfigs = do
2426 Just doc <- currentDocument
2527 Just hd <- getHead doc
lib/frontend/src/Obelisk/Frontend.hs+11−12
@@ -151,21 +151,20 @@ runFrontend
151151 -> Frontend (R route)
152152 -> JSM ()
153153runFrontend validFullEncoder frontend = do
154+#if defined(ghcjs_HOST_OS) || defined(wasm32_HOST_ARCH)
154155 let mode = FrontendMode
155− { _frontendMode_hydrate =
156−#ifdef ghcjs_HOST_OS
157− True
158−#else
159− False
160−#endif
161− , _frontendMode_adjustRoute =
162−#ifdef ghcjs_HOST_OS
163− False
156+ { _frontendMode_hydrate = True
157+ , _frontendMode_adjustRoute = False
158+ }
159+ doLift = Prelude.id
164160#else
165− True
166−#endif
161+ let mode = FrontendMode
162+ { _frontendMode_hydrate = False
163+ , _frontendMode_adjustRoute = True
167164 }
168− configs <- liftIO Lookup.getConfigs
165+ doLift = liftIO
166+#endif
167+ configs <- doLift Lookup.getConfigs
169168 when (_frontendMode_hydrate mode) removeHTMLConfigs
170169 -- There's no fundamental reason that adjustRoute needs to control setting the
171170 -- initial route and *also* the useHash parameter; that's why these are
lib/run/src/Obelisk/Run.hs+2−1
@@ -97,9 +97,10 @@ run port serveStaticAsset backend frontend = do
9797 Identity r -> case r of
9898 FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
9999 FullRoute_Frontend obeliskRoute :/ a ->
100− serveDefaultObeliskApp appRouteToUrl (($ allJsUrl) <$> defaultGhcjsWidgets) serveStaticAsset frontend publicConfigs $ obeliskRoute :/ a
100+ serveDefaultObeliskApp appRouteToUrl (($ assetsUrl) <$> defaultGhcjsWidgets) serveStaticAsset frontend publicConfigs $ obeliskRoute :/ a
101101 where
102102 appRouteToUrl (k :/ v) = renderObeliskRoute validFullEncoder (FullRoute_Frontend (ObeliskRoute_App k) :/ v)
103+ assetsUrl = GhcjsWasmAssets allJsUrl Nothing
103104 allJsUrl = renderAllJsPath validFullEncoder
104105
105106 let conf = defRunConfig { _runConfig_redirectPort = port }
skeleton/frontend/frontend.cabal+2−1
@@ -29,6 +29,7 @@ executable frontend
2929 , obelisk-generated-static
3030 , frontend
3131 --TODO: Make these ghc-options optional
32− ghc-options: -threaded
32+ if !arch(wasm32)
33+ ghc-options: -threaded
3334 if os(darwin)
3435 ghc-options: -dynamic