Skip to content

Commit

Permalink
feat: add buildStart buildEnd hook (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
underfin committed Nov 10, 2023
1 parent 5e250b8 commit e878c3b
Show file tree
Hide file tree
Showing 17 changed files with 180 additions and 22 deletions.
2 changes: 1 addition & 1 deletion crates/rolldown/src/bundler/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl<T: FileSystemExt + Default + 'static> Bundler<T> {
tracing::trace!("NormalizedOutputOptions: {output_options:#?}",);

let mut graph = Graph::default();
graph.generate_module_graph(&self.input_options, Arc::clone(&self.plugin_driver), fs).await?;
graph.build(&self.input_options, Arc::clone(&self.plugin_driver), fs).await?;

let mut bundle = Bundle::new(&mut graph, &output_options);
let assets = bundle.generate(&self.input_options);
Expand Down
31 changes: 28 additions & 3 deletions crates/rolldown/src/bundler/graph/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
runtime::Runtime,
},
error::BatchedResult,
plugin::args::HookBuildEndArgs,
};
use rolldown_common::ModuleId;
use rolldown_fs::FileSystemExt;
Expand All @@ -24,20 +25,44 @@ pub struct Graph {
}

impl Graph {
pub async fn generate_module_graph<T: FileSystemExt + Default + 'static>(
pub async fn build<T: FileSystemExt + Default + 'static>(
&mut self,
input_options: &NormalizedInputOptions,
plugin_driver: SharedPluginDriver,
fs: Arc<T>,
) -> BatchedResult<()> {
ModuleLoader::new(input_options, plugin_driver, self, fs).fetch_all_modules().await?;
plugin_driver.build_start().await?;

tracing::trace!("{:#?}", self);
if let Err(e) = self.generate_module_graph(input_options, Arc::clone(&plugin_driver), fs).await
{
let error = e.get().expect("should have a error");
plugin_driver
.build_end(Some(&HookBuildEndArgs {
error: format!("{:?}\n{:?}", error.code(), error.to_diagnostic().print_to_string()),
}))
.await?;
return Err(e);
}

self.sort_modules();

self.link();

plugin_driver.build_end(None).await?;

Ok(())
}

pub async fn generate_module_graph<T: FileSystemExt + Default + 'static>(
&mut self,
input_options: &NormalizedInputOptions,
plugin_driver: SharedPluginDriver,
fs: Arc<T>,
) -> BatchedResult<()> {
ModuleLoader::new(input_options, plugin_driver, self, fs).fetch_all_modules().await?;

tracing::trace!("{:#?}", self);

Ok(())
}

Expand Down
22 changes: 20 additions & 2 deletions crates/rolldown/src/bundler/plugin_driver/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
use std::sync::Arc;

use crate::{
plugin::plugin::BoxPlugin, HookLoadArgs, HookLoadReturn, HookResolveIdArgs, HookResolveIdReturn,
HookTransformArgs, HookTransformReturn, PluginContext,
plugin::{
args::HookBuildEndArgs,
plugin::{BoxPlugin, HookNoopReturn},
},
HookLoadArgs, HookLoadReturn, HookResolveIdArgs, HookResolveIdReturn, HookTransformArgs,
HookTransformReturn, PluginContext,
};

pub type SharedPluginDriver = Arc<PluginDriver>;
Expand All @@ -16,6 +20,13 @@ impl PluginDriver {
Self { plugins }
}

pub async fn build_start(&self) -> HookNoopReturn {
for plugin in &self.plugins {
plugin.build_start(&mut PluginContext::new()).await?;
}
Ok(())
}

pub async fn resolve_id(&self, args: &HookResolveIdArgs<'_>) -> HookResolveIdReturn {
for plugin in &self.plugins {
if let Some(r) = plugin.resolve_id(&mut PluginContext::new(), args).await? {
Expand All @@ -42,4 +53,11 @@ impl PluginDriver {
}
Ok(None)
}

pub async fn build_end(&self, _args: Option<&HookBuildEndArgs>) -> HookNoopReturn {
for plugin in &self.plugins {
plugin.build_end(&mut PluginContext::new(), _args).await?;
}
Ok(())
}
}
4 changes: 4 additions & 0 deletions crates/rolldown/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ impl BatchedErrors {
self.0.push(err);
}

pub fn get(&self) -> Option<&BuildError> {
self.0.get(0)
}

/// Try to take the Err() of the given result and return Some(T) if it's Ok(T).
pub fn take_err_from<T>(&mut self, res: Result<T, rolldown_error::BuildError>) -> Option<T> {
match res {
Expand Down
4 changes: 2 additions & 2 deletions crates/rolldown/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ pub use crate::{
},
},
plugin::{
args::{HookLoadArgs, HookResolveIdArgs, HookTransformArgs},
args::{HookBuildEndArgs, HookLoadArgs, HookResolveIdArgs, HookTransformArgs},
context::PluginContext,
output::{HookLoadOutput, HookResolveIdOutput},
plugin::{HookLoadReturn, HookResolveIdReturn, HookTransformReturn, Plugin},
plugin::{HookLoadReturn, HookNoopReturn, HookResolveIdReturn, HookTransformReturn, Plugin},
},
};
5 changes: 5 additions & 0 deletions crates/rolldown/src/plugin/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ pub struct HookTransformArgs<'a> {
pub struct HookLoadArgs<'a> {
pub id: &'a str,
}

