From 007573b303c4d99e4270af2e16047ace83a00219 Mon Sep 17 00:00:00 2001
From: daxpedda <daxpedda@gmail.com>
Date: Tue, 10 Dec 2024 11:56:11 +0100
Subject: [PATCH] Import memory and enable `module()` for Node.js

---
 CHANGELOG.md                     |   5 +-
 crates/cli-support/src/js/mod.rs | 103 +++++++++++++++++++------------
 crates/cli-support/src/lib.rs    |   4 --
 3 files changed, 68 insertions(+), 44 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 95b8f96cbfa..fa034f1024b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@
 * Add a `copy_to_uninit()` method to all `TypedArray`s. It takes `&mut [MaybeUninit<T>]` and returns `&mut [T]`.
   [#4340](https://github.com/rustwasm/wasm-bindgen/pull/4340)
 
+* Support importing memory and using `wasm_bindgen::module()` in Node.js.
+  [#4349](https://github.com/rustwasm/wasm-bindgen/pull/4349)
+
 ### Changed
 
 * Optional parameters are now typed as `T | undefined | null` to reflect the actual JS behavior.
@@ -47,7 +50,7 @@ Released 2024-12-07
 
 ### Added
 
-* Add support for multi-threading in Node.js.
+* Add support for compiling with `atomics` for Node.js.
   [#4318](https://github.com/rustwasm/wasm-bindgen/pull/4318)
 
 * Add `WASM_BINDGEN_TEST_DRIVER_TIMEOUT` environment variable to control the timeout to start and connect to the test driver.
diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index a650ab76772..d33edbd8128 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -261,7 +261,12 @@ impl<'a> Context<'a> {
 
     fn generate_node_imports(&self) -> String {
         let mut imports = BTreeSet::new();
-        for import in self.module.imports.iter() {
+        for import in self
+            .module
+            .imports
+            .iter()
+            .filter(|i| !(matches!(i.kind, walrus::ImportKind::Memory(_))))
+        {
             imports.insert(&import.module);
         }
 
@@ -296,9 +301,27 @@ impl<'a> Context<'a> {
         reset_indentation(&shim)
     }
 
-    fn generate_node_wasm_loading(&self, path: &Path) -> String {
+    fn generate_node_wasm_loading(&mut self, path: &Path) -> String {
         let mut shim = String::new();
 
+        let module_name = "wbg";
+        if let Some(mem) = self.module.memories.iter().next() {
+            if let Some(id) = mem.import {
+                self.module.imports.get_mut(id).module = module_name.to_string();
+                shim.push_str(&format!(
+                    "imports.{module_name} = {{ memory: new WebAssembly.Memory({{"
+                ));
+                shim.push_str(&format!("initial:{}", mem.initial));
+                if let Some(max) = mem.maximum {
+                    shim.push_str(&format!(",maximum:{}", max));
+                }
+                if mem.shared {
+                    shim.push_str(",shared:true");
+                }
+                shim.push_str("}) };");
+            }
+        }
+
         if self.config.mode.uses_es_modules() {
             // On windows skip the leading `/` which comes out when we parse a
             // url to use `C:\...` instead of `\C:\...`
@@ -460,8 +483,6 @@ 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");
@@ -505,8 +526,6 @@ 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);
@@ -524,26 +543,17 @@ impl<'a> Context<'a> {
                     }
                 }
 
-                self.imports_post.push_str(
-                    "\
-                    let wasm;
-                    export function __wbg_set_wasm(val) {
-                        wasm = val;
-                    }
-                    ",
-                );
-
-                if matches!(self.config.mode, OutputMode::Node { module: true }) {
-                    let start = start.get_or_insert_with(String::new);
-                    start.push_str(&self.generate_node_imports());
-                    start.push_str(&self.generate_node_wasm_loading(Path::new(&format!(
-                        "./{}_bg.wasm",
-                        module_name
-                    ))));
-                }
-
                 match self.config.mode {
                     OutputMode::Bundler { .. } => {
+                        self.imports_post.push_str(
+                            "\
+                            let wasm;
+                            export function __wbg_set_wasm(val) {
+                                wasm = val;
+                            }
+                            ",
+                        );
+
                         start.get_or_insert_with(String::new).push_str(&format!(
                             "\
 import {{ __wbg_set_wasm }} from \"./{module_name}_bg.js\";
@@ -552,8 +562,26 @@ __wbg_set_wasm(wasm);"
                     }
 
                     OutputMode::Node { module: true } => {
-                        start.get_or_insert_with(String::new).push_str(&format!(
-                            "imports[\"./{module_name}_bg.js\"].__wbg_set_wasm(wasm);"
+                        self.imports_post.push_str(
+                            "\
+                            let wasm;
+                            let wasmModule;
+                            export function __wbg_set_wasm(exports, module) {
+                                wasm = exports;
+                                wasmModule = module;
+                            }
+                            ",
+                        );
+
+                        let start = start.get_or_insert_with(String::new);
+                        start.push_str(&self.generate_node_imports());
+                        start.push_str(&self.generate_node_wasm_loading(Path::new(&format!(
+                            "./{}_bg.wasm",
+                            module_name
+                        ))));
+
+                        start.push_str(&format!(
+                            "imports[\"./{module_name}_bg.js\"].__wbg_set_wasm(wasm, wasmModule);"
                         ));
                     }
 
@@ -1029,14 +1057,6 @@ __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)?;
@@ -3833,13 +3853,18 @@ __wbg_set_wasm(wasm);"
 
             Intrinsic::Module => {
                 assert_eq!(args.len(), 0);
-                if !self.config.mode.no_modules() && !self.config.mode.web() {
-                    bail!(
+
+                match self.config.mode {
+                    OutputMode::Web | OutputMode::NoModules { .. } => {
+                        "__wbg_init.__wbindgen_wasm_module"
+                    }
+                    OutputMode::Node { .. } => "wasmModule",
+                    _ => bail!(
                         "`wasm_bindgen::module` is currently only supported with \
-                         `--target no-modules` and `--target web`"
-                    );
+                         `--target no-modules`, `--target web` and `--target nodejs`"
+                    ),
                 }
-                "__wbg_init.__wbindgen_wasm_module".to_string()
+                .to_string()
             }
 
             Intrinsic::Exports => {
diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs
index fb5b4f02549..4fa2ad7e6d8 100755
--- a/crates/cli-support/src/lib.rs
+++ b/crates/cli-support/src/lib.rs
@@ -580,10 +580,6 @@ impl OutputMode {
         matches!(self, OutputMode::NoModules { .. })
     }
 
-    fn web(&self) -> bool {
-        matches!(self, OutputMode::Web)
-    }
-
     fn esm_integration(&self) -> bool {
         matches!(
             self,