@vitejs/plugin-rsc
Version:
React Server Components (RSC) support for Vite.
344 lines (338 loc) • 13.9 kB
JavaScript
import { i as tinyassert } from "./dist-BRSdGcl7.js";
import MagicString from "magic-string";
import { walk } from "estree-walker";
import { analyze, extract_names } from "periscopic";
//#region src/transforms/hoist.ts
function transformHoistInlineDirective(input, ast, { runtime, rejectNonAsyncFunction, ...options }) {
if (!input.endsWith("\n")) input += "\n";
const output = new MagicString(input);
const directive = typeof options.directive === "string" ? exactRegex(options.directive) : options.directive;
walk(ast, { enter(node) {
if (node.type === "ExportAllDeclaration") this.remove();
if (node.type === "ExportNamedDeclaration" && !node.declaration) this.remove();
} });
const analyzed = analyze(ast);
const names = [];
walk(ast, { enter(node, parent) {
if ((node.type === "FunctionExpression" || node.type === "FunctionDeclaration" || node.type === "ArrowFunctionExpression") && node.body.type === "BlockStatement") {
const match = matchDirective(node.body.body, directive)?.match;
if (!match) return;
if (!node.async && rejectNonAsyncFunction) throw Object.assign(/* @__PURE__ */ new Error(`"${directive}" doesn't allow non async function`), { pos: node.start });
const scope = analyzed.map.get(node);
tinyassert(scope);
const declName = node.type === "FunctionDeclaration" && node.id.name;
const originalName = declName || parent?.type === "VariableDeclarator" && parent.id.type === "Identifier" && parent.id.name || "anonymous_server_function";
const bindVars = [...scope.references].filter((ref) => {
if (ref === declName) return false;
const owner = scope.find_owner(ref);
return owner && owner !== scope && owner !== analyzed.scope;
});
let newParams = [...bindVars, ...node.params.map((n) => input.slice(n.start, n.end))].join(", ");
if (bindVars.length > 0 && options.decode) {
newParams = ["$$hoist_encoded", ...node.params.map((n) => input.slice(n.start, n.end))].join(", ");
output.appendLeft(node.body.body[0].start, `const [${bindVars.join(",")}] = ${options.decode("$$hoist_encoded")};\n`);
}
const newName = `$$hoist_${names.length}` + (originalName ? `_${originalName}` : "");
names.push(newName);
output.update(node.start, node.body.start, `\n;${options.noExport ? "" : "export "}${node.async ? "async " : ""}function ${newName}(${newParams}) `);
output.appendLeft(node.end, `;\n/* #__PURE__ */ Object.defineProperty(${newName}, "name", { value: ${JSON.stringify(originalName)} });\n`);
output.move(node.start, node.end, input.length);
let newCode = `/* #__PURE__ */ ${runtime(newName, newName, { directiveMatch: match })}`;
if (bindVars.length > 0) {
const bindArgs = options.encode ? options.encode("[" + bindVars.join(", ") + "]") : bindVars.join(", ");
newCode = `${newCode}.bind(null, ${bindArgs})`;
}
if (declName) {
newCode = `const ${declName} = ${newCode};`;
if (parent?.type === "ExportDefaultDeclaration") {
output.remove(parent.start, node.start);
newCode = `${newCode}\nexport default ${declName};`;
}
}
output.appendLeft(node.start, newCode);
}
} });
return {
output,
names
};
}
const exactRegex = (s) => /* @__PURE__ */ new RegExp("^" + s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&") + "$");
function matchDirective(body, directive) {
for (const stmt of body) if (stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && typeof stmt.expression.value === "string") {
const match = stmt.expression.value.match(directive);
if (match) return {
match,
node: stmt.expression
};
}
}
function findDirectives(ast, directive) {
const directiveRE = exactRegex(directive);
const nodes = [];
walk(ast, { enter(node) {
if (node.type === "Program" || node.type === "BlockStatement") {
const match = matchDirective(node.body, directiveRE);
if (match) nodes.push(match.node);
}
} });
return nodes;
}
//#endregion
//#region src/transforms/wrap-export.ts
function transformWrapExport(input, ast, options) {
const output = new MagicString(input);
const exportNames = [];
const toAppend = [];
const filter = options.filter ?? (() => true);
function wrapSimple(start, end, exports) {
exportNames.push(...exports.map((e) => e.name));
const newCode = exports.map((e) => [filter(e.name, e.meta) && `${e.name} = /* #__PURE__ */ ${options.runtime(e.name, e.name, e.meta)};\n`, `export { ${e.name} };\n`]).flat().filter(Boolean).join("");
output.update(start, end, newCode);
output.move(start, end, input.length);
}
function wrapExport(name, exportName, meta = {}) {
exportNames.push(exportName);
if (!filter(exportName, meta)) {
toAppend.push(`export { ${name} as ${exportName} }`);
return;
}
toAppend.push(`const $$wrap_${name} = /* #__PURE__ */ ${options.runtime(name, exportName, meta)}`, `export { $$wrap_${name} as ${exportName} }`);
}
function validateNonAsyncFunction(node) {
if (!options.rejectNonAsyncFunction) return;
if (node.type === "ClassDeclaration" || (node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") && !node.async) throw Object.assign(/* @__PURE__ */ new Error(`unsupported non async function`), { pos: node.start });
}
for (const node of ast.body) {
if (node.type === "ExportNamedDeclaration") if (node.declaration) if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") {
/**
* export function foo() {}
*/
validateNonAsyncFunction(node.declaration);
const name = node.declaration.id.name;
wrapSimple(node.start, node.declaration.start, [{
name,
meta: {
isFunction: true,
declName: name
}
}]);
} else if (node.declaration.type === "VariableDeclaration") {
/**
* export const foo = 1, bar = 2
*/
for (const decl of node.declaration.declarations) if (decl.init) validateNonAsyncFunction(decl.init);
if (node.declaration.kind === "const") output.update(node.declaration.start, node.declaration.start + 5, "let");
const names = node.declaration.declarations.flatMap((decl) => extract_names(decl.id));
let isFunction = false;
if (node.declaration.declarations.length === 1) {
const decl = node.declaration.declarations[0];
isFunction = decl.id.type === "Identifier" && (decl.init?.type === "ArrowFunctionExpression" || decl.init?.type === "FunctionExpression");
}
wrapSimple(node.start, node.declaration.start, names.map((name) => ({
name,
meta: {
isFunction,
declName: name
}
})));
} else node.declaration;
else if (node.source) {
/**
* export { foo, bar as car } from './foo'
*/
output.remove(node.start, node.end);
for (const spec of node.specifiers) {
tinyassert(spec.local.type === "Identifier");
tinyassert(spec.exported.type === "Identifier");
const name = spec.local.name;
toAppend.push(`import { ${name} as $$import_${name} } from ${node.source.raw}`);
wrapExport(`$$import_${name}`, spec.exported.name);
}
} else {
/**
* export { foo, bar as car }
*/
output.remove(node.start, node.end);
for (const spec of node.specifiers) {
tinyassert(spec.local.type === "Identifier");
tinyassert(spec.exported.type === "Identifier");
wrapExport(spec.local.name, spec.exported.name);
}
}
/**
* export * from './foo'
*/
if (!options.ignoreExportAllDeclaration && node.type === "ExportAllDeclaration") throw Object.assign(/* @__PURE__ */ new Error("unsupported ExportAllDeclaration"), { pos: node.start });
/**
* export default function foo() {}
* export default class Foo {}
* export default () => {}
*/
if (node.type === "ExportDefaultDeclaration") {
validateNonAsyncFunction(node.declaration);
let localName;
let isFunction = false;
let declName;
let defaultExportIdentifierName;
if ((node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") && node.declaration.id) {
localName = node.declaration.id.name;
output.remove(node.start, node.declaration.start);
isFunction = node.declaration.type === "FunctionDeclaration";
declName = node.declaration.id.name;
} else {
localName = "$$default";
output.update(node.start, node.declaration.start, "const $$default = ");
if (node.declaration.type === "Identifier") defaultExportIdentifierName = node.declaration.name;
}
wrapExport(localName, "default", {
isFunction,
declName,
defaultExportIdentifierName
});
}
}
if (toAppend.length > 0) output.append([
"",
...toAppend,
""
].join(";\n"));
return {
exportNames,
output
};
}
//#endregion
//#region src/transforms/utils.ts
function hasDirective(body, directive) {
return !!body.find((stmt) => stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && typeof stmt.expression.value === "string" && stmt.expression.value === directive);
}
function getExportNames(ast, options) {
const exportNames = [];
for (const node of ast.body) {
if (node.type === "ExportNamedDeclaration") if (node.declaration) if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration")
/**
* export function foo() {}
*/
exportNames.push(node.declaration.id.name);
else if (node.declaration.type === "VariableDeclaration")
/**
* export const foo = 1, bar = 2
*/
for (const decl of node.declaration.declarations) exportNames.push(...extract_names(decl.id));
else node.declaration;
else
/**
* export { foo, bar as car } from './foo'
* export { foo, bar as car }
*/
for (const spec of node.specifiers) {
tinyassert(spec.exported.type === "Identifier");
exportNames.push(spec.exported.name);
}
/**
* export * from './foo'
*/
if (!options.ignoreExportAllDeclaration && node.type === "ExportAllDeclaration") throw new Error("unsupported ExportAllDeclaration");
/**
* export default function foo() {}
* export default class Foo {}
* export default () => {}
*/
if (node.type === "ExportDefaultDeclaration") exportNames.push("default");
}
return { exportNames };
}
//#endregion
//#region src/transforms/proxy-export.ts
function transformDirectiveProxyExport(ast, options) {
if (!hasDirective(ast.body, options.directive)) return;
return transformProxyExport(ast, options);
}
function transformProxyExport(ast, options) {
if (options.keep && typeof options.code !== "string") throw new Error("`keep` option requires `code`");
const output = new MagicString(options.code ?? " ".repeat(ast.end));
const exportNames = [];
function createExport(node, names) {
exportNames.push(...names);
const newCode = names.map((name) => (name === "default" ? `export default` : `export const ${name} =`) + ` /* #__PURE__ */ ${options.runtime(name)};\n`).join("");
output.update(node.start, node.end, newCode);
}
function validateNonAsyncFunction(node, ok) {
if (options.rejectNonAsyncFunction && !ok) throw Object.assign(/* @__PURE__ */ new Error(`unsupported non async function`), { pos: node.start });
}
for (const node of ast.body) {
if (node.type === "ExportNamedDeclaration") {
if (node.declaration) if (node.declaration.type === "FunctionDeclaration" || node.declaration.type === "ClassDeclaration") {
/**
* export function foo() {}
*/
validateNonAsyncFunction(node, node.declaration.type === "FunctionDeclaration" && node.declaration.async);
createExport(node, [node.declaration.id.name]);
} else if (node.declaration.type === "VariableDeclaration") {
/**
* export const foo = 1, bar = 2
*/
validateNonAsyncFunction(node, node.declaration.declarations.every((decl) => decl.init?.type === "ArrowFunctionExpression" && decl.init.async));
if (options.keep && options.code) {
if (node.declaration.declarations.length === 1) {
const decl = node.declaration.declarations[0];
if (decl.id.type === "Identifier" && decl.init) {
const name = decl.id.name;
const value = options.code.slice(decl.init.start, decl.init.end);
const newCode = `export const ${name} = /* #__PURE__ */ ${options.runtime(name, { value })};`;
output.update(node.start, node.end, newCode);
exportNames.push(name);
continue;
}
}
}
createExport(node, node.declaration.declarations.flatMap((decl) => extract_names(decl.id)));
} else node.declaration;
else {
/**
* export { foo, bar as car } from './foo'
* export { foo, bar as car }
*/
const names = [];
for (const spec of node.specifiers) {
tinyassert(spec.exported.type === "Identifier");
names.push(spec.exported.name);
}
createExport(node, names);
}
continue;
}
/**
* export * from './foo'
*/
if (!options.ignoreExportAllDeclaration && node.type === "ExportAllDeclaration") throw new Error("unsupported ExportAllDeclaration");
/**
* export default function foo() {}
* export default class Foo {}
* export default () => {}
*/
if (node.type === "ExportDefaultDeclaration") {
validateNonAsyncFunction(node, node.declaration.type === "Identifier" || node.declaration.type === "FunctionDeclaration" && node.declaration.async);
createExport(node, ["default"]);
continue;
}
if (options.keep) continue;
output.remove(node.start, node.end);
}
return {
exportNames,
output
};
}
//#endregion
//#region src/transforms/server-action.ts
function transformServerActionServer(input, ast, options) {
if (hasDirective(ast.body, "use server")) return transformWrapExport(input, ast, options);
return transformHoistInlineDirective(input, ast, {
...options,
directive: "use server"
});
}
//#endregion
export { hasDirective as a, transformHoistInlineDirective as c, getExportNames as i, transformDirectiveProxyExport as n, transformWrapExport as o, transformProxyExport as r, findDirectives as s, transformServerActionServer as t };