#[derive(Debug, Default)]
pub struct HookBuildEndArgs {
pub error: String,
}
17 changes: 16 additions & 1 deletion crates/rolldown/src/plugin/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,26 @@ use std::{borrow::Cow, fmt::Debug};
use rolldown_error::BuildError;

use super::{
args::{HookLoadArgs, HookResolveIdArgs, HookTransformArgs},
args::{HookBuildEndArgs, HookLoadArgs, HookResolveIdArgs, HookTransformArgs},
context::PluginContext,
output::{HookLoadOutput, HookResolveIdOutput},
};

pub type HookResolveIdReturn = Result<Option<HookResolveIdOutput>, BuildError>;
pub type HookTransformReturn = Result<Option<HookLoadOutput>, BuildError>;
pub type HookLoadReturn = Result<Option<HookLoadOutput>, BuildError>;
pub type HookNoopReturn = Result<(), BuildError>;

#[async_trait::async_trait]
pub trait Plugin: Debug + Send + Sync {
fn name(&self) -> Cow<'static, str>;

// The `option` hook consider call at node side.

async fn build_start(&self, _ctx: &mut PluginContext) -> HookNoopReturn {
Ok(())
}

async fn resolve_id(
&self,
_ctx: &mut PluginContext,
Expand All @@ -35,6 +42,14 @@ pub trait Plugin: Debug + Send + Sync {
) -> HookTransformReturn {
Ok(None)
}

async fn build_end(
&self,
_ctx: &mut PluginContext,
_args: Option<&HookBuildEndArgs>,
) -> HookNoopReturn {
Ok(())
}
}

pub type BoxPlugin = Box<dyn Plugin>;
2 changes: 2 additions & 0 deletions crates/rolldown_binding/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

export interface PluginOptions {
name: string
buildStart?: () => Promise<void>
resolveId?: (
specifier: string,
importer?: string,
) => Promise<undefined | ResolveIdResult>
load?: (id: string) => Promise<undefined | SourceResult>
transform?: (id: string, code: string) => Promise<undefined | SourceResult>
buildEnd?: (error: string) => Promise<void>
}
export interface ResolveIdResult {
id: string
Expand Down
10 changes: 10 additions & 0 deletions crates/rolldown_binding/src/options/input_options/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ use serde::Deserialize;
pub struct PluginOptions {
pub name: String,

#[derivative(Debug = "ignore")]
#[serde(skip_deserializing)]
#[napi(ts_type = "() => Promise<void>")]
pub build_start: Option<JsFunction>,

#[derivative(Debug = "ignore")]
#[serde(skip_deserializing)]
#[napi(
Expand All @@ -25,6 +30,11 @@ pub struct PluginOptions {
#[serde(skip_deserializing)]
#[napi(ts_type = "(id: string, code: string) => Promise<undefined | SourceResult>")]
pub transform: Option<JsFunction>,

#[derivative(Debug = "ignore")]
#[serde(skip_deserializing)]
#[napi(ts_type = "(error: string) => Promise<void>")]
pub build_end: Option<JsFunction>,
}

#[napi_derive::napi(object)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,43 @@ use rolldown::Plugin;

use super::plugin::{PluginOptions, ResolveIdResult, SourceResult};

pub type BuildStartCallback = JsCallback<(), ()>;
pub type ResolveIdCallback = JsCallback<(String, Option<String>), Option<ResolveIdResult>>;
pub type LoadCallback = JsCallback<(String, Option<String>), Option<SourceResult>>;
pub type LoadCallback = JsCallback<(String,), Option<SourceResult>>;
pub type TransformCallback = JsCallback<(String, String), Option<SourceResult>>;
pub type BuildEndCallback = JsCallback<(Option<String>,), ()>;

#[derive(Derivative)]
#[derivative(Debug)]
pub struct JsAdapterPlugin {
pub name: String,
#[derivative(Debug = "ignore")]
build_start_fn: Option<BuildStartCallback>,
#[derivative(Debug = "ignore")]
resolve_id_fn: Option<ResolveIdCallback>,
#[derivative(Debug = "ignore")]
load_fn: Option<LoadCallback>,
#[derivative(Debug = "ignore")]
transform_fn: Option<TransformCallback>,
#[derivative(Debug = "ignore")]
build_end_fn: Option<BuildEndCallback>,
}

impl JsAdapterPlugin {
pub fn new(option: PluginOptions) -> napi::Result<Self> {
let build_start_fn = option.build_start.as_ref().map(BuildStartCallback::new).transpose()?;
let resolve_id_fn = option.resolve_id.as_ref().map(ResolveIdCallback::new).transpose()?;
let load_fn = option.load.as_ref().map(LoadCallback::new).transpose()?;
let transform_fn = option.transform.as_ref().map(TransformCallback::new).transpose()?;
Ok(Self { name: option.name, resolve_id_fn, load_fn, transform_fn })
let build_end_fn = option.build_end.as_ref().map(BuildEndCallback::new).transpose()?;
Ok(Self {
name: option.name,
build_start_fn,
resolve_id_fn,
load_fn,
transform_fn,
build_end_fn,
})
}

pub fn new_boxed(option: PluginOptions) -> napi::Result<Box<dyn Plugin>> {
Expand All @@ -42,6 +57,14 @@ impl Plugin for JsAdapterPlugin {
Cow::Owned(self.name.to_string())
}

#[allow(clippy::redundant_closure_for_method_calls)]
async fn build_start(&self, _ctx: &mut rolldown::PluginContext) -> rolldown::HookNoopReturn {
if let Some(cb) = &self.build_start_fn {
cb.call_async(()).await.map_err(|e| e.into_bundle_error())?;
}
Ok(())
}

#[allow(clippy::redundant_closure_for_method_calls)]
async fn resolve_id(
&self,
Expand All @@ -67,8 +90,7 @@ impl Plugin for JsAdapterPlugin {
args: &rolldown::HookLoadArgs,
) -> rolldown::HookLoadReturn {
if let Some(cb) = &self.load_fn {
let res =
cb.call_async((args.id.to_string(), None)).await.map_err(|e| e.into_bundle_error())?;
let res = cb.call_async((args.id.to_string(),)).await.map_err(|e| e.into_bundle_error())?;
Ok(res.map(Into::into))
} else {
Ok(None)
Expand All @@ -91,4 +113,18 @@ impl Plugin for JsAdapterPlugin {
Ok(None)
}
}

#[allow(clippy::redundant_closure_for_method_calls)]
async fn build_end(
&self,
_ctx: &mut rolldown::PluginContext,
args: Option<&rolldown::HookBuildEndArgs>,
) -> rolldown::HookNoopReturn {
if let Some(cb) = &self.build_end_fn {
cb.call_async((args.map(|a| a.error.to_string()),))
.await
.map_err(|e| e.into_bundle_error())?;
}
Ok(())
}
}
6 changes: 6 additions & 0 deletions crates/rolldown_binding/src/utils/into_js_unknown_vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ impl_tuple_to_vec!(A, B, C, D, E, F, G);
impl_tuple_to_vec!(A, B, C, D, E, F, G, H);
impl_tuple_to_vec!(A, B, C, D, E, F, G, H, I);
impl_tuple_to_vec!(A, B, C, D, E, F, G, H, I, J);

impl IntoJsUnknownVec for () {
fn into_js_unknown_vec(self, _env: &Env) -> napi::Result<Vec<JsUnknown>> {
Ok(vec![])
}
}
4 changes: 2 additions & 2 deletions crates/rolldown_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repository.workspace = true
string_wizard = { workspace = true }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
async-scoped = { workspace = true, features = ["use-tokio"] }
async-scoped = { workspace = true, features = ["use-tokio"] }

[target.'cfg(target_arch = "wasm32")'.dependencies]
futures = { workspace = true, features = ["executor"]}
futures = { workspace = true, features = ["executor"] }
35 changes: 33 additions & 2 deletions packages/rolldown-core/src/options/create-build-plugin-adapter.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,48 @@
import type { Plugin } from '../rollup-types'
import type { Plugin, NormalizedInputOptions } from '../rollup-types'
import type {
PluginOptions,
SourceResult,
ResolveIdResult,
} from '@rolldown/node-binding'
import { unimplemented } from '../utils'

export function createBuildPluginAdapter(plugin: Plugin): PluginOptions {
export function createBuildPluginAdapter(
plugin: Plugin,
options: NormalizedInputOptions,
): PluginOptions {
return {
name: plugin.name ?? 'unknown',
buildStart: buildStart(plugin.buildStart, options),
resolveId: resolveId(plugin.resolveId),
load: load(plugin.load),
transform: transform(plugin.transform),
buildEnd: buildEnd(plugin.buildEnd),
}
}

function buildStart(
hook: Plugin['buildStart'],
options: NormalizedInputOptions,
) {
if (hook) {
if (typeof hook !== 'function') {
return unimplemented()
}
return async () => {
// Here use `Object.freeze` to prevent plugin from modifying the options.
await hook.call({} as any, Object.freeze(options))
}
}
}

function buildEnd(hook: Plugin['buildEnd']) {
if (hook) {
if (typeof hook !== 'function') {
return unimplemented()
}
return async (e: string) => {
await hook.call({} as any, new Error(e))
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion packages/rolldown-core/src/options/input-options-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ export function createInputOptionsAdapter(
): BindingInputOptions {
return {
input: normalizeInput(options.input),
plugins: options.plugins.map((plugin) => createBuildPluginAdapter(plugin)),
plugins: options.plugins.map((plugin) =>
createBuildPluginAdapter(plugin, options),
),
cwd: process.cwd(),
}
}
Expand Down

0 comments on commit e878c3b

Please sign in to comment.