Skip to content

Commit

Permalink
feat(rolldown): support compile to wasi (#467)
Browse files Browse the repository at this point in the history
This pull request is working on adding a wasi compile target for the rolldown itself.
  • Loading branch information
Brooooooklyn committed Apr 16, 2024
1 parent 07a175b commit da45d40
Show file tree
Hide file tree
Showing 26 changed files with 704 additions and 196 deletions.
27 changes: 23 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,30 @@ jobs:
with:
targets: wasm32-wasi-preview1-threads
cache-base: main
- name: Cargo Check
run: cargo check -p rolldown --target wasm32-wasi-preview1-threads
- name: Install pnpm
uses: pnpm/action-setup@v3

- name: Install node
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- name: Install dependencies
# Node.js wasi runtime has bug with `std::fs::read_link`
run: |
touch .npmrc
echo "node-linker=hoisted" >> .npmrc
pnpm install
- name: Cargo Build
run: cargo build -p rolldown --target wasm32-wasi-preview1-threads
- name: Build wasi
run: |
pnpm --filter=rolldown build-binding:wasi
pnpm --filter=rolldown build-node
pnpm --filter=@example/basic build
env:
NAPI_RS_FORCE_WASI: 1
RUST_LOG: trace

cargo-test:
needs: changes
Expand Down
13 changes: 9 additions & 4 deletions .github/workflows/reusable-release-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ jobs:
build: |
export CARGO_BUILD_TARGET=aarch64-unknown-linux-musl;
pnpm ci:build-release-binding -x --target aarch64-unknown-linux-musl
- os: ubuntu-latest
target: wasm32-wasi-preview1-threads
build: |
export CARGO_BUILD_TARGET=wasm32-wasi-preview1-threads;
pnpm --filter rolldown build-binding:wasi
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -79,9 +84,7 @@ jobs:
uses: moonrepo/setup-rust@v1
with:
cache-base: main

- name: Add Rust Target
run: rustup target add ${{ matrix.target }}
targets: ${{ matrix.target }}

- uses: goto-bus-stop/setup-zig@v2
if: ${{ contains(matrix.target, 'musl') }}
Expand Down Expand Up @@ -119,7 +122,9 @@ jobs:
with:
if-no-files-found: error
name: bindings-${{ matrix.target }}
path: packages/rolldown/src/rolldown-binding.*.node
path: |
packages/rolldown/src/rolldown-binding.*.node
packages/rolldown/src/rolldown-binding.*.wasm
build-node-packages:
strategy:
Expand Down
6 changes: 6 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ temp
tmp
pnpm-lock.yaml
CHANGELOG.md
packages/rolldown/src/binding.d.ts
packages/rolldown/src/binding.js
packages/rolldown/src/rolldown-binding.wasi-browser.js
packages/rolldown/src/rolldown-binding.wasi.cjs
packages/rolldown/src/wasi-worker-browser.mjs
packages/rolldown/src/wasi-worker.mjs
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ repository = "https://github.com/rolldown/rolldown"
debug = true
inherits = "release"

[profile.release-wasi]
codegen-units = 16
debug = true
inherits = "release"
lto = "thin"
opt-level = "z"
strip = "none"

[workspace.lints.rust]

[workspace.lints.clippy]
Expand Down
14 changes: 8 additions & 6 deletions crates/rolldown/src/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,25 @@ impl Bundler {

self.plugin_driver.write_bundle(&output.assets).await?;

self.fs.create_dir_all(dir.as_path()).unwrap_or_else(|_| {
panic!(
self.fs.create_dir_all(dir.as_path()).map_err(|err| {
anyhow::anyhow!(
"Could not create directory for output chunks: {:?} \ncwd: {}",
dir.as_path(),
self.options.cwd.display()
)
});
.context(err)
})?;
for chunk in &output.assets {
let dest = dir.as_path().join(chunk.file_name());
if let Some(p) = dest.parent() {
if !self.fs.exists(p) {
self.fs.create_dir_all(p).unwrap();
}
};
self.fs.write(dest.as_path(), chunk.content().as_bytes()).unwrap_or_else(|_| {
panic!("Failed to write file in {:?}", dir.as_path().join(chunk.file_name()))
});
self.fs.write(dest.as_path(), chunk.content().as_bytes()).map_err(|err| {
anyhow::anyhow!("Failed to write file in {:?}", dir.as_path().join(chunk.file_name()))
.context(err)
})?;
}

Ok(output)
Expand Down
9 changes: 3 additions & 6 deletions crates/rolldown/src/module_loader/module_loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,16 @@ impl ModuleLoader {
let runtime_id = intermediate_normal_modules.alloc_module_id(&mut symbols);

let task = RuntimeNormalModuleTask::new(runtime_id, tx_to_runtime_module);
let handle = tokio::runtime::Handle::current();

#[cfg(target_family = "wasm")]
{
// could not block_on/spawn the main thread in WASI
std::thread::spawn(move || {
handle.block_on(async { task.run() });
});
task.run()
}
// task is sync, but execution time is too short at the moment
// so we are using spawn instead of spawn_blocking here to avoid an additional blocking thread creation within tokio
#[cfg(not(target_family = "wasm"))]
{
let handle = tokio::runtime::Handle::current();
handle.spawn(async { task.run() });
}

Expand Down Expand Up @@ -149,7 +146,7 @@ impl ModuleLoader {
let handle = tokio::runtime::Handle::current();
// could not block_on/spawn the main thread in WASI
std::thread::spawn(move || {
handle.block_on(task.run());
handle.spawn(task.run());
});
}
#[cfg(not(target_family = "wasm"))]
Expand Down
13 changes: 12 additions & 1 deletion crates/rolldown_binding/src/bundler.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#[cfg(not(target_family = "wasm"))]
use crate::worker_manager::WorkerManager;
use crate::{
options::{BindingInputOptions, BindingOutputOptions},
parallel_js_plugin_registry::ParallelJsPluginRegistry,
types::binding_outputs::BindingOutputs,
utils::{normalize_binding_options::normalize_binding_options, try_init_custom_trace_subscriber},
worker_manager::WorkerManager,
};
use napi::{tokio::sync::Mutex, Env};
use napi_derive::napi;
Expand All @@ -20,6 +21,7 @@ pub struct Bundler {
#[napi]
impl Bundler {
#[napi(constructor)]
#[cfg_attr(target_family = "wasm", allow(unused))]
pub fn new(
env: Env,
mut input_options: BindingInputOptions,
Expand All @@ -30,18 +32,27 @@ impl Bundler {

let log_level = input_options.log_level.take().unwrap_or_else(|| "info".to_string());

#[cfg(target_family = "wasm")]
// if we don't perform this warmup, the following call to `std::fs` will stuck
if let Ok(_) = std::fs::metadata(std::env::current_dir()?) {};

#[cfg(not(target_family = "wasm"))]
let worker_count =
parallel_plugins_registry.as_ref().map(|registry| registry.worker_count).unwrap_or_default();
#[cfg(not(target_family = "wasm"))]
let parallel_plugins_map =
parallel_plugins_registry.map(|registry| registry.take_plugin_values());

#[cfg(not(target_family = "wasm"))]
let worker_manager =
if worker_count > 0 { Some(WorkerManager::new(worker_count)) } else { None };

let ret = normalize_binding_options(
input_options,
output_options,
#[cfg(not(target_family = "wasm"))]
parallel_plugins_map,
#[cfg(not(target_family = "wasm"))]
worker_manager,
)?;

Expand Down
1 change: 1 addition & 0 deletions crates/rolldown_binding/src/options/plugin/js_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl Deref for JsPlugin {
}

impl JsPlugin {
#[cfg_attr(target_family = "wasm", allow(unused))]
pub(super) fn new(inner: BindingPluginOptions) -> Self {
Self { inner }
}
Expand Down
15 changes: 13 additions & 2 deletions crates/rolldown_binding/src/options/plugin/parallel_js_plugin.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
use std::{borrow::Cow, sync::Arc};
#[cfg(not(target_family = "wasm"))]
use std::borrow::Cow;
use std::sync::Arc;

#[cfg(not(target_family = "wasm"))]
use futures::future::{self, BoxFuture};
#[cfg(not(target_family = "wasm"))]
use rolldown_plugin::Plugin;

use crate::worker_manager::WorkerManager;

use super::{BindingPluginOptions, JsPlugin};
#[cfg(not(target_family = "wasm"))]
use super::BindingPluginOptions;
use super::JsPlugin;

#[derive(Debug)]
#[cfg_attr(target_family = "wasm", allow(unused))]
pub struct ParallelJsPlugin {
plugins: Box<[JsPlugin]>,
worker_manager: Arc<WorkerManager>,
}

#[cfg(not(target_family = "wasm"))]
impl ParallelJsPlugin {
pub fn new_boxed(
plugins: Vec<BindingPluginOptions>,
Expand All @@ -26,12 +34,14 @@ impl ParallelJsPlugin {
&self.plugins[0]
}

#[cfg(not(target_family = "wasm"))]
async fn run_single<'a, R, F: FnOnce(&'a JsPlugin) -> BoxFuture<R>>(&'a self, f: F) -> R {
let permit = self.worker_manager.acquire().await;
let plugin = &self.plugins[permit.worker_index() as usize];
f(plugin).await
}

#[cfg(not(target_family = "wasm"))]
async fn run_all<'a, R, E: std::fmt::Debug, F: FnMut(&'a JsPlugin) -> BoxFuture<Result<R, E>>>(
&'a self,
f: F,
Expand All @@ -46,6 +56,7 @@ impl ParallelJsPlugin {
}
}

#[cfg(not(target_family = "wasm"))]
#[async_trait::async_trait]
impl Plugin for ParallelJsPlugin {
fn name(&self) -> Cow<'static, str> {
Expand Down
27 changes: 23 additions & 4 deletions crates/rolldown_binding/src/utils/normalize_binding_options.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use std::{path::PathBuf, sync::Arc};
use std::path::PathBuf;
#[cfg(not(target_family = "wasm"))]
use std::sync::Arc;

#[cfg_attr(target_family = "wasm", allow(unused))]
use crate::{
options::plugin::JsPlugin,
options::plugin::ParallelJsPlugin,
Expand All @@ -9,6 +12,7 @@ use crate::{
use rolldown::{AddonOutputOption, BundlerOptions, Platform};
use rolldown_plugin::BoxPlugin;

#[cfg_attr(target_family = "wasm", allow(unused))]
pub struct NormalizeBindingOptionsReturn {
pub bundler_options: BundlerOptions,
pub plugins: Vec<BoxPlugin>,
Expand All @@ -31,8 +35,10 @@ fn normalize_addon_option(
pub fn normalize_binding_options(
input_options: crate::options::BindingInputOptions,
output_options: crate::options::BindingOutputOptions,
mut parallel_plugins_map: Option<crate::parallel_js_plugin_registry::PluginValues>,
worker_manager: Option<WorkerManager>,
#[cfg(not(target_family = "wasm"))] mut parallel_plugins_map: Option<
crate::parallel_js_plugin_registry::PluginValues,
>,
#[cfg(not(target_family = "wasm"))] worker_manager: Option<WorkerManager>,
) -> napi::Result<NormalizeBindingOptionsReturn> {
debug_assert!(PathBuf::from(&input_options.cwd) != PathBuf::from("/"), "{input_options:#?}");
let cwd = PathBuf::from(input_options.cwd);
Expand Down Expand Up @@ -69,9 +75,11 @@ pub fn normalize_binding_options(
..Default::default()
};

#[cfg(not(target_family = "wasm"))]
// Deal with plugins
let worker_manager = worker_manager.map(Arc::new);

#[cfg(not(target_family = "wasm"))]
let plugins: Vec<BoxPlugin> = input_options
.plugins
.into_iter()
Expand All @@ -80,7 +88,10 @@ pub fn normalize_binding_options(
.map(|(index, plugin)| {
plugin.map_or_else(
|| {
let plugins = parallel_plugins_map.as_mut().unwrap().remove(&index).unwrap();
let plugins = parallel_plugins_map
.as_mut()
.and_then(|plugin| plugin.remove(&index))
.unwrap_or_default();
let worker_manager = worker_manager.as_ref().unwrap();
ParallelJsPlugin::new_boxed(plugins, Arc::clone(worker_manager))
},
Expand All @@ -89,5 +100,13 @@ pub fn normalize_binding_options(
})
.collect::<Vec<_>>();

#[cfg(target_family = "wasm")]
let plugins: Vec<BoxPlugin> = input_options
.plugins
.into_iter()
.chain(output_options.plugins)
.filter_map(|plugin| plugin.map(|plugin| JsPlugin::new_boxed(plugin)))
.collect::<Vec<_>>();

Ok(NormalizeBindingOptionsReturn { bundler_options, plugins })
}
7 changes: 7 additions & 0 deletions crates/rolldown_binding/src/worker_manager.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use async_channel::{Receiver, Sender};

#[derive(Debug)]
#[allow(unused)]
pub struct WorkerManager {
free_workers_sender: Sender<u16>,
free_workers_receiver: Receiver<u16>,
worker_count: u16,
}

#[cfg(not(target_family = "wasm"))]
impl WorkerManager {
pub fn new(worker_count: u16) -> Self {
let (sender, receiver) = async_channel::unbounded::<u16>();
Expand Down Expand Up @@ -34,29 +36,34 @@ impl WorkerManager {
}
}

#[cfg(not(target_family = "wasm"))]
pub struct WorkerSemaphorePermit {
worker_index: u16,
sender: Sender<u16>,
}

#[cfg(not(target_family = "wasm"))]
impl WorkerSemaphorePermit {
pub fn worker_index(&self) -> u16 {
self.worker_index
}
}

#[cfg(not(target_family = "wasm"))]
impl Drop for WorkerSemaphorePermit {
fn drop(&mut self) {
let worker_index = self.worker_index;
self.sender.send_blocking(worker_index).expect("failed to send worker_index");
}
}

#[cfg(not(target_family = "wasm"))]
pub struct WorkerAllSemaphorePermit {
worker_count: u16,
sender: Sender<u16>,
}

#[cfg(not(target_family = "wasm"))]
impl Drop for WorkerAllSemaphorePermit {
fn drop(&mut self) {
let worker_count = self.worker_count;
Expand Down
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"deconflict",
"deoptimized",
"doctest",
"emnapi",
"esbuild",
"execa",
"Finalizer",
Expand Down
1 change: 1 addition & 0 deletions packages/rolldown/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Binariy files generated by NAPI-RS
*.node
*.wasm

0 comments on commit da45d40

Please sign in to comment.