Skip to content

Commit

Permalink
feat: add generateBundle hook (#403)
Browse files Browse the repository at this point in the history
  • Loading branch information
underfin committed Jan 23, 2024
1 parent 25fca9d commit 21fe501
Show file tree
Hide file tree
Showing 19 changed files with 160 additions and 36 deletions.
16 changes: 10 additions & 6 deletions crates/rolldown/src/bundler/bundle/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@ pub struct RenderedModule {
pub rendered_length: u32,
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct OutputChunk {
pub file_name: String,
pub code: String,
// PreRenderedChunk
pub is_entry: bool,
pub is_dynamic_entry: bool,
pub facade_module_id: Option<String>,
pub modules: FxHashMap<String, RenderedModule>,
pub module_ids: Vec<String>,
pub exports: Vec<String>,
// RenderedChunk
pub file_name: String,
pub modules: FxHashMap<String, RenderedModule>,
// OutputChunk
pub code: String,
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct OutputAsset {
pub file_name: String,
pub source: String,
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub enum Output {
Chunk(Box<OutputChunk>),
Asset(Box<OutputAsset>),
Expand Down
16 changes: 11 additions & 5 deletions crates/rolldown/src/bundler/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ impl<T: FileSystem + Default + 'static> Bundler<T> {
}
}

pub async fn write(&mut self, output_options: OutputOptions) -> BuildResult<RolldownOutput> {
pub async fn write(&mut self, output_options: OutputOptions) -> BatchedResult<RolldownOutput> {
let dir =
self.input_options.cwd.as_path().join(&output_options.dir).to_string_lossy().to_string();

let output = self.bundle_up(output_options).await?;
let output = self.bundle_up(output_options, true).await?;

self.fs.create_dir_all(dir.as_path()).unwrap_or_else(|_| {
panic!(
Expand All @@ -97,8 +97,8 @@ impl<T: FileSystem + Default + 'static> Bundler<T> {
Ok(output)
}

pub async fn generate(&mut self, output_options: OutputOptions) -> BuildResult<RolldownOutput> {
self.bundle_up(output_options).await
pub async fn generate(&mut self, output_options: OutputOptions) -> BatchedResult<RolldownOutput> {
self.bundle_up(output_options, false).await
}

pub async fn build(&mut self) -> BuildResult<()> {
Expand Down Expand Up @@ -172,14 +172,20 @@ impl<T: FileSystem + Default + 'static> Bundler<T> {
}

#[tracing::instrument(skip_all)]
async fn bundle_up(&mut self, output_options: OutputOptions) -> BuildResult<RolldownOutput> {
async fn bundle_up(
&mut self,
output_options: OutputOptions,
is_write: bool,
) -> BatchedResult<RolldownOutput> {
tracing::trace!("InputOptions {:#?}", self.input_options);
tracing::trace!("OutputOptions: {output_options:#?}",);
let graph = self.build_result.as_mut().expect("Build should success");
let mut bundle_stage =
BundleStage::new(graph, &self.input_options, &output_options, &self.plugin_driver);
let assets = bundle_stage.bundle().await?;

self.plugin_driver.generate_bundle(&assets, is_write).await?;

Ok(RolldownOutput { warnings: std::mem::take(&mut graph.warnings), assets })
}
}
9 changes: 8 additions & 1 deletion crates/rolldown/src/bundler/plugin_driver/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
plugin::{BoxPlugin, HookNoopReturn},
},
HookLoadArgs, HookLoadReturn, HookResolveIdArgs, HookResolveIdReturn, HookTransformArgs,
HookTransformReturn, PluginContext,
HookTransformReturn, Output, PluginContext,
};

pub type SharedPluginDriver = Arc<PluginDriver>;
Expand Down Expand Up @@ -71,4 +71,11 @@ impl PluginDriver {
}
Ok(args.code)
}

pub async fn generate_bundle(&self, bundle: &Vec<Output>, is_write: bool) -> HookNoopReturn {
for plugin in &self.plugins {
plugin.generate_bundle(&PluginContext::new(), bundle, is_write).await?;
}
Ok(())
}
}
1 change: 1 addition & 0 deletions crates/rolldown/src/bundler/stages/bundle_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ impl<'a> BundleStage<'a> {
facade_module_id: rendered_chunk.facade_module_id,
modules: rendered_chunk.modules,
exports: rendered_chunk.exports,
module_ids: rendered_chunk.module_ids,
}))
})
.collect::<Vec<_>>();
Expand Down
12 changes: 12 additions & 0 deletions crates/rolldown/src/plugin/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use std::{borrow::Cow, fmt::Debug};

use rolldown_error::BuildError;

use crate::Output;

use super::{
args::{HookBuildEndArgs, HookLoadArgs, HookResolveIdArgs, HookTransformArgs, RenderChunkArgs},
context::PluginContext,
Expand Down Expand Up @@ -59,6 +61,16 @@ pub trait Plugin: Debug + Send + Sync {
) -> HookRenderChunkReturn {
Ok(None)
}

#[allow(clippy::ptr_arg)]
async fn generate_bundle(
&self,
_ctx: &PluginContext,
_bundle: &Vec<Output>,
_is_write: bool,
) -> HookNoopReturn {
Ok(())
}
}

