diff --git a/hspec-core.cabal b/hspec-core.cabal
--- a/hspec-core.cabal
+++ b/hspec-core.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:             hspec-core
-version:          2.9.3
+version:          2.9.4
 license:          MIT
 license-file:     LICENSE
 copyright:        (c) 2011-2021 Simon Hengel,
@@ -128,7 +128,7 @@
     , directory
     , filepath
     , hspec-expectations ==0.8.2.*
-    , hspec-meta ==2.9.2
+    , hspec-meta ==2.9.3
     , process
     , quickcheck-io >=0.2.0
     , random
diff --git a/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs b/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs
--- a/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs
+++ b/src/Test/Hspec/Core/Formatters/Pretty/Parser.hs
@@ -16,7 +16,13 @@
 import           Test.Hspec.Core.Compat hiding (fail)
 import           Test.Hspec.Core.Formatters.Pretty.Parser.Types
 
-#if __GLASGOW_HASKELL__ < 802 || __GLASGOW_HASKELL__ > 902
+#ifndef __GHCJS__
+#if __GLASGOW_HASKELL__ >= 802 && __GLASGOW_HASKELL__ <= 902
+#define PRETTY_PRINTING_SUPPORTED
+#endif
+#endif
+
+#ifndef PRETTY_PRINTING_SUPPORTED
 
 parseExpression :: String -> Maybe Expression
 parseExpression _ = Nothing
diff --git a/src/Test/Hspec/Core/Hooks.hs b/src/Test/Hspec/Core/Hooks.hs
--- a/src/Test/Hspec/Core/Hooks.hs
+++ b/src/Test/Hspec/Core/Hooks.hs
@@ -95,7 +95,7 @@
 
 -- | Run a custom action after the last spec item.
 afterAll :: HasCallStack => ActionWith a -> SpecWith a -> SpecWith a
-afterAll action spec = runIO (runSpecM spec) >>= fromSpecList . return . NodeWithCleanup location action
+afterAll action = mapSpecForest (return . NodeWithCleanup location action)
 
 -- | Run a custom action after the last spec item.
 afterAll_ :: HasCallStack => IO () -> SpecWith a -> SpecWith a
diff --git a/src/Test/Hspec/Core/Runner.hs b/src/Test/Hspec/Core/Runner.hs
--- a/src/Test/Hspec/Core/Runner.hs
+++ b/src/Test/Hspec/Core/Runner.hs
@@ -43,6 +43,7 @@
 import           System.IO
 import           System.Environment (getArgs, withArgs)
 import           System.Exit
+import           Control.Arrow
 import qualified Control.Exception as E
 import           System.Random
 import           Control.Monad.ST
@@ -155,7 +156,10 @@
 --   >>= `evaluateSummary`
 -- @
 runSpec :: Spec -> Config -> IO Summary
-runSpec spec c_ = do
+runSpec spec config = runSpecM spec >>= runSpecForest config
+
+runSpecForest :: Config -> [SpecTree ()] -> IO Summary
+runSpecForest c_ spec = do
   oldFailureReport <- readFailureReportOnRerun c_
 
   c <- ensureSeed (applyFailureReport oldFailureReport c_)
@@ -172,13 +176,16 @@
     then rerunAllMode c oldFailureReport
     else normalMode c
   where
-    normalMode c = runSpec_ c spec
+    normalMode c = runSpecForest_ c spec
     rerunAllMode c oldFailureReport = do
-      summary <- runSpec_ c spec
+      summary <- runSpecForest_ c spec
       if rerunAll c oldFailureReport summary
-        then runSpec spec c_
+        then runSpecForest c_ spec
         else return summary
 
+runSpecForest_ :: Config -> [SpecTree ()] -> IO Summary
+runSpecForest_ config spec = runEvalTree config (specToEvalForest config spec)
+
 failFocused :: Item a -> Item a
 failFocused item = item {itemExample = example}
   where
@@ -192,23 +199,22 @@
             Failure{} -> status
       | otherwise = itemExample item
 
-failFocusedItems :: Config -> Spec -> Spec
+failFocusedItems :: Config -> [SpecTree a] -> [SpecTree a]
 failFocusedItems config spec
-  | configFailOnFocused config = mapSpecItem_ failFocused spec
+  | configFailOnFocused config = map (fmap failFocused) spec
   | otherwise = spec
 
-focusSpec :: Config -> Spec -> Spec
+focusSpec :: Config -> [SpecTree a] -> [SpecTree a]
 focusSpec config spec
   | configFocusedOnly config = spec
-  | otherwise = focus spec
+  | otherwise = focusForest spec
 
-runSpec_ :: Config -> Spec -> IO Summary
-runSpec_ config spec = do
-  filteredSpec <- specToEvalForest config spec
+runEvalTree :: Config -> [EvalTree] -> IO Summary
+runEvalTree config spec = do
   let
       seed = (fromJust . configQuickCheckSeed) config
       qcArgs = configQuickCheckArgs config
