Repository: rustwasm/wasm-bindgen
Pull request #4302 (2024-11-29 22:42:24)

Improved contributing docs in `web-sys` README
Changes:
1. Steps did not mention that you need to cd into `crates/web-sys` for the commands to work.
1. The command failed to format the generated files, because the path of one file was too long. `cargo fmt` worked, so I added a note for that.
1. While `cargo fmt` did work, it also used CRLF on Windows. I have git setup to check out everything as is (and WBG is LF) to make contributing to cross-platform repos easier. So `cargo fmt` resulted in ~2k changed files because of the different line endings. I added a note with how to fix that.
1. Formatting.


Patch:
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index b5e9de05..bf6acf42 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -121,8 +121,8 @@ jobs:
     - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen -Zbuild-std=core,alloc -- -D warnings
     - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p js-sys -Zbuild-std=core,alloc -- -D warnings
     - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p web-sys -Zbuild-std=core,alloc -- -D warnings
-    - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-futures --features once_cell/critical-section -Zbuild-std=core,alloc -- -D warnings
-    - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-test --features once_cell/critical-section -Zbuild-std=core,alloc -- -D warnings
+    - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-futures -Zbuild-std=core,alloc -- -D warnings
+    - run: cargo clippy --no-deps --no-default-features --target wasm32-unknown-unknown -p wasm-bindgen-test -Zbuild-std=core,alloc -- -D warnings
 
   # Run `cargo clippy` over the project
   clippy_project:
@@ -206,15 +206,16 @@ jobs:
   test_threads:
     name: "Run wasm-bindgen crate tests with multithreading"
     runs-on: ubuntu-latest
+    env:
+      WASM_BINDGEN_SPLIT_LINKED_MODULES: 1
     steps:
     - uses: actions/checkout@v4
     - run: rustup default nightly-2024-07-06
     - run: rustup target add wasm32-unknown-unknown
     - run: rustup component add rust-src
-    # Note: we only run the browser tests here, because wasm-bindgen doesn't support threading in Node yet.
     - run: |
         RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals' \
-          cargo test --target wasm32-unknown-unknown --test headless -Z build-std=std,panic_abort
+          cargo test --target wasm32-unknown-unknown -Z build-std=std,panic_abort
 
   # I don't know why this is failing so comment this out for now, but ideally
   # this would be figured out at some point and solved.
@@ -538,6 +539,34 @@ jobs:
         name: doc_api
         path: docs.tar.gz
 
+  msrv-resolver:
+    name: Check for feature resolver v1 error message
+    runs-on: ubuntu-latest
+    strategy:
+      fail-fast: false
+      matrix:
+        rust:
+          - 1.57
+          - stable
+        target:
+          - x86_64-unknown-linux-gnu
+          - wasm32-unknown-unknown
+        features:
+          - --no-default-features
+          - ""
+    defaults:
+      run:
+        working-directory: crates/msrv/resolver
+    steps:
+    - uses: actions/checkout@v4
+    - run: rustup update --no-self-update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} && rustup target add ${{ matrix.target }}
+    - if: matrix.rust == '1.57'
+      run: |
+        cargo update -p bumpalo --precise 3.12.0
+        cargo update -p log --precise 0.4.18
+        cargo update -p scoped-tls --precise 1.0.0
+    - run: diff <(cargo build --target ${{ matrix.target }} ${{ matrix.features }} --message-format json | jq -r "select(.reason == \"compiler-message\") | .message.message") error-${{ matrix.rust }}.txt
+
   msrv-lib:
     name: Check MSRV for libraries
     runs-on: ubuntu-latest
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bbadd8d9..7181e531 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,42 @@
 # `wasm-bindgen` Change Log
 --------------------------------------------------------------------------------
 
