@@ -709,31 +709,32 @@ impl Build {
709709 emcc_opt_level_for ( & self . profile )
710710 } ;
711711
712- // TODO: re-enable emcc's `--emit-tsd` once
713- // https://github.com/emscripten-core/emscripten (TSD multi-value PR)
714- // lands. emcc currently asserts in its TSD generator on any wasm
715- // function with multiple return values, and wasm-bindgen emits
716- // those for every Rust-side String / Vec / Result / struct
717- // return. The merge code path (`merge_emscripten_and_bindgen_dts`)
718- // is kept ready below — flip `emscripten_dts` from `None` to
719- // `Some(...)` once emcc cooperates and the merge will produce a
720- // union `.d.ts` describing both EmscriptenModule and BindgenModule.
721- let emscripten_dts: Option < PathBuf > = None ;
722- let _ = (
723- & bindgen_dts,
724- merge_emscripten_and_bindgen_dts as fn ( & Path , & Path ) -> Result < ( ) > ,
725- ) ;
726-
712+ // We deliberately don't ask emcc for `--emit-tsd`. For a pure-Rust
713+ // wasm-pack package the TS surface is fully knowable to us:
714+ // wasm-bindgen owns the user-facing exports (already typed in its
715+ // own .d.ts), and emcc's runtime surface is a small curated set
716+ // wasm-pack chooses to expose. emcc's TSD would only add value if
717+ // we linked arbitrary C/C++ exports, which the wasm-pack flow
718+ // doesn't support today. Avoiding it also sidesteps the emcc
719+ // assertion on wasm-bindgen-style multi-value-return exports.
727720 emcc_post_link (
728721 & in_wasm,
729722 & in_library,
730723 & out_js,
731- emscripten_dts. as_deref ( ) ,
732724 & post_link_settings,
733725 opt_level,
734726 ) ?;
735727 info ! ( "emcc --post-link produced {out_js:?}." ) ;
736728
729+ // Decorate wasm-bindgen's bare .d.ts with the emscripten-shaped
730+ // factory: an `EmscriptenRuntime` interface for the runtime members
731+ // we surface, a `MainModule` intersection type, and a default-export
732+ // factory declaration. After this `import M from "./<name>.mjs"`
733+ // type-checks against the produced module.
734+ if !self . disable_dts && bindgen_dts. exists ( ) {
735+ decorate_bindgen_dts_for_emscripten ( & bindgen_dts) ?;
736+ }
737+
737738 // Clean up intermediate artifacts that shouldn't ship in pkg/.
738739 // `_bg.wasm` (pre-post-link) is superseded by the post-linked
739740 // `<name>.wasm`; `library_bindgen.js` was a build-time-only DSL file.
@@ -1106,18 +1107,10 @@ fn emcc_opt_level_for(profile: &BuildProfile) -> &'static str {
11061107
11071108/// Invoke `emcc --post-link` to merge the wasm-bindgen output with
11081109/// emscripten's standard JS runtime, producing the final JS module.
1109- ///
1110- /// If `emit_tsd` is supplied, emcc also tries to emit an EmscriptenModule
1111- /// `.d.ts` to that path. Currently emcc's TSD generator asserts on
1112- /// wasm-bindgen-style multi-value returns, so the file may not be
1113- /// produced — we leave it as best-effort and rely on
1114- /// `merge_emscripten_and_bindgen_dts` to fall back gracefully when the
1115- /// file is missing.
11161110fn emcc_post_link (
11171111 in_wasm : & Path ,
11181112 in_library : & Path ,
11191113 out_js : & Path ,
1120- emit_tsd : Option < & Path > ,
11211114 settings : & EmccPostLinkSettings ,
11221115 opt_level : & str ,
11231116) -> Result < ( ) > {
@@ -1134,9 +1127,6 @@ fn emcc_post_link(
11341127 if settings. source_phase_imports {
11351128 cmd. arg ( "-sSOURCE_PHASE_IMPORTS=1" ) ;
11361129 }
1137- if let Some ( tsd) = emit_tsd {
1138- cmd. arg ( "--emit-tsd" ) . arg ( tsd) ;
1139- }
11401130 cmd. arg ( "-o" ) . arg ( out_js) ;
11411131
11421132 let status = cmd. status ( ) . context ( "running emcc --post-link" ) ?;
@@ -1146,43 +1136,62 @@ fn emcc_post_link(
11461136 Ok ( ( ) )
11471137}
11481138
1149- /// Fuse emscripten's `EmscriptenModule` typings with wasm-bindgen's
1150- /// `BindgenModule` typings into a single `.d.ts` written at `bindgen_dts`
1151- /// (overwriting it in place).
1139+ /// Append the emscripten factory shape to wasm-bindgen's `.d.ts`.
11521140///
1153- /// emcc's `--emit-tsd` output ends with:
1154- /// ```ts
1155- /// export type MainModule = EmscriptenModule;
1156- /// ```
1157- /// We strip that line, append wasm-bindgen's typings, and replace it with
1158- /// an intersection:
1159- /// ```ts
1160- /// export type MainModule = EmscriptenModule & BindgenModule;
1161- /// ```
1162- /// so consumers see a single typed factory covering both surfaces.
1163- fn merge_emscripten_and_bindgen_dts ( emscripten_dts : & Path , bindgen_dts : & Path ) -> Result < ( ) > {
1164- let em_src = std:: fs:: read_to_string ( emscripten_dts)
1165- . with_context ( || format ! ( "reading emscripten .d.ts at {emscripten_dts:?}" ) ) ?;
1166- let bg_src = std:: fs:: read_to_string ( bindgen_dts)
1167- . with_context ( || format ! ( "reading bindgen .d.ts at {bindgen_dts:?}" ) ) ?;
1141+ /// wasm-bindgen's emscripten-mode output emits an unexported
1142+ /// `interface BindgenModule { ... }` and a sibling class export per
1143+ /// `#[wasm_bindgen] pub struct`, but doesn't declare the default-exported
1144+ /// module factory that emscripten's `-sMODULARIZE -sEXPORT_ES6` emits in
1145+ /// the JS. Without this decoration, `import M from "./<name>.mjs"`
1146+ /// type-checks against `any`.
1147+ ///
1148+ /// We append:
1149+ ///
1150+ /// ```ts
1151+ /// export interface EmscriptenRuntime {
1152+ /// // small curated set of emscripten-runtime members
1153+ /// }
1154+ /// export type MainModule = EmscriptenRuntime & BindgenModule;
1155+ /// declare function ModuleFactory(opts?: object): Promise<MainModule>;
1156+ /// export default ModuleFactory;
1157+ /// ```
1158+ ///
1159+ /// The runtime members we surface are the ones realistic consumers reach
1160+ /// for (heap views, `_initialize`, optional `ccall`/`cwrap`). emcc's full
1161+ /// runtime is much larger; we deliberately don't enumerate everything
1162+ /// since that would commit us to tracking emcc's evolution.
1163+ fn decorate_bindgen_dts_for_emscripten ( bindgen_dts : & Path ) -> Result < ( ) > {
1164+ const DECORATION : & str = r#"
1165+
1166+ // --- wasm-pack: emscripten factory shape ---
1167+ // Curated subset of emscripten's MODULARIZE/EXPORT_ES6 runtime surface.
1168+ // Add or remove members as your use case dictates.
1169+ export interface EmscriptenRuntime {
1170+ HEAP8: Int8Array;
1171+ HEAPU8: Uint8Array;
1172+ HEAP16: Int16Array;
1173+ HEAPU16: Uint16Array;
1174+ HEAP32: Int32Array;
1175+ HEAPU32: Uint32Array;
1176+ HEAPF32: Float32Array;
1177+ HEAPF64: Float64Array;
1178+ _initialize?: () => void;
1179+ }
11681180
1169- let mut merged = String :: new ( ) ;
1170- for line in em_src. lines ( ) {
1171- if line. trim_start ( ) . starts_with ( "export type MainModule" ) {
1172- continue ;
1173- }
1174- merged. push_str ( line) ;
1175- merged. push ( '\n' ) ;
1176- }
1177- merged. push_str ( "\n // --- wasm-bindgen-generated types ---\n " ) ;
1178- merged. push_str ( & bg_src) ;
1179- if !merged. ends_with ( '\n' ) {
1180- merged. push ( '\n' ) ;
1181- }
1182- merged. push_str ( "\n export type MainModule = EmscriptenModule & BindgenModule;\n " ) ;
1181+ export type MainModule = EmscriptenRuntime & BindgenModule;
1182+
1183+ declare function ModuleFactory(opts?: object): Promise<MainModule>;
1184+ export default ModuleFactory;
1185+ "# ;
11831186
1184- std:: fs:: write ( bindgen_dts, merged)
1185- . with_context ( || format ! ( "writing merged .d.ts to {bindgen_dts:?}" ) ) ?;
1187+ let mut existing = std:: fs:: read_to_string ( bindgen_dts)
1188+ . with_context ( || format ! ( "reading bindgen .d.ts at {bindgen_dts:?}" ) ) ?;
1189+ if !existing. ends_with ( '\n' ) {
1190+ existing. push ( '\n' ) ;
1191+ }
1192+ existing. push_str ( DECORATION ) ;
1193+ std:: fs:: write ( bindgen_dts, existing)
1194+ . with_context ( || format ! ( "writing decorated .d.ts to {bindgen_dts:?}" ) ) ?;
11861195 Ok ( ( ) )
11871196}
11881197
0 commit comments