Skip to content

Commit

Permalink
Updating build process to create .d.ts declaration files (#256)
Browse files Browse the repository at this point in the history
* Updating build process to create ts declarations

* Added no-namespaced-exports to wasm component

* Adding namespace exports option to transpile cmd

Also adding a test for the `--no-namespaced-exports` option

* Moving tsc build step fully to package.json

And other changes to make the build work on Windows

* Update README with --no-namespaced-exports

* Adding another test of namespaced exports option
  • Loading branch information
landonxjames authored Nov 20, 2023
1 parent dfe7173 commit 839d8f3
Show file tree
Hide file tree
Showing 14 changed files with 78 additions and 15 deletions.
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
- name: Install Rust
run: rustup update stable --no-self-update && rustup default stable
- name: Install wasm32-unknown-unknown target
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ package-lock.json
/test/output
/jco.sh
/docs/book
/src/**/*.d.ts
/src/**/*.d.ts.map
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ Options include:
* `--instantiation [mode]`: Instead of a direct ES module, export an `instantiate` function which can take the imports as an argument instead of implicit imports. The `instantiate` function can be async (with `--instantiation` or `--instantiation async`), or sync (with `--instantiation sync`).
* `--valid-lifting-optimization`: Internal validations are removed assuming that core Wasm binaries are valid components, providing a minor output size saving.
* `--tracing`: Emit tracing calls for all function entry and exits.
* `--no-namespaced-exports`: Removes exports of the type `test as "test:flavorful/test"` which are not compatible with typescript

#### Bindgen Crate

Expand Down
2 changes: 2 additions & 0 deletions crates/js-component-bindgen-component/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ impl Guest for JsComponentBindgenComponent {
.unwrap_or(options.compat.unwrap_or(false)),
valid_lifting_optimization: options.valid_lifting_optimization.unwrap_or(false),
tracing: options.tracing.unwrap_or(false),
no_namespaced_exports: options.no_namespaced_exports.unwrap_or(false),
};

let js_component_bindgen::Transpiled {
Expand Down Expand Up @@ -131,6 +132,7 @@ impl Guest for JsComponentBindgenComponent {
valid_lifting_optimization: false,
base64_cutoff: 0,
tracing: false,
no_namespaced_exports: false,
};

let files = generate_types(name, resolve, world, opts).map_err(|e| e.to_string())?;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ world js-component-bindgen {

/// Whether or not to emit `tracing` calls on function entry/exit.
tracing: option<bool>,

/// Whether to generate namespaced exports like `foo as "local:package/foo"`.
/// These exports can break typescript builds.
no-namespaced-exports: option<bool>,
}

variant wit {
Expand Down
23 changes: 16 additions & 7 deletions crates/js-component-bindgen/src/esm_bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use heck::ToLowerCamelCase;

use crate::names::{maybe_quote_id, maybe_quote_member, LocalNames};
use crate::source::Source;
use crate::{uwrite, uwriteln};
use crate::{uwrite, uwriteln, TranspileOpts};
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write;

Expand Down Expand Up @@ -131,6 +131,7 @@ impl EsmBindgen {
output: &mut Source,
instantiation: bool,
local_names: &mut LocalNames,
opts: &TranspileOpts,
) {
if self.exports.is_empty() {
if instantiation {
Expand Down Expand Up @@ -165,8 +166,6 @@ impl EsmBindgen {
for (alias, export_name) in &self.export_aliases {
if first {
first = false
} else {
uwrite!(output, ", ");
}
let local_name = match &self.exports[export_name] {
Binding::Local(local_name) => local_name,
Expand All @@ -175,17 +174,18 @@ impl EsmBindgen {
let alias_maybe_quoted = maybe_quote_id(alias);
if local_name == alias_maybe_quoted {
output.push_str(local_name);
uwrite!(output, ", ");
} else if instantiation {
uwrite!(output, "{alias_maybe_quoted}: {local_name}");
} else {
uwrite!(output, ", ");
} else if !self.contains_js_quote(&alias_maybe_quoted) || !opts.no_namespaced_exports {
uwrite!(output, "{local_name} as {alias_maybe_quoted}");
uwrite!(output, ", ");
}
}
for (export_name, export) in &self.exports {
if first {
first = false
} else {
uwrite!(output, ", ");
}
let local_name = match export {
Binding::Local(local_name) => local_name,
Expand All @@ -194,15 +194,24 @@ impl EsmBindgen {
let export_name_maybe_quoted = maybe_quote_id(export_name);
if local_name == export_name_maybe_quoted {
output.push_str(local_name);
uwrite!(output, ", ");
} else if instantiation {
uwrite!(output, "{export_name_maybe_quoted}: {local_name}");
} else {
uwrite!(output, ", ");
} else if !self.contains_js_quote(&export_name_maybe_quoted)
|| !opts.no_namespaced_exports
{
uwrite!(output, "{local_name} as {export_name_maybe_quoted}");
uwrite!(output, ", ");
}
}
uwrite!(output, " }}");
}

fn contains_js_quote(&self, js_string: &String) -> bool {
js_string.contains("\"") || js_string.contains("'") || js_string.contains("`")
}

fn binding_has_used(&self, binding: &Binding) -> bool {
match binding {
Binding::Interface(iface) => iface
Expand Down
9 changes: 7 additions & 2 deletions crates/js-component-bindgen/src/transpile_bindgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ pub struct TranspileOpts {
pub valid_lifting_optimization: bool,
/// Whether or not to emit `tracing` calls on function entry/exit.
pub tracing: bool,
/// Whether to generate namespaced exports like `foo as "local:package/foo"`.
/// These exports can break typescript builds.
pub no_namespaced_exports: bool,
}

#[derive(Default, Clone, Debug)]
Expand Down Expand Up @@ -152,7 +155,7 @@ pub fn transpile_bindgen(
instantiator.gen.src.js(&instantiator.src.js);
instantiator.gen.src.js_init(&instantiator.src.js_init);

instantiator.gen.finish_component(name, files);
instantiator.gen.finish_component(name, files, &opts);

let exports = instantiator
.gen
Expand All @@ -173,7 +176,7 @@ pub fn transpile_bindgen(
}

impl<'a> JsBindgen<'a> {
fn finish_component(&mut self, name: &str, files: &mut Files) {
fn finish_component(&mut self, name: &str, files: &mut Files, opts: &TranspileOpts) {
let mut output = source::Source::default();
let mut compilation_promises = source::Source::default();

Expand Down Expand Up @@ -262,6 +265,7 @@ impl<'a> JsBindgen<'a> {
&mut self.src.js,
self.opts.instantiation.is_some(),
&mut self.local_names,
opts,
);
uwrite!(
output,
Expand Down Expand Up @@ -312,6 +316,7 @@ impl<'a> JsBindgen<'a> {
&mut output,
self.opts.instantiation.is_some(),
&mut self.local_names,
opts,
);
}

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
},
"homepage": "https://github.com/bytecodealliance/jco#readme",
"scripts": {
"build": "cargo xtask build workspace",
"build": "cargo xtask build workspace && npm run build:typescript",
"build:typescript": "tsc -p tsconfig.json",
"build:types:preview2-shim": "cargo xtask generate wasi-types",
"lint": "eslint -c eslintrc.cjs lib/**/*.js packages/*/lib/**/*.js",
"test": "mocha -u tdd test/test.js --timeout 120000"
Expand Down
4 changes: 3 additions & 1 deletion src/cmd/transpile.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ async function wasm2Js (source) {
* js?: bool,
* minify?: bool,
* optimize?: bool,
* noNamespacedExports?: bool,
* optArgs?: string[],
* }} opts
* @returns {Promise<{ files: { [filename: string]: Uint8Array }, imports: string[], exports: [string, 'function' | 'instance'][] }>}
Expand Down Expand Up @@ -120,7 +121,8 @@ export async function transpileComponent (component, opts = {}) {
noNodejsCompat: !(opts.nodejsCompat ?? true),
noTypescript: opts.noTypescript || false,
tlaCompat: opts.tlaCompat ?? false,
base64Cutoff: opts.js ? 0 : opts.base64Cutoff ?? 5000
base64Cutoff: opts.js ? 0 : opts.base64Cutoff ?? 5000,
noNamespacedExports: !opts.namespacedExports,
});

let outDir = (opts.outDir ?? '').replace(/\\/g, '/');
Expand Down
1 change: 1 addition & 0 deletions src/jco.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ program.command('transpile')
.option('--js', 'output JS instead of core WebAssembly')
.addOption(new Option('-I, --instantiation [mode]', 'output for custom module instantiation').choices(['async', 'sync']).preset('async'))
.option('-q, --quiet', 'disable logging')
.option('--no-namespaced-exports', 'disable namespaced exports for typescript compatibility')
.option('--', 'for --optimize, custom wasm-opt arguments (defaults to best size optimization)')
.action(asyncAction(transpile));

Expand Down
26 changes: 25 additions & 1 deletion test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { deepStrictEqual, ok, strictEqual } from 'node:assert';
import { mkdir, readFile, rm, symlink, writeFile, mkdtemp } from 'node:fs/promises';
import { fileURLToPath, pathToFileURL } from 'url';
import { exec, jcoPath } from './helpers.js';
import { tmpdir } from 'node:os';
import { tmpdir, EOL } from 'node:os';
import { resolve, normalize, sep } from 'node:path';

export async function cliTest (fixtures) {
Expand Down Expand Up @@ -89,6 +89,30 @@ export async function cliTest (fixtures) {
ok(source.includes('export const $init'));
});

test('Transpile without namespaced exports', async () => {
const name = 'flavorful';
const { stderr } = await exec(jcoPath, 'transpile', `test/fixtures/components/${name}.component.wasm`, '--no-namespaced-exports', '--no-wasi-shim', '--name', name, '-o', outDir);
strictEqual(stderr, '');
const source = await readFile(`${outDir}/${name}.js`);
const finalLine = source.toString().split("\n").at(-1)
//Check final line is the export statement
ok(finalLine.toString().includes("export {"));
//Check that it does not contain the namespaced export
ok(!finalLine.toString().includes("test:flavorful/test"));
});

test('Transpile with namespaced exports', async () => {
const name = 'flavorful';
const { stderr } = await exec(jcoPath, 'transpile', `test/fixtures/components/${name}.component.wasm`, '--no-wasi-shim', '--name', name, '-o', outDir);
strictEqual(stderr, '');
const source = await readFile(`${outDir}/${name}.js`);
const finalLine = source.toString().split("\n").at(-1)
//Check final line is the export statement
ok(finalLine.toString().includes("export {"));
//Check that it does contain the namespaced export
ok(finalLine.toString().includes("test as 'test:flavorful/test'"));
});

test('Optimize', async () => {
const component = await readFile(`test/fixtures/components/flavorful.component.wasm`);
const { stderr, stdout } = await exec(jcoPath, 'opt', `test/fixtures/components/flavorful.component.wasm`, '-o', outFile);
Expand Down
11 changes: 11 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"include": ["src/**/*"],
"exclude": ["node_modules", "src/cmd/**", "src/*.d.ts"],
"compilerOptions": {
"allowJs": true,
"declaration": true,
"emitDeclarationOnly": true,
"declarationMap": true,
"skipLibCheck": true,
}
}
5 changes: 2 additions & 3 deletions xtask/src/build/jco.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
use anyhow::Context;
use anyhow::{Context, Result};
use std::{collections::HashMap, fs, io::Write, path::PathBuf};

use anyhow::Result;
use wit_component::ComponentEncoder;

pub(crate) fn run() -> Result<()> {
Expand Down Expand Up @@ -67,6 +65,7 @@ fn transpile(component_path: &str, name: String) -> Result<()> {
tla_compat: true,
valid_lifting_optimization: false,
tracing: false,
no_namespaced_exports: true,
};

let transpiled = js_component_bindgen::transpile(&adapted_component, opts)?;
Expand Down
1 change: 1 addition & 0 deletions xtask/src/generate/wasi_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ pub(crate) fn run() -> Result<()> {
valid_lifting_optimization: false,
base64_cutoff: 0,
tracing: false,
no_namespaced_exports: true,
};

let files = generate_types(name, resolve, world, opts)?;
Expand Down

0 comments on commit 839d8f3

Please sign in to comment.