diff --git a/cbits-cocoa/AppDelegate.m b/cbits-cocoa/AppDelegate.m
--- a/cbits-cocoa/AppDelegate.m
+++ b/cbits-cocoa/AppDelegate.m
@@ -22,7 +22,8 @@
     if (self) {
         _handler = handler;
         NSRect contentSize = NSMakeRect(0.0, 500.0, 1000.0, 700.0);
-        NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
+        // TODO replace with once nix has newer Cocoa NSUInteger windowStyleMask = NSWindowStyleMaskTitled | NSWindowStyleMaskResizable | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable;
+        NSUInteger windowStyleMask = NSTitledWindowMask | NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask;
         _window = [[NSWindow alloc] initWithContentRect:contentSize styleMask:windowStyleMask backing:NSBackingStoreBuffered defer:YES];
         _window.backgroundColor = [NSColor whiteColor];
         _window.title = progName;
@@ -60,3 +61,7 @@
     }
 }
 
+BOOL openApp(NSURL * url) {
+    NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
+    return [workspace openURL:url];
+}
diff --git a/cbits-uikit/AppDelegate.m b/cbits-uikit/AppDelegate.m
--- a/cbits-uikit/AppDelegate.m
+++ b/cbits-uikit/AppDelegate.m
@@ -55,3 +55,11 @@
     }
 }
 
+BOOL openApp(NSURL * url) {
+    UIApplication *app = [UIApplication sharedApplication];
+    if ([app canOpenURL:url]) {
+        [app openURL:url];
+        return true;
+    }
+    return false;
+}
diff --git a/cbits/WKWebView.m b/cbits/WKWebView.m
--- a/cbits/WKWebView.m
+++ b/cbits/WKWebView.m
@@ -2,6 +2,8 @@
 #import <Foundation/Foundation.h>
 #import <WebKit/WebKit.h>
 
+BOOL openApp(NSURL * url);
+
 extern void jsaddleStart(HsStablePtr);
 extern void jsaddleResult(HsStablePtr, const char *  _Nonnull result);
 
@@ -37,6 +39,20 @@
     jsaddleResult(self.resultHandler, [s UTF8String]);
 }
 
+- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
+{
+    NSURL *url = navigationAction.request.URL;
+
+    if(!navigationAction.targetFrame && ![url.scheme isEqualToString:@"file"]) {
+        if(openApp(url))
+		    decisionHandler(WKNavigationActionPolicyCancel);
+		else
+		    decisionHandler(WKNavigationActionPolicyAllow);
+    }
+    else
+        decisionHandler(WKNavigationActionPolicyAllow);
+}
+
 @end
 
 void addJSaddleHandler(WKWebView *webView, HsStablePtr startHandler, HsStablePtr resultHandler) {
@@ -46,10 +62,39 @@
 }
 
 void evaluateJavaScript(WKWebView *webView, const char * _Nonnull js) {
-    [webView evaluateJavaScript:[NSString stringWithCString:js encoding:NSUTF8StringEncoding] completionHandler:NULL];
+    NSString *jsString = [[NSString alloc] initWithCString:js encoding:NSUTF8StringEncoding];
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [webView evaluateJavaScript:jsString completionHandler:NULL];
+        [jsString release];
+    });
 }
 
 void loadHTMLString(WKWebView *webView, const char * _Nonnull html) {
-    [webView loadHTMLString:[NSString stringWithCString:html encoding:NSUTF8StringEncoding] baseURL:NULL];
+    NSString *htmlString = [NSString stringWithCString:html encoding:NSUTF8StringEncoding];
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [webView loadHTMLString:htmlString baseURL:NULL];
+    });
 }
+
+const char * mainBundleResourcePathC() {
+    NSBundle *main = [NSBundle mainBundle];
+    if(!main) return NULL;
+    NSString *path = main.resourcePath;
+    if(!path) return NULL;
+    return [path UTF8String];
+}
+
+void loadBundleFile(WKWebView *webView, const char * _Nonnull file, const char * _Nonnull allowing) {
+    NSBundle *main = [NSBundle mainBundle];
+    if(main) {
+        NSString *path = main.resourcePath;
+        if(path) {
+            NSURL *fileUrl = [NSURL fileURLWithPath:[NSString stringWithFormat: @"%@/%@", path, [NSString stringWithCString:file encoding:NSUTF8StringEncoding]]];
+            NSURL *allowingUrl = [NSURL fileURLWithPath:[NSString stringWithFormat: @"%@/%@", path, [NSString stringWithCString:allowing encoding:NSUTF8StringEncoding]]];
+            [webView loadFileURL:fileUrl allowingReadAccessToURL:allowingUrl];
+        }
+    }
+}
+
+
 
diff --git a/jsaddle-wkwebview.cabal b/jsaddle-wkwebview.cabal
--- a/jsaddle-wkwebview.cabal
+++ b/jsaddle-wkwebview.cabal
@@ -1,5 +1,5 @@
 name: jsaddle-wkwebview
-version: 0.8.2.0
+version: 0.8.3.0
 cabal-version: >=1.10
 build-type: Simple
 license: MIT
@@ -23,7 +23,7 @@
         Language.Javascript.JSaddle.WKWebView
         Language.Javascript.JSaddle.WKWebView.Internal
     build-depends:
-        aeson >=0.8.0.2 && <1.1,
+        aeson >=0.8.0.2 && <1.2,
         base <5,
         bytestring >=0.10.6.0 && <0.11,
         jsaddle >= 0.8.0.0 && <0.9
