Skip to content

Commit

Permalink
fix: fix server-only and client-only + fix(next): preserve exit code (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
hi-ogawa authored Jul 2, 2024
1 parent e04e155 commit 1c03847
Show file tree
Hide file tree
Showing 17 changed files with 237 additions and 6 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm -C packages/react-server test-e2e
- run: pnpm -C packages/react-server/examples/next test-e2e
- run: pnpm -C packages/react-server/examples/next build
- run: pnpm -C packages/react-server/examples/next test-e2e-preview
Expand All @@ -57,7 +58,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup
- run: pnpm -C packages/react-server test-e2e
- run: pnpm -C packages/react-server test-package

test-vite-node-miniflare:
runs-on: ubuntu-latest
Expand Down
5 changes: 4 additions & 1 deletion packages/react-server-next/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ function main() {
createRequire(import.meta.url).resolve("vite/package.json"),
"../bin/vite.js",
);
spawn("node", [viteBin, ...argv], {
const proc = spawn("node", [viteBin, ...argv], {
shell: false,
stdio: "inherit",
});
proc.on("close", (code) => {
process.exitCode = code ?? 1;
});
}

function setupViteConfig() {
Expand Down
51 changes: 51 additions & 0 deletions packages/react-server/e2e/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { spawn } from "child_process";
import { readFileSync, writeFileSync } from "fs";

export function createEditor(filepath: string) {
const init = readFileSync(filepath, "utf-8");
return {
edit(editFn: (data: string) => string) {
const next = editFn(init);
writeFileSync(filepath, next);
},
[Symbol.dispose]() {
writeFileSync(filepath, init);
},
};
}

export async function runCommand(command: string, ...args: string[]) {
const proc = spawn(command, args, {
stdio: "pipe",
env: {
...process.env,
NODE_ENV: undefined,
},
});

let stdout = "";
let stderr = "";
proc.stdout.on("data", (data) => {
stdout += data.toString();
});

proc.stderr.on("data", (data) => {
stderr += data.toString();
});

const code = await new Promise<number>((resolve, reject) => {
proc.once("close", (code) => {
if (code === null) {
reject(new Error("exit null"));
} else {
resolve(code);
}
});

proc.once("error", (error) => {
reject(error);
});
});

return { code, stdout, stderr };
}
40 changes: 40 additions & 0 deletions packages/react-server/e2e/server-only-client-only.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { beforeAll, expect, it } from "vitest";
import { createEditor, runCommand } from "./helper";

beforeAll(() => {
process.chdir("examples/minimal");
});

it("server only - success", async () => {
using file = createEditor("app/_action.tsx");
file.edit((s) => s + `\n\n;import "server-only"`);

const result = await runCommand("pnpm", "build");
expect(result.code).toBe(0);
});

it("server only - error", async () => {
using file = createEditor("app/_client.tsx");
file.edit((s) => s + `\n\n;import "server-only"`);

const result = await runCommand("pnpm", "build");
expect(result.code).toBe(1);
expect(result.stderr).toContain(`'server-only' is included in client build `);
});

it("client only - success", async () => {
using file = createEditor("app/_client.tsx");
file.edit((s) => s + `\n\n;import "client-only"`);

const result = await runCommand("pnpm", "build");
expect(result.code).toBe(0);
});

it("client only - error", async () => {
using file = createEditor("app/_action.tsx");
file.edit((s) => s + `\n\n;import "client-only"`);

const result = await runCommand("pnpm", "build");
expect(result.code).toBe(1);
expect(result.stderr).toContain(`'client-only' is included in server build `);
});
1 change: 1 addition & 0 deletions packages/react-server/examples/minimal/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
minimal template for testing
11 changes: 11 additions & 0 deletions packages/react-server/examples/minimal/app/_action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use server";

let count = 0;

export async function changeCount() {
count++;
}

export function getCount() {
return count;
}
9 changes: 9 additions & 0 deletions packages/react-server/examples/minimal/app/_client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use client";

import React from "react";

export function TestClient() {
const [count, setCount] = React.useState(0);

return <button onClick={() => setCount(count + 1)}>Client: {count}</button>;
}
12 changes: 12 additions & 0 deletions packages/react-server/examples/minimal/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type React from "react";

export default function Layout(props: React.PropsWithChildren) {
return (
<html>
<body>
<div>[Layout]</div>
{props.children}
</body>
</html>
);
}
14 changes: 14 additions & 0 deletions packages/react-server/examples/minimal/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { changeCount, getCount } from "./_action";
import { TestClient } from "./_client";

export default function Page() {
return (
<>
<div>[Page]</div>
<TestClient />
<form action={changeCount}>
<button>Action: {getCount()}</button>
</form>
</>
);
}
23 changes: 23 additions & 0 deletions packages/react-server/examples/minimal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "@hiogawa/react-server-example-minimal",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"@hiogawa/react-server": "workspace:*",
"next": "link:../../../react-server-next",
"react": "19.0.0-rc-c21bcd627b-20240624",
"react-dom": "19.0.0-rc-c21bcd627b-20240624",
"react-server-dom-webpack": "19.0.0-rc-c21bcd627b-20240624"
},
"devDependencies": {
"@types/react": "18.3.1",
"@types/react-dom": "18.3.0",
"vite": "latest"
}
}
17 changes: 17 additions & 0 deletions packages/react-server/examples/minimal/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"include": ["**/*.ts", "**/*.tsx"],
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ESNext",
"lib": ["ESNext", "DOM"],
"types": ["next"],
"jsx": "react-jsx"
}
}
6 changes: 6 additions & 0 deletions packages/react-server/examples/minimal/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import next from "next/vite";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [next()],
});
3 changes: 2 additions & 1 deletion packages/react-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@
"build": "tsup",
"prepack": "tsup --clean",
"test": "vitest",
"test-e2e": "bash misc/test.sh"
"test-e2e": "vitest -c vitest.config.e2e.ts",
"test-package": "bash misc/test.sh"
},
"dependencies": {
"@hiogawa/transforms": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions packages/react-server/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ export function vitePluginReactServer(options?: {
),

validateImportPlugin({
"client-only": `'client-only' is included in client build`,
"client-only": `'client-only' is included in server build`,
"server-only": true,
}),

Expand Down Expand Up @@ -453,7 +453,7 @@ function validateImportPlugin(entries: Record<string, string | true>): Plugin {
// skip validation during optimizeDeps scan since for now
// we want to allow going through server/client boundary loosely
if (
entry ||
entry === true ||
manager.buildType === "scan" ||
("scan" in options && options.scan)
) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-server/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src", "vitest.config.ts", "tsup.config.ts"],
"include": ["src", "e2e", "*.ts"],
"compilerOptions": {
"types": ["vite/client", "react/experimental", "react-dom/experimental"],
"jsx": "react-jsx"
Expand Down
14 changes: 14 additions & 0 deletions packages/react-server/vitest.config.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from "vitest/config";

export default defineConfig({
esbuild: {
supported: {
using: false,
},
},
test: {
dir: "e2e",
pool: "forks",
fileParallelism: false,
},
});
28 changes: 28 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1c03847

Please sign in to comment.