ghc-wasm-bindgen 0.1.0.0 → 0.2.0.0
raw patch · 5 files changed
+39/−81 lines, 5 files
Files
- CHANGELOG.md +0/−15
- README.md +24/−17
- app/Main.hs +3/−3
- ghc-wasm-bindgen.cabal +5/−11
- src/GhcWasmBindgen.hs +7/−35
− CHANGELOG.md
@@ -1,15 +0,0 @@-# Changelog--## 0.1.0.0--First release.--- `build`: builds a cabal project into an npm package (JS bindings,- TypeScript declarations, `.wasm`, runtime and `package.json`).-- `generate`: writes the Haskell and JS bindings for plain source files.-- Standard types: numbers (including `Integer`/`Natural` as `bigint`), `Bool`,- `Char`, `String`, `Text`, `Day`, `UTCTime`, `Maybe`, `Either`, tuples, lists,- `Vector`, `NonEmpty`, `Set`/`HashSet`/`IntSet` and `Map`/`HashMap`/`IntMap`.-- User data types: records, positional constructors and enums.-- Arguments are validated before reaching Haskell; Haskell exceptions become- `HaskellError` without breaking the module.
README.md view
@@ -1,6 +1,6 @@ # ghc-wasm-bindgen -Generates JavaScript/TypeScript bindings for a pure Haskell module compiled+Generate JavaScript/TypeScript bindings for a pure Haskell module compiled with GHC's WASM backend. ```haskell@@ -48,17 +48,8 @@ | positional | `{ $tag: "C"; contents: … }` | | all without fields (enum) | `"A" \| "B"` | -Constructors are exported as functions: `Rect({ width: 3, height: 4 })` for-records, `Node(left, 3, right)` for positional ones, and constants like `Leaf`-for nullary ones. Names that are not valid in JS get a trailing `_`-(`delete` → `delete_`).- Notes: -- `Int` and `Word` are 32 bits on wasm32.-- Map keys can be strings, numbers, `Bool`, `Char`, dates or your own types.- `Map` and `Set` compare keys by reference.-- `UTCTime` keeps millisecond precision. - Every type the API uses must be exported with all its constructors. - Not supported yet: `IO`, polymorphic functions, functions as arguments, operators, types with parameters and GADTs.@@ -70,8 +61,6 @@ before reaching Haskell. - An exception in Haskell throws a `HaskellError`. -Either way, the module keeps working.- ## Usage ### `build`: cabal projects@@ -79,14 +68,32 @@ From the cabal project that contains the module: ```sh-ghc-wasm-bindgen build src/ShapeWasm.hs --name shape --out pkg+ghc-wasm-bindgen build src/ShapeWasm.hs --name shape --out pkg/src ``` The module must be exposed by a library, in the same package as the code it-wraps or in one that depends on it. This writes the npm package `pkg/`-(`shape.js`, `shape.d.ts`, `shape.wasm`, the runtime and `package.json`).-Depend on it like any other package and import `shape`, `shape/runtime`-(`init`, `HaskellError`) and `shape/shape.wasm`.+wraps or in one that depends on it. This writes `shape.js`, `shape.d.ts`,+`shape.wasm` and the JS runtime to `pkg/src/`, and nothing else, so the+directory around it can be maintained by hand, even in another repository.++To publish it as an npm package, write its `package.json` yourself, for+example:++```json+{+ "name": "shape",+ "version": "0.1.0",+ "type": "module",+ "exports": {+ ".": { "types": "./src/shape.d.ts", "default": "./src/shape.js" },+ "./runtime": { "types": "./src/ghc_wasm_bindgen_runtime.d.ts", "default": "./src/ghc_wasm_bindgen_runtime.js" },+ "./shape.wasm": "./src/shape.wasm"+ }+}+```++Then import `shape`, `shape/runtime` (`init`, `HaskellError`) and+`shape/shape.wasm`. ### `generate`: plain source files
app/Main.hs view
@@ -15,8 +15,8 @@ , " ghc-wasm-bindgen build MODULE.hs --name NAME --out DIR" , " ghc-wasm-bindgen generate MODULE.hs --name NAME --hs-out DIR --js-out DIR [-i DIR]..." , ""- , "build From the cabal project that contains MODULE.hs, builds an npm package:"- , " NAME.js, NAME.d.ts, NAME.wasm, the runtime and a package.json."+ , "build From the cabal project that contains MODULE.hs, writes NAME.js,"+ , " NAME.d.ts, NAME.wasm and the JS runtime to --out." , " Needs cabal, and wasm32-wasi-cabal, wasm32-wasi-ghc and wasm-opt" , " (ghc-wasm-meta) on PATH." , "generate For plain source files: writes NameWasmBindings.hs and the Haskell"@@ -25,7 +25,7 @@ , "" , " MODULE.hs module whose export list is the API (e.g. src/ShapeWasm.hs)" , " --name NAME package and file name (e.g. shape)"- , " --out DIR output directory of the package (e.g. pkg)"+ , " --out DIR output directory (e.g. pkg/src)" , " -i DIR import directory for the modules MODULE.hs imports" , " (MODULE.hs's own source root is added)" ]
ghc-wasm-bindgen.cabal view
@@ -1,17 +1,12 @@ cabal-version: 3.0 name: ghc-wasm-bindgen-version: 0.1.0.0+version: 0.2.0.0 synopsis: JavaScript/TypeScript bindings for GHC's WASM backend description:- Generates JavaScript and TypeScript bindings for a pure Haskell module- compiled with GHC's WebAssembly backend. The module's export list is the- API: functions become typed JS functions, and data types become- TypeScript types with constructors.-- @ghc-wasm-bindgen build@ builds a cabal project into an npm package (JS- bindings, TypeScript declarations, the .wasm and a small runtime).- @ghc-wasm-bindgen generate@ only writes the bindings, for plain source- files compiled by hand.+ Generate JavaScript and TypeScript bindings for a pure Haskell module+ compiled with GHC's WebAssembly backend. Exported functions are exposed+ as typed JavaScript functions, and exported data types as TypeScript+ types with constructor functions. See the README for supported types and usage. category: Web, WebAssembly, JavaScript, Development@@ -26,7 +21,6 @@ tested-with: GHC == 9.10.3 extra-doc-files: README.md- CHANGELOG.md extra-source-files: runtime-js/ghc_wasm_bindgen_runtime.js runtime-js/ghc_wasm_bindgen_runtime.d.ts
src/GhcWasmBindgen.hs view
@@ -2,7 +2,7 @@ -- | Bindings for a Haskell API module, in two modes: ----- * 'build' (cabal projects): builds an npm package (JS bindings + .wasm).+-- * 'build' (cabal projects): builds the JS bindings and the .wasm. -- The package that contains the module is built natively, so the module can -- be inspected with its real dependencies; then the Haskell bindings are -- generated, together with the Haskell runtime module, as a package under@@ -25,8 +25,8 @@ import qualified Data.ByteString.Char8 as BC import Data.Char (isSpace, toLower, toUpper) import Data.FileEmbed (embedFile)-import Data.List (intercalate, isSuffixOf, stripPrefix)-import Data.Maybe (fromMaybe, mapMaybe)+import Data.List (isSuffixOf, stripPrefix)+import Data.Maybe (mapMaybe) import qualified Data.Text.Encoding as TE import System.Directory import System.Exit (ExitCode (..))@@ -46,7 +46,7 @@ , optName :: String -- ^ Base name of the outputs: NAME.js, NAME.d.ts, NAME.wasm. , optOut :: FilePath- -- ^ Directory of the npm package to write.+ -- ^ Directory to write the JS bindings, runtime and .wasm to. } newtype BuildError = BuildError String@@ -143,8 +143,7 @@ run "wasm-opt" ["-Oz", wasm, "-o", optOut o </> name <.> "wasm"] -- JS side.- mapM_ (\(file, contents) -> writeIfChanged (optOut o </> file) contents) $- ("package.json", packageJson name (pkgVersion pkg)) : jsFiles name api+ mapM_ (\(file, contents) -> writeIfChanged (optOut o </> file) contents) (jsFiles name api) pure (optOut o) -- | The JS bindings and runtime, by file name.@@ -167,7 +166,7 @@ -- Cabal -------------------------------------------------------------------- -data Package = Package {pkgName :: String, pkgVersion :: String}+newtype Package = Package {pkgName :: String} -- | The cabal package that contains the API module: the nearest .cabal file -- above it.@@ -180,7 +179,7 @@ f : _ -> do fields <- mapMaybe field . lines . BC.unpack <$> BS.readFile (dir </> f) case lookup "name" fields of- Just n -> pure (Package n (fromMaybe "0" (lookup "version" fields)))+ Just n -> pure (Package n) Nothing -> throwIO (BuildError (dir </> f ++ " has no name field")) [] | takeDirectory dir == dir -> throwIO (BuildError ("no .cabal file found above " ++ file)) | otherwise -> go (takeDirectory dir)@@ -229,33 +228,6 @@ -- | Cabal package and component names allow letters, digits and @-@. cabalName :: String -> String cabalName = map (\c -> if c == '_' then '-' else c)---- npm ------------------------------------------------------------------------packageJson :: String -> String -> ByteString-packageJson name version =- BC.pack . unlines $- [ "{"- , " \"name\": " ++ show (map toLower name) ++ ","- , " \"version\": " ++ show (semver version) ++ ","- , " \"type\": \"module\","- , " \"main\": " ++ show ("./" ++ name ++ ".js") ++ ","- , " \"types\": " ++ show ("./" ++ name ++ ".d.ts") ++ ","- , " \"exports\": {"- , " \".\": { \"types\": " ++ show ("./" ++ name ++ ".d.ts") ++ ", \"default\": " ++ show ("./" ++ name ++ ".js") ++ " },"- , " \"./runtime\": { \"types\": \"./ghc_wasm_bindgen_runtime.d.ts\", \"default\": \"./ghc_wasm_bindgen_runtime.js\" },"- , " " ++ show ("./" ++ name ++ ".wasm") ++ ": " ++ show ("./" ++ name ++ ".wasm")- , " }"- , "}"- ]---- | Cabal versions have any number of components; npm wants exactly three.-semver :: String -> String-semver v = intercalate "." (take 3 (splitOn '.' v ++ repeat "0"))- where- splitOn c s = case break (== c) s of- (a, []) -> [a]- (a, _ : rest) -> a : splitOn c rest -- Files and processes ------------------------------------------------------