diff --git a/src-ghc/Language/Javascript/JSaddle/WKWebView.hs b/src-ghc/Language/Javascript/JSaddle/WKWebView.hs
--- a/src-ghc/Language/Javascript/JSaddle/WKWebView.hs
+++ b/src-ghc/Language/Javascript/JSaddle/WKWebView.hs
@@ -1,10 +1,14 @@
 module Language.Javascript.JSaddle.WKWebView
     ( jsaddleMain
+    , jsaddleMainFile
     , WKWebView(..)
     , run
+    , runFile
+    , mainBundleResourcePath
     ) where
 
-import Language.Javascript.JSaddle.WKWebView.Internal (jsaddleMain, WKWebView(..))
+import Data.ByteString (ByteString)
+import Language.Javascript.JSaddle.WKWebView.Internal (jsaddleMain, jsaddleMainFile, WKWebView(..), mainBundleResourcePath)
 import System.Environment (getProgName)
 import Foreign.C.String (CString, withCString)
 import Foreign.StablePtr (StablePtr, newStablePtr)
@@ -12,8 +16,19 @@
 
 foreign import ccall runInWKWebView :: StablePtr (WKWebView -> IO ()) -> CString -> IO ()
 
+-- | Run JSaddle in a WKWebView
 run :: JSM () -> IO ()
 run f = do
     handler <- newStablePtr (jsaddleMain f)
+    progName <- getProgName
+    withCString progName $ runInWKWebView handler
+
+-- | Run JSaddle in a WKWebView first loading the specified file
+--   from the mainBundle (relative to the resourcePath).
+runFile :: ByteString -- ^ The file to navigate to.
+        -> ByteString -- ^ The path to allow read access to.
+        -> JSM () -> IO ()
+runFile url allowing f = do
+    handler <- newStablePtr (jsaddleMainFile url allowing f)
     progName <- getProgName
     withCString progName $ runInWKWebView handler
diff --git a/src/Language/Javascript/JSaddle/WKWebView/Internal.hs b/src/Language/Javascript/JSaddle/WKWebView/Internal.hs
--- a/src/Language/Javascript/JSaddle/WKWebView/Internal.hs
+++ b/src/Language/Javascript/JSaddle/WKWebView/Internal.hs
@@ -1,19 +1,23 @@
 {-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
 module Language.Javascript.JSaddle.WKWebView.Internal
     ( jsaddleMain
+    , jsaddleMainFile
     , WKWebView(..)
+    , mainBundleResourcePath
     ) where
 
 import Control.Monad (void, join)
-import Control.Concurrent (forkIO)
+import Control.Concurrent (forkIO, forkOS)
+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)
 
 import Data.Monoid ((<>))
 import Data.ByteString (useAsCString, packCString)
+import qualified Data.ByteString as BS (ByteString)
 import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
 import Data.Aeson (encode, decode)
 
 import Foreign.C.String (CString)
-import Foreign.Ptr (Ptr)
+import Foreign.Ptr (Ptr, nullPtr)
 import Foreign.StablePtr (StablePtr, newStablePtr, deRefStablePtr)
 
 import Language.Javascript.JSaddle (Results, JSM)
@@ -27,21 +31,43 @@
 foreign export ccall withWebView :: WKWebView -> StablePtr (WKWebView -> IO ()) -> IO ()
 foreign import ccall addJSaddleHandler :: WKWebView -> StablePtr (IO ()) -> StablePtr (Results -> IO ()) -> IO ()
 foreign import ccall loadHTMLString :: WKWebView -> CString -> IO ()
+foreign import ccall loadBundleFile :: WKWebView -> CString -> CString -> IO ()
 foreign import ccall evaluateJavaScript :: WKWebView -> CString -> IO ()
+foreign import ccall mainBundleResourcePathC :: IO CString
 
+-- | Run JSaddle in WKWebView
 jsaddleMain :: JSM () -> WKWebView -> IO ()
 jsaddleMain f webView = do
+    jsaddleMain' f webView $
+        useAsCString (toStrict indexHtml) $ loadHTMLString webView
+
+-- | Run JSaddle in a WKWebView first loading the specified file
+--   from the mainBundle (relative to the resourcePath).
+jsaddleMainFile :: BS.ByteString -- ^ The file to navigate to.
+                -> BS.ByteString -- ^ The path to allow read access to.
+                -> JSM () -> WKWebView -> IO ()
+jsaddleMainFile url allowing f webView = do
+    jsaddleMain' f webView $
+        useAsCString url $ \u ->
+            useAsCString allowing $ \a ->
+                loadBundleFile webView u a
+
+jsaddleMain' :: JSM () -> WKWebView -> IO () -> IO ()
+jsaddleMain' f webView loadHtml = do
+    ready <- newEmptyMVar
+
     (processResult, start) <- runJavaScript (\batch ->
         useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $
             evaluateJavaScript webView)
         f
 
-    startHandler <- newStablePtr (void $ forkIO start)
+    startHandler <- newStablePtr (putMVar ready ())
     resultHandler <- newStablePtr processResult
     addJSaddleHandler webView startHandler resultHandler
-
-    useAsCString (toStrict indexHtml) $ loadHTMLString webView
-    useAsCString (toStrict jsaddleJs) $ evaluateJavaScript webView
+    loadHtml
+    void . forkOS $ do
+        takeMVar ready
+        useAsCString (toStrict jsaddleJs) (evaluateJavaScript webView) >> void (forkIO start)
 
 jsaddleStart :: StablePtr (IO ()) -> IO ()
 jsaddleStart ptrHandler = join (deRefStablePtr ptrHandler)
@@ -79,3 +105,10 @@
     \<body>\n\
     \</body>\n\
     \</html>"
+
+mainBundleResourcePath :: IO (Maybe BS.ByteString)
+mainBundleResourcePath = do
+    bs <- mainBundleResourcePathC
+    if bs == nullPtr
+        then return Nothing
+        else Just <$> packCString bs
