diff --git a/crates/cli-support/src/wasm2es6js.rs b/crates/cli-support/src/wasm2es6js.rs
index bdf6f020f68..c16eb0a4504 100644
--- a/crates/cli-support/src/wasm2es6js.rs
+++ b/crates/cli-support/src/wasm2es6js.rs
@@ -62,97 +62,94 @@ fn args_are_optional(name: &str) -> bool {
 
 pub fn interface(module: &Module) -> Result<String, Error> {
     let mut exports = String::new();
+    module_export_types(module, |name, ty| {
+        writeln!(exports, "  readonly {}: {};", name, ty).unwrap();
+    });
+    Ok(exports)
+}
+
+pub fn typescript(module: &Module) -> Result<String, Error> {
+    let mut exports = "/* tslint:disable */\n/* eslint-disable */\n".to_string();
+    module_export_types(module, |name, ty| {
+        writeln!(exports, "export const {}: {};", name, ty).unwrap();
+    });
+    Ok(exports)
+}
 
+/// Iterates over all the exports in a module and generates TypeScript types. All
+/// name-type pairs are passed to the `export` function.
+fn module_export_types(module: &Module, mut export: impl FnMut(&str, &str)) {
     for entry in module.exports.iter() {
-        let id = match entry.item {
-            walrus::ExportItem::Function(i) => i,
-            walrus::ExportItem::Memory(_) => {
-                exports.push_str(&format!("  readonly {}: WebAssembly.Memory;\n", entry.name,));
-                continue;
-            }
-            walrus::ExportItem::Table(_) => {
-                exports.push_str(&format!("  readonly {}: WebAssembly.Table;\n", entry.name,));
-                continue;
+        match entry.item {
+            walrus::ExportItem::Function(id) => {
+                let func = module.funcs.get(id);
+                let ty = module.types.get(func.ty());
+                let ts_type = function_type_to_ts(ty, args_are_optional(&entry.name));
+                export(&entry.name, &ts_type);
             }
+            walrus::ExportItem::Memory(_) => export(&entry.name, "WebAssembly.Memory"),
+            walrus::ExportItem::Table(_) => export(&entry.name, "WebAssembly.Table"),
             walrus::ExportItem::Global(_) => continue,
         };
+    }
+}
+fn val_type_to_ts(ty: walrus::ValType) -> &'static str {
+    // see https://webassembly.github.io/spec/js-api/index.html#towebassemblyvalue
+    // and https://webassembly.github.io/spec/js-api/index.html#tojsvalue
+    match ty {
+        walrus::ValType::I32 | walrus::ValType::F32 | walrus::ValType::F64 => "number",
+        walrus::ValType::I64 => "bigint",
+        // there could be anything behind a reference
+        walrus::ValType::Ref(_) => "any",
+        // V128 currently isn't supported in JS and therefore doesn't have a
+        // specific type in the spec. When it does get support, this type will
+        // still be technically correct, but should be updated to something more
+        // specific.
+        walrus::ValType::V128 => "any",
+    }
+}
+fn function_type_to_ts(function: &walrus::Type, all_args_optional: bool) -> String {
+    let mut out = String::new();
 
-        let func = module.funcs.get(id);
-        let ty = module.types.get(func.ty());
-        let mut args = String::new();
-        for (i, _) in ty.params().iter().enumerate() {
-            if i > 0 {
-                args.push_str(", ");
-            }
-
-            push_index_identifier(i, &mut args);
-            if args_are_optional(&entry.name) {
-                args.push('?');
-            }
-            args.push_str(": number");
+    // parameters
+    out.push('(');
+    for (i, arg_type) in function.params().iter().enumerate() {
+        if i > 0 {
+            out.push_str(", ");
         }
 
-        exports.push_str(&format!(
-            "  readonly {name}: ({args}) => {ret};\n",
-            name = entry.name,
-            args = args,
-            ret = match ty.results().len() {
-                0 => "void",
-                1 => "number",
-                _ => "number[]",
-            },
-        ));
+        push_index_identifier(i, &mut out);
+        if all_args_optional {
+            out.push('?');
+        }
+        out.push_str(": ");
+        out.push_str(val_type_to_ts(*arg_type));
     }
+    out.push(')');
 
-    Ok(exports)
-}
-
-pub fn typescript(module: &Module) -> Result<String, Error> {
-    let mut exports = "/* tslint:disable */\n/* eslint-disable */\n".to_string();
-    for entry in module.exports.iter() {
-        let id = match entry.item {
-            walrus::ExportItem::Function(i) => i,
-            walrus::ExportItem::Memory(_) => {
-                exports.push_str(&format!(
-                    "export const {}: WebAssembly.Memory;\n",
-                    entry.name,
-                ));
-                continue;
-            }
-            walrus::ExportItem::Table(_) => {
-                exports.push_str(&format!(
-                    "export const {}: WebAssembly.Table;\n",
-                    entry.name,
-                ));
-                continue;
-            }
-            walrus::ExportItem::Global(_) => continue,
-        };
+    // arrow
+    out.push_str(" => ");
 
-        let func = module.funcs.get(id);
-        let ty = module.types.get(func.ty());
-        let mut args = String::new();
-        for (i, _) in ty.params().iter().enumerate() {
-            if i > 0 {
-                args.push_str(", ");
+    // results
+    let results = function.results();
+    // this match follows the spec:
+    // https://webassembly.github.io/spec/js-api/index.html#exported-function-exotic-objects
+    match results.len() {
+        0 => out.push_str("void"),
+        1 => out.push_str(val_type_to_ts(results[0])),
+        _ => {
+            out.push('[');
+            for (i, result) in results.iter().enumerate() {
+                if i > 0 {
+                    out.push_str(", ");
+                }
+                out.push_str(val_type_to_ts(*result));
             }
-            push_index_identifier(i, &mut args);
-            args.push_str(": number");
+            out.push(']');
         }
-
-        exports.push_str(&format!(
-            "export function {name}({args}): {ret};\n",
-            name = entry.name,
-            args = args,
-            ret = match ty.results().len() {
-                0 => "void",
-                1 => "number",
-                _ => "number[]",
-            },
-        ));
     }
 
-    Ok(exports)
+    out
 }
 
 impl Output {
diff --git a/crates/cli/tests/reference/wasm-export-types.d.ts b/crates/cli/tests/reference/wasm-export-types.d.ts
new file mode 100644
index 00000000000..a5f399db59d
--- /dev/null
+++ b/crates/cli/tests/reference/wasm-export-types.d.ts
@@ -0,0 +1,38 @@
+/* tslint:disable */
+/* eslint-disable */
+export function example(a: number, b: bigint, c: any, d: string): string;
+export function example_128(a: bigint): bigint | undefined;
+
+export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
+
+export interface InitOutput {
+  readonly memory: WebAssembly.Memory;
+  readonly example: (a: number, b: bigint, c: any, d: number, e: number) => [number, number];
+  readonly example_128: (a: bigint, b: bigint) => [number, bigint, bigint];
+  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_free: (a: number, b: number, c: number) => void;
+  readonly __wbindgen_start: () => void;
+}
+
+export type SyncInitInput = BufferSource | WebAssembly.Module;
+/**
+* Instantiates the given `module`, which can either be bytes or
+* a precompiled `WebAssembly.Module`.
+*
+* @param {{ module: SyncInitInput }} module - Passing `SyncInitInput` directly is deprecated.
+*
+* @returns {InitOutput}
+*/
+export function initSync(module: { module: SyncInitInput } | SyncInitInput): InitOutput;
+
+/**
+* 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>}
+*/
+export default function __wbg_init (module_or_path?: { module_or_path: InitInput | Promise<InitInput> } | InitInput | Promise<InitInput>): Promise<InitOutput>;
diff --git a/crates/cli/tests/reference/wasm-export-types.js b/crates/cli/tests/reference/wasm-export-types.js
new file mode 100644
index 00000000000..de290e90502
--- /dev/null
+++ b/crates/cli/tests/reference/wasm-export-types.js
@@ -0,0 +1,226 @@
+let wasm;
+
+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;
+}
+/**
+ * @param {number} a
+ * @param {bigint} b
+ * @param {any} c
+ * @param {string} d
+ * @returns {string}
+ */
+export function example(a, b, c, d) {
+    let deferred2_0;
+    let deferred2_1;
+    try {
+        const ptr0 = passStringToWasm0(d, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
+        const len0 = WASM_VECTOR_LEN;
+        const ret = wasm.example(a, b, c, ptr0, len0);
+        deferred2_0 = ret[0];
+        deferred2_1 = ret[1];
+        return getStringFromWasm0(ret[0], ret[1]);
+    } finally {
+        wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
+    }
+}
+
+/**
+ * @param {bigint} a
+ * @returns {bigint | undefined}
+ */
+export function example_128(a) {
+    const ret = wasm.example_128(a, a >> BigInt(64));
+    return ret[0] === 0 ? undefined : (BigInt.asUintN(64, ret[1]) | (BigInt.asUintN(64, ret[2]) << BigInt(64)));
+}
+
+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') {
+        module_or_path = new URL('reference_test_bg.wasm', import.meta.url);
+    }
+    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);
+}
+
+export { initSync };
+export default __wbg_init;
diff --git a/crates/cli/tests/reference/wasm-export-types.rs b/crates/cli/tests/reference/wasm-export-types.rs
new file mode 100644
index 00000000000..06f89d912e0
--- /dev/null
+++ b/crates/cli/tests/reference/wasm-export-types.rs
@@ -0,0 +1,17 @@
+// FLAGS: --target=web
+
+use wasm_bindgen::prelude::*;
+
+// This is for testing the type generation of the wasm-exported functions.
+// Here, example should be exported as `(arg0: number, arg1: bigint, arg2: any, arg3: number, arg4: number) => [number, number]`.
+// Notes: `arg2: any` is an external reference to a JS value, and the ABI of strings is `number, number` (pointer, length).
+
+#[wasm_bindgen]
+pub fn example(a: u32, b: u64, c: JsValue, d: &str) -> String {
+    todo!()
+}
+
+#[wasm_bindgen]
+pub fn example_128(a: u128) -> Option<u128> {
+    None
+}
diff --git a/crates/cli/tests/reference/wasm-export-types.wat b/crates/cli/tests/reference/wasm-export-types.wat
new file mode 100644
index 00000000000..195c25a5723
--- /dev/null
+++ b/crates/cli/tests/reference/wasm-export-types.wat
@@ -0,0 +1,26 @@
+(module $reference_test.wasm
+  (type (;0;) (func))
+  (type (;1;) (func (param i32 i32) (result i32)))
+  (type (;2;) (func (param i32 i32 i32)))
+  (type (;3;) (func (param i32 i32 i32 i32) (result i32)))
+  (type (;4;) (func (param i32 i64 externref i32 i32) (result i32 i32)))
+  (type (;5;) (func (param i64 i64) (result i32 i64 i64)))
+  (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 1) (param i32 i32) (result i32))
+  (func $__wbindgen_free (;3;) (type 2) (param i32 i32 i32))
+  (func $"example externref shim multivalue shim" (;4;) (type 4) (param i32 i64 externref i32 i32) (result i32 i32))
+  (func $"example_128 multivalue shim" (;5;) (type 5) (param i64 i64) (result i32 i64 i64))
+  (table (;0;) 128 externref)
+  (memory (;0;) 17)
+  (export "memory" (memory 0))
+  (export "example" (func $"example externref shim multivalue shim"))
+  (export "example_128" (func $"example_128 multivalue shim"))
+  (export "__wbindgen_export_0" (table 0))
+  (export "__wbindgen_malloc" (func $__wbindgen_malloc))
+  (export "__wbindgen_realloc" (func $__wbindgen_realloc))
+  (export "__wbindgen_free" (func $__wbindgen_free))
+  (export "__wbindgen_start" (func 0))
+  (@custom "target_features" (after code) "\04+\0amultivalue+\0fmutable-globals+\0freference-types+\08sign-ext")
+)
+