-      !numberOfItems = countSpecItems filteredSpec
+      !numberOfItems = countSpecItems spec
 
   concurrentJobs <- case configConcurrentJobs config of
     Nothing -> getDefaultConcurrentJobs
@@ -241,7 +247,7 @@
       , evalConfigConcurrentJobs = concurrentJobs
       , evalConfigFailFast = configFailFast config
       }
-    runFormatter evalConfig filteredSpec
+    runFormatter evalConfig spec
 
   let failures = filter resultItemIsFailure results
 
@@ -252,16 +258,21 @@
   , summaryFailures = length failures
   }
 
-specToEvalForest :: Config -> Spec -> IO [EvalTree]
-specToEvalForest config spec = do
-  let
+specToEvalForest :: Config -> [SpecTree ()] -> [EvalTree]
+specToEvalForest config =
+      failFocusedItems config
+  >>> focusSpec config
+  >>> toEvalForest params
+  >>> applyDryRun config
+  >>> applyFilterPredicates config
+  >>> pruneForest
+  >>> randomize
+  where
     seed = (fromJust . configQuickCheckSeed) config
-    focusedSpec = focusSpec config (failFocusedItems config spec)
     params = Params (configQuickCheckArgs config) (configSmallCheckDepth config)
     randomize
       | configRandomize config = randomizeForest seed
       | otherwise = id
-  randomize . pruneForest . applyFilterPredicates config . applyDryRun config . toEvalForest params <$> runSpecM focusedSpec
 
 toEvalForest :: Params -> [SpecTree ()] -> [EvalTree]
 toEvalForest params = bimapForest withUnit toEvalItem . filterForest itemIsFocused
diff --git a/src/Test/Hspec/Core/Spec.hs b/src/Test/Hspec/Core/Spec.hs
--- a/src/Test/Hspec/Core/Spec.hs
+++ b/src/Test/Hspec/Core/Spec.hs
@@ -36,6 +36,7 @@
 
 -- * Internal representation of a spec tree
 , module Test.Hspec.Core.Tree
+, focusForest
 ) where
 
 import           Prelude ()
@@ -53,7 +54,7 @@
 
 -- | The @describe@ function combines a list of specs into a larger spec.
 describe :: HasCallStack => String -> SpecWith a -> SpecWith a
-describe label spec = runIO (runSpecM spec) >>= fromSpecList . return . specGroup label
+describe label = mapSpecForest (return . specGroup label)
 
 -- | @context@ is an alias for `describe`.
 context :: HasCallStack => String -> SpecWith a -> SpecWith a
@@ -103,13 +104,12 @@
 --
 -- Applying `focus` to a spec with focused spec items has no effect.
 focus :: SpecWith a -> SpecWith a
-focus spec = do
-  xs <- runIO (runSpecM spec)
-  let
-    ys
-      | any (any itemIsFocused) xs = xs
-      | otherwise = bimapForest id (\ item -> item {itemIsFocused = True}) xs
-  fromSpecList ys
+focus = mapSpecForest focusForest
+
+focusForest :: [SpecTree a] -> [SpecTree a]
+focusForest xs
+  | any (any itemIsFocused) xs = xs
+  | otherwise = bimapForest id (\ item -> item {itemIsFocused = True}) xs
 
 -- | @fit@ is an alias for @fmap focus . it@
 fit :: (HasCallStack, Example a) => String -> a -> SpecWith (Arg a)
diff --git a/src/Test/Hspec/Core/Spec/Monad.hs b/src/Test/Hspec/Core/Spec/Monad.hs
--- a/src/Test/Hspec/Core/Spec/Monad.hs
+++ b/src/Test/Hspec/Core/Spec/Monad.hs
@@ -9,6 +9,7 @@
 , fromSpecList
 , runIO
 
+, mapSpecForest
 , mapSpecItem
 , mapSpecItem_
 , modifyParams
diff --git a/test/Test/Hspec/Core/HooksSpec.hs b/test/Test/Hspec/Core/HooksSpec.hs
--- a/test/Test/Hspec/Core/HooksSpec.hs
+++ b/test/Test/Hspec/Core/HooksSpec.hs
@@ -18,7 +18,7 @@
 evalSpec_ = void . evalSpec
 
 evalSpec :: H.Spec -> IO [([String], Item)]
-evalSpec = fmap normalize . (H.specToEvalForest H.defaultConfig >=> runFormatter config)
+evalSpec = fmap normalize . (fmap (H.specToEvalForest H.defaultConfig) . H.runSpecM >=> runFormatter config)
   where
     config = EvalConfig {
       evalConfigFormat = \ _ -> return ()
diff --git a/version.yaml b/version.yaml
--- a/version.yaml
+++ b/version.yaml
@@ -1,1 +1,1 @@
-&version 2.9.3
+&version 2.9.4
