Skip to content

Commit

Permalink
Introduce the build command in the CLI (#721)
Browse files Browse the repository at this point in the history
* Introduce the `build` command in the CLI

This commit introduces a `build` command in the CLI, which behaves
exactly as the compile command and introduces
a deprecation warning to the existing `compile` command.

Additionally, as a preparation for the CLI redesign discussed in
#702, this commit
introduces a small refactoring of the code generation process, by:

* Introducing a code generator builder, to abstract and validate all the
  code generation option combinations.
* Introducing proper static and dynamic code generator modules, making
  it easier to divide the responsilibities of each.

  The main motivation for the refactoring is to:

  * Make it easier to finalize the CLI redesign.
  * Share code between the `compile` and `build` command while they must
    be equivalent.
  * Make it easier to keep the `compile` command frozen while it becomes
    deprecated and at the same time evolve the `build` command
    independently.

NB: Given that the `compile` and `build` command are exactly the same,
and that this change is purely mechanical; this change doesn't introduce
integration tests for the `build` command. The plan is to introduce
tests once other options are added to the `build` command as part of the
CLI redesign. Alternatively, tests can be added, however, it would
require either duplicating the entire test suite or adding testing
infrastructure to minimize duplication, which will increase the size of
the change.

* Collapse `provider_version` call in `builder`

* Improve some documentation

* Clarify `source_compression` docs

* Stylistic changes

* Add NOTICE to help text of  `compile`

* Use `eprintln!` instead of `println!`

* Use `build` in the README.md
  • Loading branch information
saulecabrera authored Aug 14, 2024
1 parent c2135a0 commit ff4be7e
Show file tree
Hide file tree
Showing 12 changed files with 749 additions and 371 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function writeOutput(output) {
Create a WebAssembly binary from your JavaScript by:

```bash
javy compile index.js -o destination/index.wasm
javy build index.js -o destination/index.wasm
```

For more information on the commands you can run `javy --help`
Expand All @@ -147,12 +147,12 @@ $ echo '{ "n": 2, "bar": "baz" }' | wasmtime index.wasm
If you have a lot of JavaScript and you want to reduce compile times, try using the `--no-source-compression` flag. It will skip compressing the JavaScript source code when generating the Wasm module but will result in the Wasm module being larger.

```bash
javy compile index.js -o destination/index.wasm --no-source-compression
javy build index.js -o destination/index.wasm --no-source-compression
```

### Exporting functions

To export exported JavaScript functions, you can pass a WIT file and WIT world when running `javy compile`. Only ESM exports are supported (that is, Node.js/CommonJS exports are _not_ supported). For each exported JavaScript function, Javy will add an additional function export to the WebAssembly module. Exported functions with arguments and generators are not supported. Return values will also be dropped and not returned. The Wasm module generated is a core Wasm module, **not** a Wasm component.
To export exported JavaScript functions, you can pass a WIT file and WIT world when running `javy build`. Only ESM exports are supported (that is, Node.js/CommonJS exports are _not_ supported). For each exported JavaScript function, Javy will add an additional function export to the WebAssembly module. Exported functions with arguments and generators are not supported. Return values will also be dropped and not returned. The Wasm module generated is a core Wasm module, **not** a Wasm component.

An example looks like:

Expand All @@ -176,13 +176,13 @@ world index-world {

In the terminal:
```bash
$ javy compile index.js --wit index.wit -n index-world -o index.wasm
$ javy build index.js --wit index.wit -n index-world -o index.wasm
$ wasmtime run --invoke foo index.wasm
Hello world!
Hello from foo!
```

The WIT package name and WIT world name do not matter as long as they are present and syntactically correct WIT (that is, it needs to be two names separated by a `:`). The name of the WIT world (that is, the value after `world` and before `{`) must be passed as the `-n` argument. The `-n` argument identifies the WIT world in the WIT file for the Wasm module generated by `javy compile`.
The WIT package name and WIT world name do not matter as long as they are present and syntactically correct WIT (that is, it needs to be two names separated by a `:`). The name of the WIT world (that is, the value after `world` and before `{`) must be passed as the `-n` argument. The `-n` argument identifies the WIT world in the WIT file for the Wasm module generated by `javy build`.

#### Exports with multiple words

Expand All @@ -206,7 +206,7 @@ world index {

In the terminal:
```bash
$ javy compile index.js --wit index.wit -n index -o index.wasm
$ javy build index.js --wit index.wit -n index -o index.wasm
$ wasmtime run --invoke foo-bar index.wasm
In foo-bar
```
Expand All @@ -233,7 +233,7 @@ world index {

In the terminal:
```bash
$ javy compile index.js --wit index.wit -n index -o index.wasm
$ javy build index.js --wit index.wit -n index -o index.wasm
$ wasmtime run --invoke default index.wasm
In default
```
Expand Down Expand Up @@ -271,7 +271,7 @@ The `javy_quickjs_provider.wasm` module is available as an asset on the Javy rel

```
$ echo 'console.log("hello world!");' > my_code.js
$ javy compile -d -o my_code.wasm my_code.js
$ javy build -d -o my_code.wasm my_code.js
$ javy emit-provider -o provider.wasm
$ wasmtime run --preload javy_quickjs_provider_v2=provider.wasm my_code.wasm
hello world!
Expand Down
115 changes: 115 additions & 0 deletions crates/cli/src/codegen/builder.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
use crate::codegen::{CodeGen, CodeGenType, DynamicGenerator, StaticGenerator};
use anyhow::{bail, Result};
use std::path::PathBuf;

/// Options for using WIT in the code generation process.
#[derive(Default)]
pub(crate) struct WitOptions {
/// The path of the .wit file to use.
pub path: Option<PathBuf>,
/// The name of the wit world to use.
pub world: Option<String>,
}

impl WitOptions {
pub fn from_tuple(opts: (Option<PathBuf>, Option<String>)) -> Result<Self> {
match opts {
(None, None) => Ok(Self {
path: None,
world: None,
}),
(None, Some(_)) => Ok(Self {
path: None,
world: None,
}),
(Some(_), None) => bail!("Must provide WIT world when providing WIT file"),
(path, world) => Ok(Self { path, world }),
}
}

/// Whether WIT options were defined.
pub fn defined(&self) -> bool {
self.path.is_some() && self.world.is_some()
}

/// Unwraps a refernce to the .wit file path.
pub fn unwrap_path(&self) -> &PathBuf {
self.path.as_ref().unwrap()
}

/// Unwraps a reference to the WIT world name.
pub fn unwrap_world(&self) -> &String {
self.world.as_ref().unwrap()
}
}

/// A code generation builder.
#[derive(Default)]
pub(crate) struct CodeGenBuilder {
/// The QuickJS provider module version.
provider_version: Option<&'static str>,
/// WIT options for code generation.
wit_opts: WitOptions,
/// Whether to compress the original JS source.
source_compression: bool,
}

impl CodeGenBuilder {
/// Create a new [`CodeGenBuilder`].
pub fn new() -> Self {
Self::default()
}

/// Set the provider version.
pub fn provider_version(&mut self, v: &'static str) -> &mut Self {
self.provider_version = Some(v);
self
}

/// Set the wit options.
pub fn wit_opts(&mut self, opts: WitOptions) -> &mut Self {
self.wit_opts = opts;
self
}

/// Whether to compress the JS source.
pub fn source_compression(&mut self, compress: bool) -> &mut Self {
self.source_compression = compress;
self
}

/// Build a [`CodeGenerator`].
pub fn build<T>(self) -> Result<Box<dyn CodeGen>>
where
T: CodeGen,
{
match T::classify() {
CodeGenType::Static => self.build_static(),
CodeGenType::Dynamic => self.build_dynamic(),
}
}

fn build_static(self) -> Result<Box<dyn CodeGen>> {
let mut static_gen = Box::new(StaticGenerator::new());

static_gen.source_compression = self.source_compression;
static_gen.wit_opts = self.wit_opts;

Ok(static_gen)
}

fn build_dynamic(self) -> Result<Box<dyn CodeGen>> {
let mut dynamic_gen = Box::new(DynamicGenerator::new());

if let Some(v) = self.provider_version {
dynamic_gen.import_namespace = String::from("javy_quickjs_provider_v");
dynamic_gen.import_namespace.push_str(v);
} else {
bail!("Provider version not specified")
}

dynamic_gen.wit_opts = self.wit_opts;

Ok(dynamic_gen)
}
}
Loading

0 comments on commit ff4be7e

Please sign in to comment.