Skip to content

Commit 09e0e6d

Browse files
Add experimental Node module output target (#4027)
1 parent cf186ac commit 09e0e6d

File tree

13 files changed

+181
-120
lines changed

13 files changed

+181
-120
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@
6969
* Added bindings to `NavigatorOptions.vibrate`.
7070
[#4041](https://github.com/rustwasm/wasm-bindgen/pull/4041)
7171

72+
* Added an experimental Node.JS ES module target, in comparison the current `node` target uses CommonJS, with `--target experimental-nodejs-module` or when testing with `wasm_bindgen_test_configure!(run_in_node_experimental)`.
73+
[#4027](https://github.com/rustwasm/wasm-bindgen/pull/4027)
74+
7275
### Changed
7376

7477
* Stabilize Web Share API.

crates/cli-support/src/js/mod.rs

Lines changed: 65 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,7 @@ impl<'a> Context<'a> {
142142
self.globals.push_str(c);
143143
}
144144
let global = match self.config.mode {
145-
OutputMode::Node {
146-
experimental_modules: false,
147-
} => {
145+
OutputMode::Node { module: false } => {
148146
if contents.starts_with("class") {
149147
format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
150148
} else {
@@ -159,9 +157,7 @@ impl<'a> Context<'a> {
159157
}
160158
}
161159
OutputMode::Bundler { .. }
162-
| OutputMode::Node {
163-
experimental_modules: true,
164-
}
160+
| OutputMode::Node { module: true }
165161
| OutputMode::Web
166162
| OutputMode::Deno => {
167163
if let Some(body) = contents.strip_prefix("function") {
@@ -217,26 +213,29 @@ impl<'a> Context<'a> {
217213

218214
let mut shim = String::new();
219215

220-
shim.push_str("let imports = {};\n");
216+
shim.push_str("\nlet imports = {};\n");
221217

222-
if self.config.mode.nodejs_experimental_modules() {
218+
if self.config.mode.uses_es_modules() {
223219
for (i, module) in imports.iter().enumerate() {
224220
if module.as_str() != PLACEHOLDER_MODULE {
225221
shim.push_str(&format!("import * as import{} from '{}';\n", i, module));
226222
}
227223
}
228-
}
229-
230-
for (i, module) in imports.iter().enumerate() {
231-
if module.as_str() == PLACEHOLDER_MODULE {
232-
shim.push_str(&format!(
233-
"imports['{0}'] = module.exports;\n",
234-
PLACEHOLDER_MODULE
235-
));
236-
} else if self.config.mode.nodejs_experimental_modules() {
237-
shim.push_str(&format!("imports['{}'] = import{};\n", module, i));
238-
} else {
239-
shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module));
224+
for (i, module) in imports.iter().enumerate() {
225+
if module.as_str() != PLACEHOLDER_MODULE {
226+
shim.push_str(&format!("imports['{}'] = import{};\n", module, i));
227+
}
228+
}
229+
} else {
230+
for module in imports.iter() {
231+
if module.as_str() == PLACEHOLDER_MODULE {
232+
shim.push_str(&format!(
233+
"imports['{0}'] = module.exports;\n",
234+
PLACEHOLDER_MODULE
235+
));
236+
} else {
237+
shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module));
238+
}
240239
}
241240
}
242241

@@ -246,24 +245,31 @@ impl<'a> Context<'a> {
246245
fn generate_node_wasm_loading(&self, path: &Path) -> String {
247246
let mut shim = String::new();
248247

249-
if self.config.mode.nodejs_experimental_modules() {
248+
if self.config.mode.uses_es_modules() {
250249
// On windows skip the leading `/` which comes out when we parse a
251250
// url to use `C:\...` instead of `\C:\...`
252251
shim.push_str(&format!(
253252
"
254-
import * as path from 'path';
255-
import * as fs from 'fs';
256-
import * as url from 'url';
257-
import * as process from 'process';
253+
import * as path from 'node:path';
254+
import * as fs from 'node:fs';
255+
import * as process from 'node:process';
258256
259-
let file = path.dirname(url.parse(import.meta.url).pathname);
257+
let file = path.dirname(new URL(import.meta.url).pathname);
260258
if (process.platform === 'win32') {{
261259
file = file.substring(1);
262260
}}
263261
const bytes = fs.readFileSync(path.join(file, '{}'));
264262
",
265263
path.file_name().unwrap().to_str().unwrap()
266264
));
265+
shim.push_str(
266+
"
267+
const wasmModule = new WebAssembly.Module(bytes);
268+
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
269+
const wasm = wasmInstance.exports;
270+
export const __wasm = wasm;
271+
",
272+
);
267273
} else {
268274
shim.push_str(&format!(
269275
"
@@ -272,17 +278,16 @@ impl<'a> Context<'a> {
272278
",
273279
path.file_name().unwrap().to_str().unwrap()
274280
));
281+
shim.push_str(
282+
"
283+
const wasmModule = new WebAssembly.Module(bytes);
284+
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
285+
wasm = wasmInstance.exports;
286+
module.exports.__wasm = wasm;
287+
",
288+
);
275289
}
276290

277-
shim.push_str(
278-
"
279-
const wasmModule = new WebAssembly.Module(bytes);
280-
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
281-
wasm = wasmInstance.exports;
282-
module.exports.__wasm = wasm;
283-
",
284-
);
285-
286291
reset_indentation(&shim)
287292
}
288293

@@ -400,9 +405,7 @@ impl<'a> Context<'a> {
400405

401406
// With normal CommonJS node we need to defer requiring the wasm
402407
// until the end so most of our own exports are hooked up
403-
OutputMode::Node {
404-
experimental_modules: false,
405-
} => {
408+
OutputMode::Node { module: false } => {
406409
js.push_str(&self.generate_node_imports());
407410

408411
js.push_str("let wasm;\n");
@@ -442,13 +445,10 @@ impl<'a> Context<'a> {
442445
}
443446
}
444447

445-
// With Bundlers and modern ES6 support in Node we can simply import
446-
// the wasm file as if it were an ES module and let the
447-
// bundler/runtime take care of it.
448-
OutputMode::Bundler { .. }
449-
| OutputMode::Node {
450-
experimental_modules: true,
451-
} => {
448+
// With Bundlers we can simply import the wasm file as if it were an ES module
449+
// and let the bundler/runtime take care of it.
450+
// With Node we manually read the wasm file from the filesystem and instantiate it.
451+
OutputMode::Bundler { .. } | OutputMode::Node { module: true } => {
452452
for (id, js) in crate::sorted_iter(&self.wasm_import_definitions) {
453453
let import = self.module.imports.get_mut(*id);
454454
import.module = format!("./{}_bg.js", module_name);
@@ -475,8 +475,18 @@ impl<'a> Context<'a> {
475475
",
476476
);
477477

478+
if matches!(self.config.mode, OutputMode::Node { module: true }) {
479+
let start = start.get_or_insert_with(String::new);
480+
start.push_str(&self.generate_node_imports());
481+
start.push_str(&self.generate_node_wasm_loading(Path::new(&format!(
482+
"./{}_bg.wasm",
483+
module_name
484+
))));
485+
}
478486
if needs_manual_start {
479-
start = Some("\nwasm.__wbindgen_start();\n".to_string());
487+
start
488+
.get_or_insert_with(String::new)
489+
.push_str("\nwasm.__wbindgen_start();\n");
480490
}
481491
}
482492

@@ -509,7 +519,9 @@ impl<'a> Context<'a> {
509519
// Emit all the JS for importing all our functionality
510520
assert!(
511521
!self.config.mode.uses_es_modules() || js.is_empty(),
512-
"ES modules require imports to be at the start of the file"
522+
"ES modules require imports to be at the start of the file, but we \
523+
generated some JS before the imports: {}",
524+
js
513525
);
514526

515527
let mut push_with_newline = |s| {
@@ -556,9 +568,7 @@ impl<'a> Context<'a> {
556568
}
557569
}
558570

559-
OutputMode::Node {
560-
experimental_modules: false,
561-
} => {
571+
OutputMode::Node { module: false } => {
562572
for (module, items) in crate::sorted_iter(&self.js_imports) {
563573
imports.push_str("const { ");
564574
for (i, (item, rename)) in items.iter().enumerate() {
@@ -582,9 +592,7 @@ impl<'a> Context<'a> {
582592
}
583593

584594
OutputMode::Bundler { .. }
585-
| OutputMode::Node {
586-
experimental_modules: true,
587-
}
595+
| OutputMode::Node { module: true }
588596
| OutputMode::Web
589597
| OutputMode::Deno => {
590598
for (module, items) in crate::sorted_iter(&self.js_imports) {
@@ -3216,12 +3224,10 @@ impl<'a> Context<'a> {
32163224
OutputMode::Web
32173225
| OutputMode::Bundler { .. }
32183226
| OutputMode::Deno
3219-
| OutputMode::Node {
3220-
experimental_modules: true,
3221-
} => "import.meta.url",
3222-
OutputMode::Node {
3223-
experimental_modules: false,
3224-
} => "require('url').pathToFileURL(__filename)",
3227+
| OutputMode::Node { module: true } => "import.meta.url",
3228+
OutputMode::Node { module: false } => {
3229+
"require('url').pathToFileURL(__filename)"
3230+
}
32253231
OutputMode::NoModules { .. } => {
32263232
prelude.push_str(
32273233
"if (script_src === undefined) {

crates/cli-support/src/lib.rs

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ enum OutputMode {
6666
Bundler { browser_only: bool },
6767
Web,
6868
NoModules { global: String },
69-
Node { experimental_modules: bool },
69+
Node { module: bool },
7070
Deno,
7171
}
7272

@@ -154,23 +154,16 @@ impl Bindgen {
154154

155155
pub fn nodejs(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
156156
if node {
157-
self.switch_mode(
158-
OutputMode::Node {
159-
experimental_modules: false,
160-
},
161-
"--target nodejs",
162-
)?;
157+
self.switch_mode(OutputMode::Node { module: false }, "--target nodejs")?;
163158
}
164159
Ok(self)
165160
}
166161

167-
pub fn nodejs_experimental_modules(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
162+
pub fn nodejs_module(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
168163
if node {
169164
self.switch_mode(
170-
OutputMode::Node {
171-
experimental_modules: true,
172-
},
173-
"--nodejs-experimental-modules",
165+
OutputMode::Node { module: true },
166+
"--target experimental-nodejs-module",
174167
)?;
175168
}
176169
Ok(self)
@@ -548,22 +541,11 @@ impl OutputMode {
548541
self,
549542
OutputMode::Bundler { .. }
550543
| OutputMode::Web
551-
| OutputMode::Node {
552-
experimental_modules: true,
553-
}
544+
| OutputMode::Node { module: true }
554545
| OutputMode::Deno
555546
)
556547
}
557548

558-
fn nodejs_experimental_modules(&self) -> bool {
559-
match self {
560-
OutputMode::Node {
561-
experimental_modules,
562-
} => *experimental_modules,
563-
_ => false,
564-
}
565-
}
566-
567549
fn nodejs(&self) -> bool {
568550
matches!(self, OutputMode::Node { .. })
569551
}
@@ -579,10 +561,7 @@ impl OutputMode {
579561
fn esm_integration(&self) -> bool {
580562
matches!(
581563
self,
582-
OutputMode::Bundler { .. }
583-
| OutputMode::Node {
584-
experimental_modules: true,
585-
}
564+
OutputMode::Bundler { .. } | OutputMode::Node { module: true }
586565
)
587566
}
588567
}
@@ -687,11 +666,7 @@ impl Output {
687666

688667
// And now that we've got all our JS and TypeScript, actually write it
689668
// out to the filesystem.
690-
let extension = if gen.mode.nodejs_experimental_modules() {
691-
"mjs"
692-
} else {
693-
"js"
694-
};
669+
let extension = "js";
695670

696671
fn write<P, C>(path: P, contents: C) -> Result<(), anyhow::Error>
697672
where
@@ -709,17 +684,30 @@ impl Output {
709684

710685
let start = gen.start.as_deref().unwrap_or("");
711686

712-
write(
713-
&js_path,
714-
format!(
715-
"import * as wasm from \"./{wasm_name}.wasm\";
687+
if matches!(gen.mode, OutputMode::Node { .. }) {
688+
write(
689+
&js_path,
690+
format!(
691+
"
692+
import {{ __wbg_set_wasm }} from \"./{js_name}\";
693+
{start}
694+
__wbg_set_wasm(wasm);
695+
export * from \"./{js_name}\";",
696+
),
697+
)?;
698+
} else {
699+
write(
700+
&js_path,
701+
format!(
702+
"
703+
import * as wasm from \"./{wasm_name}.wasm\";
716704
import {{ __wbg_set_wasm }} from \"./{js_name}\";
717705
__wbg_set_wasm(wasm);
718706
export * from \"./{js_name}\";
719707
{start}"
720-
),
721-
)?;
722-
708+
),
709+
)?;
710+
}
723711
write(out_dir.join(&js_name), reset_indentation(&gen.js))?;
724712
} else {
725713
write(&js_path, reset_indentation(&gen.js))?;

0 commit comments

Comments
 (0)