From 631c5a70d6715043b2f7e9fbf2d4008a1237ba8a Mon Sep 17 00:00:00 2001
From: RunDevelopment <mitchi5000.ms@googlemail.com>
Date: Sat, 9 Nov 2024 18:42:43 +0100
Subject: [PATCH 1/7] Add support for static enum methods via TS namespaces

---
 crates/cli-support/src/js/mod.rs          | 309 ++++++++++++++++++----
 crates/cli/tests/reference/enums.js       |   8 +-
 crates/cli/tests/reference/namespace.d.ts |  23 ++
 crates/cli/tests/reference/namespace.js   | 127 +++++++++
 crates/cli/tests/reference/namespace.rs   |  42 +++
 crates/cli/tests/reference/namespace.wat  |  17 ++
 6 files changed, 475 insertions(+), 51 deletions(-)
 create mode 100644 crates/cli/tests/reference/namespace.d.ts
 create mode 100644 crates/cli/tests/reference/namespace.js
 create mode 100644 crates/cli/tests/reference/namespace.rs
 create mode 100644 crates/cli/tests/reference/namespace.wat

diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index aff91c6cfcc..3e160e0a9df 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -61,6 +61,8 @@ pub struct Context<'a> {
 
     exported_classes: Option<BTreeMap<String, ExportedClass>>,
 
+    exported_namespaces: Option<BTreeMap<String, ExportedNamespace>>,
+
     /// A map of the name of npm dependencies we've loaded so far to the path
     /// they're defined in as well as their version specification.
     pub npm_dependencies: HashMap<String, (PathBuf, String)>,
@@ -120,6 +122,35 @@ struct FieldAccessor {
     is_optional: bool,
 }
 
