UNPKG

@vitejs/plugin-rsc

Version:
344 lines (338 loc) 13.9 kB
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 };