+## Unreleased
+
+### Added
+
+* Add support for multi-threading in Node.js.
+  [#4318](https://github.com/rustwasm/wasm-bindgen/pull/4318)
+
+* Add clear error message to communicate new feature resolver version requirements.
+  [#4312](https://github.com/rustwasm/wasm-bindgen/pull/4312)
+
+* Remove `once_cell/critical-section` requirement for `no_std` with atomics.
+  [#4322](https://github.com/rustwasm/wasm-bindgen/pull/4322)
+
+### Changed
+
+* `static FOO: Option<T>` now returns `None` if undeclared in JS instead of throwing an error in JS.
+  [#4319](https://github.com/rustwasm/wasm-bindgen/pull/4319)
+
+### Fixed
+
+* Fix macro-hygiene for calls to `std::thread_local!`.
+  [#4315](https://github.com/rustwasm/wasm-bindgen/pull/4315)
+
+--------------------------------------------------------------------------------
+
+## [0.2.97](https://github.com/rustwasm/wasm-bindgen/compare/0.2.96...0.2.97)
+
+Released 2024-11-30
+
+### Fixed
+
+* Fixed `js-sys` and `wasm-bindgen-futures` relying on internal paths of `wasm-bindgen` that are not crate feature additive.
+  [#4305](https://github.com/rustwasm/wasm-bindgen/pull/4305)
+
+--------------------------------------------------------------------------------
+
 ## [0.2.96](https://github.com/rustwasm/wasm-bindgen/compare/0.2.95...0.2.96)
 
 Released 2024-11-29
diff --git a/Cargo.toml b/Cargo.toml
index 79ce037b..d0aaa348 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,7 +13,7 @@ name = "wasm-bindgen"
 readme = "README.md"
 repository = "https://github.com/rustwasm/wasm-bindgen"
 rust-version = "1.57"
-version = "0.2.96"
+version = "0.2.97"
 
 [package.metadata.docs.rs]
 features = ["serde-serialize"]
@@ -45,18 +45,23 @@ cfg-if = "1.0.0"
 once_cell = { version = "1.12", default-features = false }
 serde = { version = "1.0", optional = true }
 serde_json = { version = "1.0", optional = true }
-wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.96", default-features = false }
+wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.97", default-features = false }
 
 [target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"), target_feature = "atomics"))'.dependencies]
-wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.96", default-features = false, features = [
+wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.97", default-features = false, features = [
   "atomics",
 ] }
 
 [target.'cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"), wasm_bindgen_unstable_test_coverage))'.dependencies]
-wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.96", default-features = false, features = [
+wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.97", default-features = false, features = [
   "coverage",
 ] }
 
+[target.'cfg(__wasm_bindgen_resolver_1)'.dependencies]
+wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.97", default-features = false, features = [
+  "xxx_resolver_1",
+] }
+
 [dev-dependencies]
 wasm-bindgen-test = { path = 'crates/test' }
 
diff --git a/README.md b/README.md
index d8e07bc0..05266a62 100644
--- a/README.md
+++ b/README.md
@@ -114,6 +114,8 @@ Libraries that are released on [crates.io](https://crates.io) have a MSRV of v1.
 
 CLI tools and their corresponding support libraries have a MSRV of v1.76. Changes to the MSRV will be accompanied by a patch version bump.
 
+[Feature resolver version 2](https://doc.rust-lang.org/1.83.0/cargo/reference/resolver.html#feature-resolver-version-2) is required.
+
 ## License
 
 This project is licensed under either of
diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml
index 207dac68..70323125 100644
--- a/crates/backend/Cargo.toml
+++ b/crates/backend/Cargo.toml
@@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-backend"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend"
 rust-version = "1.57"
-version = "0.2.96"
+version = "0.2.97"
 
 [features]
 atomics = []
@@ -20,6 +20,7 @@ default = ["std"]
 extra-traits = ["syn/extra-traits"]
 spans = []
 std = []
+xxx_resolver_1 = []
 
 [dependencies]
 bumpalo = "3.0.0"
@@ -28,7 +29,7 @@ once_cell = "1.12"
 proc-macro2 = "1.0"
 quote = '1.0'
 syn = { version = '2.0', features = ['full'] }
-wasm-bindgen-shared = { path = "../shared", version = "=0.2.96" }
+wasm-bindgen-shared = { path = "../shared", version = "=0.2.97" }
 
 [lints]
 workspace = true
diff --git a/crates/backend/src/codegen.rs b/crates/backend/src/codegen.rs
index d899d991..a715ec75 100644
--- a/crates/backend/src/codegen.rs
+++ b/crates/backend/src/codegen.rs
@@ -201,8 +201,8 @@ impl TryToTokens for ast::LinkToModule {
                 #program
                 #extern_fn
 
-                static __VAL: #wasm_bindgen::__rt::once_cell::sync::Lazy<#wasm_bindgen::__rt::alloc::string::String> =
-                    #wasm_bindgen::__rt::once_cell::sync::Lazy::new(|| unsafe {
+                static __VAL: #wasm_bindgen::__rt::LazyLock<#wasm_bindgen::__rt::alloc::string::String> =
+                    #wasm_bindgen::__rt::LazyLock::new(|| unsafe {
                         <#wasm_bindgen::__rt::alloc::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name().join())
                     });
 
@@ -229,7 +229,6 @@ impl ToTokens for ast::Struct {
             #[automatically_derived]
             impl #wasm_bindgen::describe::WasmDescribe for #name {
                 fn describe() {
-                    use #wasm_bindgen::__wbindgen_if_not_std;
                     use #wasm_bindgen::describe::*;
                     inform(RUST_STRUCT);
                     inform(#name_len);
@@ -1711,7 +1710,7 @@ impl ToTokens for ast::ImportStatic {
                         fn init() -> #ty {
                             #init
                         }
-                        thread_local!(static _VAL: #ty = init(););
+                        #wasm_bindgen::__rt::std::thread_local!(static _VAL: #ty = init(););
                         #wasm_bindgen::JsStatic {
                             __inner: &_VAL,
                         }
@@ -1763,7 +1762,7 @@ fn thread_local_import(
 
     match thread_local {
         ast::ThreadLocal::V1 => quote! {
-            thread_local! {
+            #wasm_bindgen::__rt::std::thread_local! {
                 #[automatically_derived]
                 #[deprecated = "use with `#[wasm_bindgen(thread_local_v2)]` instead"]
                 #vis static #name: #actual_ty = {
@@ -1774,7 +1773,7 @@ fn thread_local_import(
         ast::ThreadLocal::V2 => {
             #[cfg(feature = "std")]
             let inner = quote! {
-                thread_local!(static _VAL: #actual_ty = init(););
+                #wasm_bindgen::__rt::std::thread_local!(static _VAL: #actual_ty = init(););
                 #wasm_bindgen::JsThreadLocal {
                     __inner: &_VAL,
                 }
@@ -1966,8 +1965,8 @@ fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream {
 }
 
 fn coverage() -> Option<TokenStream> {
-    #[cfg(feature = "coverage")]
+    #[cfg(all(not(feature = "xxx_resolver_1"), feature = "coverage"))]
     return Some(quote! { #[coverage(off)] });
-    #[cfg(not(feature = "coverage"))]
+    #[cfg(any(feature = "xxx_resolver_1", not(feature = "coverage")))]
     None
 }
diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml
index f136e983..b5c4750d 100644
--- a/crates/cli-support/Cargo.toml
+++ b/crates/cli-support/Cargo.toml
@@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-cli-support"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli-support"
 rust-version = "1.76"
-version = "0.2.96"
+version = "0.2.97"
 
 [dependencies]
 anyhow = "1.0"
@@ -23,12 +23,12 @@ serde_json = "1.0"
 tempfile = "3.0"
 unicode-ident = "1.0.5"
 walrus = "0.23"
-wasm-bindgen-externref-xform = { path = '../externref-xform', version = '=0.2.96' }
-wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.96' }
-wasm-bindgen-shared = { path = "../shared", version = '=0.2.96' }
-wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.96' }
-wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.96' }
-wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.96' }
+wasm-bindgen-externref-xform = { path = '../externref-xform', version = '=0.2.97' }
+wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.97' }
+wasm-bindgen-shared = { path = "../shared", version = '=0.2.97' }
+wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.97' }
+wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.97' }
+wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.97' }
 
 [lints]
 workspace = true
diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index 72b0273d..e26f7a6d 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -440,6 +440,8 @@ impl<'a> Context<'a> {
             // With normal CommonJS node we need to defer requiring the wasm
             // until the end so most of our own exports are hooked up
             OutputMode::Node { module: false } => {
+                self.nodejs_memory();
+
                 js.push_str(&self.generate_node_imports());
 
                 js.push_str("let wasm;\n");
@@ -483,6 +485,8 @@ impl<'a> Context<'a> {
             // and let the bundler/runtime take care of it.
             // With Node we manually read the Wasm file from the filesystem and instantiate it.
             OutputMode::Bundler { .. } | OutputMode::Node { module: true } => {
+                self.nodejs_memory();
+
                 for (id, js) in iter_by_import(&self.wasm_import_definitions, self.module) {
                     let import = self.module.imports.get_mut(*id);
                     import.module = format!("./{}_bg.js", module_name);
@@ -1005,6 +1009,14 @@ __wbg_set_wasm(wasm);"
         Ok((js, ts))
     }
 
+    fn nodejs_memory(&mut self) {
+        if let Some(mem) = self.module.memories.iter_mut().next() {
+            if let Some(id) = mem.import.take() {
+                self.module.imports.delete(id);
+            }
+        }
+    }
+
     fn write_classes(&mut self) -> Result<(), Error> {
         for (class, exports) in self.exported_classes.take().unwrap() {
             self.write_class(&class, &exports)?;
@@ -2566,6 +2578,30 @@ __wbg_set_wasm(wasm);"
         Ok(name)
     }
 
+    fn import_static(&mut self, import: &JsImport, optional: bool) -> Result<String, Error> {
+        let mut name = self.import_name(&JsImport {
+            name: import.name.clone(),
+            fields: Vec::new(),
+        })?;
+
+        // After we've got an actual name handle field projections
+        if optional {
+            name = format!("typeof {name} === 'undefined' ? null : {name}");
+
+            for field in import.fields.iter() {
+                name.push_str("?.");
+                name.push_str(field);
+            }
+        } else {
+            for field in import.fields.iter() {
+                name.push('.');
+                name.push_str(field);
+            }
+        }
+
+        Ok(name)
+    }
+
     /// If a start function is present, it removes it from the `start` section
     /// of the Wasm module and then moves it to an exported function, named
     /// `__wbindgen_start`.
@@ -2718,7 +2754,7 @@ __wbg_set_wasm(wasm);"
                 | AuxImport::Value(AuxValue::Setter(js, ..))
                 | AuxImport::ValueWithThis(js, ..)
                 | AuxImport::Instanceof(js)
-                | AuxImport::Static(js)
+                | AuxImport::Static { js, .. }
                 | AuxImport::StructuralClassGetter(js, ..)
                 | AuxImport::StructuralClassSetter(js, ..)
                 | AuxImport::IndexingGetterOfClass(js)
@@ -3253,11 +3289,11 @@ __wbg_set_wasm(wasm);"
                 Ok("result".to_owned())
             }
 
-            AuxImport::Static(js) => {
+            AuxImport::Static { js, optional } => {
                 assert!(kind == AdapterJsImportKind::Normal);
                 assert!(!variadic);
                 assert_eq!(args.len(), 0);
-                self.import_name(js)
+                self.import_static(js, *optional)
             }
 
             AuxImport::String(string) => {
diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs
index 2f1d7f52..07fb5090 100644
--- a/crates/cli-support/src/wit/mod.rs
+++ b/crates/cli-support/src/wit/mod.rs
@@ -788,6 +788,7 @@ impl<'a> Context<'a> {
             None => return Ok(()),
             Some(d) => d,
         };
+        let optional = matches!(descriptor, Descriptor::Option(_));
 
         // Register the signature of this imported shim
         let id = self.import_adapter(
@@ -803,8 +804,10 @@ impl<'a> Context<'a> {
 
         // And then save off that this function is is an instanceof shim for an
         // imported item.
-        let import = self.determine_import(import, static_.name)?;
-        self.aux.import_map.insert(id, AuxImport::Static(import));
+        let js = self.determine_import(import, static_.name)?;
+        self.aux
+            .import_map
+            .insert(id, AuxImport::Static { js, optional });
         Ok(())
     }
 
diff --git a/crates/cli-support/src/wit/nonstandard.rs b/crates/cli-support/src/wit/nonstandard.rs
index 35f30a74..1e9579dc 100644
--- a/crates/cli-support/src/wit/nonstandard.rs
+++ b/crates/cli-support/src/wit/nonstandard.rs
@@ -233,7 +233,7 @@ pub enum AuxImport {
 
     /// This import is expected to be a shim that returns the JS value named by
     /// `JsImport`.
-    Static(JsImport),
+    Static { js: JsImport, optional: bool },
 
     /// This import is expected to be a shim that returns an exported `JsString`.
     String(String),
diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
index f16b5b68..a171a958 100644
--- a/crates/cli/Cargo.toml
+++ b/crates/cli/Cargo.toml
@@ -14,7 +14,7 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-cli"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli"
 rust-version = "1.76"
-version = "0.2.96"
+version = "0.2.97"
 
 [package.metadata.binstall]
 bin-dir = "wasm-bindgen-{ version }-{ target }/{ bin }{ binary-ext }"
@@ -32,8 +32,8 @@ serde_derive = "1.0"
 serde_json = "1.0"
 ureq = { version = "2.7", default-features = false, features = ["brotli", "gzip"] }
 walrus = { version = "0.23", features = ['parallel'] }
-wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.96" }
-wasm-bindgen-shared = { path = "../shared", version = "=0.2.96" }
+wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.97" }
+wasm-bindgen-shared = { path = "../shared", version = "=0.2.97" }
 
 [dev-dependencies]
 assert_cmd = "2"
diff --git a/crates/externref-xform/Cargo.toml b/crates/externref-xform/Cargo.toml
index 4615e53b..26f5d2c0 100644
--- a/crates/externref-xform/Cargo.toml
+++ b/crates/externref-xform/Cargo.toml
@@ -11,12 +11,12 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-externref-xform"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/externref-xform"
 rust-version = "1.76"
-version = "0.2.96"
+version = "0.2.97"
 
 [dependencies]
 anyhow = "1.0"
 walrus = "0.23"
-wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.96" }
+wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.97" }
 
 [dev-dependencies]
 rayon = "1.0"
diff --git a/crates/futures/Cargo.toml b/crates/futures/Cargo.toml
index c3257bb8..b34f4299 100644
--- a/crates/futures/Cargo.toml
+++ b/crates/futures/Cargo.toml
@@ -10,7 +10,7 @@ name = "wasm-bindgen-futures"
 readme = "./README.md"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures"
 rust-version = "1.57"
-version = "0.4.46"
+version = "0.4.47"
 
 [package.metadata.docs.rs]
 all-features = true
@@ -19,9 +19,9 @@ rustdoc-args = ["--cfg", "docsrs"]
 [dependencies]
 cfg-if = "1.0.0"
 futures-core = { version = '0.3.8', default-features = false, optional = true }
-js-sys = { path = "../js-sys", version = '=0.3.73', default-features = false }
+js-sys = { path = "../js-sys", version = '=0.3.74', default-features = false }
 once_cell = { version = "1.12", default-features = false }
-wasm-bindgen = { path = "../..", version = '=0.2.96', default-features = false }
+wasm-bindgen = { path = "../..", version = '=0.2.97', default-features = false }
 
 [features]
 default = ["std"]
@@ -29,7 +29,7 @@ futures-core-03-stream = ['futures-core']
 std = ["wasm-bindgen/std", "js-sys/std", "web-sys/std", "once_cell/std"]
 
 [target.'cfg(target_feature = "atomics")'.dependencies]
-web-sys = { path = "../web-sys", version = "=0.3.73", default-features = false, features = [
+web-sys = { path = "../web-sys", version = "=0.3.74", default-features = false, features = [
   "MessageEvent",
   "Worker",
 ] }
diff --git a/crates/futures/src/queue.rs b/crates/futures/src/queue.rs
index fe438cb1..e0eb9e0d 100644
--- a/crates/futures/src/queue.rs
+++ b/crates/futures/src/queue.rs
@@ -118,11 +118,19 @@ impl Queue {
 
     #[cfg(not(feature = "std"))]
     pub(crate) fn with<R>(f: impl FnOnce(&Self) -> R) -> R {
-        use wasm_bindgen::__rt::LazyCell;
+        use once_cell::unsync::Lazy;
+
+        struct Wrapper<T>(Lazy<T>);
+
+        #[cfg(not(target_feature = "atomics"))]
+        unsafe impl<T> Sync for Wrapper<T> {}
+
+        #[cfg(not(target_feature = "atomics"))]
+        unsafe impl<T> Send for Wrapper<T> {}
 
         #[cfg_attr(target_feature = "atomics", thread_local)]
-        static QUEUE: LazyCell<Queue> = LazyCell::new(Queue::new);
+        static QUEUE: Wrapper<Queue> = Wrapper(Lazy::new(Queue::new));
 
-        f(&QUEUE)
+        f(&QUEUE.0)
     }
 }
diff --git a/crates/js-sys/Cargo.toml b/crates/js-sys/Cargo.toml
index 62159e87..86d074e7 100644
--- a/crates/js-sys/Cargo.toml
+++ b/crates/js-sys/Cargo.toml
@@ -14,7 +14,7 @@ name = "js-sys"
 readme = "./README.md"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/js-sys"
 rust-version = "1.57"
-version = "0.3.73"
+version = "0.3.74"
 
 [lib]
 doctest = false
@@ -25,7 +25,8 @@ default = ["std"]
 std = ["wasm-bindgen/std"]
 
 [dependencies]
-wasm-bindgen = { path = "../..", version = "=0.2.96", default-features = false }
+once_cell = { version = "1.12", default-features = false }
+wasm-bindgen = { path = "../..", version = "=0.2.97", default-features = false }
 
 [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 wasm-bindgen-futures = { path = '../futures' }
diff --git a/crates/js-sys/src/lib.rs b/crates/js-sys/src/lib.rs
index 167e1876..aa2f3a07 100644
--- a/crates/js-sys/src/lib.rs
+++ b/crates/js-sys/src/lib.rs
@@ -6038,23 +6038,23 @@ pub fn global() -> Object {
     }
     #[cfg(not(feature = "std"))]
     {
-        use wasm_bindgen::__rt::LazyCell;
+        use once_cell::unsync::Lazy;
+
+        struct Wrapper<T>(Lazy<T>);
+
+        #[cfg(not(target_feature = "atomics"))]
+        unsafe impl<T> Sync for Wrapper<T> {}
+
+        #[cfg(not(target_feature = "atomics"))]
+        unsafe impl<T> Send for Wrapper<T> {}
 
         #[cfg_attr(target_feature = "atomics", thread_local)]
-        static GLOBAL: LazyCell<Object> = LazyCell::new(get_global_object);
+        static GLOBAL: Wrapper<Object> = Wrapper(Lazy::new(get_global_object));
 
-        return GLOBAL.clone();
+        return GLOBAL.0.clone();
     }
 
     fn get_global_object() -> Object {
-        // This is a bit wonky, but we're basically using `#[wasm_bindgen]`
-        // attributes to synthesize imports so we can access properties of the
-        // form:
-        //
-        // * `globalThis.globalThis`
-        // * `self.self`
-        // * ... (etc)
-        //
         // Accessing the global object is not an easy thing to do, and what we
         // basically want is `globalThis` but we can't rely on that existing
         // everywhere. In the meantime we've got the fallbacks mentioned in:
@@ -6068,26 +6068,27 @@ pub fn global() -> Object {
         extern "C" {
             type Global;
 
-            #[wasm_bindgen(getter, catch, static_method_of = Global, js_class = globalThis, js_name = globalThis)]
-            fn get_global_this() -> Result<Object, JsValue>;
+            #[wasm_bindgen(thread_local_v2, js_name = globalThis)]
+            static GLOBAL_THIS: Option<Object>;
 
-            #[wasm_bindgen(getter, catch, static_method_of = Global, js_class = self, js_name = self)]
-            fn get_self() -> Result<Object, JsValue>;
+            #[wasm_bindgen(thread_local_v2, js_name = self)]
+            static SELF: Option<Object>;
 
-            #[wasm_bindgen(getter, catch, static_method_of = Global, js_class = window, js_name = window)]
-            fn get_window() -> Result<Object, JsValue>;
+            #[wasm_bindgen(thread_local_v2, js_name = window)]
+            static WINDOW: Option<Object>;
 
-            #[wasm_bindgen(getter, catch, static_method_of = Global, js_class = global, js_name = global)]
-            fn get_global() -> Result<Object, JsValue>;
+            #[wasm_bindgen(thread_local_v2, js_name = global)]
+            static GLOBAL: Option<Object>;
         }
 
         // The order is important: in Firefox Extension Content Scripts `globalThis`
         // is a Sandbox (not Window), so `globalThis` must be checked after `window`.
-        let static_object = Global::get_self()
-            .or_else(|_| Global::get_window())
-            .or_else(|_| Global::get_global_this())
-            .or_else(|_| Global::get_global());
-        if let Ok(obj) = static_object {
+        let static_object = SELF
+            .with(Option::clone)
+            .or_else(|| WINDOW.with(Option::clone))
+            .or_else(|| GLOBAL_THIS.with(Option::clone))
+            .or_else(|| GLOBAL.with(Option::clone));
+        if let Some(obj) = static_object {
             if !obj.is_undefined() {
                 return obj;
             }
diff --git a/crates/macro-support/Cargo.toml b/crates/macro-support/Cargo.toml
index 9165e959..ed92337b 100644
--- a/crates/macro-support/Cargo.toml
+++ b/crates/macro-support/Cargo.toml
@@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-macro-support"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support"
 rust-version = "1.57"
-version = "0.2.96"
+version = "0.2.97"
 
 [features]
 atomics = ["wasm-bindgen-backend/atomics"]
@@ -21,13 +21,14 @@ extra-traits = ["syn/extra-traits"]
 spans = ["wasm-bindgen-backend/spans"]
 std = ["wasm-bindgen-backend/std"]
 strict-macro = []
+xxx_resolver_1 = ["wasm-bindgen-backend/xxx_resolver_1"]
 
 [dependencies]
 proc-macro2 = "1.0"
 quote = '1.0'
 syn = { version = '2.0', features = ['visit', 'visit-mut', 'full'] }
-wasm-bindgen-backend = { path = "../backend", version = "=0.2.96", default-features = false }
-wasm-bindgen-shared = { path = "../shared", version = "=0.2.96" }
+wasm-bindgen-backend = { path = "../backend", version = "=0.2.97", default-features = false }
+wasm-bindgen-shared = { path = "../shared", version = "=0.2.97" }
 
 [lints]
 workspace = true
diff --git a/crates/macro/Cargo.toml b/crates/macro/Cargo.toml
index 10001c05..ebb4377f 100644
--- a/crates/macro/Cargo.toml
+++ b/crates/macro/Cargo.toml
@@ -11,7 +11,7 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-macro"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro"
 rust-version = "1.57"
-version = "0.2.96"
+version = "0.2.97"
 
 [lib]
 proc-macro = true
@@ -24,10 +24,11 @@ spans = ["wasm-bindgen-macro-support/spans"]
 std = ["wasm-bindgen-macro-support/std"]
 strict-macro = ["wasm-bindgen-macro-support/strict-macro"]
 xxx_debug_only_print_generated_code = []
+xxx_resolver_1 = ["wasm-bindgen-macro-support/xxx_resolver_1"]
 
 [dependencies]
 quote = "1.0"
-wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.96", default-features = false }
+wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.97", default-features = false }
 
 [dev-dependencies]
 js-sys = { path = "../js-sys" }
diff --git a/crates/macro/src/lib.rs b/crates/macro/src/lib.rs
index 6dc0cbf1..5ae63303 100644
--- a/crates/macro/src/lib.rs
+++ b/crates/macro/src/lib.rs
@@ -1,6 +1,9 @@
 #![doc(html_root_url = "https://docs.rs/wasm-bindgen-macro/0.2")]
 #![cfg_attr(
-    any(feature = "coverage", all(not(feature = "std"), feature = "atomics")),
+    all(
+        not(feature = "xxx_resolver_1"),
+        any(feature = "coverage", all(not(feature = "std"), feature = "atomics"))
+    ),
     feature(allow_internal_unstable),
     allow(internal_features)
 )]
@@ -10,10 +13,23 @@ extern crate proc_macro;
 use proc_macro::TokenStream;
 use quote::quote;
 
+#[cfg(feature = "xxx_resolver_1")]
+compile_error!("Feature resolver version 2 or up is required.\n\
+                Please add `resolver = \"2\"` to your `Cargo.toml`.\n\
+                \n\
+                See <https://doc.rust-lang.org/1.83.0/cargo/reference/resolver.html#feature-resolver-version-2>.");
+
 #[proc_macro_attribute]
-#[cfg_attr(feature = "coverage", allow_internal_unstable(coverage_attribute))]
 #[cfg_attr(
-    all(not(feature = "std"), feature = "atomics"),
+    all(not(feature = "xxx_resolver_1"), feature = "coverage"),
+    allow_internal_unstable(coverage_attribute)
+)]
+#[cfg_attr(
+    all(
+        not(feature = "xxx_resolver_1"),
+        not(feature = "std"),
+        feature = "atomics"
+    ),
     allow_internal_unstable(thread_local)
 )]
 pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream {
@@ -42,7 +58,10 @@ pub fn wasm_bindgen(attr: TokenStream, input: TokenStream) -> TokenStream {
 /// let worker = Worker::new(&wasm_bindgen::link_to!(module = "/src/worker.js"));
 /// ```
 #[proc_macro]
-#[cfg_attr(feature = "coverage", allow_internal_unstable(coverage_attribute))]
+#[cfg_attr(
+    all(not(feature = "xxx_resolver_1"), feature = "coverage"),
+    allow_internal_unstable(coverage_attribute)
+)]
 pub fn link_to(input: TokenStream) -> TokenStream {
     match wasm_bindgen_macro_support::expand_link_to(input.into()) {
         Ok(tokens) => {
@@ -59,7 +78,10 @@ pub fn link_to(input: TokenStream) -> TokenStream {
 }
 
 #[proc_macro_attribute]
-#[cfg_attr(feature = "coverage", allow_internal_unstable(coverage_attribute))]
+#[cfg_attr(
+    all(not(feature = "xxx_resolver_1"), feature = "coverage"),
+    allow_internal_unstable(coverage_attribute)
+)]
 pub fn __wasm_bindgen_class_marker(attr: TokenStream, input: TokenStream) -> TokenStream {
     match wasm_bindgen_macro_support::expand_class_marker(attr.into(), input.into()) {
         Ok(tokens) => {
diff --git a/crates/msrv/resolver/Cargo.toml b/crates/msrv/resolver/Cargo.toml
new file mode 100644
index 00000000..0f44e15a
--- /dev/null
+++ b/crates/msrv/resolver/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+edition = "2021"
+name = "msrv-library-test"
+publish = false
+resolver = "1"
+version = "0.0.0"
+
+[features]
+default = ["std"]
+std = [
+  "wasm-bindgen/std",
+  "js-sys/std",
+  "wasm-bindgen-futures/std",
+  "web-sys/std",
+  "wasm-bindgen-test/std",
+]
+
+[dependencies]
+js-sys = { path = "../../js-sys", default-features = false }
+wasm-bindgen = { path = "../../../", default-features = false }
+wasm-bindgen-futures = { path = "../../futures", default-features = false }
+wasm-bindgen-test = { path = "../../test", default-features = false }
+web-sys = { path = "../../web-sys", default-features = false }
diff --git a/crates/msrv/resolver/error-1.57.txt b/crates/msrv/resolver/error-1.57.txt
new file mode 100644
index 00000000..5a255fbc
--- /dev/null
+++ b/crates/msrv/resolver/error-1.57.txt
@@ -0,0 +1,5 @@
+Feature resolver version 2 or up is required.
+Please add `resolver = "2"` to your `Cargo.toml`.
+
+See <https://doc.rust-lang.org/1.83.0/cargo/reference/resolver.html#feature-resolver-version-2>.
+aborting due to previous error
diff --git a/crates/msrv/resolver/error-stable.txt b/crates/msrv/resolver/error-stable.txt
new file mode 100644
index 00000000..5dd74366
--- /dev/null
+++ b/crates/msrv/resolver/error-stable.txt
@@ -0,0 +1,4 @@
+Feature resolver version 2 or up is required.
+Please add `resolver = "2"` to your `Cargo.toml`.
+
+See <https://doc.rust-lang.org/1.83.0/cargo/reference/resolver.html#feature-resolver-version-2>.
diff --git a/crates/msrv/resolver/src/lib.rs b/crates/msrv/resolver/src/lib.rs
new file mode 100644
index 00000000..e69de29b
diff --git a/crates/multi-value-xform/Cargo.toml b/crates/multi-value-xform/Cargo.toml
index 9457b807..da6e4e87 100644
--- a/crates/multi-value-xform/Cargo.toml
+++ b/crates/multi-value-xform/Cargo.toml
@@ -11,12 +11,12 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-multi-value-xform"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/multi-value-xform"
 rust-version = "1.76"
-version = "0.2.96"
+version = "0.2.97"
 
 [dependencies]
 anyhow = "1.0"
 walrus = "0.23"
-wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.96" }
+wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.97" }
 
 [dev-dependencies]
 rayon = "1.0"
diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml
index 168898fd..4aa1b4ee 100644
--- a/crates/shared/Cargo.toml
+++ b/crates/shared/Cargo.toml
@@ -12,7 +12,7 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-shared"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared"
 rust-version = "1.57"
-version = "0.2.96"
+version = "0.2.97"
 
 # Because only a single `wasm_bindgen` version can be used in a dependency
 # graph, pretend we link a native library so that `cargo` will provide better
diff --git a/crates/threads-xform/Cargo.toml b/crates/threads-xform/Cargo.toml
index d055fb24..18c7e46b 100644
--- a/crates/threads-xform/Cargo.toml
+++ b/crates/threads-xform/Cargo.toml
@@ -11,12 +11,12 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-threads-xform"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/threads-xform"
 rust-version = "1.76"
-version = "0.2.96"
+version = "0.2.97"
 
 [dependencies]
 anyhow = "1.0"
 walrus = "0.23"
-wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.96" }
+wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.97" }
 
 [dev-dependencies]
 rayon = "1.0"
diff --git a/crates/wasm-conventions/Cargo.toml b/crates/wasm-conventions/Cargo.toml
index 3f16295f..13971811 100644
--- a/crates/wasm-conventions/Cargo.toml
+++ b/crates/wasm-conventions/Cargo.toml
@@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-wasm-conventions"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/wasm-conventions"
 rust-version = "1.76"
-version = "0.2.96"
+version = "0.2.97"
 
 [dependencies]
 leb128 = "0.2"
diff --git a/crates/wasm-interpreter/Cargo.toml b/crates/wasm-interpreter/Cargo.toml
index efb22bd8..dae80605 100644
--- a/crates/wasm-interpreter/Cargo.toml
+++ b/crates/wasm-interpreter/Cargo.toml
@@ -11,13 +11,13 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-wasm-interpreter"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/main/crates/wasm-interpreter"
 rust-version = "1.76"
-version = "0.2.96"
+version = "0.2.97"
 
 [dependencies]
 anyhow = "1.0"
 log = "0.4"
 walrus = "0.23"
-wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "0.2.96" }
+wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "0.2.97" }
 
 [dev-dependencies]
 tempfile = "3"
diff --git a/crates/web-sys/Cargo.toml b/crates/web-sys/Cargo.toml
index df11949d..1ae534fc 100644
--- a/crates/web-sys/Cargo.toml
+++ b/crates/web-sys/Cargo.toml
@@ -12,7 +12,7 @@ name = "web-sys"
 readme = "./README.md"
 repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/web-sys"
 rust-version = "1.57"
-version = "0.3.73"
+version = "0.3.74"
 
 [package.metadata.docs.rs]
 all-features = true
@@ -23,8 +23,8 @@ doctest = false
 test = false
 
 [dependencies]
-js-sys = { path = '../js-sys', version = '=0.3.73', default-features = false }
-wasm-bindgen = { path = "../..", version = "=0.2.96", default-features = false }
+js-sys = { path = '../js-sys', version = '=0.3.74', default-features = false }
+wasm-bindgen = { path = "../..", version = "=0.2.97", default-features = false }
 
 [target.'cfg(target_arch = "wasm32")'.dev-dependencies]
 futures = "0.3"
diff --git a/crates/web-sys/README.md b/crates/web-sys/README.md
index ab0cf1a2..7693680c 100644
--- a/crates/web-sys/README.md
+++ b/crates/web-sys/README.md
@@ -2,9 +2,9 @@
 
 Raw bindings to Web APIs for projects using `wasm-bindgen`.
 
-* [The `web-sys` section of the `wasm-bindgen`
+- [The `web-sys` section of the `wasm-bindgen`
   guide](https://rustwasm.github.io/wasm-bindgen/web-sys/index.html)
-* [API Documentation](https://rustwasm.github.io/wasm-bindgen/api/web_sys/)
+- [API Documentation](https://rustwasm.github.io/wasm-bindgen/api/web_sys/)
 
 ## Crate features
 
@@ -30,19 +30,23 @@ If you don't see a particular web API in `web-sys`, here is how to add it.
    [very bottom](https://w3c.github.io/mediasession/#idl-index) of _that_ page
    is the IDL.
 2. Annotate the functions that can throw with `[Throws]`
-3. Run `cargo run --release --package wasm-bindgen-webidl -- webidls src/features ./Cargo.toml`
-4. Run `git add .` to add all the generated files into git.
-5. Add an entry in CHANGELOG.md like the following
+3. `cd crates/web-sys`
+4. Run `cargo run --release --package wasm-bindgen-webidl -- webidls src/features ./Cargo.toml`
+
+   If formatting fails, you can run `cargo fmt` in the `crates/web-sys` directory. On Windows, you might also want to run `cargo fmt -- --config newline_style=Unix` depending on your git configuration.
+
+5. Run `git add .` to add all the generated files into git.
+6. Add an entry in CHANGELOG.md like the following
+
    ```md
    ...
-   
+
    ## Unreleased
-   
+
    ### Added
-   
+
    ...
-   
+
    * Added <your addition>
-     [#1234](https://github.com/rustwasm/wasm-bindgen/pull/1234)  # <- link to your PR
+     [#1234](https://github.com/rustwasm/wasm-bindgen/pull/1234) # <- link to your PR
    ```
-
diff --git a/guide/src/reference/static-js-objects.md b/guide/src/reference/static-js-objects.md
index b6aee687..92b699d5 100644
--- a/guide/src/reference/static-js-objects.md
+++ b/guide/src/reference/static-js-objects.md
@@ -4,8 +4,13 @@ JavaScript modules will often export arbitrary static objects for use with
 their provided interfaces. These objects can be accessed from Rust by declaring
 a named `static` in the `extern` block with an
 `#[wasm_bindgen(thread_local_v2)]` attribute. `wasm-bindgen` will bind a
-`JsThreadLocal` for these objects, which can be cloned into a `JsValue`. For
-example, given the following JavaScript:
+`JsThreadLocal` for these objects, which can be cloned into a `JsValue`.
+
+These values are cached in a thread-local and are meant to bind static values
+or objects only. For getters which can change their return value or throw see
+[how to import getters](attributes/on-js-imports/getter-and-setter.md).
+
+For example, given the following JavaScript:
 
 ```js
 let COLORS = {
@@ -65,6 +70,23 @@ extern "C" {
 }
 ```
 
+## Optional statics
+
+If you expect the JavaScript value you're trying to access to not always be
+available you can use `Option<T>` to handle this:
+
+```rust
+extern "C" {
+    type Crypto;
+    #[wasm_bindgen(thread_local_v2, js_name = crypto)]
+    static CRYPTO: Option<Crypto>;
+}
+```
+
+If `crypto` is not declared or nullish (`null` or `undefined`) in JavaScript,
+it will simply return `None` in Rust. This will also account for namespaces: it
+will return `Some(T)` only if all parts are declared and not nullish.
+
 ## Static strings
 
 Strings can be imported to avoid going through `TextDecoder/Encoder` when requiring just a `JsString`. This can be useful when dealing with environments where `TextDecoder/Encoder` is not available, like in audio worklets.
diff --git a/src/convert/slices.rs b/src/convert/slices.rs
index 249c0326..6c23926e 100644
--- a/src/convert/slices.rs
+++ b/src/convert/slices.rs
@@ -1,6 +1,3 @@
-#[cfg(feature = "std")]
-use std::prelude::v1::*;
-
 use alloc::boxed::Box;
 use alloc::string::String;
 use alloc::vec::Vec;
diff --git a/src/lib.rs b/src/lib.rs
index 403a119f..6e17e245 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1572,6 +1572,8 @@ pub mod __rt {
     use core::convert::Infallible;
     use core::mem;
     use core::ops::{Deref, DerefMut};
+    #[cfg(all(target_feature = "atomics", not(feature = "std")))]
+    use core::sync::atomic::{AtomicU8, Ordering};
 
     pub extern crate alloc;
     pub extern crate core;
@@ -1582,16 +1584,6 @@ pub mod __rt {
     use alloc::boxed::Box;
     use alloc::rc::Rc;
 
-    pub mod once_cell {
-        #[cfg(any(target_feature = "atomics", feature = "std"))]
-        pub use once_cell::*;
-
-        #[cfg(all(not(target_feature = "atomics"), not(feature = "std")))]
-        pub mod sync {
-            pub use super::super::LazyCell as Lazy;
-        }
-    }
-
     /// Wrapper around [`::once_cell::unsync::Lazy`] adding some compatibility methods with
     /// [`std::thread::LocalKey`] and adding `Send + Sync` when `atomics` is not enabled.
     #[cfg(not(feature = "std"))]
@@ -1633,18 +1625,87 @@ pub mod __rt {
         }
     }
 
-    #[macro_export]
-    #[doc(hidden)]
     #[cfg(feature = "std")]
-    macro_rules! __wbindgen_if_not_std {
-        ($($i:item)*) => {};
+    pub use once_cell::sync::Lazy as LazyLock;
+
+    #[cfg(all(not(target_feature = "atomics"), not(feature = "std")))]
+    pub use LazyCell as LazyLock;
+
+    #[cfg(all(target_feature = "atomics", not(feature = "std")))]
+    pub struct LazyLock<T, F = fn() -> T> {
+        state: AtomicU8,
+        data: UnsafeCell<Data<T, F>>,
     }
 
-    #[macro_export]
-    #[doc(hidden)]
-    #[cfg(not(feature = "std"))]
-    macro_rules! __wbindgen_if_not_std {
-        ($($i:item)*) => ($($i)*)
+    #[cfg(all(target_feature = "atomics", not(feature = "std")))]
+    enum Data<T, F> {
+        Value(T),
+        Init(F),
+    }
+
+    #[cfg(all(target_feature = "atomics", not(feature = "std")))]
+    unsafe impl<T, F> Sync for LazyLock<T, F> {}
+
+    #[cfg(all(target_feature = "atomics", not(feature = "std")))]
+    unsafe impl<T, F> Send for LazyLock<T, F> {}
+
+    #[cfg(all(target_feature = "atomics", not(feature = "std")))]
+    impl<T, F> LazyLock<T, F> {
+        const STATE_UNINIT: u8 = 0;
+        const STATE_INITIALIZING: u8 = 1;
+        const STATE_INIT: u8 = 2;
+
+        pub const fn new(init: F) -> LazyLock<T, F> {
+            Self {
+                state: AtomicU8::new(Self::STATE_UNINIT),
+                data: UnsafeCell::new(Data::Init(init)),
+            }
+        }
+    }
+
+    #[cfg(all(target_feature = "atomics", not(feature = "std")))]
+    impl<T> Deref for LazyLock<T> {
+        type Target = T;
+
+        fn deref(&self) -> &T {
+            let mut state = self.state.load(Ordering::Acquire);
+
+            loop {
+                match state {
+                    Self::STATE_INIT => {
+                        let Data::Value(value) = (unsafe { &*self.data.get() }) else {
+                            unreachable!()
+                        };
+                        return value;
+                    }
+                    Self::STATE_UNINIT => {
+                        if let Err(new_state) = self.state.compare_exchange_weak(
+                            Self::STATE_UNINIT,
+                            Self::STATE_INITIALIZING,
+                            Ordering::Acquire,
+                            Ordering::Relaxed,
+                        ) {
+                            state = new_state;
+                            continue;
+                        }
+
+                        let data = unsafe { &mut *self.data.get() };
+                        let Data::Init(init) = data else {
+                            unreachable!()
+                        };
+                        *data = Data::Value(init());
+                        self.state.store(Self::STATE_INIT, Ordering::Release);
+                        state = Self::STATE_INIT;
+                    }
+                    Self::STATE_INITIALIZING => {
+                        // TODO: Block here if possible. This would require
+                        // detecting if we can in the first place.
+                        state = self.state.load(Ordering::Acquire);
+                    }
+                    _ => unreachable!(),
+                }
+            }
+        }
     }
 
     #[inline]


Test patch:
diff --git a/crates/cli/tests/reference/static.d.ts b/crates/cli/tests/reference/static.d.ts
new file mode 100644
index 00000000..091dbcc5
--- /dev/null
+++ b/crates/cli/tests/reference/static.d.ts
@@ -0,0 +1,3 @@
+/* tslint:disable */
+/* eslint-disable */
+export function exported(): void;
diff --git a/crates/cli/tests/reference/static.js b/crates/cli/tests/reference/static.js
new file mode 100644
index 00000000..34aa7975
--- /dev/null
+++ b/crates/cli/tests/reference/static.js
@@ -0,0 +1,85 @@
+let wasm;
+export function __wbg_set_wasm(val) {
+    wasm = val;
+}
+
+
+function isLikeNone(x) {
+    return x === undefined || x === null;
+}
+
+function addToExternrefTable0(obj) {
+    const idx = wasm.__externref_table_alloc();
+    wasm.__wbindgen_export_1.set(idx, obj);
+    return idx;
+}
+
+const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder;
+
+let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true });
+
+cachedTextDecoder.decode();
+
+let cachedUint8ArrayMemory0 = null;
+
+function getUint8ArrayMemory0() {
+    if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
+        cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
+    }
+    return cachedUint8ArrayMemory0;
+}
+
+function getStringFromWasm0(ptr, len) {
+    ptr = ptr >>> 0;
+    return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
+}
+
+export function exported() {
+    wasm.exported();
+}
+
+export function __wbg_static_accessor_NAMESPACE_OPTIONAL_c9a4344c544120f4() {
+    const ret = typeof test === 'undefined' ? null : test?.NAMESPACE_OPTIONAL;
+    return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+};
+
+export function __wbg_static_accessor_NAMESPACE_PLAIN_784c8d7f5bbac62a() {
+    const ret = test.NAMESPACE_PLAIN;
+    return ret;
+};
+
+export function __wbg_static_accessor_NESTED_NAMESPACE_OPTIONAL_a414abbeb018a35a() {
+    const ret = typeof test1 === 'undefined' ? null : test1?.test2?.NESTED_NAMESPACE_OPTIONAL;
+    return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+};
+
+export function __wbg_static_accessor_NESTED_NAMESPACE_PLAIN_1121b285cb8479df() {
+    const ret = test1.test2.NESTED_NAMESPACE_PLAIN;
+    return ret;
+};
+
+export function __wbg_static_accessor_OPTIONAL_ade71b6402851d0c() {
+    const ret = typeof OPTIONAL === 'undefined' ? null : OPTIONAL;
+    return isLikeNone(ret) ? 0 : addToExternrefTable0(ret);
+};
+
+export function __wbg_static_accessor_PLAIN_c0f08eb2f0db194c() {
+    const ret = PLAIN;
+    return ret;
+};
+
+export function __wbindgen_init_externref_table() {
+    const table = wasm.__wbindgen_export_1;
+    const offset = table.grow(4);
+    table.set(0, undefined);
+    table.set(offset + 0, undefined);
+    table.set(offset + 1, null);
+    table.set(offset + 2, true);
+    table.set(offset + 3, false);
+    ;
+};
+
+export function __wbindgen_throw(arg0, arg1) {
+    throw new Error(getStringFromWasm0(arg0, arg1));
+};
+
diff --git a/crates/cli/tests/reference/static.rs b/crates/cli/tests/reference/static.rs
new file mode 100644
index 00000000..4437f550
--- /dev/null
+++ b/crates/cli/tests/reference/static.rs
@@ -0,0 +1,30 @@
+// DEPENDENCY: js-sys = { path = '{root}/crates/js-sys' }
+
+use wasm_bindgen::prelude::*;
+use js_sys::Number;
+
+#[wasm_bindgen]
+extern "C" {
+    #[wasm_bindgen(thread_local_v2)]
+    static PLAIN: JsValue;
+    #[wasm_bindgen(thread_local_v2)]
+    static OPTIONAL: Option<Number>;
+    #[wasm_bindgen(thread_local_v2, js_namespace = test)]
+    static NAMESPACE_PLAIN: JsValue;
+    #[wasm_bindgen(thread_local_v2, js_namespace = test)]
+    static NAMESPACE_OPTIONAL: Option<Number>;
+    #[wasm_bindgen(thread_local_v2, js_namespace = ["test1", "test2"])]
+    static NESTED_NAMESPACE_PLAIN: JsValue;
+    #[wasm_bindgen(thread_local_v2, js_namespace = ["test1", "test2"])]
+    static NESTED_NAMESPACE_OPTIONAL: Option<Number>;
+}
+
+#[wasm_bindgen]
+pub fn exported() {
+    let _ = PLAIN.with(JsValue::clone);
+    let _ = OPTIONAL.with(Option::clone);
+    let _ = NAMESPACE_PLAIN.with(JsValue::clone);
+    let _ = NAMESPACE_OPTIONAL.with(Option::clone);
+    let _ = NESTED_NAMESPACE_PLAIN.with(JsValue::clone);
+    let _ = NESTED_NAMESPACE_OPTIONAL.with(Option::clone);
+}
diff --git a/crates/cli/tests/reference/static.wat b/crates/cli/tests/reference/static.wat
new file mode 100644
index 00000000..b4ea3e1c
--- /dev/null
+++ b/crates/cli/tests/reference/static.wat
@@ -0,0 +1,16 @@
+(module $reference_test.wasm
+  (type (;0;) (func))
+  (type (;1;) (func (result i32)))
+  (import "./reference_test_bg.js" "__wbindgen_init_externref_table" (func (;0;) (type 0)))
+  (func $__externref_table_alloc (;1;) (type 1) (result i32))
+  (func $exported (;2;) (type 0))
+  (table (;0;) 128 externref)
+  (memory (;0;) 17)
+  (export "memory" (memory 0))
+  (export "exported" (func $exported))
+  (export "__externref_table_alloc" (func $__externref_table_alloc))
+  (export "__wbindgen_export_1" (table 0))
+  (export "__wbindgen_start" (func 0))
+  (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")
+)
+
diff --git a/crates/cli/tests/reference/web-sys.js b/crates/cli/tests/reference/web-sys.js
index 99886e52..071fcab8 100644
--- a/crates/cli/tests/reference/web-sys.js
+++ b/crates/cli/tests/reference/web-sys.js
@@ -188,7 +188,7 @@ export function get_media_source() {
 
 const __wbindgen_enum_MediaSourceEnum = ["camera", "screen", "application", "window", "browser", "microphone", "audioCapture", "other"];
 
-export function __wbg_new_dce808fb3f528314() { return handleError(function (arg0, arg1) {
+export function __wbg_new_6684d1d61b5a2140() { return handleError(function (arg0, arg1) {
     const ret = new URL(getStringFromWasm0(arg0, arg1));
     return ret;
 }, arguments) };
diff --git a/crates/test-macro/Cargo.toml b/crates/test-macro/Cargo.toml
index 6428ec85..cfbfc747 100644
--- a/crates/test-macro/Cargo.toml
+++ b/crates/test-macro/Cargo.toml
@@ -7,13 +7,14 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-test-macro"
 repository = "https://github.com/rustwasm/wasm-bindgen"
 rust-version = "1.57"
-version = "0.3.46"
+version = "0.3.47"
 
 [lib]
 proc-macro = true
 
 [features]
 coverage = []
+xxx_resolver_1 = []
 
 [dependencies]
 proc-macro2 = "1.0"
diff --git a/crates/test-macro/src/lib.rs b/crates/test-macro/src/lib.rs
index 3b0f722b..0916132d 100644
--- a/crates/test-macro/src/lib.rs
+++ b/crates/test-macro/src/lib.rs
@@ -2,7 +2,7 @@
 //! going on here.
 
 #![cfg_attr(
-    feature = "coverage",
+    all(not(feature = "xxx_resolver_1"), feature = "coverage"),
     feature(allow_internal_unstable),
     allow(internal_features)
 )]
@@ -18,7 +18,10 @@ use std::sync::atomic::*;
 static CNT: AtomicUsize = AtomicUsize::new(0);
 
 #[proc_macro_attribute]
-#[cfg_attr(feature = "coverage", allow_internal_unstable(coverage_attribute))]
+#[cfg_attr(
+    all(not(feature = "xxx_resolver_1"), feature = "coverage"),
+    allow_internal_unstable(coverage_attribute)
+)]
 pub fn wasm_bindgen_test(
     attr: proc_macro::TokenStream,
     body: proc_macro::TokenStream,
diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml
index 6140a3d5..72825958 100644
--- a/crates/test/Cargo.toml
+++ b/crates/test/Cargo.toml
@@ -7,24 +7,28 @@ license = "MIT OR Apache-2.0"
 name = "wasm-bindgen-test"
 repository = "https://github.com/rustwasm/wasm-bindgen"
 rust-version = "1.57"
-version = "0.3.46"
+version = "0.3.47"
 
 [features]
 default = ["std"]
-std = ["wasm-bindgen/std", "js-sys/std", "wasm-bindgen-futures/std", "once_cell/std", "scoped-tls"]
+std = ["wasm-bindgen/std", "js-sys/std", "wasm-bindgen-futures/std", "scoped-tls"]
 
 [dependencies]
 gg-alloc = { version = "1.0", optional = true }
-js-sys = { path = '../js-sys', version = '=0.3.73', default-features = false }
-once_cell = { version = "1.12", default-features = false }
+js-sys = { path = '../js-sys', version = '=0.3.74', default-features = false }
 scoped-tls = { version = "1.0", optional = true }
-wasm-bindgen = { path = '../..', version = '=0.2.96', default-features = false }
-wasm-bindgen-futures = { path = '../futures', version = '=0.4.46', default-features = false }
-wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.46' }
+wasm-bindgen = { path = '../..', version = '=0.2.97', default-features = false }
+wasm-bindgen-futures = { path = '../futures', version = '=0.4.47', default-features = false }
+wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.47' }
 
 [target.'cfg(all(target_arch = "wasm32", wasm_bindgen_unstable_test_coverage))'.dependencies]
 minicov = "0.3"
-wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.46', features = ["coverage"] }
+wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.47', features = ["coverage"] }
+
+[target.'cfg(__wasm_bindgen_resolver_1)'.dependencies]
+wasm-bindgen-test-macro = { path = "../test-macro", version = "=0.3.47", features = [
+  "xxx_resolver_1",
+] }
 
 [lints.rust]
 unexpected_cfgs = { level = "warn", check-cfg = ['cfg(wasm_bindgen_unstable_test_coverage)'] }
diff --git a/tests/wasm/imports.rs b/tests/wasm/imports.rs
index a6eab0c5..119aefef 100644
--- a/tests/wasm/imports.rs
+++ b/tests/wasm/imports.rs
@@ -90,6 +90,15 @@ extern "C" {
 
     #[wasm_bindgen(js_name = "\"string'literal\nbreakers\r")]
     fn string_literal_breakers() -> u32;
+
+    #[wasm_bindgen(thread_local_v2)]
+    static UNDECLARED: Option<u32>;
+
+    #[wasm_bindgen(thread_local_v2, js_namespace = test)]
+    static UNDECLARED_NAMESPACE: Option<u32>;
+
+    #[wasm_bindgen(thread_local_v2, js_namespace = ["test1", "test2"])]
+    static UNDECLARED_NESTED_NAMESPACE: Option<u32>;
 }
 
 #[wasm_bindgen(module = "tests/wasm/imports_2.js")]
@@ -336,3 +345,10 @@ fn invalid_idents() {
     assert_eq!(kebab_case(), 42);
     assert_eq!(string_literal_breakers(), 42);
 }
+
+#[wasm_bindgen_test]
+fn undeclared() {
+    assert_eq!(UNDECLARED.with(Option::clone), None);
+    assert_eq!(UNDECLARED_NAMESPACE.with(Option::clone), None);
+    assert_eq!(UNDECLARED_NESTED_NAMESPACE.with(Option::clone), None);
+}