Skip to content

Commit

Permalink
Add experimental Node module output target (#4027)
Browse files Browse the repository at this point in the history
  • Loading branch information
Systemcluster committed Aug 4, 2024
1 parent cf186ac commit 09e0e6d
Show file tree
Hide file tree
Showing 13 changed files with 181 additions and 120 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@
* Added bindings to `NavigatorOptions.vibrate`.
[#4041](https://github.com/rustwasm/wasm-bindgen/pull/4041)

* 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)`.
[#4027](https://github.com/rustwasm/wasm-bindgen/pull/4027)

### Changed

* Stabilize Web Share API.
Expand Down
124 changes: 65 additions & 59 deletions crates/cli-support/src/js/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,7 @@ impl<'a> Context<'a> {
self.globals.push_str(c);
}
let global = match self.config.mode {
OutputMode::Node {
experimental_modules: false,
} => {
OutputMode::Node { module: false } => {
if contents.starts_with("class") {
format!("{}\nmodule.exports.{1} = {1};\n", contents, export_name)
} else {
Expand All @@ -159,9 +157,7 @@ impl<'a> Context<'a> {
}
}
OutputMode::Bundler { .. }
| OutputMode::Node {
experimental_modules: true,
}
| OutputMode::Node { module: true }
| OutputMode::Web
| OutputMode::Deno => {
if let Some(body) = contents.strip_prefix("function") {
Expand Down Expand Up @@ -217,26 +213,29 @@ impl<'a> Context<'a> {

let mut shim = String::new();

shim.push_str("let imports = {};\n");
shim.push_str("\nlet imports = {};\n");

if self.config.mode.nodejs_experimental_modules() {
if self.config.mode.uses_es_modules() {
for (i, module) in imports.iter().enumerate() {
if module.as_str() != PLACEHOLDER_MODULE {
shim.push_str(&format!("import * as import{} from '{}';\n", i, module));
}
}
}

for (i, module) in imports.iter().enumerate() {
if module.as_str() == PLACEHOLDER_MODULE {
shim.push_str(&format!(
"imports['{0}'] = module.exports;\n",
PLACEHOLDER_MODULE
));
} else if self.config.mode.nodejs_experimental_modules() {
shim.push_str(&format!("imports['{}'] = import{};\n", module, i));
} else {
shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module));
for (i, module) in imports.iter().enumerate() {
if module.as_str() != PLACEHOLDER_MODULE {
shim.push_str(&format!("imports['{}'] = import{};\n", module, i));
}
}
} else {
for module in imports.iter() {
if module.as_str() == PLACEHOLDER_MODULE {
shim.push_str(&format!(
"imports['{0}'] = module.exports;\n",
PLACEHOLDER_MODULE
));
} else {
shim.push_str(&format!("imports['{0}'] = require('{0}');\n", module));
}
}
}

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

if self.config.mode.nodejs_experimental_modules() {
if self.config.mode.uses_es_modules() {
// On windows skip the leading `/` which comes out when we parse a
// url to use `C:\...` instead of `\C:\...`
shim.push_str(&format!(
"
import * as path from 'path';
import * as fs from 'fs';
import * as url from 'url';
import * as process from 'process';
import * as path from 'node:path';
import * as fs from 'node:fs';
import * as process from 'node:process';
let file = path.dirname(url.parse(import.meta.url).pathname);
let file = path.dirname(new URL(import.meta.url).pathname);
if (process.platform === 'win32') {{
file = file.substring(1);
}}
const bytes = fs.readFileSync(path.join(file, '{}'));
",
path.file_name().unwrap().to_str().unwrap()
));
shim.push_str(
"
const wasmModule = new WebAssembly.Module(bytes);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
const wasm = wasmInstance.exports;
export const __wasm = wasm;
",
);
} else {
shim.push_str(&format!(
"
Expand All @@ -272,17 +278,16 @@ impl<'a> Context<'a> {
",
path.file_name().unwrap().to_str().unwrap()
));
shim.push_str(
"
const wasmModule = new WebAssembly.Module(bytes);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
wasm = wasmInstance.exports;
module.exports.__wasm = wasm;
",
);
}

shim.push_str(
"
const wasmModule = new WebAssembly.Module(bytes);
const wasmInstance = new WebAssembly.Instance(wasmModule, imports);
wasm = wasmInstance.exports;
module.exports.__wasm = wasm;
",
);

reset_indentation(&shim)
}

Expand Down Expand Up @@ -400,9 +405,7 @@ impl<'a> Context<'a> {

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

js.push_str("let wasm;\n");
Expand Down Expand Up @@ -442,13 +445,10 @@ impl<'a> Context<'a> {
}
}

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

if matches!(self.config.mode, OutputMode::Node { module: true }) {
let start = start.get_or_insert_with(String::new);
start.push_str(&self.generate_node_imports());
start.push_str(&self.generate_node_wasm_loading(Path::new(&format!(
"./{}_bg.wasm",
module_name
))));
}
if needs_manual_start {
start = Some("\nwasm.__wbindgen_start();\n".to_string());
start
.get_or_insert_with(String::new)
.push_str("\nwasm.__wbindgen_start();\n");
}
}

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

let mut push_with_newline = |s| {
Expand Down Expand Up @@ -556,9 +568,7 @@ impl<'a> Context<'a> {
}
}

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

OutputMode::Bundler { .. }
| OutputMode::Node {
experimental_modules: true,
}
| OutputMode::Node { module: true }
| OutputMode::Web
| OutputMode::Deno => {
for (module, items) in crate::sorted_iter(&self.js_imports) {
Expand Down Expand Up @@ -3216,12 +3224,10 @@ impl<'a> Context<'a> {
OutputMode::Web
| OutputMode::Bundler { .. }
| OutputMode::Deno
| OutputMode::Node {
experimental_modules: true,
} => "import.meta.url",
OutputMode::Node {
experimental_modules: false,
} => "require('url').pathToFileURL(__filename)",
| OutputMode::Node { module: true } => "import.meta.url",
OutputMode::Node { module: false } => {
"require('url').pathToFileURL(__filename)"
}
OutputMode::NoModules { .. } => {
prelude.push_str(
"if (script_src === undefined) {
Expand Down
68 changes: 28 additions & 40 deletions crates/cli-support/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ enum OutputMode {
Bundler { browser_only: bool },
Web,
NoModules { global: String },
Node { experimental_modules: bool },
Node { module: bool },
Deno,
}

Expand Down Expand Up @@ -154,23 +154,16 @@ impl Bindgen {

pub fn nodejs(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
if node {
self.switch_mode(
OutputMode::Node {
experimental_modules: false,
},
"--target nodejs",
)?;
self.switch_mode(OutputMode::Node { module: false }, "--target nodejs")?;
}
Ok(self)
}

pub fn nodejs_experimental_modules(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
pub fn nodejs_module(&mut self, node: bool) -> Result<&mut Bindgen, Error> {
if node {
self.switch_mode(
OutputMode::Node {
experimental_modules: true,
},
"--nodejs-experimental-modules",
OutputMode::Node { module: true },
"--target experimental-nodejs-module",
)?;
}
Ok(self)
Expand Down Expand Up @@ -548,22 +541,11 @@ impl OutputMode {
self,
OutputMode::Bundler { .. }
| OutputMode::Web
| OutputMode::Node {
experimental_modules: true,
}
| OutputMode::Node { module: true }
| OutputMode::Deno
)
}

fn nodejs_experimental_modules(&self) -> bool {
match self {
OutputMode::Node {
experimental_modules,
} => *experimental_modules,
_ => false,
}
}

fn nodejs(&self) -> bool {
matches!(self, OutputMode::Node { .. })
}
Expand All @@ -579,10 +561,7 @@ impl OutputMode {
fn esm_integration(&self) -> bool {
matches!(
self,
OutputMode::Bundler { .. }
| OutputMode::Node {
experimental_modules: true,
}
OutputMode::Bundler { .. } | OutputMode::Node { module: true }
)
}
}
Expand Down Expand Up @@ -687,11 +666,7 @@ impl Output {

// And now that we've got all our JS and TypeScript, actually write it
// out to the filesystem.
let extension = if gen.mode.nodejs_experimental_modules() {
"mjs"
} else {
"js"
};
let extension = "js";

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

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

write(
&js_path,
format!(
"import * as wasm from \"./{wasm_name}.wasm\";
if matches!(gen.mode, OutputMode::Node { .. }) {
write(
&js_path,
format!(
"
import {{ __wbg_set_wasm }} from \"./{js_name}\";
{start}
__wbg_set_wasm(wasm);
export * from \"./{js_name}\";",
),
)?;
} else {
write(
&js_path,
format!(
"
import * as wasm from \"./{wasm_name}.wasm\";
import {{ __wbg_set_wasm }} from \"./{js_name}\";
__wbg_set_wasm(wasm);
export * from \"./{js_name}\";
{start}"
),
)?;

),
)?;
}
write(out_dir.join(&js_name), reset_indentation(&gen.js))?;
} else {
write(&js_path, reset_indentation(&gen.js))?;
Expand Down
Loading

0 comments on commit 09e0e6d

Please sign in to comment.