diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,13 @@
 # Revision history for jsaddle-wasm
 
+## 0.1.2.1 -- 2025-07-10
+
+ * Internally, stop using JS `eval`. This allows usage with a `Content-Security-Policy` without `unsafe-eval` (but still with `wasm-unsafe-eval`).
+
+   For the same reason, expose `eval` and `evalFile` from `Language.Javascript.JSaddle.Wasm.TH` which allow to generate corresponding Wasm JSFFI imports for statically known strings.
+
+   Useful as a replacement of JSaddle's `eval` in downstream libraries
+
 ## 0.1.2.0 -- 2025-06-27
 
  * Internally, stop using JS `eval`. This allows usage with a `Content-Security-Policy` without `unsafe-eval` (but still with `wasm-unsafe-eval`).
diff --git a/jsaddle-wasm.cabal b/jsaddle-wasm.cabal
--- a/jsaddle-wasm.cabal
+++ b/jsaddle-wasm.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: jsaddle-wasm
-version: 0.1.2.0
+version: 0.1.2.1
 synopsis: Run JSaddle JSM with the GHC Wasm backend
 description: Run JSaddle @JSM@ with the GHC Wasm backend.
 category: Web, Javascript
@@ -28,6 +28,7 @@
 common common
   ghc-options:
     -Wall
+    -Wimplicit-lift
     -Wunused-packages
     -Wredundant-constraints
 
diff --git a/src-wasm/Language/Javascript/JSaddle/Wasm/Internal.hs b/src-wasm/Language/Javascript/JSaddle/Wasm/Internal.hs
--- a/src-wasm/Language/Javascript/JSaddle/Wasm/Internal.hs
+++ b/src-wasm/Language/Javascript/JSaddle/Wasm/Internal.hs
@@ -58,11 +58,10 @@
                            ]
                    JSaddle.Wasm.TH.eval (BLC8.unpack s) (replicate 3 [t|JSVal|])
                )
-        evaluate
-          =<< eval
-            processResultCallback
-            processResultSyncCallback
-            readBatchCallback
+        eval
+          processResultCallback
+          processResultSyncCallback
+          readBatchCallback
 
   $(JSaddle.Wasm.TH.eval JSaddle.Wasm.TH.patchedGhcjsHelpers [])
 
diff --git a/src-wasm/Language/Javascript/JSaddle/Wasm/Internal/TH.hs b/src-wasm/Language/Javascript/JSaddle/Wasm/Internal/TH.hs
--- a/src-wasm/Language/Javascript/JSaddle/Wasm/Internal/TH.hs
+++ b/src-wasm/Language/Javascript/JSaddle/Wasm/Internal/TH.hs
@@ -5,6 +5,7 @@
 where
 
 import Control.Applicative (asum, many)
+import Control.Exception (evaluate)
 import Data.ByteString.Lazy.Char8 qualified as BLC8
 import Language.Haskell.TH qualified as TH
 import Language.Haskell.TH.Syntax qualified as TH
@@ -24,7 +25,14 @@
             ffiImportName
             sig
   TH.addTopDecls [ffiImport]
-  TH.varE ffiImportName
+
+  argNames <- traverse (\_ -> TH.newName "x") argTys
+  let argPats = TH.varP <$> argNames
+      argExps = TH.varE <$> argNames
+  -- Safe FFI imports return a thunk that needs to be evaluated to make sure
+  -- that the FFI call actually completed ('unsafeInterleaveIO'-like). To avoid
+  -- surprises, use this unconditionally.
+  TH.lamE argPats [|evaluate =<< $(TH.appsE $ TH.varE ffiImportName : argExps)|]
   where
     mkSig = \case
       [] -> [t|IO ()|]
