mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
Apply workflow transformation with export { fnName } syntax (#547)
Added support for workflow transformation with `export { fnName }` syntax.
### What changed?
Enhanced the SWC plugin to properly handle workflow functions that are exported using the named export syntax (`export { fnName }`). Previously, the plugin only properly handled workflow functions that were directly exported with `export function` or `export const`.
The implementation now:
- Collects names that are exported via `export { ... }` syntax in a first pass
- Applies the appropriate transformations to workflow functions that are later exported
- Ensures workflowId is correctly assigned to functions exported with this syntax
- Works across all transformation modes (Client, Step, and Workflow)
### How to test?
Test with files that use the named export syntax:
```javascript
async function workflowFunction() {
'use workflow';
// function body
}
export { workflowFunction };
```
Verify that the transformation correctly:
- Adds workflowId to the function
- Applies the appropriate mode-specific transformations
- Preserves the export statement
### Why make this change?
This change ensures consistent behavior across different export syntaxes. Previously, workflow functions exported with the `export { fnName }` syntax weren't properly transformed, which could lead to runtime errors or unexpected behavior. This enhancement provides developers with more flexibility in how they structure and export their workflow functions.
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@workflow/swc-plugin": patch
|
||||
---
|
||||
|
||||
Apply workflow transformation with `export { fnName }` syntax
|
||||
@@ -3503,6 +3503,27 @@ impl VisitMut for StepTransform {
|
||||
}
|
||||
}
|
||||
|
||||
// First pass: Collect names that are exported via `export { ... }` syntax
|
||||
// This is needed to determine which non-exported workflow functions need workflowId in Workflow mode
|
||||
let mut named_export_names: HashSet<String> = HashSet::new();
|
||||
for item in items.iter() {
|
||||
if let ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) = item {
|
||||
// Only process local exports (not re-exports from other modules)
|
||||
if named.src.is_none() {
|
||||
for specifier in &named.specifiers {
|
||||
if let ExportSpecifier::Named(named_spec) = specifier {
|
||||
// Get the local name (the original identifier, not the exported alias)
|
||||
let local_name = match &named_spec.orig {
|
||||
ModuleExportName::Ident(ident) => ident.sym.to_string(),
|
||||
ModuleExportName::Str(s) => s.value.to_string_lossy().to_string(),
|
||||
};
|
||||
named_export_names.insert(local_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process items and collect functions that need workflowId assignments
|
||||
let mut items_to_insert = Vec::new();
|
||||
|
||||
@@ -3700,9 +3721,14 @@ impl VisitMut for StepTransform {
|
||||
item.visit_mut_with(self);
|
||||
|
||||
// After visiting the item, check if we need to add a workflowId assignment
|
||||
if matches!(self.mode, TransformMode::Client | TransformMode::Step) {
|
||||
match item {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
|
||||
// In Client/Step modes: add for all workflow functions
|
||||
// In Workflow mode: add for non-exported workflow functions that are later exported via `export { ... }`
|
||||
// (directly exported ones are handled by workflow_exports_to_expand at end of file)
|
||||
match item {
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
|
||||
// Directly exported - only handle in Client/Step modes
|
||||
// (Workflow mode handles these via workflow_exports_to_expand)
|
||||
if matches!(self.mode, TransformMode::Client | TransformMode::Step) {
|
||||
if let Decl::Fn(fn_decl) = &export_decl.decl {
|
||||
let fn_name = fn_decl.ident.sym.to_string();
|
||||
if self.workflow_function_names.contains(&fn_name) {
|
||||
@@ -3737,7 +3763,10 @@ impl VisitMut for StepTransform {
|
||||
}
|
||||
}
|
||||
}
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(default_decl)) => {
|
||||
}
|
||||
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(default_decl)) => {
|
||||
// Default exports - only handle in Client/Step modes
|
||||
if matches!(self.mode, TransformMode::Client | TransformMode::Step) {
|
||||
if let DefaultDecl::Fn(fn_expr) = &default_decl.decl {
|
||||
// Check if this is a workflow function by checking for "default" key
|
||||
if self.workflow_function_names.contains("default") {
|
||||
@@ -3758,9 +3787,18 @@ impl VisitMut for StepTransform {
|
||||
}
|
||||
}
|
||||
}
|
||||
ModuleItem::Stmt(Stmt::Decl(Decl::Fn(fn_decl))) => {
|
||||
let fn_name = fn_decl.ident.sym.to_string();
|
||||
if self.workflow_function_names.contains(&fn_name) {
|
||||
}
|
||||
ModuleItem::Stmt(Stmt::Decl(Decl::Fn(fn_decl))) => {
|
||||
// Non-exported function declaration
|
||||
let fn_name = fn_decl.ident.sym.to_string();
|
||||
if self.workflow_function_names.contains(&fn_name) {
|
||||
// In Client/Step modes: always add workflowId
|
||||
// In Workflow mode: only add if later exported via `export { ... }`
|
||||
let should_add = match self.mode {
|
||||
TransformMode::Client | TransformMode::Step => true,
|
||||
TransformMode::Workflow => named_export_names.contains(&fn_name),
|
||||
};
|
||||
if should_add {
|
||||
items_to_insert.push((
|
||||
i + 1,
|
||||
ModuleItem::Stmt(self.create_workflow_id_assignment(
|
||||
@@ -3770,11 +3808,20 @@ impl VisitMut for StepTransform {
|
||||
));
|
||||
}
|
||||
}
|
||||
ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => {
|
||||
for declarator in &var_decl.decls {
|
||||
if let Pat::Ident(binding) = &declarator.name {
|
||||
let name = binding.id.sym.to_string();
|
||||
if self.workflow_function_names.contains(&name) {
|
||||
}
|
||||
ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => {
|
||||
// Non-exported variable declaration
|
||||
for declarator in &var_decl.decls {
|
||||
if let Pat::Ident(binding) = &declarator.name {
|
||||
let name = binding.id.sym.to_string();
|
||||
if self.workflow_function_names.contains(&name) {
|
||||
// In Client/Step modes: always add workflowId
|
||||
// In Workflow mode: only add if later exported via `export { ... }`
|
||||
let should_add = match self.mode {
|
||||
TransformMode::Client | TransformMode::Step => true,
|
||||
TransformMode::Workflow => named_export_names.contains(&name),
|
||||
};
|
||||
if should_add {
|
||||
if let Some(init) = &declarator.init {
|
||||
let span = match &**init {
|
||||
Expr::Fn(fn_expr) => fn_expr.function.span,
|
||||
@@ -3792,8 +3839,8 @@ impl VisitMut for StepTransform {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3802,7 +3849,8 @@ impl VisitMut for StepTransform {
|
||||
items.insert(index, item);
|
||||
}
|
||||
|
||||
// In workflow mode, add workflowId property to workflow functions
|
||||
// In workflow mode, add workflowId property to workflow functions (at end of file)
|
||||
// Note: This handles directly exported workflow functions
|
||||
if self.mode == TransformMode::Workflow && !self.workflow_exports_to_expand.is_empty() {
|
||||
// Process workflow functions to add workflowId property
|
||||
let workflow_functions: Vec<_> = self.workflow_exports_to_expand.drain(..).collect();
|
||||
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
async function stepFunction(a, b) {
|
||||
'use step';
|
||||
return a + b;
|
||||
}
|
||||
|
||||
async function workflowFunction(a, b) {
|
||||
'use workflow';
|
||||
const result = await stepFunction(a, b);
|
||||
return result * 2;
|
||||
}
|
||||
|
||||
async function normalFunction(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
export { workflowFunction, stepFunction, normalFunction };
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/**__internal_workflows{"workflows":{"input.js":{"workflowFunction":{"workflowId":"workflow//input.js//workflowFunction"}}},"steps":{"input.js":{"stepFunction":{"stepId":"step//input.js//stepFunction"}}}}*/;
|
||||
async function stepFunction(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
async function workflowFunction(a, b) {
|
||||
throw new Error("You attempted to execute workflow workflowFunction function directly. To start a workflow, use start(workflowFunction) from workflow/api");
|
||||
}
|
||||
workflowFunction.workflowId = "workflow//input.js//workflowFunction";
|
||||
async function normalFunction(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
export { workflowFunction, stepFunction, normalFunction };
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { registerStepFunction } from "workflow/internal/private";
|
||||
/**__internal_workflows{"workflows":{"input.js":{"workflowFunction":{"workflowId":"workflow//input.js//workflowFunction"}}},"steps":{"input.js":{"stepFunction":{"stepId":"step//input.js//stepFunction"}}}}*/;
|
||||
async function stepFunction(a, b) {
|
||||
return a + b;
|
||||
}
|
||||
async function workflowFunction(a, b) {
|
||||
throw new Error("You attempted to execute workflow workflowFunction function directly. To start a workflow, use start(workflowFunction) from workflow/api");
|
||||
}
|
||||
workflowFunction.workflowId = "workflow//input.js//workflowFunction";
|
||||
async function normalFunction(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
export { workflowFunction, stepFunction, normalFunction };
|
||||
registerStepFunction("step//input.js//stepFunction", stepFunction);
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
/**__internal_workflows{"workflows":{"input.js":{"workflowFunction":{"workflowId":"workflow//input.js//workflowFunction"}}},"steps":{"input.js":{"stepFunction":{"stepId":"step//input.js//stepFunction"}}}}*/;
|
||||
var stepFunction = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("step//input.js//stepFunction");
|
||||
async function workflowFunction(a, b) {
|
||||
const result = await stepFunction(a, b);
|
||||
return result * 2;
|
||||
}
|
||||
workflowFunction.workflowId = "workflow//input.js//workflowFunction";
|
||||
async function normalFunction(a, b) {
|
||||
return a * b;
|
||||
}
|
||||
export { workflowFunction, stepFunction, normalFunction };
|
||||
Reference in New Issue
Block a user