Skip to content

Commit c3409aa

Browse files
committed
Add inline server actions
1 parent 66d98ba commit c3409aa

File tree

3 files changed

+3732
-3463
lines changed

3 files changed

+3732
-3463
lines changed

src/components/MDX/Sandpack/sandpack-rsc/sandbox-code/src/rsc-client.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,18 @@ export function initClient() {
6363

6464
var rootEl = document.getElementById('root');
6565
if (!rootEl) throw new Error('#root element not found');
66-
var root = createRoot(rootEl);
66+
var root = createRoot(rootEl, {
67+
onUncaughtError: function (error) {
68+
var msg =
69+
error && error.digest
70+
? error.digest
71+
: error && error.message
72+
? error.message
73+
: String(error);
74+
console.error('[RSC Client Error] digest:', error && error.digest);
75+
showError(msg);
76+
},
77+
});
6778
startTransition(function () {
6879
root.render(jsx(Root, {initialPromise: initialPromise}));
6980
});

src/components/MDX/Sandpack/sandpack-rsc/sandbox-code/src/rsc-server.js

Lines changed: 154 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,129 @@ function parseDirective(code) {
7272
return null;
7373
}
7474

75+
// Transform inline 'use server' functions (inside function bodies) into
76+
// registered server references. Module-level 'use server' is handled
77+
// separately by executeModule.
78+
function transformInlineServerActions(code) {
79+
if (code.indexOf('use server') === -1) return code;
80+
var ast;
81+
try {
82+
ast = acorn.parse(code, {ecmaVersion: '2024', sourceType: 'source'});
83+
} catch (x) {
84+
return code;
85+
}
86+
87+
var edits = [];
88+
var counter = 0;
89+
90+
function visit(node, fnDepth) {
91+
if (!node || typeof node !== 'object') return;
92+
var isFn =
93+
node.type === 'FunctionDeclaration' ||
94+
node.type === 'FunctionExpression' ||
95+
node.type === 'ArrowFunctionExpression';
96+
97+
// Only look for 'use server' inside nested functions (fnDepth > 0)
98+
if (
99+
isFn &&
100+
fnDepth > 0 &&
101+
node.body &&
102+
node.body.type === 'BlockStatement'
103+
) {
104+
var body = node.body.body;
105+
for (var s = 0; s < body.length; s++) {
106+
var stmt = body[s];
107+
if (stmt.type !== 'ExpressionStatement') break;
108+
if (stmt.directive === 'use server') {
109+
edits.push({
110+
funcStart: node.start,
111+
funcEnd: node.end,
112+
dStart: stmt.start,
113+
dEnd: stmt.end,
114+
name: node.id ? node.id.name : 'action' + counter,
115+
isDecl: node.type === 'FunctionDeclaration',
116+
});
117+
counter++;
118+
return; // don't recurse into this function
119+
}
120+
if (!stmt.directive) break;
121+
}
122+
}
123+
124+
var nextDepth = isFn ? fnDepth + 1 : fnDepth;
125+
for (var key in node) {
126+
if (key === 'start' || key === 'end' || key === 'type') continue;
127+
var child = node[key];
128+
if (Array.isArray(child)) {
129+
for (var i = 0; i < child.length; i++) {
130+
if (child[i] && typeof child[i].type === 'string') {
131+
visit(child[i], nextDepth);
132+
}
133+
}
134+
} else if (child && typeof child.type === 'string') {
135+
visit(child, nextDepth);
136+
}
137+
}
138+
}
139+
140+
ast.body.forEach(function (stmt) {
141+
visit(stmt, 0);
142+
});
143+
if (edits.length === 0) return code;
144+
145+
// Apply in reverse order to preserve positions
146+
edits.sort(function (a, b) {
147+
return b.funcStart - a.funcStart;
148+
});
149+
150+
var result = code;
151+
for (var i = 0; i < edits.length; i++) {
152+
var e = edits[i];
153+
// Remove the 'use server' directive + trailing whitespace
154+
var dEnd = e.dEnd;
155+
var ch = result.charAt(dEnd);
156+
while (
157+
dEnd < result.length &&
158+
(ch === ' ' || ch === '\n' || ch === '\r' || ch === '\t')
159+
) {
160+
dEnd++;
161+
ch = result.charAt(dEnd);
162+
}
163+
result = result.slice(0, e.dStart) + result.slice(dEnd);
164+
var removed = dEnd - e.dStart;
165+
var adjEnd = e.funcEnd - removed;
166+
167+
// Wrap function with __rsa (register server action)
168+
var funcCode = result.slice(e.funcStart, adjEnd);
169+
if (e.isDecl) {
170+
// async function foo() { ... } →
171+
// var foo = __rsa(async function foo() { ... }, 'foo');
172+
result =
173+
result.slice(0, e.funcStart) +
174+
'var ' +
175+
e.name +
176+
' = __rsa(' +
177+
funcCode +
178+
", '" +
179+
e.name +
180+
"');" +
181+
result.slice(adjEnd);
182+
} else {
183+
// expression/arrow: just wrap in __rsa(...)
184+
result =
185+
result.slice(0, e.funcStart) +
186+
'__rsa(' +
187+
funcCode +
188+
", '" +
189+
e.name +
190+
"')" +
191+
result.slice(adjEnd);
192+
}
193+
}
194+
195+
return result;
196+
}
197+
75198
// Resolve relative paths (e.g., './Counter.js' from '/src/App.js' → '/src/Counter.js')
76199
function resolvePath(from, to) {
77200
if (!to.startsWith('.')) return to;
@@ -171,12 +294,22 @@ function deploy(files) {
171294
return executeModule(resolved);
172295
};
173296

174-
new Function('module', 'exports', 'require', 'React', compiled[filePath])(
175-
mod,
176-
mod.exports,
177-
localRequire,
178-
React
179-
);
297+
// Transform inline 'use server' functions before execution
298+
var codeToExecute = compiled[filePath];
299+
if (directive !== 'use server') {
300+
codeToExecute = transformInlineServerActions(codeToExecute);
301+
}
302+
303+
new Function(
304+
'module',
305+
'exports',
306+
'require',
307+
'React',
308+
'__rsa',
309+
codeToExecute
310+
)(mod, mod.exports, localRequire, React, function (fn, name) {
311+
return registerServerReference(fn, filePath, name);
312+
});
180313

181314
modules[filePath] = mod.exports;
182315

@@ -259,7 +392,12 @@ function render() {
259392
var App = deployed.module.default || deployed.module;
260393
var element = React.createElement(App);
261394
return RSDWServer.renderToReadableStream(element, createModuleMap(), {
262-
onError: console.error,
395+
onError: function (err) {
396+
var msg = err && err.message ? err.message : String(err);
397+
var stack = err && err.stack ? err.stack : '';
398+
console.error('[RSC Server Error]', msg, stack);
399+
return msg;
400+
},
263401
});
264402
}
265403

@@ -289,7 +427,15 @@ function callAction(actionId, encodedArgs) {
289427
var App = deployed.module.default || deployed.module;
290428
return RSDWServer.renderToReadableStream(
291429
{root: React.createElement(App), returnValue: resultPromise},
292-
createModuleMap()
430+
createModuleMap(),
431+
{
432+
onError: function (err) {
433+
var msg = err && err.message ? err.message : String(err);
434+
var stack = err && err.stack ? err.stack : '';
435+
console.error('[RSC Server Error]', msg, stack);
436+
return msg;
437+
},
438+
}
293439
);
294440
});
295441
});

0 commit comments

Comments
 (0)