pub type BoxPlugin = Box<dyn Plugin>;
5 changes: 3 additions & 2 deletions crates/rolldown/tests/common/fixture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,13 @@ impl Fixture {
}

bundler.build().await?;
bundler
let value = bundler
.write(OutputOptions {
entry_file_names: FileNameTemplate::from("[name].mjs".to_string()),
chunk_file_names: FileNameTemplate::from("[name].mjs".to_string()),
..Default::default()
})
.await
.await?;
Ok(value)
}
}
8 changes: 5 additions & 3 deletions crates/rolldown_binding/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface PluginOptions {
code: string,
chunk: RenderedChunk,
) => Promise<undefined | HookRenderChunkOutput>
generateBundle?: (bundle: Outputs, isWrite: boolean) => Promise<void>
}
export interface HookResolveIdArgsOptions {
isEntry: boolean
Expand Down Expand Up @@ -90,13 +91,14 @@ export interface RenderedModule {
renderedLength: number
}
export interface OutputChunk {
code: string
fileName: string
isEntry: boolean
isDynamicEntry: boolean
facadeModuleId?: string
modules: Record<string, RenderedModule>
moduleIds: Array<string>
exports: Array<string>
fileName: string
modules: Record<string, RenderedModule>
code: string
}
export interface OutputAsset {
fileName: string
Expand Down
8 changes: 2 additions & 6 deletions crates/rolldown_binding/src/bundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,7 @@ impl Bundler {
Ok(outputs) => outputs,
Err(err) => {
// TODO: better handing errors
for err in err {
eprintln!("{err:?}");
}
eprintln!("{err:?}");
return Err(napi::Error::from_reason("Build failed"));
}
};
Expand All @@ -126,9 +124,7 @@ impl Bundler {
Ok(outputs) => outputs,
Err(err) => {
// TODO: better handing errors
for err in err {
eprintln!("{err:?}");
}
eprintln!("{err:?}");
return Err(napi::Error::from_reason("Build failed"));
}
};
Expand Down
5 changes: 5 additions & 0 deletions crates/rolldown_binding/src/options/input_options/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ pub struct PluginOptions {
ts_type = "(code: string, chunk: RenderedChunk) => Promise<undefined | HookRenderChunkOutput>"
)]
pub render_chunk: Option<JsFunction>,

#[derivative(Debug = "ignore")]
#[serde(skip_deserializing)]
#[napi(ts_type = "(bundle: Outputs, isWrite: boolean) => Promise<void>")]
pub generate_bundle: Option<JsFunction>,
}

#[napi_derive::napi(object)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::borrow::Cow;

use crate::utils::napi_error_ext::NapiErrorExt;
use crate::utils::JsCallback;
use crate::{output::Outputs, utils::napi_error_ext::NapiErrorExt};
use derivative::Derivative;
use rolldown::Plugin;

Expand All @@ -17,6 +17,7 @@ pub type LoadCallback = JsCallback<(String,), Option<SourceResult>>;
pub type TransformCallback = JsCallback<(String, String), Option<SourceResult>>;
pub type BuildEndCallback = JsCallback<(Option<String>,), ()>;
pub type RenderChunkCallback = JsCallback<(String, RenderedChunk), Option<HookRenderChunkOutput>>;
pub type GenerateBundleCallback = JsCallback<(Outputs, bool), Option<HookRenderChunkOutput>>;

#[derive(Derivative)]
#[derivative(Debug)]
Expand All @@ -34,6 +35,8 @@ pub struct JsAdapterPlugin {
build_end_fn: Option<BuildEndCallback>,
#[derivative(Debug = "ignore")]
render_chunk_fn: Option<RenderChunkCallback>,
#[derivative(Debug = "ignore")]
generate_bundle_fn: Option<GenerateBundleCallback>,
}

