Skip to main content

rolldown_plugin_utils/
to_output_file_path.rs

1use std::{path::Path, pin::Pin};
2
3use itertools::Either;
4use sugar_path::SugarPath as _;
5
6use super::{join_url_segments, uri::encode_uri_path};
7
8pub type RenderBuiltUrl = dyn Fn(
9    &str,
10    &RenderBuiltUrlConfig,
11  ) -> Pin<
12    Box<dyn Future<Output = anyhow::Result<Option<Either<String, RenderBuiltUrlRet>>>> + Send>,
13  > + Send
14  + Sync;
15
16pub struct RenderBuiltUrlConfig<'a> {
17  pub is_ssr: bool,
18  pub r#type: &'a str,
19  pub host_id: &'a str,
20  pub host_type: &'a str,
21}
22
23pub struct RenderBuiltUrlRet {
24  pub relative: Option<bool>,
25  pub runtime: Option<String>,
26}
27
28pub enum AssetUrlResult {
29  WithRuntime(String),
30  WithoutRuntime(String),
31}
32
33impl AssetUrlResult {
34  pub fn to_asset_url_in_js(self) -> anyhow::Result<String> {
35    match self {
36      AssetUrlResult::WithRuntime(v) => Ok(rolldown_utils::concat_string!("\"+", v, "+\"")),
37      AssetUrlResult::WithoutRuntime(v) => {
38        let string = serde_json::to_string(&encode_uri_path(v))?;
39        Ok(string[1..string.len() - 1].to_owned())
40      }
41    }
42  }
43
44  pub fn to_asset_url_in_css_or_html(self) -> String {
45    match self {
46      AssetUrlResult::WithRuntime(_) => unreachable!("The asset url should not have a runtime"),
47      AssetUrlResult::WithoutRuntime(v) => v,
48    }
49  }
50}
51
52pub struct ToOutputFilePathEnv<'a> {
53  pub is_ssr: bool,
54  pub host_id: &'a str,
55  pub url_base: &'a str,
56  pub decoded_base: &'a str,
57  pub render_built_url: Option<&'a RenderBuiltUrl>,
58}
59
60impl ToOutputFilePathEnv<'_> {
61  pub async fn to_output_file_path(
62    &self,
63    filename: &str,
64    host_type: &str,
65    is_public_asset: bool,
66    to_relative: impl Fn(&Path, &Path) -> AssetUrlResult,
67  ) -> anyhow::Result<AssetUrlResult> {
68    let mut relative = self.url_base.is_empty() || self.url_base == "./";
69    if let Some(render_built_url) = self.render_built_url {
70      if let Some(result) = render_built_url(
71        filename,
72        &RenderBuiltUrlConfig {
73          is_ssr: self.is_ssr,
74          host_id: self.host_id,
75          r#type: if is_public_asset { "public" } else { "asset" },
76          host_type,
77        },
78      )
79      .await?
80      {
81        match result {
82          Either::Left(result) => return Ok(AssetUrlResult::WithoutRuntime(result)),
83          Either::Right(result) => {
84            if let Some(runtime) = result.runtime {
85              if matches!(host_type, "css" | "html") {
86                return Err(anyhow::anyhow!(
87                  "The `{{ runtime: '{runtime}' }}` is not supported for assets in {host_type} files: {filename}"
88                ));
89              }
90              return Ok(AssetUrlResult::WithRuntime(runtime));
91            }
92            if let Some(r) = result.relative {
93              relative = r;
94            }
95          }
96        }
97      }
98    }
99    Ok(if relative && !self.is_ssr {
100      to_relative(filename.as_path(), self.host_id.as_path())
101    } else {
102      AssetUrlResult::WithoutRuntime(join_url_segments(self.decoded_base, filename).into_owned())
103    })
104  }
105}