next_swc_napi/
turbopack.rs

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