impl JsAdapterPlugin {
Expand All @@ -44,6 +47,8 @@ impl JsAdapterPlugin {
let transform_fn = option.transform.as_ref().map(TransformCallback::new).transpose()?;
let build_end_fn = option.build_end.as_ref().map(BuildEndCallback::new).transpose()?;
let render_chunk_fn = option.render_chunk.as_ref().map(RenderChunkCallback::new).transpose()?;
let generate_bundle_fn =
option.generate_bundle.as_ref().map(GenerateBundleCallback::new).transpose()?;
Ok(Self {
name: option.name,
build_start_fn,
Expand All @@ -52,6 +57,7 @@ impl JsAdapterPlugin {
transform_fn,
build_end_fn,
render_chunk_fn,
generate_bundle_fn,
})
}

Expand Down Expand Up @@ -156,4 +162,17 @@ impl Plugin for JsAdapterPlugin {
}
Ok(None)
}

#[allow(clippy::redundant_closure_for_method_calls)]
async fn generate_bundle(
&self,
_ctx: &rolldown::PluginContext,
bundle: &Vec<rolldown::Output>,
is_write: bool,
) -> rolldown::HookNoopReturn {
if let Some(cb) = &self.generate_bundle_fn {
cb.call_async((bundle.clone().into(), is_write)).await.map_err(|e| e.into_bundle_error())?;
}
Ok(())
}
}
11 changes: 8 additions & 3 deletions crates/rolldown_binding/src/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,17 @@ impl From<rolldown::RenderedModule> for RenderedModule {
#[serde(rename_all = "camelCase")]
#[derivative(Debug)]
pub struct OutputChunk {
pub code: String,
pub file_name: String,
// PreRenderedChunk
pub is_entry: bool,
pub is_dynamic_entry: bool,
pub facade_module_id: Option<String>,
pub modules: HashMap<String, RenderedModule>,
pub module_ids: Vec<String>,
pub exports: Vec<String>,
// RenderedChunk
pub file_name: String,
pub modules: HashMap<String, RenderedModule>,
// OutputChunk
pub code: String,
}

impl From<Box<rolldown::OutputChunk>> for OutputChunk {
Expand All @@ -53,6 +57,7 @@ impl From<Box<rolldown::OutputChunk>> for OutputChunk {
facade_module_id: chunk.facade_module_id,
modules: chunk.modules.into_iter().map(|(key, value)| (key, value.into())).collect(),
exports: chunk.exports,
module_ids: chunk.module_ids,
}
}
}
Expand Down
27 changes: 26 additions & 1 deletion packages/node/src/options/create-build-plugin-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import type {
ResolveIdResult,
RenderedChunk,
HookRenderChunkOutput,
Outputs,
} from '@rolldown/node-binding'
import { unimplemented } from '../utils'
import { transformToRollupOutput, unimplemented } from '../utils'

// Note: because napi not catch error, so we need to catch error and print error to debugger in adapter.
export function createBuildPluginAdapter(
Expand All @@ -21,6 +22,30 @@ export function createBuildPluginAdapter(
transform: transform(plugin.transform),
buildEnd: buildEnd(plugin.buildEnd),
renderChunk: renderChunk(plugin.renderChunk),
generateBundle: generateBundle(plugin.generateBundle),
}
}

function generateBundle(hook: Plugin['generateBundle']) {
if (hook) {
if (typeof hook !== 'function') {
return unimplemented()
}
return async (outputs: Outputs, isWrite: boolean) => {
const bundle = Object.fromEntries(
transformToRollupOutput(outputs).output.map((item) => [
item.fileName,
item,
]),
)
try {
// TODO outputOptions
await hook.call({} as any, {} as any, bundle, isWrite)
} catch (error) {
console.error(error)
throw error
}
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/node/src/rolldown-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ export class RolldownBuild implements Omit<RollupBuild, 'generate' | 'write'> {
async generate(outputOptions: OutputOptions = {}): Promise<RolldownOutput> {
const bindingOptions = normalizeOutputOptions(outputOptions)
const output = await this.#bundler.write(bindingOptions)
return transformToRollupOutput(output)
return transformToRollupOutput(output) as RolldownOutput
}

async write(outputOptions: OutputOptions = {}): Promise<RolldownOutput> {
const bindingOptions = normalizeOutputOptions(outputOptions)
const output = await this.#bundler.write(bindingOptions)
return transformToRollupOutput(output)
return transformToRollupOutput(output) as RolldownOutput
}

async close() {
Expand Down
7 changes: 3 additions & 4 deletions packages/node/src/utils/transform-to-rollup-output.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { OutputAsset, OutputChunk, Outputs } from '@rolldown/node-binding'
import type {
RollupOutput,
OutputChunk as RollupOutputChunk,
OutputAsset as RollupOutputAsset,
} from '../rollup-types'
Expand All @@ -16,6 +17,7 @@ function transformToRollupOutputChunk(chunk: OutputChunk): RollupOutputChunk {
isEntry: chunk.isEntry,
facadeModuleId: chunk.facadeModuleId || null,
isDynamicEntry: chunk.isDynamicEntry,
moduleIds: chunk.moduleIds,
get dynamicImports() {
return unimplemented()
},
Expand All @@ -37,9 +39,6 @@ function transformToRollupOutputChunk(chunk: OutputChunk): RollupOutputChunk {
get isImplicitEntry() {
return unimplemented()
},
get moduleIds() {
return unimplemented()
},
get name() {
return unimplemented()
},
Expand All @@ -66,7 +65,7 @@ function transformToRollupOutputAsset(asset: OutputAsset): RollupOutputAsset {
}
}

export function transformToRollupOutput(output: Outputs): RolldownOutput {
export function transformToRollupOutput(output: Outputs): RollupOutput {
const { chunks, assets } = output

return {
Expand Down

0 comments on commit 21fe501

Please sign in to comment.