next_swc_napi/
turbopack.rs

1use std::path::PathBuf;
2
3use anyhow::Context;
4use napi::bindgen_prelude::*;
5use next_build::{
6    BuildOptions as NextBuildOptions,
7    build_options::{BuildContext, DefineEnv},
8};
9use next_core::next_config::{Rewrite, Rewrites, RouteHas};
10
11use crate::next_api::project::NapiDefineEnv;
12
13#[napi(object, object_to_js = false)]
14#[derive(Debug)]
15pub struct NextBuildContext {
16    // Added by Next.js for next build --turbopack specifically.
17    /// The root directory of the workspace.
18    pub root: Option<String>,
19
20    /// The project's directory.
21    pub dir: Option<String>,
22
23    /// next.config.js's distDir. Current there's some early stage setup
24    /// requires this Before construct a context to read next.config.js,
25    /// which we passes separately here.
26    pub dist_dir: Option<String>,
27
28    /// The build ID.
29    pub build_id: Option<String>,
30
31    /// The rewrites, as computed by Next.js.
32    pub rewrites: Option<NapiRewrites>,
33    // TODO(alexkirsz) These are detected directly by Turbopack for now.
34    // pub app_dir: Option<String>,
35    // pub pages_dir: Option<String>,
36    // TODO(alexkirsz) These are used to generate route types.
37    // pub original_rewrites: Option<Rewrites>,
38    // pub original_redirects: Option<Vec<Redirect>>,
39    pub define_env: NapiDefineEnv,
40}
41
42impl TryFrom<NextBuildContext> for NextBuildOptions {
43    type Error = napi::Error;
44
45    fn try_from(value: NextBuildContext) -> Result<Self> {
46        Ok(Self {
47            dir: value.dir.map(PathBuf::try_from).transpose()?,
48            root: value.root.map(PathBuf::try_from).transpose()?,
49            log_level: None,
50            show_all: true,
51            log_detail: true,
52            full_stats: true,
53            memory_limit: None,
54            dist_dir: value.dist_dir,
55            build_context: Some(BuildContext {
56                build_id: value
57                    .build_id
58                    .context("NextBuildContext must provide a build ID")?,
59                rewrites: value
60                    .rewrites
61                    .context("NextBuildContext must provide rewrites")?
62                    .into(),
63            }),
64            define_env: value.define_env.into(),
65        })
66    }
67}
68
69impl From<NapiDefineEnv> for DefineEnv {
70    fn from(val: NapiDefineEnv) -> Self {
71        DefineEnv {
72            client: val
73                .client
74                .into_iter()
75                .map(|var| (var.name, var.value))
76                .collect(),
77            edge: val
78                .edge
79                .into_iter()
80                .map(|var| (var.name, var.value))
81                .collect(),
82            nodejs: val
83                .nodejs
84                .into_iter()
85                .map(|var| (var.name, var.value))
86                .collect(),
87        }
88    }
89}
90
91/// Keep in sync with [`next_core::next_config::Rewrites`]
92#[napi(object, object_to_js = false)]
93#[derive(Debug)]
94pub struct NapiRewrites {
95    pub fallback: Vec<NapiRewrite>,
96    pub after_files: Vec<NapiRewrite>,
97    pub before_files: Vec<NapiRewrite>,
98}
99
100impl From<NapiRewrites> for Rewrites {
101    fn from(val: NapiRewrites) -> Self {
102        Rewrites {
103            fallback: val
104                .fallback
105                .into_iter()
106                .map(|rewrite| rewrite.into())
107                .collect(),
108            after_files: val
109                .after_files
110                .into_iter()
111                .map(|rewrite| rewrite.into())
112                .collect(),
113            before_files: val
114                .before_files
115                .into_iter()
116                .map(|rewrite| rewrite.into())
117                .collect(),
118        }
119    }
120}
121
122/// Keep in sync with [`next_core::next_config::Rewrite`]
123#[napi(object, object_to_js = false)]
124#[derive(Debug)]
125pub struct NapiRewrite {
126    pub source: String,
127    pub destination: String,
128    pub base_path: Option<bool>,
129    pub locale: Option<bool>,
130    pub has: Option<Vec<NapiRouteHas>>,
131    pub missing: Option<Vec<NapiRouteHas>>,
132}
133
134impl From<NapiRewrite> for Rewrite {
135    fn from(val: NapiRewrite) -> Self {
136        Rewrite {
137            source: val.source,
138            destination: val.destination,
139            base_path: val.base_path,
140            locale: val.locale,
141            has: val
142                .has
143                .map(|has| has.into_iter().map(|has| has.into()).collect()),
144            missing: val
145                .missing
146                .map(|missing| missing.into_iter().map(|missing| missing.into()).collect()),
147        }
148    }
149}
150
151/// Keep in sync with [`next_core::next_config::RouteHas`]
152#[derive(Debug)]
153pub enum NapiRouteHas {
154    Header { key: String, value: Option<String> },
155    Query { key: String, value: Option<String> },
156    Cookie { key: String, value: Option<String> },
157    Host { value: String },
158}
159
160impl FromNapiValue for NapiRouteHas {
161    unsafe fn from_napi_value(env: sys::napi_env, napi_val: sys::napi_value) -> Result<Self> {
162        let object = unsafe { Object::from_napi_value(env, napi_val)? };
163        let type_ = object.get_named_property::<String>("type")?;
164        Ok(match type_.as_str() {
165            "header" => NapiRouteHas::Header {
166                key: object.get_named_property("key")?,
167                value: object.get_named_property("value")?,
168            },
169            "query" => NapiRouteHas::Query {
170                key: object.get_named_property("key")?,
171                value: object.get_named_property("value")?,
172            },
173            "cookie" => NapiRouteHas::Cookie {
174                key: object.get_named_property("key")?,
175                value: object.get_named_property("value")?,
176            },
177            "host" => NapiRouteHas::Host {
178                value: object.get_named_property("value")?,
179            },
180            _ => {
181                return Err(napi::Error::new(
182                    Status::GenericFailure,
183                    format!("invalid type for RouteHas: {type_}"),
184                ));
185            }
186        })
187    }
188}
189
190impl From<NapiRouteHas> for RouteHas {
191    fn from(val: NapiRouteHas) -> Self {
192        match val {
193            NapiRouteHas::Header { key, value } => RouteHas::Header {
194                key: key.into(),
195                value: value.map(From::from),
196            },
197            NapiRouteHas::Query { key, value } => RouteHas::Query {
198                key: key.into(),
199                value: value.map(From::from),
200            },
201            NapiRouteHas::Cookie { key, value } => RouteHas::Cookie {
202                key: key.into(),
203                value: value.map(From::from),
204            },
205            NapiRouteHas::Host { value } => RouteHas::Host {
206                value: value.into(),
207            },
208        }
209    }
210}