Skip to content

Commit 8ee5a04

Browse files
authored
add ServerDirective transform which reports unsupported (vercel/turborepo#4477)
### Description "use server" isn't supported yet, but we want to have an error message
1 parent 428a7df commit 8ee5a04

File tree

4 files changed

+105
-29
lines changed

4 files changed

+105
-29
lines changed

crates/turbopack-ecmascript/src/parse.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use turbo_tasks::{
2323
primitives::{StringVc, U64Vc},
2424
Value, ValueToString,
2525
};
26-
use turbo_tasks_fs::{FileContent, FileSystemPath};
26+
use turbo_tasks_fs::{FileContent, FileSystemPath, FileSystemPathVc};
2727
use turbo_tasks_hash::hash_xxh3_hash64;
2828
use turbopack_core::{
2929
asset::{Asset, AssetContent, AssetVc},
@@ -142,7 +142,8 @@ pub async fn parse(
142142
transforms: EcmascriptInputTransformsVc,
143143
) -> Result<ParseResultVc> {
144144
let content = source.content();
145-
let fs_path = &*source.ident().path().await?;
145+
let fs_path_vc = source.ident().path();
146+
let fs_path = &*fs_path_vc.await?;
146147
let ident = &*source.ident().to_string().await?;
147148
let file_path_hash = *hash_ident(source.ident().to_string()).await? as u128;
148149
let ty = ty.into_value();
@@ -154,6 +155,7 @@ pub async fn parse(
154155
let transforms = &*transforms.await?;
155156
match parse_content(
156157
string.into_owned(),
158+
fs_path_vc,
157159
fs_path,
158160
ident,
159161
file_path_hash,
@@ -182,6 +184,7 @@ pub async fn parse(
182184

183185
async fn parse_content(
184186
string: String,
187+
fs_path_vc: FileSystemPathVc,
185188
fs_path: &FileSystemPath,
186189
ident: &str,
187190
file_path_hash: u128,
@@ -297,6 +300,7 @@ async fn parse_content(
297300
file_path_str: &fs_path.path,
298301
file_name_str: fs_path.file_name(),
299302
file_name_hash: file_path_hash,
303+
file_path: fs_path_vc,
300304
};
301305
for transform in transforms.iter() {
302306
transform.apply(&mut parsed_program, &context).await?;

crates/turbopack-ecmascript/src/transform/mod.rs

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod server_to_client_proxy;
2+
mod util;
23

34
use std::{fmt::Debug, path::Path, sync::Arc};
45

@@ -15,17 +16,25 @@ use swc_core::{
1516
},
1617
visit::{FoldWith, VisitMutWith},
1718
},
19+
quote,
1820
};
1921
use turbo_tasks::primitives::{OptionStringVc, StringVc};
20-
use turbo_tasks_fs::json::parse_json_with_source_context;
21-
use turbopack_core::environment::EnvironmentVc;
22+
use turbo_tasks_fs::{json::parse_json_with_source_context, FileSystemPathVc};
23+
use turbopack_core::{
24+
environment::EnvironmentVc,
25+
issue::{Issue, IssueSeverity, IssueSeverityVc, IssueVc},
26+
};
2227

23-
use self::server_to_client_proxy::{create_proxy_module, is_client_module};
28+
use self::{
29+
server_to_client_proxy::create_proxy_module,
30+
util::{is_client_module, is_server_module},
31+
};
2432

2533
#[turbo_tasks::value(serialization = "auto_for_input")]
2634
#[derive(Debug, Clone, PartialOrd, Ord, Hash)]
2735
pub enum EcmascriptInputTransform {
2836
ClientDirective(StringVc),
37+
ServerDirective(StringVc),
2938
CommonJs,
3039
Custom(CustomTransformVc),
3140
Emotion,
@@ -104,6 +113,7 @@ pub struct TransformContext<'a> {
104113
pub file_path_str: &'a str,
105114
pub file_name_str: &'a str,
106115
pub file_name_hash: u128,
116+
pub file_path: FileSystemPathVc,
107117
}
108118

109119
impl EcmascriptInputTransform {
@@ -115,6 +125,7 @@ impl EcmascriptInputTransform {
115125
unresolved_mark,
116126
file_name_str,
117127
file_name_hash,
128+
file_path,
118129
..
119130
} = ctx;
120131
match self {
@@ -250,12 +261,28 @@ impl EcmascriptInputTransform {
250261
));
251262
}
252263
EcmascriptInputTransform::ClientDirective(transition_name) => {
253-
let transition_name = &*transition_name.await?;
254264
if is_client_module(program) {
265+
let transition_name = &*transition_name.await?;
255266
*program = create_proxy_module(transition_name, &format!("./{file_name_str}"));
256267
program.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
257268
}
258269
}
270+
EcmascriptInputTransform::ServerDirective(_transition_name) => {
271+
if is_server_module(program) {
272+
let stmt = quote!(
273+
"throw new Error('Server actions (\"use server\") are not yet supported in \
274+
Turbopack');" as Stmt
275+
);
276+
match program {
277+
Program::Module(m) => m.body = vec![ModuleItem::Stmt(stmt)],
278+
Program::Script(s) => s.body = vec![stmt],
279+
}
280+
UnsupportedServerActionIssue { context: file_path }
281+
.cell()
282+
.as_issue()
283+
.emit();
284+
}
285+
}
259286
EcmascriptInputTransform::Custom(transform) => {
260287
if let Some(output) = transform.await?.transform(program, ctx) {
261288
*program = output;
@@ -291,3 +318,36 @@ fn unwrap_module_program(program: &mut Program) -> Program {
291318
}),
292319
}
293320
}
321+
322+
#[turbo_tasks::value(shared)]
323+
pub struct UnsupportedServerActionIssue {
324+
pub context: FileSystemPathVc,
325+
}
326+
327+
#[turbo_tasks::value_impl]
328+
impl Issue for UnsupportedServerActionIssue {
329+
#[turbo_tasks::function]
330+
fn severity(&self) -> IssueSeverityVc {
331+
IssueSeverity::Error.into()
332+
}
333+
334+
#[turbo_tasks::function]
335+
fn category(&self) -> StringVc {
336+
StringVc::cell("unsupported".to_string())
337+
}
338+
339+
#[turbo_tasks::function]
340+
fn title(&self) -> StringVc {
341+
StringVc::cell("Server actions (\"use server\") are not yet supported in Turbopack".into())
342+
}
343+
344+
#[turbo_tasks::function]
345+
fn context(&self) -> FileSystemPathVc {
346+
self.context
347+
}
348+
349+
#[turbo_tasks::function]
350+
async fn description(&self) -> Result<StringVc> {
351+
Ok(StringVc::cell("".to_string()))
352+
}
353+
}

crates/turbopack-ecmascript/src/transform/server_to_client_proxy.rs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,6 @@ use swc_core::{
1313

1414
use crate::references::TURBOPACK_HELPER;
1515

16-
macro_rules! has_client_directive {
17-
($stmts:expr) => {
18-
$stmts
19-
.map(|item| {
20-
if let Lit::Str(str) = item?.as_expr()?.expr.as_lit()? {
21-
Some(str)
22-
} else {
23-
None
24-
}
25-
})
26-
.take_while(Option::is_some)
27-
.map(Option::unwrap)
28-
.any(|s| &*s.value == "use client")
29-
};
30-
}
31-
32-
pub fn is_client_module(program: &Program) -> bool {
33-
match program {
34-
Program::Module(m) => has_client_directive!(m.body.iter().map(|item| item.as_stmt())),
35-
Program::Script(s) => has_client_directive!(s.body.iter().map(Some)),
36-
}
37-
}
38-
3916
pub fn create_proxy_module(transition_name: &str, target_import: &str) -> Program {
4017
let ident = private_ident!("createProxy");
4118
Program::Module(Module {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
use swc_core::ecma::ast::{Lit, Program};
2+
3+
macro_rules! has_directive {
4+
($stmts:expr, $name:literal) => {
5+
$stmts
6+
.map(|item| {
7+
if let Lit::Str(str) = item?.as_expr()?.expr.as_lit()? {
8+
Some(str)
9+
} else {
10+
None
11+
}
12+
})
13+
.take_while(Option::is_some)
14+
.map(Option::unwrap)
15+
.any(|s| &*s.value == $name)
16+
};
17+
}
18+
19+
pub fn is_client_module(program: &Program) -> bool {
20+
match program {
21+
Program::Module(m) => {
22+
has_directive!(m.body.iter().map(|item| item.as_stmt()), "use client")
23+
}
24+
Program::Script(s) => has_directive!(s.body.iter().map(Some), "use client"),
25+
}
26+
}
27+
28+
pub fn is_server_module(program: &Program) -> bool {
29+
match program {
30+
Program::Module(m) => {
31+
has_directive!(m.body.iter().map(|item| item.as_stmt()), "use server")
32+
}
33+
Program::Script(s) => has_directive!(s.body.iter().map(Some), "use server"),
34+
}
35+
}

0 commit comments

Comments
 (0)