Repository: vercel/next.js
Pull request: Turbopack: don't fail on export type in use server

Allow `export type ... from "...";`

Closes PACK-5731

Changed files:
filename: crates/next-custom-transforms/src/transforms/server_actions.rs
patch: @@ -1534,64 +1534,78 @@ impl<C: Comments> VisitMut for ServerActions<C> {
                         }
                     }
                     ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
-                        if named.src.is_some() {
-                            disallowed_export_span = named.span;
-                        } else {
-                            for spec in &mut named.specifiers {
-                                if let ExportSpecifier::Named(ExportNamedSpecifier {
-                                    orig: ModuleExportName::Ident(ident),
-                                    exported,
-                                    ..
-                                }) = spec
-                                {
-                                    if let Some(export_name) = exported {
-                                        if let ModuleExportName::Ident(Ident { sym, .. }) =
-                                            export_name
-                                        {
-                                            // export { foo as bar }
-                                            self.exported_idents.push((
-                                                ident.clone(),
-                                                sym.clone(),
-                                                self.generate_server_reference_id(
-                                                    sym.as_ref(),
-                                                    in_cache_file,
-                                                    None,
-                                                ),
-                                            ));
-                                        } else if let ModuleExportName::Str(str) = export_name {
-                                            // export { foo as "bar" }
-                                            self.exported_idents.push((
-                                                ident.clone(),
-                                                str.value.clone(),
-                                                self.generate_server_reference_id(
-                                                    str.value.as_ref(),
-                                                    in_cache_file,
-                                                    None,
-                                                ),
-                                            ));
+                        if !named.type_only {
+                            if named.src.is_some() {
+                                if named.specifiers.iter().any(|s| match s {
+                                    ExportSpecifier::Namespace(_) | ExportSpecifier::Default(_) => {
+                                        true
+                                    }
+                                    ExportSpecifier::Named(s) => !s.is_type_only,
+                                }) {
+                                    disallowed_export_span = named.span;
+                                }
+                            } else {
+                                for spec in &mut named.specifiers {
+                                    if let ExportSpecifier::Named(ExportNamedSpecifier {
+                                        orig: ModuleExportName::Ident(ident),
+                                        exported,
+                                        is_type_only,
+                                        ..
+                                    }) = spec
+                                    {
+                                        if !*is_type_only {
+                                            if let Some(export_name) = exported {
+                                                if let ModuleExportName::Ident(Ident {
+                                                    sym, ..
+                                                }) = export_name
+                                                {
+                                                    // export { foo as bar }
+                                                    self.exported_idents.push((
+                                                        ident.clone(),
+                                                        sym.clone(),
+                                                        self.generate_server_reference_id(
+                                                            sym.as_ref(),
+                                                            in_cache_file,
+                                                            None,
+                                                        ),
+                                                    ));
+                                                } else if let ModuleExportName::Str(str) =
+                                                    export_name
+                                                {
+                                                    // export { foo as "bar" }
+                                                    self.exported_idents.push((
+                                                        ident.clone(),
+                                                        str.value.clone(),
+                                                        self.generate_server_reference_id(
+                                                            str.value.as_ref(),
+                                                            in_cache_file,
+                                                            None,
+                                                        ),
+                                                    ));
+                                                }
+                                            } else {
+                                                // export { foo }
+                                                self.exported_idents.push((
+                                                    ident.clone(),
+                                                    ident.sym.clone(),
+                                                    self.generate_server_reference_id(
+                                                        ident.sym.as_ref(),
+                                                        in_cache_file,
+                                                        None,
+                                                    ),
+                                                ));
+                                            }
                                         }
                                     } else {
-                                        // export { foo }
-                                        self.exported_idents.push((
-                                            ident.clone(),
-                                            ident.sym.clone(),
-                                            self.generate_server_reference_id(
-                                                ident.sym.as_ref(),
-                                                in_cache_file,
-                                                None,
-                                            ),
-                                        ));
+                                        disallowed_export_span = named.span;
                                     }
-                                } else {
-                                    disallowed_export_span = named.span;
                                 }
                             }
                         }
                     }
                     ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
                         decl,
                         span,
-                        ..
                     })) => match decl {
                         DefaultDecl::Fn(f) => {
                             let (is_action_fn, is_cache_fn) = has_body_directive(&f.function.body);
@@ -1760,8 +1774,14 @@ impl<C: Comments> VisitMut for ServerActions<C> {
                             }
                         }
                     }
-                    ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll { span, .. })) => {
-                        disallowed_export_span = *span;
+                    ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll {
+                        span,
+                        type_only,
+                        ..
+                    })) => {
+                        if !*type_only {
+                            disallowed_export_span = *span;
+                        }
                     }
                     _ => {}
                 }

filename: crates/next-custom-transforms/tests/fixture/server-actions/server-graph/62/input.ts
patch: @@ -0,0 +1,13 @@
+'use server'
+
+export type X = string
+// @ts-ignore -- that file does not exist
+export { type A } from './a'
+// @ts-ignore -- that file does not exist
+export type { B } from './b'
+// @ts-ignore -- that file does not exist
+export type * from './c'
+
+export async function actionA(): Promise<string> {
+  return 'hello from actionA'
+}

filename: crates/next-custom-transforms/tests/fixture/server-actions/server-graph/62/output.ts
patch: @@ -0,0 +1,17 @@
+/* __next_internal_action_entry_do_not_use__ {"0095ef8ede0a8a4c822fcbdb018cb264731cb15281":"actionA"} */ import { registerServerReference } from "private-next-rsc-server-reference";
+import { encryptActionBoundArgs, decryptActionBoundArgs } from "private-next-rsc-action-encryption";
+export type X = string;
+// @ts-ignore -- that file does not exist
+export { type A } from './a';
+// @ts-ignore -- that file does not exist
+export type { B } from './b';
+// @ts-ignore -- that file does not exist
+export type * from './c';
+export async function actionA(): Promise<string> {
+    return 'hello from actionA';
+}
+import { ensureServerEntryExports } from "private-next-rsc-action-validate";
+ensureServerEntryExports([
+    actionA
+]);
+registerServerReference(actionA, "0095ef8ede0a8a4c822fcbdb018cb264731cb15281", null);