Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add node module output format #4027

Merged
merged 16 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
}
}
Systemcluster marked this conversation as resolved.
Show resolved Hide resolved
} 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 } => {
daxpedda marked this conversation as resolved.
Show resolved Hide resolved
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");
}
Systemcluster marked this conversation as resolved.
Show resolved Hide resolved
}

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"
};
Systemcluster marked this conversation as resolved.
Show resolved Hide resolved
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