@@ -20,6 +20,7 @@ module Obelisk.Backend
20 , runBackendWith
20 , runBackendWith
21 -- * Configuration of backend
21 -- * Configuration of backend
22 , GhcjsWidgets(..)
22 , GhcjsWidgets(..)
23+ , GhcjsWasmAssets(..)
23 , defaultGhcjsWidgets
24 , defaultGhcjsWidgets
24 -- * all.js script loading functions
25 -- * all.js script loading functions
25 , deferredGhcjsScript
26 , deferredGhcjsScript
@@ -39,6 +40,7 @@ module Obelisk.Backend
39 , getPublicConfigs
40 , getPublicConfigs
43+import Control.Exception (try)
42import Control.Monad
44import Control.Monad
43import Control.Monad.Except
45import Control.Monad.Except
44import Control.Monad.Fail (MonadFail)
46import Control.Monad.Fail (MonadFail)
@@ -46,6 +48,7 @@ import Data.ByteString (ByteString)
46import qualified Data.ByteString.Char8 as BSC8
48import qualified Data.ByteString.Char8 as BSC8
47import Data.Default (Default (..))
49import Data.Default (Default (..))
48import Data.Dependent.Sum
50import Data.Dependent.Sum
51+import Data.Either (isRight)
49import Data.Functor.Identity
52import Data.Functor.Identity
50import Data.Map (Map)
53import Data.Map (Map)
51import qualified Data.Map as Map
54import qualified Data.Map as Map
@@ -63,6 +66,7 @@ import Snap (MonadSnap, Snap, commandLineConfig, defaultConfig, getsRequest, htt
63 , rqPathInfo, rqQueryString, setContentType, writeBS, writeText
66 , rqPathInfo, rqQueryString, setContentType, writeBS, writeText
64 , rqCookies, Cookie(..) , setHeader)
67 , rqCookies, Cookie(..) , setHeader)
65import Snap.Internal.Http.Server.Config (Config (accessLog, errorLog), ConfigLog (ConfigIoLog))
68import Snap.Internal.Http.Server.Config (Config (accessLog, errorLog), ConfigLog (ConfigIoLog))
69+import System.FilePath ((</>))
66import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
70import System.IO (BufferMode (..), hSetBuffering, stderr, stdout)
68data Backend backendRoute frontendRoute = Backend
72data Backend backendRoute frontendRoute = Backend
@@ -73,8 +77,9 @@ data Backend backendRoute frontendRoute = Backend
73data BackendConfig frontendRoute = BackendConfig
77data BackendConfig frontendRoute = BackendConfig
74 { _backendConfig_runSnap :: !(Snap () -> IO ()) -- ^ Function to run the snap server
78 { _backendConfig_runSnap :: !(Snap () -> IO ()) -- ^ Function to run the snap server
75 , _backendConfig_staticAssets :: !StaticAssets -- ^ Static assets
79 , _backendConfig_staticAssets :: !StaticAssets -- ^ Static assets
80+ , _backendConfig_ghcjsAssets :: !StaticAssets -- ^ Frontend ghcjs (and wasm) assets
76− , _backendConfig_ghcjsWidgets :: !(GhcjsWidgets (Text -> FrontendWidgetT (R frontendRoute) ()))
81+ , _backendConfig_ghcjsWidgets :: !(GhcjsWidgets (GhcjsWasmAssets -> FrontendWidgetT (R frontendRoute) ()))
77− -- ^ Given the URL of all.js, return the widgets which are responsible for
82+ -- ^ Given the URL of all.js and wasm files, return the widgets which are responsible for
78 -- loading the script.
83 -- loading the script.
79 } deriving (Generic)
84 } deriving (Generic)
@@ -92,10 +97,14 @@ data GhcjsWidgets a = GhcjsWidgets
92 -- ^ A script widget, placed in the document body
97 -- ^ A script widget, placed in the document body
93 } deriving (Functor, Generic)
98 } deriving (Functor, Generic)
100+data GhcjsWasmAssets = GhcjsWasmAssets
101+ { _ghcjsWasmAssets_allJs :: Text
102+ , _ghcjsWasmAssets_wasmPathRoot :: Maybe Text
96-- | Given the URL of all.js, return the widgets which are responsible for
105-- | Given the URL of all.js, return the widgets which are responsible for
97-- loading the script. Defaults to 'preloadGhcjs' and 'deferredGhcjsScript'.
106-- loading the script. Defaults to 'preloadGhcjs' and 'deferredGhcjsScript'.
98−defaultGhcjsWidgets :: GhcjsWidgets (Text -> FrontendWidgetT r ())
107+defaultGhcjsWidgets :: GhcjsWidgets (GhcjsWasmAssets -> FrontendWidgetT r ())
99defaultGhcjsWidgets = GhcjsWidgets
108defaultGhcjsWidgets = GhcjsWidgets
100 { _ghcjsWidgets_preload = preloadGhcjs
109 { _ghcjsWidgets_preload = preloadGhcjs
101 , _ghcjsWidgets_script = deferredGhcjsScript
110 , _ghcjsWidgets_script = deferredGhcjsScript
@@ -132,6 +141,9 @@ defaultStaticAssets = StaticAssets
132 , _staticAssets_unprocessed = "static"
141 , _staticAssets_unprocessed = "static"
144+-- This has both ghcjs and wasm assets.
145+-- The 'unprocessed' is a fallback path, and in case of wasm is invalid (does not contain wasm files)
146+-- For the standard obelisk deployment the unprocessed path would not be used.
135defaultFrontendGhcjsAssets :: StaticAssets
147defaultFrontendGhcjsAssets :: StaticAssets
136defaultFrontendGhcjsAssets = StaticAssets
148defaultFrontendGhcjsAssets = StaticAssets
137 { _staticAssets_processed = "frontend.jsexe.assets"
149 { _staticAssets_processed = "frontend.jsexe.assets"
@@ -167,6 +179,10 @@ renderAllJsPath :: Encoder Identity Identity (R (FullRoute a b)) PageName -> Tex
167renderAllJsPath validFullEncoder =
179renderAllJsPath validFullEncoder =
168 renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_Ghcjs) :/ ["all.js"]
180 renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_Ghcjs) :/ ["all.js"]
182+renderWasmPathRoot :: Encoder Identity Identity (R (FullRoute a b)) PageName -> Text
183+renderWasmPathRoot validFullEncoder =
184+ renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_Resource ResourceRoute_Ghcjs) :/ []
170serveObeliskApp
186serveObeliskApp
171 :: (MonadSnap m, HasCookies m, MonadFail m)
187 :: (MonadSnap m, HasCookies m, MonadFail m)
172 => (R appRoute -> Text)
188 => (R appRoute -> Text)
@@ -221,7 +237,7 @@ serveGhcjsApp urlEnc ghcjsWidgets app config = \case
222-- | Default obelisk backend configuration.
238-- | Default obelisk backend configuration.
223defaultBackendConfig :: BackendConfig frontendRoute
239defaultBackendConfig :: BackendConfig frontendRoute
224−defaultBackendConfig = BackendConfig runSnapWithCommandLineArgs defaultStaticAssets defaultGhcjsWidgets
240+defaultBackendConfig = BackendConfig runSnapWithCommandLineArgs defaultStaticAssets defaultFrontendGhcjsAssets defaultGhcjsWidgets
226-- | Run an obelisk backend with the default configuration.
242-- | Run an obelisk backend with the default configuration.
227runBackend :: Backend backendRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
243runBackend :: Backend backendRoute frontendRoute -> Frontend (R frontendRoute) -> IO ()
@@ -233,21 +249,34 @@ runBackendWith
233 -> Backend backendRoute frontendRoute
249 -> Backend backendRoute frontendRoute
234 -> Frontend (R frontendRoute)
250 -> Frontend (R frontendRoute)
236−runBackendWith (BackendConfig runSnap staticAssets ghcjsWidgets) backend frontend = case checkEncoder $ _backend_routeEncoder backend of
252+runBackendWith (BackendConfig runSnap staticAssets ghcjsAssets ghcjsWidgets) backend frontend = case checkEncoder $ _backend_routeEncoder backend of
237 Left e -> fail $ "backend error:\n" <> T.unpack e
253 Left e -> fail $ "backend error:\n" <> T.unpack e
238 Right validFullEncoder -> do
254 Right validFullEncoder -> do
239 publicConfigs <- getPublicConfigs
255 publicConfigs <- getPublicConfigs
256+ enableWasm <- checkWasmFile
240 _backend_run backend $ \serveRoute ->
257 _backend_run backend $ \serveRoute ->
241 runSnap $
258 runSnap $
242 getRouteWith validFullEncoder >>= \case
259 getRouteWith validFullEncoder >>= \case
243 Identity r -> case r of
260 Identity r -> case r of
244 FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
261 FullRoute_Backend backendRoute :/ a -> serveRoute $ backendRoute :/ a
245 FullRoute_Frontend obeliskRoute :/ a ->
262 FullRoute_Frontend obeliskRoute :/ a ->
246− serveDefaultObeliskApp routeToUrl (($ allJsUrl) <$> ghcjsWidgets) (serveStaticAssets staticAssets) frontend publicConfigs $
263+ serveObeliskApp routeToUrl widgets (serveStaticAssets staticAssets) frontendApp publicConfigs $
247 obeliskRoute :/ a
264 obeliskRoute :/ a
249 routeToUrl (k :/ v) = renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_App k) :/ v
266 routeToUrl (k :/ v) = renderObeliskRoute validFullEncoder $ FullRoute_Frontend (ObeliskRoute_App k) :/ v
250 allJsUrl = renderAllJsPath validFullEncoder
267 allJsUrl = renderAllJsPath validFullEncoder
268+ mWasmUrls = if enableWasm then Just (renderWasmPathRoot validFullEncoder) else Nothing
269+ widgets = ($ GhcjsWasmAssets allJsUrl mWasmUrls) <$> ghcjsWidgets
270+ frontendApp = GhcjsApp
271+ { _ghcjsApp_compiled = ghcjsAssets
272+ , _ghcjsApp_value = frontend
276+ let base = _staticAssets_processed ghcjsAssets
278+ assetType :: (Either IOError ByteString) <- liftIO $ try $ BSC8.readFile $ base </> p </> "type"
279+ pure $ isRight assetType
252renderGhcjsFrontend
281renderGhcjsFrontend
253 :: (MonadSnap m, HasCookies m)
282 :: (MonadSnap m, HasCookies m)
@@ -263,13 +292,60 @@ renderGhcjsFrontend urlEnc ghcjsWidgets route configs f = do
264-- | Preload all.js in a link tag.
293-- | Preload all.js in a link tag.
265-- This is the default preload method.
294-- This is the default preload method.
266−preloadGhcjs :: Text -> FrontendWidgetT r ()
295+preloadGhcjs :: GhcjsWasmAssets -> FrontendWidgetT r ()
296+preloadGhcjs (GhcjsWasmAssets allJsUrl mWasm) = case mWasm of
267−preloadGhcjs allJsUrl = elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: allJsUrl) blank
297+ Nothing -> elAttr "link" ("rel" =: "preload" <> "as" =: "script" <> "href" =: allJsUrl) blank
298+ Just wasmAssets -> fst (wasmScripts allJsUrl wasmAssets)
269-- | Load the script from the given URL in a deferred script tag.
300-- | Load the script from the given URL in a deferred script tag.
270-- This is the default method.
301-- This is the default method.
271−deferredGhcjsScript :: Text -> FrontendWidgetT r ()
302+deferredGhcjsScript :: GhcjsWasmAssets -> FrontendWidgetT r ()
303+deferredGhcjsScript (GhcjsWasmAssets allJsUrl mWasm) = case mWasm of
272−deferredGhcjsScript allJsUrl = elAttr "script" ("type" =: "text/javascript" <> "src" =: allJsUrl <> "defer" =: "defer") blank
304+ Nothing -> elAttr "script" ("type" =: "text/javascript" <> "src" =: allJsUrl <> "defer" =: "defer") blank
305+ Just wasmAssets -> snd (wasmScripts allJsUrl wasmAssets)
307+wasmScripts :: Text -> Text -> (FrontendWidgetT r (), FrontendWidgetT r ())
308+wasmScripts allJsUrl wasmRoot = (script preloadScript, script runJsScript)
310+ script t = elAttr "script" ("type" =: "text/javascript") $ text t
311+ jsaddleJs = wasmRoot <> "/jsaddle_core.js"
312+ interfaceJs = wasmRoot <> "/jsaddle_mainthread_interface.js"
313+ runnerJs = wasmRoot <> "/mainthread_runner.js"
314+ wasmUrl = wasmRoot <> "/frontend.wasm"
317+ "add_preload_tag = function (docSrc, docType) { \
318+ \ var link_tag = document.createElement('link'); \
319+ \ link_tag.rel = 'preload'; \
320+ \ link_tag.as = docType; \
321+ \ link_tag.href = docSrc; \
322+ \ document.head.appendChild(link_tag); \
324+ \if (typeof(WebAssembly) === 'undefined') { \
325+ \ add_preload_tag('" <> allJsUrl <> "', 'script'); \
327+ \ add_preload_tag('" <> wasmUrl <> "', 'script'); \
328+ \ add_preload_tag('" <> jsaddleJs <> "', 'script'); \
329+ \ add_preload_tag('" <> interfaceJs <> "', 'script'); \
330+ \ add_preload_tag('" <> runnerJs <> "', 'script'); \
334+ "add_deferload_tag = function (docSrc) { \
335+ \ var tag = document.createElement('script'); \
336+ \ tag.type = 'text/javascript'; \
337+ \ tag.src = docSrc; \
338+ \ tag.setAttribute = ('defer', 'defer'); \
339+ \ document.body.appendChild(tag); \
341+ \if (typeof(WebAssembly) === 'undefined') { \
342+ \ add_deferload_tag('" <> allJsUrl <> "'); \
344+ \ var wasmFile = '" <> wasmUrl <> "'; \
345+ \ add_deferload_tag('" <> jsaddleJs <> "'); \
346+ \ add_deferload_tag('" <> interfaceJs <> "'); \
347+ \ add_deferload_tag('" <> runnerJs <> "'); \
274-- | An all.js script which is loaded after waiting for some time to pass. This
350-- | An all.js script which is loaded after waiting for some time to pass. This
275-- is useful to ensure any CSS animations on the page can play smoothly before
351-- is useful to ensure any CSS animations on the page can play smoothly before