Skip to content

Commit

Permalink
feat: define component by name
Browse files Browse the repository at this point in the history
  • Loading branch information
infodusha committed May 16, 2024
1 parent cd9c559 commit bfcd873
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 94 deletions.
12 changes: 10 additions & 2 deletions examples/general/src/app/page.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { Comp } from "./Comp";
import { Comp as Comp2 } from "./Comp";

export const dynamic = 'force-dynamic';

export default async function Page() {
// throw new Error("This is a Page error");

function renderTest() {
throw new Error("This is a render method error");
// return <span>Should not be wrapped</span>
// throw new Error("This is a render method error");
return <span>Should not be wrapped</span>
}

function ABC() {
// throw new Error("This is a ABC error");
return <div>ABC</div>
}

return (
<div>
<h1>Page</h1>
<Comp />
<Comp2 />
{renderTest()}
<ABC />
</div>
);
}
7 changes: 4 additions & 3 deletions packages/next-rsc-error-handler/src/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { NextConfig } from "next";

export interface RscErrorHandlerOptions {}
export interface RscErrorHandlerOptions {
componentName: RegExp;
}

export function rscErrorHandler(
options: RscErrorHandlerOptions
): (nextConfig: NextConfig) => NextConfig;

export interface GlobalServerErrorContext {
filePath: string;
functionName: string;
options: RscErrorHandlerOptions;
componentName: string;
}

export type GlobalServerError = (
Expand Down
4 changes: 3 additions & 1 deletion packages/next-rsc-error-handler/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const defaultOptions = {};
const defaultOptions = {
componentName: /^[A-Z]/,
};

export function rscErrorHandler(options = {}) {
return function withLoader(nextConfig = {}) {
Expand Down
51 changes: 26 additions & 25 deletions packages/next-rsc-error-handler/src/loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import * as t from "@babel/types";
import {
getRelativePath,
isClientComponent,
isReactElement,
wrapWithFunction,
wrapFunctionDeclaration,
wrapArrowFunction,
getOptionsExpressionLiteral,
} from "./utils.js";

const WRAPPER_NAME = "__rscWrapper";
Expand All @@ -28,26 +29,31 @@ export default function (source) {

let wasWrapped = false;

traverse.default(ast, {
enter(p) {
if (!p.isFunctionDeclaration() && !p.isArrowFunctionExpression()) {
return;
}
function wrapIfComponent(functionName, p, wrapFn) {
if (!options.componentName.test(functionName)) {
return;
}

if (!isReactElement(p)) {
return;
}
const ctx = {
filePath: getRelativePath(resourcePath),
componentName: functionName,
};
const optionsExpression = getOptionsExpressionLiteral(ctx);

const ctx = {
filePath: getRelativePath(resourcePath),
functionName: getFunctionName(p),
options,
};
wasWrapped = true;

wasWrapped = true;
wrapWithFunction(p, WRAPPER_NAME, ctx);
wrapFn(p, WRAPPER_NAME, optionsExpression);
}

p.skip();
traverse.default(ast, {
// TODO add FunctionExpression
FunctionDeclaration(p) {
const functionName = p.node.id?.name ?? "";
wrapIfComponent(functionName, p, wrapFunctionDeclaration);
},
ArrowFunctionExpression(p) {
const functionName = getArrowFunctionName(p);
wrapIfComponent(functionName, p, wrapArrowFunction);
},
});

Expand All @@ -59,19 +65,14 @@ export default function (source) {
return output.code;
}

function getFunctionName(p) {
if (p.isFunctionDeclaration()) {
return p.node.id?.name;
}

function getArrowFunctionName(p) {
if (p.isArrowFunctionExpression()) {
const parent = p.parentPath;
if (parent.isVariableDeclarator() && parent.node.id.type === "Identifier") {
return parent.node.id.name;
}
}

return "(anonymous)";
return "";
}

function addImport(ast) {
Expand Down
71 changes: 8 additions & 63 deletions packages/next-rsc-error-handler/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,64 +10,6 @@ export function isClientComponent(source) {
return source.includes("__next_internal_client_entry_do_not_use__");
}

/**
* Return true if node is a function and returns jsx.
* @param {Path} p
* @returns {boolean}
*/
export function isReactElement(p) {
let isReactElement = false;
if (p.isFunctionDeclaration() || p.isArrowFunctionExpression()) {
isReactElement = isReturningJSXElement(p);
}

return isReactElement;
}

const JSX_FN_NAMES =
process.env.NODE_ENV === "production"
? ["_jsx", "_jsxs"]
: ["_jsxDEV", "_jsxsDEV"];

/**
* Helper function that returns true if node returns a JSX element.
* @param {Path} p
* @returns {boolean}
*/
function isReturningJSXElement(p) {
let foundJSX = false;

p.traverse({
CallExpression(innerP) {
const calleePath = innerP.get("callee");
if (
t.isIdentifier(calleePath.node) &&
JSX_FN_NAMES.includes(calleePath.node.name)
) {
foundJSX = true;
innerP.stop();
}
},
});

return foundJSX;
}

/**
* Wraps FunctionDeclaration or ArrowFunctionExpression with a function call with context.
*/
export function wrapWithFunction(p, wrapFunctionName, context) {
const optionsExpression = getOptionsExpressionLiteral(context);

if (p.isArrowFunctionExpression()) {
return wrapArrowFunction(p, wrapFunctionName, optionsExpression);
} else if (p.isFunctionDeclaration()) {
return wrapFunctionDeclaration(p, wrapFunctionName, optionsExpression);
} else {
throw new Error("Unsupported type of function");
}
}

function getOptionsExpression(obj) {
return t.objectExpression(
Object.entries(obj).map(([key, value]) =>
Expand All @@ -76,7 +18,7 @@ function getOptionsExpression(obj) {
);
}

function getOptionsExpressionLiteral(value) {
export function getOptionsExpressionLiteral(value) {
if (value === null) {
return t.nullLiteral();
}
Expand All @@ -96,13 +38,17 @@ function getOptionsExpressionLiteral(value) {
}
}

function wrapArrowFunction(p, wrapFunctionName, optionsNode) {
export function wrapArrowFunction(p, wrapFunctionName, optionsNode) {
return p.replaceWith(
t.callExpression(t.identifier(wrapFunctionName), [p.node, optionsNode])
);
}

function wrapFunctionDeclaration(p, wrapFunctionName, argumentsExpression) {
export function wrapFunctionDeclaration(
p,
wrapFunctionName,
argumentsExpression
) {
const expression = t.functionExpression(
null,
p.node.params,
Expand All @@ -112,8 +58,7 @@ function wrapFunctionDeclaration(p, wrapFunctionName, argumentsExpression) {
);

if (p.node.id == null) {
// FIXME should work no matter if there is no function name
throw new Error("FunctionDeclaration has no id.");
throw new Error("FunctionDeclaration has no name");
}

const originalFunctionIdentifier = t.identifier(p.node.id.name);
Expand Down

0 comments on commit bfcd873

Please sign in to comment.