Repository: graphql-rust/juniper (language: rust)

Issue:
wasm32-unknown-unknown support in non-browser environment
When compiling projects targeting wasm32-unknown-unknown, dependencies on wbindgen are emitted in the wasm file.
```
(import "__wbindgen_placeholder__" "__wbindgen_describe" (func $_ZN12wasm_bindgen19__wbindgen_describe17hd0bb5db14a487f56E (type $t0)))
(import "__wbindgen_placeholder__" "__wbindgen_throw" (func $_ZN12wasm_bindgen16__wbindgen_throw17h1f7ec3f048b25bd4E (type $t10)))
(import "__wbindgen_externref_xform__" "__wbindgen_externref_table_grow" (func $_ZN12wasm_bindgen9externref31__wbindgen_externref_table_grow17h23c6ebadcc732f10E (type $t1)))
(import "__wbindgen_externref_xform__" "__wbindgen_externref_table_set_null" (func $_ZN12wasm_bindgen9externref35__wbindgen_externref_table_set_null17h8f133612ea24d34dE (type $t0)))
```
In a non-browser environment, these dependencies will not be supported. A non-browser environment is a substantial use case for a graphql server (perhaps more than a browser environment), and should be supported.

I came across this issue in the context of [wasmcloud](https://wasmcloud.dev/):

```
[error] Failed to start actor: {:bad_return_value, {:error, "Cannot Instantiate: Link(Import(\"__wbindgen_placeholder__\", \"__wbindgen_describe\", UnknownImport(Function(FunctionType { params: [I32], results: [] }))))"}}
```

There is a detailed discussion in the wasmcloud Slack on this issue about the `time` crate.
  
## More info

The [feature request for wasm32-uknown-unknown support](https://github.com/graphql-rust/juniper/issues/218) from four years ago was limited to browsers with `offline` support. With the more recent developments in non-browser use cases, this should be revisited.
  
Some versions of dependencies such as `time` and `chrono` assume that `wasm-bindgen` is used, and require `js-sys`, which is only available in browser environments. Providing proper support for non-browser environments will involve scrutinizing/pinning the dependency versions to ensure they do not suffer from this problem.

Here are some example issues:

- https://github.com/time-rs/time/pull/499
- https://github.com/chronotope/chrono/issues/334

Ultimately, it seems like the wasm and rust communities need to define more appropriate build targets to differentiate browser and non-browser targets, but until then, support needs to be gated through dependencies.


Discussion / hints:
Hmmm, not too familiar with the wasm stuff, but we do check in CI: https://github.com/graphql-rust/juniper/blob/master/.github/workflows/ci.yml#L302
@dustin-rcg do you have an example of CI testing `wasm` builds outside the browser? Or can you roughly describe what would it take?
@LegNeato The issue will not show up in cargo check because it does not validate the dependencies against a particular wasm runtime environment. 

@ilslv I can give an example in terms of wasmcloud. The steps would roughly be:

In dev environment:

1. [Install the wasmcloud shell](https://wasmcloud.dev/overview/installation/) -- `cargo install wash-cli` 
2. [Generate an actor from template](https://wasmcloud.dev/app-dev/create-actor/generate/) -- `wash new actor hello --template-name hello`
3. In the actor code, add a reference to the juniper version to be tested. It should be pretty easy to implement `handle_request` as a simple juniper server with minimal schema using GraphQLRequest#execute.
4. Assuming in CI we will use a setup similar to [this docker-compose](https://raw.githubusercontent.com/wasmCloud/examples/main/docker/docker-compose.yml), we modify the actor `Makefile` by changing `localhost:5000` to `registry:5000`, so that the make commands are relative to the local OCI `registry` host that exists in CI. 

In CI build:

1. Setup the hosts like [this docker-compose](https://raw.githubusercontent.com/wasmCloud/examples/main/docker/docker-compose.yml). Also add `WASMCLOUD_OCI_ALLOWED_INSECURE: registry:5000` to the environment of the `wasmcloud` host (which allows the `make start` command work with the insecure `registry` host in a later step).
2. Include an additional host, i.e. `runner`, which is based on an image with a rust build environment.
3. In the Dockerfile for `runner` you will COPY in the actor source code. RUN `make` to build the actor. Add `WASMCLOUD_CTL_HOST: wasmcloud` to the environment for `runner` so that the `wash` commands target the wasmcloud host instead of localhost.
4. In the CI commands for `runner`, run `make push` to push the actor into the local OCI registry. Run `make start` to cause the wasmcloud host to load the actor from the OCI registry and instantiate it.

The `make start` command is where it fails for me. The wasmcloud host logs contain output like
```
17:14:47.985 [error] Failed to start actor: {:bad_return_value, {:error, "Cannot Instantiate: Link(Import(\"__wbindgen_placeholder__\", \"__wbindgen_describe\", UnknownImport(Function(FunctionType { params: [I32], results: [] }))))"}}
```

On the other hand, if these commands succeed, then this should validate the non-browser environment.

You can of course, also validate that the graphql server works by sending a graphql query. This requires a [few additional steps](https://wasmcloud.dev/overview/getting-started/) to start the HTTP _capability provider_, _link_ the actor to the provider, and send the HTTP request to the wasmcloud host on the port specified in the link values. Roughly, those commands (for `runner`) would be:

1. `wash ctl start provider wasmcloud.azurecr.io/httpserver:0.16.2` to start the HTTP _capability provider_.
2. `wash ctl get hosts --output json` to get the wasmcloud host-id
3. `wash ctl get inventory <host-id> --output json` to get the actor-id and provider-id.
5. `wash ctl link put <actor-id> <provider-id> wasmcloud:httpserver address=0.0.0.0:8080`
6. `curl wasmcloud:8080 <graphql-query>`
Does the above work with no default features? Curious to know if the issue is only contained to dependencies or it is also in Juniper proper.
@LegNeato Yes, I am able to successfully run `make start` with this dependency: `juniper = { version = "0.10", default-features = false }`. What are the downsides to excluding the default features?
I just realized that I am on an old version of juniper. It seems I was referring to an old version of the juniper book somehow. I will upgrade my project and update this issue accordingly.
OK, using juniper 0.15.10, this is the minimum feature set I can get that passes `make start`:

```
juniper = { version = "0.15.10", default-features = false, features = [
    "uuid",
    "url",
    "schema-language",
] }
```

As soon as I add `bson` and `chrono`, I have the problem.
OK, It looks like the problem is that juniper depends on an old version of `bson` that is not properly gating features from `chrono`. Juniper should use a newer version of bson (current is 2.4.0, but juniper is on ^1.0).

juniper 0.15.10 -> bson ^1.0 = 1.2.4: https://github.com/graphql-rust/juniper/blob/juniper-v0.15.10/juniper/Cargo.toml#L39
bson 1.2.4 -> chrono ^0.4 = 0.4.22 **without feature gates**: https://github.com/mongodb/bson-rust/blob/v1.2.4/Cargo.toml#L47

This is pulling in the unwanted dependencies `js-sys` and `wasm-bindgen` in my Cargo.lock:
```
[[package]]
name = "chrono"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
 "iana-time-zone",
 "js-sys",
 "num-integer",
 "num-traits",
 "time 0.1.44",
 "wasm-bindgen",
 "winapi",
]
```
Looks like this is already fixed in `master`, so looking forward to the next release!
@dustin-rcg could you check this directly from `master`?
Actually, there is still the problem in master: `juniper = { git = "https://github.com/graphql-rust/juniper", branch = "master" }`

```
$ cargo why wasm-bindgen
graphql -> juniper -> bson -> rand -> rand_chacha -> rand_core -> getrandom -> js-sys -> wasm-bindgen
graphql -> juniper -> bson -> rand -> rand_chacha -> rand_core -> getrandom -> wasm-bindgen
graphql -> juniper -> bson -> rand -> rand_core -> getrandom -> js-sys -> wasm-bindgen
graphql -> juniper -> bson -> rand -> rand_core -> getrandom -> wasm-bindgen
graphql -> juniper -> bson -> uuid -> getrandom -> js-sys -> wasm-bindgen
graphql -> juniper -> bson -> uuid -> getrandom -> wasm-bindgen
graphql -> juniper -> getrandom -> js-sys -> wasm-bindgen
graphql -> juniper -> getrandom -> wasm-bindgen
graphql -> juniper -> uuid -> getrandom -> js-sys -> wasm-bindgen
graphql -> juniper -> uuid -> getrandom -> wasm-bindgen
```

```
$ cargo why js-sys
graphql -> juniper -> bson -> rand -> rand_chacha -> rand_core -> getrandom -> js-sys
graphql -> juniper -> bson -> rand -> rand_core -> getrandom -> js-sys
graphql -> juniper -> bson -> uuid -> getrandom -> js-sys
graphql -> juniper -> getrandom -> js-sys
graphql -> juniper -> uuid -> getrandom -> js-sys
```
Relevant:

https://github.com/mongodb/bson-rust/issues/334 and https://jira.mongodb.org/browse/RUST-1130
Here is how `getrandom` controls:

https://github.com/rust-random/getrandom/blob/master/Cargo.toml#L23
Likely blocked on https://github.com/rust-random/getrandom/issues/319
Yet more info / another example of a library requiring `getrandom` and wanting to support this: https://github.com/briansmith/ring/pull/1546

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 832602595..df18e58d1 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -125,7 +125,6 @@ jobs:
           - { feature: graphql-parser, crate: juniper }
           - { feature: rust_decimal, crate: juniper }
           - { feature: schema-language, crate: juniper }
-          - { feature: serde_json, crate: juniper }
           - { feature: time, crate: juniper }
           - { feature: url, crate: juniper }
           - { feature: uuid, crate: juniper }
@@ -280,8 +279,10 @@ jobs:
       fail-fast: false
       matrix:
         crate:
-          - juniper_codegen
           - juniper
+        target:
+          - wasm32-unknown-unknown
+          - wasm32-wasi
         toolchain:
           - stable
           - beta
@@ -292,9 +293,12 @@ jobs:
       - uses: dtolnay/rust-toolchain@v1
         with:
           toolchain: ${{ matrix.toolchain }}
-          target: wasm32-unknown-unknown
+          target: ${{ matrix.target }}
 
-      - run: cargo check --target wasm32-unknown-unknown -p ${{ matrix.crate }}
+      - run: cargo check --target ${{ matrix.target }} -p ${{ matrix.crate }}
+                   ${{ matrix.target == 'wasm32-unknown-unknown'
+                       && '--features js'
+                       || '' }}
 
 
 
diff --git a/juniper/CHANGELOG.md b/juniper/CHANGELOG.md
index fa547fddb..1a16e5707 100644
--- a/juniper/CHANGELOG.md
+++ b/juniper/CHANGELOG.md
@@ -61,6 +61,7 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
 - `#[derive(GraphQLInterface)]` macro allowing using structs as GraphQL interfaces. ([#1026])
 - [`bigdecimal` crate] integration behind `bigdecimal` [Cargo feature]. ([#1060])
 - [`rust_decimal` crate] integration behind `rust_decimal` [Cargo feature]. ([#1060])
+- `js` [Cargo feature] enabling `js-sys` and `wasm-bindgen` support for `wasm32-unknown-unknown` target. ([#1118], [#1147])
 
 ### Changed
 
@@ -112,7 +113,9 @@ All user visible changes to `juniper` crate will be documented in this file. Thi
 [#1081]: /../../pull/1081
 [#1085]: /../../issues/1085
 [#1086]: /../../pull/1086
-[#1114]: /../../pull/1119
+[#1118]: /../../issues/1118
+[#1119]: /../../pull/1119
+[#1147]: /../../pull/1147
 [ba1ed85b]: /../../commit/ba1ed85b3c3dd77fbae7baf6bc4e693321a94083
 [CVE-2022-31173]: /../../security/advisories/GHSA-4rx6-g5vg-5f3j
 
diff --git a/juniper/Cargo.toml b/juniper/Cargo.toml
index 5a017b14b..d1e35e43a 100644
--- a/juniper/Cargo.toml
+++ b/juniper/Cargo.toml
@@ -31,11 +31,19 @@ default = [
     "url",
     "uuid",
 ]
+bigdecimal = ["dep:bigdecimal"]
+bson = ["dep:bson"]
+chrono = ["dep:chrono"]
 chrono-clock = ["chrono", "chrono/clock"]
 chrono-tz = ["dep:chrono-tz", "dep:regex"]
-expose-test-schema = ["anyhow", "serde_json"]
+expose-test-schema = ["dep:anyhow", "dep:serde_json"]
 graphql-parser = ["dep:graphql-parser", "dep:void"]
+js = ["chrono?/wasmbind", "time?/wasm-bindgen", "uuid?/js"]
+rust_decimal = ["dep:rust_decimal"]
 schema-language = ["graphql-parser"]
+time = ["dep:time"]
+url = ["dep:url"]
+uuid = ["dep:uuid"]
 
 [dependencies]
 anyhow = { version = "1.0.47", default-features = false, optional = true }
@@ -57,17 +65,14 @@ smartstring = "1.0"
 static_assertions = "1.1"
 time = { version = "0.3", features = ["formatting", "macros", "parsing"], optional = true }
 url = { version = "2.0", optional = true }
-uuid = { version = "1.0", default-features = false, optional = true }
+uuid = { version = "1.3", default-features = false, optional = true }
 
 # Fixes for MSRV check.
 # TODO: Try remove on upgrade of `chrono-tz` crate.
 regex = { version = "1.6", default-features = false, optional = true }
-# TODO: Remove on upgrade to 4.0.1 version of `graphql-parser`.
+# TODO: Remove on upgrade to 0.4.1 version of `graphql-parser`.
 void = { version = "1.0.2", optional = true }
 
-[target.'cfg(target_arch = "wasm32")'.dependencies]
-getrandom = { version = "0.2", features = ["js"] }
-
 [dev-dependencies]
 bencher = "0.1.2"
 chrono = { version = "0.4.20", features = ["alloc"], default-features = false }


Test patch: