Skip to main content

deno_bundle_runtime/
lib.rs

1// Copyright 2018-2026 the Deno authors. MIT license.
2
3use std::cell::RefCell;
4use std::rc::Rc;
5use std::sync::Arc;
6
7use async_trait::async_trait;
8use deno_core::FromV8;
9use deno_core::OpState;
10use deno_core::ToV8;
11use deno_core::convert::Uint8Array;
12use deno_core::error::AnyError;
13use deno_core::op2;
14use deno_error::JsErrorBox;
15
16deno_core::extension!(
17  deno_bundle_runtime,
18  deps = [
19    deno_web
20  ],
21  ops = [
22    op_bundle,
23  ],
24  esm = [
25    "bundle.ts"
26  ],
27  options = {
28    bundle_provider: Option<Arc<dyn BundleProvider>>,
29  },
30  state = |state, options| {
31    if let Some(bundle_provider) = options.bundle_provider {
32      state.put(bundle_provider);
33    } else {
34      state.put::<Arc<dyn BundleProvider>>(Arc::new(()));
35    }
36  },
37);
38
39#[async_trait]
40impl BundleProvider for () {
41  async fn bundle(
42    &self,
43    _options: BundleOptions,
44  ) -> Result<BuildResponse, AnyError> {
45    Err(deno_core::anyhow::anyhow!(
46      "default BundleProvider does not do anything"
47    ))
48  }
49}
50
51#[async_trait]
52pub trait BundleProvider: Send + Sync {
53  async fn bundle(
54    &self,
55    options: BundleOptions,
56  ) -> Result<BuildResponse, AnyError>;
57}
58
59#[derive(Clone, Debug, Eq, PartialEq, Default, FromV8)]
60pub struct BundleOptions {
61  pub entrypoints: Vec<String>,
62  pub output_path: Option<String>,
63  pub output_dir: Option<String>,
64  #[from_v8(default)]
65  pub external: Vec<String>,
66  #[from_v8(serde, default)]
67  pub format: BundleFormat,
68  #[from_v8(default)]
69  pub minify: bool,
70  #[from_v8(default)]
71  pub keep_names: bool,
72  #[from_v8(default)]
73  pub code_splitting: bool,
74  #[from_v8(default = true)]
75  pub inline_imports: bool,
76  #[from_v8(serde, default)]
77  pub packages: PackageHandling,
78  #[from_v8(serde)]
79  pub sourcemap: Option<SourceMapType>,
80  #[from_v8(serde, default)]
81  pub platform: BundlePlatform,
82  #[from_v8(default = true)]
83  pub write: bool,
84}
85
86#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
87#[serde(rename_all = "camelCase")]
88pub enum BundlePlatform {
89  Browser,
90  #[default]
91  Deno,
92}
93
94#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub enum BundleFormat {
97  #[default]
98  Esm,
99  Cjs,
100  Iife,
101}
102
103#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub enum SourceMapType {
106  #[default]
107  Linked,
108  Inline,
109  External,
110}
111
112impl std::fmt::Display for BundleFormat {
113  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114    match self {
115      BundleFormat::Esm => write!(f, "esm"),
116      BundleFormat::Cjs => write!(f, "cjs"),
117      BundleFormat::Iife => write!(f, "iife"),
118    }
119  }
120}
121
122impl std::fmt::Display for SourceMapType {
123  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124    match self {
125      SourceMapType::Linked => write!(f, "linked"),
126      SourceMapType::Inline => write!(f, "inline"),
127      SourceMapType::External => write!(f, "external"),
128    }
129  }
130}
131
132#[derive(Clone, Debug, Eq, PartialEq, Copy, Default, serde::Deserialize)]
133#[serde(rename_all = "camelCase")]
134pub enum PackageHandling {
135  #[default]
136  Bundle,
137  External,
138}
139
140impl std::fmt::Display for PackageHandling {
141  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142    match self {
143      PackageHandling::Bundle => write!(f, "bundle"),
144      PackageHandling::External => write!(f, "external"),
145    }
146  }
147}
148#[derive(Debug, Clone, FromV8, ToV8)]
149pub struct Message {
150  pub text: String,
151  pub location: Option<Location>,
152  pub notes: Vec<Note>,
153}
154
155#[derive(Debug, Clone, FromV8, ToV8)]
156pub struct PartialMessage {
157  pub id: Option<String>,
158  pub plugin_name: Option<String>,
159  pub text: Option<String>,
160  pub location: Option<Location>,
161  pub notes: Option<Vec<Note>>,
162  pub detail: Option<u32>,
163}
164
165#[derive(Debug, Clone, ToV8)]
166pub struct BuildOutputFile {
167  pub path: String,
168  pub contents: Option<Uint8Array>,
169  pub hash: String,
170}
171#[derive(Debug, Clone, ToV8)]
172pub struct BuildResponse {
173  pub errors: Vec<Message>,
174  pub warnings: Vec<Message>,
175  pub output_files: Option<Vec<BuildOutputFile>>,
176}
177#[derive(Debug, Clone, FromV8, ToV8)]
178pub struct Note {
179  pub text: String,
180  pub location: Option<Location>,
181}
182#[derive(Debug, Clone, FromV8, ToV8)]
183pub struct Location {
184  pub file: String,
185  pub namespace: Option<String>,
186  pub line: u32,
187  pub column: u32,
188  pub length: Option<u32>,
189  pub suggestion: Option<String>,
190}
191
192fn deserialize_regex<'de, D>(deserializer: D) -> Result<regex::Regex, D::Error>
193where
194  D: serde::Deserializer<'de>,
195{
196  use serde::Deserialize;
197  let s = String::deserialize(deserializer)?;
198  regex::Regex::new(&s).map_err(serde::de::Error::custom)
199}
200
201#[derive(Debug, Clone, serde::Deserialize)]
202#[serde(rename_all = "camelCase")]
203pub struct OnResolveOptions {
204  #[serde(deserialize_with = "deserialize_regex")]
205  pub filter: regex::Regex,
206  pub namespace: Option<String>,
207}
208
209#[derive(Debug, Clone, serde::Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct OnLoadOptions {
212  #[serde(deserialize_with = "deserialize_regex")]
213  pub filter: regex::Regex,
214  pub namespace: Option<String>,
215}
216
217#[op2]
218pub async fn op_bundle(
219  state: Rc<RefCell<OpState>>,
220  #[scoped] options: BundleOptions,
221) -> Result<BuildResponse, JsErrorBox> {
222  // eprintln!("op_bundle: {:?}", options);
223  let provider = {
224    let state = state.borrow();
225    state.borrow::<Arc<dyn BundleProvider>>().clone()
226  };
227
228  provider
229    .bundle(options)
230    .await
231    .map_err(|e| JsErrorBox::generic(e.to_string()))
232}