on develop help sign in
Merge pull request #326 from reflex-frp/ts-data-ssr
419dc17Ali Abrar
reflex-dom-core/ChangeLog.md+5
@@ -1,5 +1,10 @@
1# Revision history for reflex-dom-core
1# Revision history for reflex-dom-core
2
2
3+## Unreleased
4+
5+* Reintroduce "data-ssr": elements without this attribute are skipped during
6+ hydration.
7+
3## 0.5.2
8## 0.5.2
4
9
5* Update to use new dependent-sum/map packages and drop dependency on `*Tag` classes (e.g., `ShowTag`).
10* Update to use new dependent-sum/map packages and drop dependency on `*Tag` classes (e.g., `ShowTag`).
reflex-dom-core/reflex-dom-core.cabal+1
@@ -95,6 +95,7 @@ library
95 Reflex.Dom.Builder.Class.Events
95 Reflex.Dom.Builder.Class.Events
96 Reflex.Dom.Builder.Immediate
96 Reflex.Dom.Builder.Immediate
97 Reflex.Dom.Builder.InputDisabled
97 Reflex.Dom.Builder.InputDisabled
98+ Reflex.Dom.Builder.Hydratable
98 Reflex.Dom.Builder.Static
99 Reflex.Dom.Builder.Static
99 Reflex.Dom.Class
100 Reflex.Dom.Class
100 Reflex.Dom.Core
101 Reflex.Dom.Core
reflex-dom-core/src/Reflex/Dom/Builder/Hydratable.hsnew file+116
@@ -0,0 +1,116 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE FlexibleInstances #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE MultiParamTypeClasses #-}
5+{-# LANGUAGE OverloadedStrings #-}
6+{-# LANGUAGE StandaloneDeriving #-}
7+{-# LANGUAGE TypeFamilies #-}
8+{-# LANGUAGE UndecidableInstances #-}
9+module Reflex.Dom.Builder.Hydratable where
10+
11+import Control.Monad.Fix
12+import Control.Monad.Primitive
13+import Control.Monad.Ref
14+import Control.Monad.Trans
15+import Control.Monad.Trans.Control
16+import Data.Coerce
17+import qualified Data.Map as Map
18+import Foreign.JavaScript.TH
19+#ifndef ghcjs_HOST_OS
20+import GHCJS.DOM.Types (MonadJSM (..))
21+#endif
22+import Reflex
23+import Reflex.Dom.Builder.Class
24+import Reflex.Dom.Builder.Immediate (HasDocument (..))
25+import Reflex.Host.Class
26+
27+-- | A DomBuilder transformer that adds "data-ssr" to all elements such that the
28+-- hydration builder knows which bits of DOM were added by us, and which were
29+-- added by external scripts.
30+newtype HydratableT m a = HydratableT { runHydratableT :: m a } deriving (Functor, Applicative, Monad, MonadAtomicRef, MonadFix, MonadIO)
31+
32+#ifndef ghcjs_HOST_OS
33+instance MonadJSM m => MonadJSM (HydratableT m) where
34+ liftJSM' = HydratableT . liftJSM'
35+#endif
36+
37+deriving instance MonadSample t m => MonadSample t (HydratableT m)
38+deriving instance MonadHold t m => MonadHold t (HydratableT m)
39+
40+instance MonadTrans HydratableT where
41+ lift = HydratableT
42+
43+instance MonadTransControl HydratableT where
44+ type StT HydratableT a = a
45+ liftWith f = HydratableT $ f runHydratableT
46+ restoreT = HydratableT
47+
48+instance MonadRef m => MonadRef (HydratableT m) where
49+ type Ref (HydratableT m) = Ref m
50+ newRef = lift . newRef
51+ readRef = lift . readRef
52+ writeRef ref = lift . writeRef ref
53+
54+instance PerformEvent t m => PerformEvent t (HydratableT m) where
55+ type Performable (HydratableT m) = Performable m
56+ performEvent_ = lift . performEvent_
57+ performEvent = lift . performEvent
58+
59+instance PrimMonad m => PrimMonad (HydratableT m) where
60+ type PrimState (HydratableT m) = PrimState m
61+ primitive = lift . primitive
62+
63+makeHydratable :: Reflex t => ElementConfig er t m -> ElementConfig er t m
64+makeHydratable cfg = cfg
65+ { _elementConfig_initialAttributes = Map.insert "data-ssr" "" $ _elementConfig_initialAttributes cfg
66+ , _elementConfig_modifyAttributes = fmap (Map.delete "data-ssr") <$> _elementConfig_modifyAttributes cfg
67+ }
68+
69+instance PostBuild t m => PostBuild t (HydratableT m) where
70+ getPostBuild = lift getPostBuild
71+
72+deriving instance TriggerEvent t m => TriggerEvent t (HydratableT m)
73+
74+instance MonadReflexCreateTrigger t m => MonadReflexCreateTrigger t (HydratableT m) where
75+ newEventWithTrigger = lift . newEventWithTrigger
76+ newFanEventWithTrigger f = lift $ newFanEventWithTrigger f
77+
78+instance Adjustable t m => Adjustable t (HydratableT m) where
79+ runWithReplace a0 a' = HydratableT $ runWithReplace (coerce a0) (coerceEvent a')
80+ traverseDMapWithKeyWithAdjust f dm0 dm' = HydratableT $ traverseDMapWithKeyWithAdjust (\k v -> runHydratableT $ f k v) (coerce dm0) (coerceEvent dm')
81+ traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = HydratableT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runHydratableT $ f k v) (coerce dm0) (coerceEvent dm')
82+ traverseIntMapWithKeyWithAdjust f m0 m' = HydratableT $ traverseIntMapWithKeyWithAdjust (\k v -> runHydratableT $ f k v) (coerce m0) (coerceEvent m')
83+
84+instance NotReady t m => NotReady t (HydratableT m) where
85+ notReadyUntil = lift . notReadyUntil
86+ notReady = lift notReady
87+
88+instance DomBuilder t m => DomBuilder t (HydratableT m) where
89+ type DomBuilderSpace (HydratableT m) = DomBuilderSpace m
90+ element t cfg = lift . element t (makeHydratable cfg) . runHydratableT
91+ inputElement cfg = lift $ inputElement $ cfg
92+ { _inputElementConfig_elementConfig = makeHydratable $ _inputElementConfig_elementConfig cfg
93+ }
94+ textAreaElement cfg = lift $ textAreaElement $ cfg
95+ { _textAreaElementConfig_elementConfig = makeHydratable $ _textAreaElementConfig_elementConfig cfg
96+ }
97+ selectElement cfg child = do
98+ let cfg' = cfg
99+ { _selectElementConfig_elementConfig = makeHydratable $ _selectElementConfig_elementConfig cfg
100+ }
101+ lift $ selectElement cfg' $ runHydratableT child
102+
103+instance HasDocument m => HasDocument (HydratableT m)
104+
105+instance HasJSContext m => HasJSContext (HydratableT m) where
106+ type JSContextPhantom (HydratableT m) = JSContextPhantom m
107+ askJSContext = lift askJSContext
108+
109+instance HasJS js m => HasJS js (HydratableT m) where
110+ type JSX (HydratableT m) = JSX m
111+ liftJS = lift . liftJS
112+
113+instance DomRenderHook t m => DomRenderHook t (HydratableT m) where
114+ withRenderHook f = HydratableT . withRenderHook f . runHydratableT
115+ requestDomAction = HydratableT . requestDomAction
116+ requestDomAction_ = HydratableT . requestDomAction_
reflex-dom-core/src/Reflex/Dom/Builder/Immediate.hs+12−5
@@ -765,10 +765,17 @@ hydrateElement elementTag cfg child = do
765 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
765 result <- HydrationDomBuilderT $ lift $ runReaderT (unHydrationDomBuilderT child) env'
766 wrapResult <- liftIO newEmptyMVar
766 wrapResult <- liftIO newEmptyMVar
767 let skipAttr = "data-hydration-skip" :: DOM.JSString
767 let skipAttr = "data-hydration-skip" :: DOM.JSString
768+ ssrAttr = "data-ssr" :: DOM.JSString
768− hasSkipAttribute :: DOM.Element -> HydrationRunnerT t m Bool
769+ shouldSkip :: DOM.Element -> HydrationRunnerT t m Bool
769− hasSkipAttribute e = case cfg ^. namespace of
770+ shouldSkip e = case cfg ^. namespace of
771+ Nothing -> do
770− Nothing -> hasAttribute e skipAttr
772+ skip <- hasAttribute e skipAttr
773+ ssr <- hasAttribute e ssrAttr
774+ pure $ skip || not ssr
775+ Just ns -> do
771− Just ns -> hasAttributeNS e (Just ns) skipAttr
776+ skip <- hasAttributeNS e (Just ns) skipAttr
777+ ssr <- hasAttributeNS e (Just ns) ssrAttr
778+ pure $ skip || not ssr
772 childDom <- liftIO $ readIORef childDelayedRef
779 childDom <- liftIO $ readIORef childDelayedRef
773 let rawCfg = extractRawElementConfig cfg
780 let rawCfg = extractRawElementConfig cfg
774 doc <- askDocument
781 doc <- askDocument
@@ -783,7 +790,7 @@ hydrateElement elementTag cfg child = do
783 pure e
790 pure e
784 Just node -> DOM.castTo DOM.Element node >>= \case
791 Just node -> DOM.castTo DOM.Element node >>= \case
785 Nothing -> go (Just node) -- this node is not an element, skip
792 Nothing -> go (Just node) -- this node is not an element, skip
786− Just e -> hasSkipAttribute e >>= \case
793+ Just e -> shouldSkip e >>= \case
787 True -> go (Just node) -- this element is explicitly marked for being skipped by hydration
794 True -> go (Just node) -- this element is explicitly marked for being skipped by hydration
788 False -> do
795 False -> do
789 t <- Element.getTagName e
796 t <- Element.getTagName e
reflex-dom-core/src/Reflex/Dom/Builder/InputDisabled.hs+6
@@ -80,6 +80,7 @@ instance Adjustable t m => Adjustable t (InputDisabledT m) where
80 runWithReplace a0 a' = InputDisabledT $ runWithReplace (coerce a0) (coerceEvent a')
80 runWithReplace a0 a' = InputDisabledT $ runWithReplace (coerce a0) (coerceEvent a')
81 traverseDMapWithKeyWithAdjust f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
81 traverseDMapWithKeyWithAdjust f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
82 traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
82 traverseDMapWithKeyWithAdjustWithMove f dm0 dm' = InputDisabledT $ traverseDMapWithKeyWithAdjustWithMove (\k v -> runInputDisabledT $ f k v) (coerce dm0) (coerceEvent dm')
83+ traverseIntMapWithKeyWithAdjust f m0 m' = InputDisabledT $ traverseIntMapWithKeyWithAdjust (\k v -> runInputDisabledT $ f k v) (coerce m0) (coerceEvent m')
83
84
84instance NotReady t m => NotReady t (InputDisabledT m) where
85instance NotReady t m => NotReady t (InputDisabledT m) where
85 notReadyUntil = lift . notReadyUntil
86 notReadyUntil = lift . notReadyUntil
@@ -108,3 +109,8 @@ instance HasJSContext m => HasJSContext (InputDisabledT m) where
108instance HasJS js m => HasJS js (InputDisabledT m) where
109instance HasJS js m => HasJS js (InputDisabledT m) where
109 type JSX (InputDisabledT m) = JSX m
110 type JSX (InputDisabledT m) = JSX m
110 liftJS = lift . liftJS
111 liftJS = lift . liftJS
112+
113+instance DomRenderHook t m => DomRenderHook t (InputDisabledT m) where
114+ withRenderHook f = InputDisabledT . withRenderHook f . runInputDisabledT
115+ requestDomAction = InputDisabledT . requestDomAction
116+ requestDomAction_ = InputDisabledT . requestDomAction_
reflex-dom-core/src/Reflex/Dom/Core.hs+1
@@ -6,6 +6,7 @@ module Reflex.Dom.Core (module X) where
6
6
7import Reflex as X hiding (askEvents)
7import Reflex as X hiding (askEvents)
8import Reflex.Dom.Builder.Class as X
8import Reflex.Dom.Builder.Class as X
9+import Reflex.Dom.Builder.Hydratable as X
9import Reflex.Dom.Builder.Immediate as X
10import Reflex.Dom.Builder.Immediate as X
10import Reflex.Dom.Builder.InputDisabled as X
11import Reflex.Dom.Builder.InputDisabled as X
11import Reflex.Dom.Builder.Static as X
12import Reflex.Dom.Builder.Static as X
reflex-dom-core/src/Reflex/Dom/Prerender.hs+7−2
@@ -33,6 +33,7 @@ import Foreign.JavaScript.TH
33import GHCJS.DOM.Types (MonadJSM)
33import GHCJS.DOM.Types (MonadJSM)
34import Reflex hiding (askEvents)
34import Reflex hiding (askEvents)
35import Reflex.Dom.Builder.Class
35import Reflex.Dom.Builder.Class
36+import Reflex.Dom.Builder.Hydratable
36import Reflex.Dom.Builder.Immediate
37import Reflex.Dom.Builder.Immediate
37import Reflex.Dom.Builder.InputDisabled
38import Reflex.Dom.Builder.InputDisabled
38import Reflex.Dom.Builder.Static
39import Reflex.Dom.Builder.Static
@@ -283,8 +284,12 @@ instance (Prerender js t m, Monad m, Reflex t, MonadFix m, Group q, Additive q,
283 pure a
284 pure a
284
285
285instance (Prerender js t m, Monad m) => Prerender js t (InputDisabledT m) where
286instance (Prerender js t m, Monad m) => Prerender js t (InputDisabledT m) where
286− type Client (InputDisabledT m) = Client m
287+ type Client (InputDisabledT m) = InputDisabledT (Client m)
287− prerender (InputDisabledT server) client = InputDisabledT $ prerender server client
288+ prerender (InputDisabledT server) (InputDisabledT client) = InputDisabledT $ prerender server client
289+
290+instance (Prerender js t m, Monad m) => Prerender js t (HydratableT m) where
291+ type Client (HydratableT m) = HydratableT (Client m)
292+ prerender (HydratableT server) (HydratableT client) = HydratableT $ prerender server client
288
293
289instance (Prerender js t m, Monad m, ReflexHost t) => Prerender js t (PostBuildT t m) where
294instance (Prerender js t m, Monad m, ReflexHost t) => Prerender js t (PostBuildT t m) where
290 type Client (PostBuildT t m) = PostBuildT t (Client m)
295 type Client (PostBuildT t m) = PostBuildT t (Client m)
reflex-dom-core/test/hydration.hs+1−1
@@ -1347,7 +1347,7 @@ testWidgetDebug' withDebugging beforeJS afterSwitchover bodyWidget = do
1347 bodyWidget
1347 bodyWidget
1348 el "script" $ text $ TE.decodeUtf8 $ LBS.toStrict $ jsaddleJs False
1348 el "script" $ text $ TE.decodeUtf8 $ LBS.toStrict $ jsaddleJs False
1349 putStrLnDebug "rendering static"
1349 putStrLnDebug "rendering static"
1350− ((), html) <- liftIO $ renderStatic staticApp
1350+ ((), html) <- liftIO $ renderStatic $ runHydratableT staticApp
1351 putStrLnDebug "rendered static"
1351 putStrLnDebug "rendered static"
1352 waitBeforeJS <- liftIO newEmptyMVar -- Empty until JS should be run
1352 waitBeforeJS <- liftIO newEmptyMVar -- Empty until JS should be run
1353 waitUntilSwitchover <- liftIO newEmptyMVar -- Empty until switchover
1353 waitUntilSwitchover <- liftIO newEmptyMVar -- Empty until switchover