+struct ExportedNamespace {
+    name: String,
+    contents: String,
+    /// The TypeScript for the namespace's methods.
+    typescript: String,
+    /// Whether TypeScript for this namespace should be emitted (i.e., `skip_typescript` wasn't specified).
+    generate_typescript: bool,
+}
+
+enum ClassOrNamespace<'a> {
+    Class(&'a mut ExportedClass),
+    Namespace(&'a mut ExportedNamespace),
+}
+
+/// Different JS constructs that can be exported.
+enum ExportJs<'a> {
+    /// A class of the form `class Name {...}`.
+    Class(&'a str),
+    /// An anonymous function expression of the form `function(...) {...}`.
+    ///
+    /// Note that the function name is not included in the string.
+    Function(&'a str),
+    /// An arbitrary JS expression.
+    Expression(&'a str),
+    /// A namespace as a function expression for initiating the namespace. The
+    /// function expression is of the form `(function(Name) {...})`.
+    Namespace(&'a str),
+}
+
 const INITIAL_HEAP_VALUES: &[&str] = &["undefined", "null", "true", "false"];
 // Must be kept in sync with `src/lib.rs` of the `wasm-bindgen` crate
 const INITIAL_HEAP_OFFSET: usize = 128;
@@ -143,6 +174,7 @@ impl<'a> Context<'a> {
             typescript_refs: Default::default(),
             used_string_enums: Default::default(),
             exported_classes: Some(Default::default()),
+            exported_namespaces: Some(Default::default()),
             config,
             threads_enabled: config.threads.is_enabled(module),
             module,
@@ -163,38 +195,72 @@ impl<'a> Context<'a> {
     fn export(
         &mut self,
         export_name: &str,
-        contents: &str,
+        export: ExportJs,
         comments: Option<&str>,
     ) -> Result<(), Error> {
-        let definition_name = self.generate_identifier(export_name);
-        if contents.starts_with("class") && definition_name != export_name {
+        // The definition is intended to allow for exports to be renamed to
+        // avoid conflicts. Since namespaces intentionally have the same name as
+        // other exports, we must not rename them.
+        let definition_name = if matches!(export, ExportJs::Namespace(_)) {
+            export_name.to_owned()
+        } else {
+            self.generate_identifier(export_name)
+        };
+
+        if matches!(export, ExportJs::Class(_)) && definition_name != export_name {
             bail!("cannot shadow already defined class `{}`", export_name);
         }
 
-        let contents = contents.trim();
+        // write out comments
         if let Some(c) = comments {
             self.globals.push_str(c);
         }
+
+        fn namespace_init_arg(name: &str) -> String {
+            format!("{name} || ({name} = {{}})", name = name)
+        }
+
         let global = match self.config.mode {
-            OutputMode::Node { module: false } => {
-                if contents.starts_with("class") {
-                    format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
-                } else {
-                    format!("module.exports.{} = {};\n", export_name, contents)
+            OutputMode::Node { module: false } => match export {
+                ExportJs::Class(class) => {
+                    format!("{}\nmodule.exports.{1} = {1};\n", class, export_name)
                 }
-            }
-            OutputMode::NoModules { .. } => {
-                if contents.starts_with("class") {
-                    format!("{}\n__exports.{1} = {1};\n", contents, export_name)
-                } else {
-                    format!("__exports.{} = {};\n", export_name, contents)
+                ExportJs::Function(expr) | ExportJs::Expression(expr) => {
+                    format!("module.exports.{} = {};\n", export_name, expr)
                 }
-            }
+                ExportJs::Namespace(namespace) => {
+                    format!(
+                        "{}({});\n",
+                        namespace,
+                        namespace_init_arg(&format!("module.exports.{}", export_name))
+                    )
+                }
+            },
+            OutputMode::NoModules { .. } => match export {
+                ExportJs::Class(class) => {
+                    format!("{}\n__exports.{1} = {1};\n", class, export_name)
+                }
+                ExportJs::Function(expr) | ExportJs::Expression(expr) => {
+                    format!("__exports.{} = {};\n", export_name, expr)
+                }
+                ExportJs::Namespace(namespace) => {
+                    format!(
+                        "{}({});\n",
+                        namespace,
+                        namespace_init_arg(&format!("__exports.{}", export_name))
+                    )
+                }
+            },
             OutputMode::Bundler { .. }
             | OutputMode::Node { module: true }
             | OutputMode::Web
-            | OutputMode::Deno => {
-                if let Some(body) = contents.strip_prefix("function") {
+            | OutputMode::Deno => match export {
+                ExportJs::Class(class) => {
+                    assert_eq!(export_name, definition_name);
+                    format!("export {}\n", class)
+                }
+                ExportJs::Function(function) => {
+                    let body = function.strip_prefix("function").unwrap();
                     if export_name == definition_name {
                         format!("export function {}{}\n", export_name, body)
                     } else {
@@ -203,14 +269,25 @@ impl<'a> Context<'a> {
                             definition_name, body, definition_name, export_name,
                         )
                     }
-                } else if contents.starts_with("class") {
+                }
+                ExportJs::Expression(expr) => {
                     assert_eq!(export_name, definition_name);
-                    format!("export {}\n", contents)
-                } else {
+                    format!("export const {} = {};\n", export_name, expr)
+                }
+                ExportJs::Namespace(namespace) => {
                     assert_eq!(export_name, definition_name);
-                    format!("export const {} = {};\n", export_name, contents)
+
+                    // In some cases (e.g. string enums), a namespace may be
+                    // exported without an existing object of the same name.
+                    // In that case, we need to create the object before
+                    // initializing the namespace.
+                    let mut definition = String::new();
+                    if !self.defined_identifiers.contains_key(export_name) {
+                        definition = format!("export const {} = {{}};\n", export_name)
+                    }
+                    format!("{}{}({});\n", definition, namespace, export_name)
                 }
-            }
+            },
         };
         self.global(&global);
         Ok(())
@@ -225,6 +302,9 @@ impl<'a> Context<'a> {
         // `__wrap` and such.
         self.write_classes()?;
 
+        // Write out generated JS for namespaces.
+        self.write_namespaces()?;
+
         // Initialization is just flat out tricky and not something we
         // understand super well. To try to handle various issues that have come
         // up we always remove the `start` function if one is present. The JS
@@ -1005,6 +1085,22 @@ __wbg_set_wasm(wasm);"
         Ok((js, ts))
     }
 
+    fn require_class_or_namespace(&mut self, name: &str) -> ClassOrNamespace {
+        if self.aux.enums.contains_key(name) || self.aux.string_enums.contains_key(name) {
+            ClassOrNamespace::Namespace(self.require_namespace(name))
+        } else {
+            ClassOrNamespace::Class(self.require_class(name))
+        }
+    }
+
+    fn require_class(&mut self, name: &str) -> &'_ mut ExportedClass {
+        self.exported_classes
+            .as_mut()
+            .expect("classes already written")
+            .entry(name.to_string())
+            .or_default()
+    }
+
     fn write_classes(&mut self) -> Result<(), Error> {
         for (class, exports) in self.exported_classes.take().unwrap() {
             self.write_class(&class, &exports)?;
@@ -1152,10 +1248,10 @@ __wbg_set_wasm(wasm);"
 
         self.write_class_field_types(class, &mut ts_dst);
 
-        dst.push_str("}\n");
+        dst.push('}');
         ts_dst.push_str("}\n");
 
-        self.export(name, &dst, Some(&class.comments))?;
+        self.export(name, ExportJs::Class(&dst), Some(&class.comments))?;
 
         if class.generate_typescript {
             self.typescript.push_str(&class.comments);
@@ -1283,6 +1379,57 @@ __wbg_set_wasm(wasm);"
         }
     }
 
+    fn require_namespace(&mut self, name: &str) -> &'_ mut ExportedNamespace {
+        self.exported_namespaces
+            .as_mut()
+            .expect("namespaces already written")
+            .entry(name.to_string())
+            .or_insert_with(|| {
+                let _enum = self.aux.enums.get(name);
+                let string_enum = self.aux.string_enums.get(name);
+
+                let generate_typescript = _enum.map_or(true, |e| e.generate_typescript)
+                    && string_enum.map_or(true, |e| e.generate_typescript);
+
+                ExportedNamespace {
+                    name: name.to_string(),
+                    contents: String::new(),
+                    typescript: String::new(),
+                    generate_typescript,
+                }
+            })
+    }
+
+    fn write_namespaces(&mut self) -> Result<(), Error> {
+        for (class, namespace) in self.exported_namespaces.take().unwrap() {
+            self.write_namespace(&class, &namespace)?;
+        }
+        Ok(())
+    }
+
+    fn write_namespace(&mut self, name: &str, namespace: &ExportedNamespace) -> Result<(), Error> {
+        if namespace.contents.is_empty() {
+            // don't emit empty namespaces
+            return Ok(());
+        }
+
+        let dst = format!(
+            "(function({name}) {{\n{contents}}})",
+            contents = namespace.contents
+        );
+        self.export(name, ExportJs::Namespace(&dst), None)?;
+
+        if namespace.generate_typescript {
+            let ts_dst = format!(
+                "export namespace {name} {{\n{ts}}}\n",
+                ts = namespace.typescript
+            );
+            self.typescript.push_str(&ts_dst);
+        }
+
+        Ok(())
+    }
+
     fn expose_drop_ref(&mut self) {
         if !self.should_write_global("drop_ref") {
             return;
@@ -2461,11 +2608,11 @@ __wbg_set_wasm(wasm);"
     }
 
     fn require_class_wrap(&mut self, name: &str) {
-        require_class(&mut self.exported_classes, name).wrap_needed = true;
+        self.require_class(name).wrap_needed = true;
     }
 
     fn require_class_unwrap(&mut self, name: &str) {
-        require_class(&mut self.exported_classes, name).unwrap_needed = true;
+        self.require_class(name).unwrap_needed = true;
     }
 
     fn add_module_import(&mut self, module: String, name: &str, actual: &str) {
@@ -2831,11 +2978,15 @@ __wbg_set_wasm(wasm);"
                             self.typescript.push_str(";\n");
                         }
 
-                        self.export(name, &format!("function{}", code), Some(&js_docs))?;
+                        self.export(
+                            name,
+                            ExportJs::Function(&format!("function{}", code)),
+                            Some(&js_docs),
+                        )?;
                         self.globals.push('\n');
                     }
                     AuxExportKind::Constructor(class) => {
-                        let exported = require_class(&mut self.exported_classes, class);
+                        let exported = self.require_class(class);
 
                         if exported.has_constructor {
                             bail!("found duplicate constructor for class `{}`", class);
@@ -2850,7 +3001,7 @@ __wbg_set_wasm(wasm);"
                         receiver,
                         kind,
                     } => {
-                        let exported = require_class(&mut self.exported_classes, class);
+                        let mut exported = self.require_class_or_namespace(class);
 
                         let mut prefix = String::new();
                         if receiver.is_static() {
@@ -2859,6 +3010,17 @@ __wbg_set_wasm(wasm);"
                         let ts = match kind {
                             AuxExportedMethodKind::Method => ts_sig,
                             AuxExportedMethodKind::Getter => {
+                                let class = match exported {
+                                    ClassOrNamespace::Class(ref mut class) => class,
+                                    ClassOrNamespace::Namespace(_) => {
+                                        bail!(
+                                            "the getter `{}` is not supported on `{}`. Enums only support static methods on them.",
+                                            name,
+                                            class
+                                        );
+                                    }
+                                };
+
                                 prefix += "get ";
                                 // For getters and setters, we generate a separate TypeScript definition.
                                 if export.generate_typescript {
@@ -2873,14 +3035,25 @@ __wbg_set_wasm(wasm);"
                                         is_optional: false,
                                     };
 
-                                    exported.push_accessor_ts(location, accessor, false);
+                                    class.push_accessor_ts(location, accessor, false);
                                 }
                                 // Add the getter to the list of readable fields (used to generate `toJSON`)
-                                exported.readable_properties.push(name.clone());
+                                class.readable_properties.push(name.clone());
                                 // Ignore the raw signature.
                                 None
                             }
                             AuxExportedMethodKind::Setter => {
+                                let class = match exported {
+                                    ClassOrNamespace::Class(ref mut class) => class,
+                                    ClassOrNamespace::Namespace(_) => {
+                                        bail!(
+                                            "the setter `{}` is not supported on `{}`. Enums only support static methods on them.",
+                                            name,
+                                            class
+                                        );
+                                    }
+                                };
+
                                 prefix += "set ";
                                 if export.generate_typescript {
                                     let location = FieldLocation {
@@ -2893,13 +3066,27 @@ __wbg_set_wasm(wasm);"
                                         is_optional: might_be_optional_field,
                                     };
 
-                                    exported.push_accessor_ts(location, accessor, true);
+                                    class.push_accessor_ts(location, accessor, true);
                                 }
                                 None
                             }
                         };
 
-                        exported.push(name, &prefix, &js_docs, &code, &ts_docs, ts);
+                        match exported {
+                            ClassOrNamespace::Class(class) => {
+                                class.push(name, &prefix, &js_docs, &code, &ts_docs, ts);
+                            }
+                            ClassOrNamespace::Namespace(ns) => {
+                                if !receiver.is_static() {
+                                    bail!(
+                                        "non-static method `{}` on namespace `{}`",
+                                        name,
+                                        ns.name
+                                    );
+                                }
+                                ns.push(name, &js_docs, &code, &ts_docs, ts);
+                            }
+                        }
                     }
                 }
             }
@@ -3957,7 +4144,7 @@ __wbg_set_wasm(wasm);"
 
         self.export(
             &enum_.name,
-            &format!("Object.freeze({{\n{}}})", variants),
+            ExportJs::Expression(&format!("{{\n{}}}", variants)),
             Some(&docs),
         )?;
 
@@ -4008,7 +4195,7 @@ __wbg_set_wasm(wasm);"
     }
 
     fn generate_struct(&mut self, struct_: &AuxStruct) -> Result<(), Error> {
-        let class = require_class(&mut self.exported_classes, &struct_.name);
+        let class = self.require_class(&struct_.name);
         class.comments = format_doc_comments(&struct_.comments, None);
         class.is_inspectable = struct_.is_inspectable;
         class.generate_typescript = struct_.generate_typescript;
@@ -4464,17 +4651,6 @@ fn format_doc_comments(comments: &str, js_doc_comments: Option<String>) -> Strin
     }
 }
 
-fn require_class<'a>(
-    exported_classes: &'a mut Option<BTreeMap<String, ExportedClass>>,
-    name: &str,
-) -> &'a mut ExportedClass {
-    exported_classes
-        .as_mut()
-        .expect("classes already written")
-        .entry(name.to_string())
-        .or_default()
-}
-
 /// Returns whether a character has the Unicode `ID_Start` properly.
 ///
 /// This is only ever-so-slightly different from `XID_Start` in a few edge
@@ -4588,6 +4764,45 @@ impl ExportedClass {
     }
 }
 
+impl ExportedNamespace {
+    fn push(
+        &mut self,
+        function_name: &str,
+        js_docs: &str,
+        js: &str,
+        ts_docs: &str,
+        ts: Option<&str>,
+    ) {
+        self.contents.push_str(js_docs);
+        self.contents.push_str("function ");
+        self.contents.push_str(function_name);
+        self.contents.push_str(js);
+        self.contents.push('\n');
+        self.contents.push_str(&self.name);
+        self.contents.push('.');
+        self.contents.push_str(function_name);
+        self.contents.push_str(" = ");
+        self.contents.push_str(function_name);
+        self.contents.push_str(";\n");
+
+        if let Some(ts) = ts {
+            if !ts_docs.is_empty() {
+                for line in ts_docs.lines() {
+                    self.typescript.push_str("  ");
+                    self.typescript.push_str(line);
+                    self.typescript.push('\n');
+                }
+            }
+            self.typescript.push_str("  export function ");
+            self.typescript.push_str(function_name);
+            self.typescript.push_str(ts);
+            self.typescript.push_str(";\n");
+        }
+    }
+}
+
+impl ClassOrNamespace<'_> {}
+
 struct MemView {
     name: Cow<'static, str>,
     num: usize,
diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js
index a425ef2874f..216796dc4be 100644
--- a/crates/cli/tests/reference/enums.js
+++ b/crates/cli/tests/reference/enums.js
@@ -66,7 +66,7 @@ export function option_string_enum_echo(color) {
  * A color.
  * @enum {0 | 1 | 2}
  */
-export const Color = Object.freeze({
+export const Color = {
     /**
      * Green as a leaf.
      */
@@ -79,16 +79,16 @@ export const Color = Object.freeze({
      * Red as a rose.
      */
     Red: 2, "2": "Red",
-});
+};
 /**
  * @enum {0 | 1 | 42 | 43}
  */
-export const ImplicitDiscriminant = Object.freeze({
+export const ImplicitDiscriminant = {
     A: 0, "0": "A",
     B: 1, "1": "B",
     C: 42, "42": "C",
     D: 43, "43": "D",
-});
+};
 
 const __wbindgen_enum_ColorName = ["green", "yellow", "red"];
 
diff --git a/crates/cli/tests/reference/namespace.d.ts b/crates/cli/tests/reference/namespace.d.ts
new file mode 100644
index 00000000000..05e0fb23e34
--- /dev/null
+++ b/crates/cli/tests/reference/namespace.d.ts
@@ -0,0 +1,23 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * C-style enum
+ */
+export enum ImageFormat {
+  PNG = 0,
+  JPEG = 1,
+  GIF = 2,
+}
+/**
+ * String enum
+ */
+type Status = "success" | "failure";
+export namespace ImageFormat {
+  export function from_str(s: string): ImageFormat;
+}
+export namespace Status {
+  /**
+   * I have documentation.
+   */
+  export function from_bool(success: boolean): Status;
+}
diff --git a/crates/cli/tests/reference/namespace.js b/crates/cli/tests/reference/namespace.js
new file mode 100644
index 00000000000..04a63628d18
--- /dev/null
+++ b/crates/cli/tests/reference/namespace.js
@@ -0,0 +1,127 @@
+let wasm;
+export function __wbg_set_wasm(val) {
+    wasm = val;
+}
+
+
+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));
+}
+
+let WASM_VECTOR_LEN = 0;
+
+const lTextEncoder = typeof TextEncoder === 'undefined' ? (0, module.require)('util').TextEncoder : TextEncoder;
+
+let cachedTextEncoder = new lTextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+    ? function (arg, view) {
+    return cachedTextEncoder.encodeInto(arg, view);
+}
+    : function (arg, view) {
+    const buf = cachedTextEncoder.encode(arg);
+    view.set(buf);
+    return {
+        read: arg.length,
+        written: buf.length
+    };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+    if (realloc === undefined) {
+        const buf = cachedTextEncoder.encode(arg);
+        const ptr = malloc(buf.length, 1) >>> 0;
+        getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
+        WASM_VECTOR_LEN = buf.length;
+        return ptr;
+    }
+
+    let len = arg.length;
+    let ptr = malloc(len, 1) >>> 0;
+
+    const mem = getUint8ArrayMemory0();
+
+    let offset = 0;
+
+    for (; offset < len; offset++) {
+        const code = arg.charCodeAt(offset);
+        if (code > 0x7F) break;
+        mem[ptr + offset] = code;
+    }
+
+    if (offset !== len) {
+        if (offset !== 0) {
+            arg = arg.slice(offset);
+        }
+        ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+        const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
+        const ret = encodeString(arg, view);
+
+        offset += ret.written;
+        ptr = realloc(ptr, len, offset, 1) >>> 0;
+    }
+
+    WASM_VECTOR_LEN = offset;
+    return ptr;
+}
+/**
+ * C-style enum
+ * @enum {0 | 1 | 2}
+ */
+export const ImageFormat = {
+    PNG: 0, "0": "PNG",
+    JPEG: 1, "1": "JPEG",
+    GIF: 2, "2": "GIF",
+};
+
+const __wbindgen_enum_Status = ["success", "failure"];
+
+(function(ImageFormat) {
+    /**
+     * @param {string} s
+     * @returns {ImageFormat}
+     */
+    function from_str(s) {
+        const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ret = wasm.imageformat_from_str(ptr0, len0);
+        return ret;
+    }
+    ImageFormat.from_str = from_str;
+})(ImageFormat);
+
+export const Status = {};
+(function(Status) {
+    /**
+     * I have documentation.
+     * @param {boolean} success
+     * @returns {Status}
+     */
+    function from_bool(success) {
+        const ret = wasm.status_from_bool(success);
+        return __wbindgen_enum_Status[ret];
+    }
+    Status.from_bool = from_bool;
+})(Status);
+
+export function __wbindgen_throw(arg0, arg1) {
+    throw new Error(getStringFromWasm0(arg0, arg1));
+};
+
diff --git a/crates/cli/tests/reference/namespace.rs b/crates/cli/tests/reference/namespace.rs
new file mode 100644
index 00000000000..6970afdb1dd
--- /dev/null
+++ b/crates/cli/tests/reference/namespace.rs
@@ -0,0 +1,42 @@
+use wasm_bindgen::prelude::*;
+
+/// C-style enum
+#[wasm_bindgen]
+#[derive(Copy, Clone)]
+pub enum ImageFormat {
+    PNG,
+    JPEG,
+    GIF,
+}
+
+#[wasm_bindgen]
+impl ImageFormat {
+    pub fn from_str(s: &str) -> ImageFormat {
+        match s {
+            "PNG" => ImageFormat::PNG,
+            "JPEG" => ImageFormat::JPEG,
+            "GIF" => ImageFormat::GIF,
+            _ => panic!("unknown image format: {}", s),
+        }
+    }
+}
+
+/// String enum
+#[wasm_bindgen]
+#[derive(Copy, Clone)]
+pub enum Status {
+    Success = "success",
+    Failure = "failure",
+}
+
+#[wasm_bindgen]
+impl Status {
+    /// I have documentation.
+    pub fn from_bool(success: bool) -> Status {
+        if success {
+            Status::Success
+        } else {
+            Status::Failure
+        }
+    }
+}
diff --git a/crates/cli/tests/reference/namespace.wat b/crates/cli/tests/reference/namespace.wat
new file mode 100644
index 00000000000..3e5ad71a48c
--- /dev/null
+++ b/crates/cli/tests/reference/namespace.wat
@@ -0,0 +1,17 @@
+(module $reference_test.wasm
+  (type (;0;) (func (param i32) (result i32)))
+  (type (;1;) (func (param i32 i32) (result i32)))
+  (type (;2;) (func (param i32 i32 i32 i32) (result i32)))
+  (func $__wbindgen_realloc (;0;) (type 2) (param i32 i32 i32 i32) (result i32))
+  (func $__wbindgen_malloc (;1;) (type 1) (param i32 i32) (result i32))
+  (func $imageformat_from_str (;2;) (type 1) (param i32 i32) (result i32))
+  (func $status_from_bool (;3;) (type 0) (param i32) (result i32))
+  (memory (;0;) 17)
+  (export "memory" (memory 0))
+  (export "imageformat_from_str" (func $imageformat_from_str))
+  (export "status_from_bool" (func $status_from_bool))
+  (export "__wbindgen_malloc" (func $__wbindgen_malloc))
+  (export "__wbindgen_realloc" (func $__wbindgen_realloc))
+  (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")
+)
+

From afd00bbd982204a34bc52499c79278f91e03331f Mon Sep 17 00:00:00 2001
From: RunDevelopment <mitchi5000.ms@googlemail.com>
Date: Sat, 9 Nov 2024 18:57:25 +0100
Subject: [PATCH 2/7] Terser code gen

---
 crates/cli-support/src/js/mod.rs        | 10 +++++-----
 crates/cli/tests/reference/namespace.js | 10 ++++------
 2 files changed, 9 insertions(+), 11 deletions(-)

diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index 3e160e0a9df..a71cc9cb1a4 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -281,6 +281,9 @@ impl<'a> Context<'a> {
                     // exported without an existing object of the same name.
                     // In that case, we need to create the object before
                     // initializing the namespace.
+                    //
+                    // It's only correct to do it like this, because namespaces
+                    // are always the last things to be exported.
                     let mut definition = String::new();
                     if !self.defined_identifiers.contains_key(export_name) {
                         definition = format!("export const {} = {{}};\n", export_name)
@@ -4774,15 +4777,12 @@ impl ExportedNamespace {
         ts: Option<&str>,
     ) {
         self.contents.push_str(js_docs);
-        self.contents.push_str("function ");
-        self.contents.push_str(function_name);
-        self.contents.push_str(js);
-        self.contents.push('\n');
         self.contents.push_str(&self.name);
         self.contents.push('.');
         self.contents.push_str(function_name);
-        self.contents.push_str(" = ");
+        self.contents.push_str(" = function ");
         self.contents.push_str(function_name);
+        self.contents.push_str(js);
         self.contents.push_str(";\n");
 
         if let Some(ts) = ts {
diff --git a/crates/cli/tests/reference/namespace.js b/crates/cli/tests/reference/namespace.js
index 04a63628d18..9ee8cb20f29 100644
--- a/crates/cli/tests/reference/namespace.js
+++ b/crates/cli/tests/reference/namespace.js
@@ -98,13 +98,12 @@ const __wbindgen_enum_Status = ["success", "failure"];
      * @param {string} s
      * @returns {ImageFormat}
      */
-    function from_str(s) {
+    ImageFormat.from_str = function from_str(s) {
         const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
         const len0 = WASM_VECTOR_LEN;
         const ret = wasm.imageformat_from_str(ptr0, len0);
         return ret;
-    }
-    ImageFormat.from_str = from_str;
+    };
 })(ImageFormat);
 
 export const Status = {};
@@ -114,11 +113,10 @@ export const Status = {};
      * @param {boolean} success
      * @returns {Status}
      */
-    function from_bool(success) {
+    Status.from_bool = function from_bool(success) {
         const ret = wasm.status_from_bool(success);
         return __wbindgen_enum_Status[ret];
-    }
-    Status.from_bool = from_bool;
+    };
 })(Status);
 
 export function __wbindgen_throw(arg0, arg1) {

From 04cef11b70fada9e727a0e322cf477e58324c1d5 Mon Sep 17 00:00:00 2001
From: RunDevelopment <mitchi5000.ms@googlemail.com>
Date: Wed, 13 Nov 2024 17:13:50 +0100
Subject: [PATCH 3/7] Updated enum.js

---
 crates/cli/tests/reference/enums.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/crates/cli/tests/reference/enums.js b/crates/cli/tests/reference/enums.js
index 713fa4f7f9d..39b01f0df91 100644
--- a/crates/cli/tests/reference/enums.js
+++ b/crates/cli/tests/reference/enums.js
@@ -102,11 +102,11 @@ export const ImplicitDiscriminant = {
  * A C-style enum with negative discriminants.
  * @enum {-1 | 0 | 1}
  */
-export const Ordering = Object.freeze({
+export const Ordering = {
     Less: -1, "-1": "Less",
     Equal: 0, "0": "Equal",
     Greater: 1, "1": "Greater",
-});
+};
 
 const __wbindgen_enum_ColorName = ["green", "yellow", "red"];
 

From b6ff56fbf0f883ff284a2242dfc1338863cfd68a Mon Sep 17 00:00:00 2001
From: RunDevelopment <mitchi5000.ms@googlemail.com>
Date: Wed, 13 Nov 2024 17:35:33 +0100
Subject: [PATCH 4/7] Disallow enum constructors and ignore enum methods

---
 crates/cli-support/src/js/mod.rs         | 35 +++++++++++++++++-------
 crates/cli/tests/reference/namespace.rs  | 10 +++++++
 crates/cli/tests/reference/namespace.wat |  4 +++
 3 files changed, 39 insertions(+), 10 deletions(-)

diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index a71cc9cb1a4..5077ac13ec0 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -2989,7 +2989,16 @@ __wbg_set_wasm(wasm);"
                         self.globals.push('\n');
                     }
                     AuxExportKind::Constructor(class) => {
-                        let exported = self.require_class(class);
+                        let exported = self.require_class_or_namespace(class);
+                        let exported = match exported {
+                            ClassOrNamespace::Class(class) => class,
+                            ClassOrNamespace::Namespace(_) => {
+                                bail!(
+                                    "constructor is not supported on `{}`. Enums do not support exported constructors.",
+                                    class
+                                );
+                            }
+                        };
 
                         if exported.has_constructor {
                             bail!("found duplicate constructor for class `{}`", class);
@@ -3004,10 +3013,11 @@ __wbg_set_wasm(wasm);"
                         receiver,
                         kind,
                     } => {
+                        let is_static = receiver.is_static();
                         let mut exported = self.require_class_or_namespace(class);
 
                         let mut prefix = String::new();
-                        if receiver.is_static() {
+                        if is_static {
                             prefix += "static ";
                         }
                         let ts = match kind {
@@ -3029,7 +3039,7 @@ __wbg_set_wasm(wasm);"
                                 if export.generate_typescript {
                                     let location = FieldLocation {
                                         name: name.clone(),
-                                        is_static: receiver.is_static(),
+                                        is_static,
                                     };
                                     let accessor = FieldAccessor {
                                         // This is only set to `None` when generating a constructor.
@@ -3061,7 +3071,7 @@ __wbg_set_wasm(wasm);"
                                 if export.generate_typescript {
                                     let location = FieldLocation {
                                         name: name.clone(),
-                                        is_static: receiver.is_static(),
+                                        is_static,
                                     };
                                     let accessor = FieldAccessor {
                                         ty: ts_arg_tys[0].clone(),
@@ -3080,14 +3090,19 @@ __wbg_set_wasm(wasm);"
                                 class.push(name, &prefix, &js_docs, &code, &ts_docs, ts);
                             }
                             ClassOrNamespace::Namespace(ns) => {
-                                if !receiver.is_static() {
-                                    bail!(
-                                        "non-static method `{}` on namespace `{}`",
-                                        name,
-                                        ns.name
+                                if !is_static {
+                                    let msg = format!(
+                                        "The enum `{}` cannot support the instance method `{}`. \
+                                        No binding will be generated for this method. \
+                                        Consider moving the method in an `impl` block with the `#[wasm_bindgen]` attribute to avoid exporting it, \
+                                        or making it a static method by replacing `self` with `value: Self`.",
+                                        ns.name,
+                                        name
                                     );
+                                    println!("WARNING: {}", msg);
+                                } else {
+                                    ns.push(name, &js_docs, &code, &ts_docs, ts);
                                 }
-                                ns.push(name, &js_docs, &code, &ts_docs, ts);
                             }
                         }
                     }
diff --git a/crates/cli/tests/reference/namespace.rs b/crates/cli/tests/reference/namespace.rs
index 6970afdb1dd..a8ff9efd1ce 100644
--- a/crates/cli/tests/reference/namespace.rs
+++ b/crates/cli/tests/reference/namespace.rs
@@ -19,6 +19,11 @@ impl ImageFormat {
             _ => panic!("unknown image format: {}", s),
         }
     }
+
+    /// I will be ignored by the generated JS bindings.
+    pub fn is_lossless(self) -> bool {
+        matches!(self, ImageFormat::PNG | ImageFormat::GIF)
+    }
 }
 
 /// String enum
@@ -39,4 +44,9 @@ impl Status {
             Status::Failure
         }
     }
+
+    /// I will be ignored by the generated JS bindings.
+    pub fn to_bool(self) -> bool {
+        matches!(self, Status::Success)
+    }
 }
diff --git a/crates/cli/tests/reference/namespace.wat b/crates/cli/tests/reference/namespace.wat
index 3e5ad71a48c..e68b54b461e 100644
--- a/crates/cli/tests/reference/namespace.wat
+++ b/crates/cli/tests/reference/namespace.wat
@@ -6,10 +6,14 @@
   (func $__wbindgen_malloc (;1;) (type 1) (param i32 i32) (result i32))
   (func $imageformat_from_str (;2;) (type 1) (param i32 i32) (result i32))
   (func $status_from_bool (;3;) (type 0) (param i32) (result i32))
+  (func $imageformat_is_lossless (;4;) (type 0) (param i32) (result i32))
+  (func $status_to_bool (;5;) (type 0) (param i32) (result i32))
   (memory (;0;) 17)
   (export "memory" (memory 0))
   (export "imageformat_from_str" (func $imageformat_from_str))
+  (export "imageformat_is_lossless" (func $imageformat_is_lossless))
   (export "status_from_bool" (func $status_from_bool))
+  (export "status_to_bool" (func $status_to_bool))
   (export "__wbindgen_malloc" (func $__wbindgen_malloc))
   (export "__wbindgen_realloc" (func $__wbindgen_realloc))
   (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")

From 6b4a16fc86f145fd7e49c5a1e11c5b14dab7a52a Mon Sep 17 00:00:00 2001
From: RunDevelopment <mitchi5000.ms@googlemail.com>
Date: Thu, 14 Nov 2024 16:06:11 +0100
Subject: [PATCH 5/7] Test all export modes

---
 .../{namespace.d.ts => namespace-0.d.ts}      |   0
 .../{namespace.js => namespace-0.js}          |   0
 .../{namespace.wat => namespace-0.wat}        |   0
 crates/cli/tests/reference/namespace-1.d.ts   |  23 ++
 crates/cli/tests/reference/namespace-1.js     | 141 ++++++++++
 crates/cli/tests/reference/namespace-1.wat    |  26 ++
 crates/cli/tests/reference/namespace-2.d.ts   |  50 ++++
 crates/cli/tests/reference/namespace-2.js     | 240 ++++++++++++++++++
 crates/cli/tests/reference/namespace-2.wat    |  26 ++
 crates/cli/tests/reference/namespace.rs       |   5 +
 10 files changed, 511 insertions(+)
 rename crates/cli/tests/reference/{namespace.d.ts => namespace-0.d.ts} (100%)
 rename crates/cli/tests/reference/{namespace.js => namespace-0.js} (100%)
 rename crates/cli/tests/reference/{namespace.wat => namespace-0.wat} (100%)
 create mode 100644 crates/cli/tests/reference/namespace-1.d.ts
 create mode 100644 crates/cli/tests/reference/namespace-1.js
 create mode 100644 crates/cli/tests/reference/namespace-1.wat
 create mode 100644 crates/cli/tests/reference/namespace-2.d.ts
 create mode 100644 crates/cli/tests/reference/namespace-2.js
 create mode 100644 crates/cli/tests/reference/namespace-2.wat

diff --git a/crates/cli/tests/reference/namespace.d.ts b/crates/cli/tests/reference/namespace-0.d.ts
similarity index 100%
rename from crates/cli/tests/reference/namespace.d.ts
rename to crates/cli/tests/reference/namespace-0.d.ts
diff --git a/crates/cli/tests/reference/namespace.js b/crates/cli/tests/reference/namespace-0.js
similarity index 100%
rename from crates/cli/tests/reference/namespace.js
rename to crates/cli/tests/reference/namespace-0.js
diff --git a/crates/cli/tests/reference/namespace.wat b/crates/cli/tests/reference/namespace-0.wat
similarity index 100%
rename from crates/cli/tests/reference/namespace.wat
rename to crates/cli/tests/reference/namespace-0.wat
diff --git a/crates/cli/tests/reference/namespace-1.d.ts b/crates/cli/tests/reference/namespace-1.d.ts
new file mode 100644
index 00000000000..05e0fb23e34
--- /dev/null
+++ b/crates/cli/tests/reference/namespace-1.d.ts
@@ -0,0 +1,23 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * C-style enum
+ */
+export enum ImageFormat {
+  PNG = 0,
+  JPEG = 1,
+  GIF = 2,
+}
+/**
+ * String enum
+ */
+type Status = "success" | "failure";
+export namespace ImageFormat {
+  export function from_str(s: string): ImageFormat;
+}
+export namespace Status {
+  /**
+   * I have documentation.
+   */
+  export function from_bool(success: boolean): Status;
+}
diff --git a/crates/cli/tests/reference/namespace-1.js b/crates/cli/tests/reference/namespace-1.js
new file mode 100644
index 00000000000..628c4180589
--- /dev/null
+++ b/crates/cli/tests/reference/namespace-1.js
@@ -0,0 +1,141 @@
+
+let imports = {};
+imports['__wbindgen_placeholder__'] = module.exports;
+let wasm;
+const { TextDecoder, TextEncoder } = require(`util`);
+
+let cachedTextDecoder = new TextDecoder('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));
+}
+
+let WASM_VECTOR_LEN = 0;
+
+let cachedTextEncoder = new TextEncoder('utf-8');
+
+const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+    ? function (arg, view) {
+    return cachedTextEncoder.encodeInto(arg, view);
+}
+    : function (arg, view) {
+    const buf = cachedTextEncoder.encode(arg);
+    view.set(buf);
+    return {
+        read: arg.length,
+        written: buf.length
+    };
+});
+
+function passStringToWasm0(arg, malloc, realloc) {
+
+    if (realloc === undefined) {
+        const buf = cachedTextEncoder.encode(arg);
+        const ptr = malloc(buf.length, 1) >>> 0;
+        getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
+        WASM_VECTOR_LEN = buf.length;
+        return ptr;
+    }
+
+    let len = arg.length;
+    let ptr = malloc(len, 1) >>> 0;
+
+    const mem = getUint8ArrayMemory0();
+
+    let offset = 0;
+
+    for (; offset < len; offset++) {
+        const code = arg.charCodeAt(offset);
+        if (code > 0x7F) break;
+        mem[ptr + offset] = code;
+    }
+
+    if (offset !== len) {
+        if (offset !== 0) {
+            arg = arg.slice(offset);
+        }
+        ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+        const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
+        const ret = encodeString(arg, view);
+
+        offset += ret.written;
+        ptr = realloc(ptr, len, offset, 1) >>> 0;
+    }
+
+    WASM_VECTOR_LEN = offset;
+    return ptr;
+}
+/**
+ * C-style enum
+ * @enum {0 | 1 | 2}
+ */
+module.exports.ImageFormat = {
+    PNG: 0, "0": "PNG",
+    JPEG: 1, "1": "JPEG",
+    GIF: 2, "2": "GIF",
+};
+
+const __wbindgen_enum_Status = ["success", "failure"];
+
+(function(ImageFormat) {
+    /**
+     * @param {string} s
+     * @returns {ImageFormat}
+     */
+    ImageFormat.from_str = function from_str(s) {
+        const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ret = wasm.imageformat_from_str(ptr0, len0);
+        return ret;
+    };
+})(module.exports.ImageFormat || (module.exports.ImageFormat = {}));
+
+(function(Status) {
+    /**
+     * I have documentation.
+     * @param {boolean} success
+     * @returns {Status}
+     */
+    Status.from_bool = function from_bool(success) {
+        const ret = wasm.status_from_bool(success);
+        return __wbindgen_enum_Status[ret];
+    };
+})(module.exports.Status || (module.exports.Status = {}));
+
+module.exports.__wbindgen_init_externref_table = function() {
+    const table = wasm.__wbindgen_export_0;
+    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);
+    ;
+};
+
+module.exports.__wbindgen_throw = function(arg0, arg1) {
+    throw new Error(getStringFromWasm0(arg0, arg1));
+};
+
+const path = require('path').join(__dirname, 'reference_test_bg.wasm');
+const bytes = require('fs').readFileSync(path);
+
+const wasmModule = new WebAssembly.Module(bytes);
+const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
+wasm = wasmInstance.exports;
+module.exports.__wasm = wasm;
+
+wasm.__wbindgen_start();
+
diff --git a/crates/cli/tests/reference/namespace-1.wat b/crates/cli/tests/reference/namespace-1.wat
new file mode 100644
index 00000000000..edd83863750
--- /dev/null
+++ b/crates/cli/tests/reference/namespace-1.wat
@@ -0,0 +1,26 @@
+(module $reference_test.wasm
+  (type (;0;) (func))
+  (type (;1;) (func (param i32) (result i32)))
+  (type (;2;) (func (param i32 i32) (result i32)))
+  (type (;3;) (func (param i32 i32 i32 i32) (result i32)))
+  (import "__wbindgen_placeholder__" "__wbindgen_init_externref_table" (func (;0;) (type 0)))
+  (func $__wbindgen_realloc (;1;) (type 3) (param i32 i32 i32 i32) (result i32))
+  (func $__wbindgen_malloc (;2;) (type 2) (param i32 i32) (result i32))
+  (func $imageformat_from_str (;3;) (type 2) (param i32 i32) (result i32))
+  (func $status_from_bool (;4;) (type 1) (param i32) (result i32))
+  (func $imageformat_is_lossless (;5;) (type 1) (param i32) (result i32))
+  (func $status_to_bool (;6;) (type 1) (param i32) (result i32))
+  (table (;0;) 128 externref)
+  (memory (;0;) 17)
+  (export "memory" (memory 0))
+  (export "imageformat_from_str" (func $imageformat_from_str))
+  (export "imageformat_is_lossless" (func $imageformat_is_lossless))
+  (export "status_from_bool" (func $status_from_bool))
+  (export "status_to_bool" (func $status_to_bool))
+  (export "__wbindgen_export_0" (table 0))
+  (export "__wbindgen_malloc" (func $__wbindgen_malloc))
+  (export "__wbindgen_realloc" (func $__wbindgen_realloc))
+  (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/namespace-2.d.ts b/crates/cli/tests/reference/namespace-2.d.ts
new file mode 100644
index 00000000000..7cf2dd4c810
--- /dev/null
+++ b/crates/cli/tests/reference/namespace-2.d.ts
@@ -0,0 +1,50 @@
+declare namespace wasm_bindgen {
+	/* tslint:disable */
+	/* eslint-disable */
+	/**
+	 * C-style enum
+	 */
+	export enum ImageFormat {
+	  PNG = 0,
+	  JPEG = 1,
+	  GIF = 2,
+	}
+	/**
+	 * String enum
+	 */
+	type Status = "success" | "failure";
+	export namespace ImageFormat {
+	  export function from_str(s: string): ImageFormat;
+	}
+	export namespace Status {
+	  /**
+	   * I have documentation.
+	   */
+	  export function from_bool(success: boolean): Status;
+	}
+	
+}
+
+declare type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
+
+declare interface InitOutput {
+  readonly memory: WebAssembly.Memory;
+  readonly imageformat_from_str: (a: number, b: number) => number;
+  readonly imageformat_is_lossless: (a: number) => number;
+  readonly status_from_bool: (a: number) => number;
+  readonly status_to_bool: (a: number) => number;
+  readonly __wbindgen_export_0: WebAssembly.Table;
+  readonly __wbindgen_malloc: (a: number, b: number) => number;
+  readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
+  readonly __wbindgen_start: () => void;
+}
+
+/**
+* If `module_or_path` is {RequestInfo} or {URL}, makes a request and
+* for everything else, calls `WebAssembly.instantiate` directly.
+*
+* @param {{ module_or_path: InitInput | Promise<InitInput> }} module_or_path - Passing `InitInput` directly is deprecated.
+*
+* @returns {Promise<InitOutput>}
+*/
+declare function wasm_bindgen (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
diff --git a/crates/cli/tests/reference/namespace-2.js b/crates/cli/tests/reference/namespace-2.js
new file mode 100644
index 00000000000..b1ff3b25611
--- /dev/null
+++ b/crates/cli/tests/reference/namespace-2.js
@@ -0,0 +1,240 @@
+let wasm_bindgen;
+(function() {
+    const __exports = {};
+    let script_src;
+    if (typeof document !== 'undefined' && document.currentScript !== null) {
+        script_src = new URL(document.currentScript.src, location.href).toString();
+    }
+    let wasm = undefined;
+
+    const cachedTextDecoder = (typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { ignoreBOM: true, fatal: true }) : { decode: () => { throw Error('TextDecoder not available') } } );
+
+    if (typeof TextDecoder !== 'undefined') { 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));
+    }
+
+    let WASM_VECTOR_LEN = 0;
+
+    const cachedTextEncoder = (typeof TextEncoder !== 'undefined' ? new TextEncoder('utf-8') : { encode: () => { throw Error('TextEncoder not available') } } );
+
+    const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
+        ? function (arg, view) {
+        return cachedTextEncoder.encodeInto(arg, view);
+    }
+        : function (arg, view) {
+        const buf = cachedTextEncoder.encode(arg);
+        view.set(buf);
+        return {
+            read: arg.length,
+            written: buf.length
+        };
+    });
+
+    function passStringToWasm0(arg, malloc, realloc) {
+
+        if (realloc === undefined) {
+            const buf = cachedTextEncoder.encode(arg);
+            const ptr = malloc(buf.length, 1) >>> 0;
+            getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
+            WASM_VECTOR_LEN = buf.length;
+            return ptr;
+        }
+
+        let len = arg.length;
+        let ptr = malloc(len, 1) >>> 0;
+
+        const mem = getUint8ArrayMemory0();
+
+        let offset = 0;
+
+        for (; offset < len; offset++) {
+            const code = arg.charCodeAt(offset);
+            if (code > 0x7F) break;
+            mem[ptr + offset] = code;
+        }
+
+        if (offset !== len) {
+            if (offset !== 0) {
+                arg = arg.slice(offset);
+            }
+            ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
+            const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
+            const ret = encodeString(arg, view);
+
+            offset += ret.written;
+            ptr = realloc(ptr, len, offset, 1) >>> 0;
+        }
+
+        WASM_VECTOR_LEN = offset;
+        return ptr;
+    }
+    /**
+     * C-style enum
+     * @enum {0 | 1 | 2}
+     */
+    __exports.ImageFormat = {
+        PNG: 0, "0": "PNG",
+        JPEG: 1, "1": "JPEG",
+        GIF: 2, "2": "GIF",
+    };
+
+    const __wbindgen_enum_Status = ["success", "failure"];
+
+    (function(ImageFormat) {
+        /**
+         * @param {string} s
+         * @returns {ImageFormat}
+         */
+        ImageFormat.from_str = function from_str(s) {
+            const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+            const len0 = WASM_VECTOR_LEN;
+            const ret = wasm.imageformat_from_str(ptr0, len0);
+            return ret;
+        };
+    })(__exports.ImageFormat || (__exports.ImageFormat = {}));
+
+    (function(Status) {
+        /**
+         * I have documentation.
+         * @param {boolean} success
+         * @returns {Status}
+         */
+        Status.from_bool = function from_bool(success) {
+            const ret = wasm.status_from_bool(success);
+            return __wbindgen_enum_Status[ret];
+        };
+    })(__exports.Status || (__exports.Status = {}));
+
+    async function __wbg_load(module, imports) {
+        if (typeof Response === 'function' && module instanceof Response) {
+            if (typeof WebAssembly.instantiateStreaming === 'function') {
+                try {
+                    return await WebAssembly.instantiateStreaming(module, imports);
+
+                } catch (e) {
+                    if (module.headers.get('Content-Type') != 'application/wasm') {
+                        console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
+
+                    } else {
+                        throw e;
+                    }
+                }
+            }
+
+            const bytes = await module.arrayBuffer();
+            return await WebAssembly.instantiate(bytes, imports);
+
+        } else {
+            const instance = await WebAssembly.instantiate(module, imports);
+
+            if (instance instanceof WebAssembly.Instance) {
+                return { instance, module };
+
+            } else {
+                return instance;
+            }
+        }
+    }
+
+    function __wbg_get_imports() {
+        const imports = {};
+        imports.wbg = {};
+        imports.wbg.__wbindgen_init_externref_table = function() {
+            const table = wasm.__wbindgen_export_0;
+            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);
+            ;
+        };
+        imports.wbg.__wbindgen_throw = function(arg0, arg1) {
+            throw new Error(getStringFromWasm0(arg0, arg1));
+        };
+
+        return imports;
+    }
+
+    function __wbg_init_memory(imports, memory) {
+
+    }
+
+    function __wbg_finalize_init(instance, module) {
+        wasm = instance.exports;
+        __wbg_init.__wbindgen_wasm_module = module;
+        cachedUint8ArrayMemory0 = null;
+
+
+        wasm.__wbindgen_start();
+        return wasm;
+    }
+
+    function initSync(module) {
+        if (wasm !== undefined) return wasm;
+
+
+        if (typeof module !== 'undefined') {
+            if (Object.getPrototypeOf(module) === Object.prototype) {
+                ({module} = module)
+            } else {
+                console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
+            }
+        }
+
+        const imports = __wbg_get_imports();
+
+        __wbg_init_memory(imports);
+
+        if (!(module instanceof WebAssembly.Module)) {
+            module = new WebAssembly.Module(module);
+        }
+
+        const instance = new WebAssembly.Instance(module, imports);
+
+        return __wbg_finalize_init(instance, module);
+    }
+
+    async function __wbg_init(module_or_path) {
+        if (wasm !== undefined) return wasm;
+
+
+        if (typeof module_or_path !== 'undefined') {
+            if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
+                ({module_or_path} = module_or_path)
+            } else {
+                console.warn('using deprecated parameters for the initialization function; pass a single object instead')
+            }
+        }
+
+        if (typeof module_or_path === 'undefined' && typeof script_src !== 'undefined') {
+            module_or_path = script_src.replace(/\.js$/, '_bg.wasm');
+        }
+        const imports = __wbg_get_imports();
+
+        if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
+            module_or_path = fetch(module_or_path);
+        }
+
+        __wbg_init_memory(imports);
+
+        const { instance, module } = await __wbg_load(await module_or_path, imports);
+
+        return __wbg_finalize_init(instance, module);
+    }
+
+    wasm_bindgen = Object.assign(__wbg_init, { initSync }, __exports);
+
+})();
diff --git a/crates/cli/tests/reference/namespace-2.wat b/crates/cli/tests/reference/namespace-2.wat
new file mode 100644
index 00000000000..9b1e880fdbf
--- /dev/null
+++ b/crates/cli/tests/reference/namespace-2.wat
@@ -0,0 +1,26 @@
+(module $reference_test.wasm
+  (type (;0;) (func))
+  (type (;1;) (func (param i32) (result i32)))
+  (type (;2;) (func (param i32 i32) (result i32)))
+  (type (;3;) (func (param i32 i32 i32 i32) (result i32)))
+  (import "wbg" "__wbindgen_init_externref_table" (func (;0;) (type 0)))
+  (func $__wbindgen_realloc (;1;) (type 3) (param i32 i32 i32 i32) (result i32))
+  (func $__wbindgen_malloc (;2;) (type 2) (param i32 i32) (result i32))
+  (func $imageformat_from_str (;3;) (type 2) (param i32 i32) (result i32))
+  (func $status_from_bool (;4;) (type 1) (param i32) (result i32))
+  (func $imageformat_is_lossless (;5;) (type 1) (param i32) (result i32))
+  (func $status_to_bool (;6;) (type 1) (param i32) (result i32))
+  (table (;0;) 128 externref)
+  (memory (;0;) 17)
+  (export "memory" (memory 0))
+  (export "imageformat_from_str" (func $imageformat_from_str))
+  (export "imageformat_is_lossless" (func $imageformat_is_lossless))
+  (export "status_from_bool" (func $status_from_bool))
+  (export "status_to_bool" (func $status_to_bool))
+  (export "__wbindgen_export_0" (table 0))
+  (export "__wbindgen_malloc" (func $__wbindgen_malloc))
+  (export "__wbindgen_realloc" (func $__wbindgen_realloc))
+  (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/namespace.rs b/crates/cli/tests/reference/namespace.rs
index a8ff9efd1ce..06682dd172d 100644
--- a/crates/cli/tests/reference/namespace.rs
+++ b/crates/cli/tests/reference/namespace.rs
@@ -1,3 +1,8 @@
+// test the next export code
+// FLAGS: --target=bundler
+// FLAGS: --target=nodejs
+// FLAGS: --target=no-modules
+
 use wasm_bindgen::prelude::*;
 
 /// C-style enum

From b0c06a58e4afd5d95ead2ff5e2365323b07c0bf0 Mon Sep 17 00:00:00 2001
From: RunDevelopment <mitchi5000.ms@googlemail.com>
Date: Thu, 14 Nov 2024 16:14:22 +0100
Subject: [PATCH 6/7] Remove IIFE for ESM output

---
 crates/cli-support/src/js/mod.rs          | 19 +++++-----
 crates/cli/tests/reference/namespace-0.js | 42 ++++++++++-------------
 2 files changed, 28 insertions(+), 33 deletions(-)

diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index 5077ac13ec0..1ae5c420027 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -146,8 +146,8 @@ enum ExportJs<'a> {
     Function(&'a str),
     /// An arbitrary JS expression.
     Expression(&'a str),
-    /// A namespace as a function expression for initiating the namespace. The
-    /// function expression is of the form `(function(Name) {...})`.
+    /// A namespace as a statement with multiple assignments of the form
+    /// `<namespace name>.prop = value;`
     Namespace(&'a str),
 }
 
@@ -230,7 +230,8 @@ impl<'a> Context<'a> {
                 }
                 ExportJs::Namespace(namespace) => {
                     format!(
-                        "{}({});\n",
+                        "(function({}) {{\n{}}})({});\n",
+                        export_name,
                         namespace,
                         namespace_init_arg(&format!("module.exports.{}", export_name))
                     )
@@ -245,7 +246,8 @@ impl<'a> Context<'a> {
                 }
                 ExportJs::Namespace(namespace) => {
                     format!(
-                        "{}({});\n",
+                        "(function({}) {{\n{}}})({});\n",
+                        export_name,
                         namespace,
                         namespace_init_arg(&format!("__exports.{}", export_name))
                     )
@@ -288,7 +290,8 @@ impl<'a> Context<'a> {
                     if !self.defined_identifiers.contains_key(export_name) {
                         definition = format!("export const {} = {{}};\n", export_name)
                     }
-                    format!("{}{}({});\n", definition, namespace, export_name)
+                    definition.push_str(namespace);
+                    definition
                 }
             },
         };
@@ -1416,11 +1419,7 @@ __wbg_set_wasm(wasm);"
             return Ok(());
         }
 
-        let dst = format!(
-            "(function({name}) {{\n{contents}}})",
-            contents = namespace.contents
-        );
-        self.export(name, ExportJs::Namespace(&dst), None)?;
+        self.export(name, ExportJs::Namespace(&namespace.contents), None)?;
 
         if namespace.generate_typescript {
             let ts_dst = format!(
diff --git a/crates/cli/tests/reference/namespace-0.js b/crates/cli/tests/reference/namespace-0.js
index 9ee8cb20f29..fac130709fb 100644
--- a/crates/cli/tests/reference/namespace-0.js
+++ b/crates/cli/tests/reference/namespace-0.js
@@ -93,31 +93,27 @@ export const ImageFormat = {
 
 const __wbindgen_enum_Status = ["success", "failure"];
 
-(function(ImageFormat) {
-    /**
-     * @param {string} s
-     * @returns {ImageFormat}
-     */
-    ImageFormat.from_str = function from_str(s) {
-        const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
-        const len0 = WASM_VECTOR_LEN;
-        const ret = wasm.imageformat_from_str(ptr0, len0);
-        return ret;
-    };
-})(ImageFormat);
+/**
+ * @param {string} s
+ * @returns {ImageFormat}
+ */
+ImageFormat.from_str = function from_str(s) {
+    const ptr0 = passStringToWasm0(s, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+    const len0 = WASM_VECTOR_LEN;
+    const ret = wasm.imageformat_from_str(ptr0, len0);
+    return ret;
+};
 
 export const Status = {};
-(function(Status) {
-    /**
-     * I have documentation.
-     * @param {boolean} success
-     * @returns {Status}
-     */
-    Status.from_bool = function from_bool(success) {
-        const ret = wasm.status_from_bool(success);
-        return __wbindgen_enum_Status[ret];
-    };
-})(Status);
+/**
+ * I have documentation.
+ * @param {boolean} success
+ * @returns {Status}
+ */
+Status.from_bool = function from_bool(success) {
+    const ret = wasm.status_from_bool(success);
+    return __wbindgen_enum_Status[ret];
+};
 
 export function __wbindgen_throw(arg0, arg1) {
     throw new Error(getStringFromWasm0(arg0, arg1));

From 7771e6475168f018681adb12a0327b28da65f955 Mon Sep 17 00:00:00 2001
From: RunDevelopment <mitchi5000.ms@googlemail.com>
Date: Thu, 14 Nov 2024 19:54:18 +0100
Subject: [PATCH 7/7] Replace errors with warnings

---
 crates/cli-support/src/js/mod.rs | 27 +++++++++++++++++----------
 1 file changed, 17 insertions(+), 10 deletions(-)

diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs
index 1ae5c420027..18ba1208606 100644
--- a/crates/cli-support/src/js/mod.rs
+++ b/crates/cli-support/src/js/mod.rs
@@ -2958,6 +2958,10 @@ __wbg_set_wasm(wasm);"
 
         self.typescript_refs.extend(ts_refs);
 
+        fn warn(message: &str) {
+            println!("warning: {}", message);
+        }
+
         // Once we've got all the JS then put it in the right location depending
         // on what's being exported.
         match kind {
@@ -2992,10 +2996,11 @@ __wbg_set_wasm(wasm);"
                         let exported = match exported {
                             ClassOrNamespace::Class(class) => class,
                             ClassOrNamespace::Namespace(_) => {
-                                bail!(
+                                warn(&format!(
                                     "constructor is not supported on `{}`. Enums do not support exported constructors.",
                                     class
-                                );
+                                ));
+                                return Ok(());
                             }
                         };
 
@@ -3025,11 +3030,12 @@ __wbg_set_wasm(wasm);"
                                 let class = match exported {
                                     ClassOrNamespace::Class(ref mut class) => class,
                                     ClassOrNamespace::Namespace(_) => {
-                                        bail!(
+                                        warn(&format!(
                                             "the getter `{}` is not supported on `{}`. Enums only support static methods on them.",
                                             name,
                                             class
-                                        );
+                                        ));
+                                        return Ok(());
                                     }
                                 };
 
@@ -3058,11 +3064,12 @@ __wbg_set_wasm(wasm);"
                                 let class = match exported {
                                     ClassOrNamespace::Class(ref mut class) => class,
                                     ClassOrNamespace::Namespace(_) => {
-                                        bail!(
-                                            "the setter `{}` is not supported on `{}`. Enums only support static methods on them.",
+                                        warn(&format!(
+                                           "the setter `{}` is not supported on `{}`. Enums only support static methods on them.",
                                             name,
                                             class
-                                        );
+                                        ));
+                                        return Ok(());
                                     }
                                 };
 
@@ -3090,15 +3097,15 @@ __wbg_set_wasm(wasm);"
                             }
                             ClassOrNamespace::Namespace(ns) => {
                                 if !is_static {
-                                    let msg = format!(
+                                    warn(&format!(
                                         "The enum `{}` cannot support the instance method `{}`. \
                                         No binding will be generated for this method. \
                                         Consider moving the method in an `impl` block with the `#[wasm_bindgen]` attribute to avoid exporting it, \
                                         or making it a static method by replacing `self` with `value: Self`.",
                                         ns.name,
                                         name
-                                    );
-                                    println!("WARNING: {}", msg);
+                                    ));
+                                    return Ok(());
                                 } else {
                                     ns.push(name, &js_docs, &code, &ts_docs, ts);
                                 }