diff --git a/examples/general/src/app/Comp.jsx b/examples/general/src/app/Comp.jsx
index 1cd65ad..dc22a1a 100644
--- a/examples/general/src/app/Comp.jsx
+++ b/examples/general/src/app/Comp.jsx
@@ -1,4 +1,8 @@
+'use client';
+
+import { Inner } from './Inner';
+
export function Comp() {
- throw new Error('Comp error thrown');
- return Math.random() > 0.5 ?
Comp content
: 'Comp content';
+ // throw new Error('Comp error thrown');
+ return Comp content
;
}
diff --git a/examples/general/src/app/Inner.jsx b/examples/general/src/app/Inner.jsx
new file mode 100644
index 0000000..9b7e722
--- /dev/null
+++ b/examples/general/src/app/Inner.jsx
@@ -0,0 +1,5 @@
+'use client';
+
+export function Inner() {
+ return Inner
;
+}
\ No newline at end of file
diff --git a/examples/general/src/app/page.jsx b/examples/general/src/app/page.jsx
index 459db49..6f5b7b7 100644
--- a/examples/general/src/app/page.jsx
+++ b/examples/general/src/app/page.jsx
@@ -1,5 +1,6 @@
import { Comp } from "./Comp";
import { Comp as Comp2 } from "./Comp";
+import { Inner } from "./Inner";
export const dynamic = 'force-dynamic';
@@ -23,6 +24,7 @@ export default async function Page() {
{renderTest()}
+
);
}
diff --git a/packages/next-rsc-error-handler/README.md b/packages/next-rsc-error-handler/README.md
index 6d23abc..c2f34cd 100644
--- a/packages/next-rsc-error-handler/README.md
+++ b/packages/next-rsc-error-handler/README.md
@@ -2,7 +2,7 @@
Webpack plugin that allow to handle RSC errors on the server side.
-**This plugin requires all the client components to be marked with `'use client;'`**
+**This plugin does not allow dual client and server components**
## Get started
diff --git a/packages/next-rsc-error-handler/src/loader.js b/packages/next-rsc-error-handler/src/loader.js
index 41e5117..905f42e 100644
--- a/packages/next-rsc-error-handler/src/loader.js
+++ b/packages/next-rsc-error-handler/src/loader.js
@@ -2,6 +2,7 @@ import parser from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import * as t from "@babel/types";
+import path from "node:path";
import {
getRelativePath,
@@ -14,15 +15,36 @@ import {
const WRAPPER_NAME = "__rscWrapper";
const WRAPPER_PATH = "next-rsc-error-handler/inserted/wrapper";
+const clientComponents = new Set();
+const serverComponents = new Set();
+
export default function (source) {
- if (isClientComponent(source)) {
+ const resourcePath = this.resourcePath;
+ const relativePath = getRelativePath(resourcePath);
+
+ if (isRoute(relativePath)) {
return source;
}
- const resourcePath = this.resourcePath;
- const filePath = getRelativePath(resourcePath);
+ const noExtRelativePath = dropExtension(relativePath);
+ const isTrulyClientComponent = isClientComponent(source);
+
+ if (isTrulyClientComponent || clientComponents.has(noExtRelativePath)) {
+ if (!isTrulyClientComponent && serverComponents.has(noExtRelativePath)) {
+ throw new Error(`${relativePath} is used on both client and server`);
+ }
+
+ const ast = parser.parse(source, {
+ sourceType: "module",
+ plugins: ["typescript", "jsx"],
+ });
+
+ traverse.default(ast, {
+ ImportDeclaration(p) {
+ clientComponents.add(getImportRelativePath(resourcePath, p));
+ },
+ });
- if (isRoute(filePath)) {
return source;
}
@@ -41,17 +63,21 @@ export default function (source) {
}
const ctx = {
- filePath,
+ filePath: relativePath,
componentName: functionName,
};
const optionsExpression = getOptionsExpressionLiteral(ctx);
wasWrapped = true;
-
wrapFn(p, WRAPPER_NAME, optionsExpression);
}
+ const innerServerComponents = new Set();
+
traverse.default(ast, {
+ ImportDeclaration(p) {
+ innerServerComponents.add(getImportRelativePath(resourcePath, p));
+ },
// TODO add FunctionExpression
FunctionDeclaration(p) {
const functionName = p.node.id?.name ?? "";
@@ -67,19 +93,21 @@ export default function (source) {
return source;
}
+ innerServerComponents.forEach((c) => serverComponents.add(c));
+
addImport(ast);
const output = generate.default(ast);
return output.code;
}
-function isInApp(resourcePath) {
- return /^(src(\/|\\))?app(\/|\\)/.test(resourcePath);
+function isInApp(relativePath) {
+ return /^(src(\/|\\))?app(\/|\\)/.test(relativePath);
}
-function isRoute(resourcePath) {
+function isRoute(relativePath) {
return (
- isInApp(resourcePath) && /(\/|\\)route\.(c|m)?(t|j)s$/.test(resourcePath)
+ isInApp(relativePath) && /(\/|\\)route\.(c|m)?(t|j)s$/.test(relativePath)
);
}
@@ -101,3 +129,15 @@ function addImport(ast) {
ast.program.body.unshift(wrapperImport);
}
+
+function dropExtension(relativePath) {
+ return relativePath.replace(/\.[^/.]+$/, "");
+}
+
+function getImportRelativePath(resourcePath, p) {
+ return dropExtension(
+ getRelativePath(
+ path.resolve(path.dirname(resourcePath), p.node.source.value)
+ )
+ );
+}