diff --git a/e2e/fixtures/rsc-css-modules/README.md b/e2e/fixtures/rsc-css-modules/README.md new file mode 100644 index 000000000..088740a69 --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/README.md @@ -0,0 +1,3 @@ +# RSC CSS Modules + +Only RSC features, no SSR. Using css modules to style the application, both on server and client components. diff --git a/e2e/fixtures/rsc-css-modules/declaration.d.ts b/e2e/fixtures/rsc-css-modules/declaration.d.ts new file mode 100644 index 000000000..60260a3ad --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/declaration.d.ts @@ -0,0 +1 @@ +declare module '*.module.css'; diff --git a/e2e/fixtures/rsc-css-modules/package.json b/e2e/fixtures/rsc-css-modules/package.json new file mode 100644 index 000000000..4466cd536 --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/package.json @@ -0,0 +1,22 @@ +{ + "name": "rsc-css-modules", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "dev": "waku dev", + "build": "waku build", + "start": "waku start" + }, + "dependencies": { + "react": "19.0.0-beta-4508873393-20240430", + "react-dom": "19.0.0-beta-4508873393-20240430", + "react-server-dom-webpack": "19.0.0-beta-4508873393-20240430", + "waku": "workspace:*" + }, + "devDependencies": { + "@types/react": "18.3.1", + "@types/react-dom": "18.3.0", + "typescript": "5.4.4" + } +} diff --git a/e2e/fixtures/rsc-css-modules/src/components/App.tsx b/e2e/fixtures/rsc-css-modules/src/components/App.tsx new file mode 100644 index 000000000..74d730263 --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/src/components/App.tsx @@ -0,0 +1,16 @@ +import styles from './app.module.css'; +import { ClientCounter } from './ClientCounter.js'; + +const App = ({ name }: { name: string }) => { + return ( +
+ Waku example +

+ {name} +

+ +
+ ); +}; + +export default App; diff --git a/e2e/fixtures/rsc-css-modules/src/components/ClientCounter.tsx b/e2e/fixtures/rsc-css-modules/src/components/ClientCounter.tsx new file mode 100644 index 000000000..731cc2f6b --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/src/components/ClientCounter.tsx @@ -0,0 +1,20 @@ +'use client'; +import { useState } from 'react'; +import styles from './clientCounter.module.css'; + +export const ClientCounter = () => { + const [count, setCount] = useState(0); + + return ( +
+

{count}

+ +
+ ); +}; diff --git a/e2e/fixtures/rsc-css-modules/src/components/app.module.css b/e2e/fixtures/rsc-css-modules/src/components/app.module.css new file mode 100644 index 000000000..68d6e759c --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/src/components/app.module.css @@ -0,0 +1,9 @@ +.wrapper { + display: flex; + flex-direction: column; +} + +.text { + font-size: 18px; + color: blue; +} diff --git a/e2e/fixtures/rsc-css-modules/src/components/clientCounter.module.css b/e2e/fixtures/rsc-css-modules/src/components/clientCounter.module.css new file mode 100644 index 000000000..af2190d17 --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/src/components/clientCounter.module.css @@ -0,0 +1,9 @@ +.counterWrapper { + display: flex; + flex-direction: row; + gap: 4px; +} + +.counterButton { + padding: 8px; +} diff --git a/e2e/fixtures/rsc-css-modules/src/entries.tsx b/e2e/fixtures/rsc-css-modules/src/entries.tsx new file mode 100644 index 000000000..00cd7ab33 --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/src/entries.tsx @@ -0,0 +1,19 @@ +import { lazy } from 'react'; +import { defineEntries } from 'waku/server'; + +const App = lazy(() => import('./components/App.js')); + +export default defineEntries( + // renderEntries + async (input) => { + return { + App: , + }; + }, + // getBuildConfig + async () => [{ pathname: '/', entries: [{ input: '' }] }], + // getSsrConfig + () => { + throw new Error('SSR should not be used in this test.'); + }, +); diff --git a/e2e/fixtures/rsc-css-modules/src/main.tsx b/e2e/fixtures/rsc-css-modules/src/main.tsx new file mode 100644 index 000000000..057e44a90 --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { Root, Slot } from 'waku/client'; + +const rootElement = ( + + + + + +); + +createRoot(document.body).render(rootElement); diff --git a/e2e/fixtures/rsc-css-modules/tsconfig.json b/e2e/fixtures/rsc-css-modules/tsconfig.json new file mode 100644 index 000000000..2f2c65b2d --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "strict": true, + "target": "esnext", + "downlevelIteration": true, + "esModuleInterop": true, + "module": "nodenext", + "skipLibCheck": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "types": ["react/experimental"], + "jsx": "react-jsx", + "outDir": "./dist", + "composite": true + }, + "include": ["./src", "./waku.config.ts", "declaration.d.ts"] +} diff --git a/e2e/fixtures/rsc-css-modules/waku.config.ts b/e2e/fixtures/rsc-css-modules/waku.config.ts new file mode 100644 index 000000000..419997de7 --- /dev/null +++ b/e2e/fixtures/rsc-css-modules/waku.config.ts @@ -0,0 +1,16 @@ +const DO_NOT_BUNDLE = ''; + +/** @type {import('waku/config').Config} */ +export default { + middleware: (cmd: 'dev' | 'start') => [ + ...(cmd === 'dev' + ? [ + import( + /* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server' + ), + ] + : []), + import('waku/middleware/rsc'), + import('waku/middleware/fallback'), + ], +}; diff --git a/e2e/rsc-css-modules.spec.ts b/e2e/rsc-css-modules.spec.ts new file mode 100644 index 000000000..721259593 --- /dev/null +++ b/e2e/rsc-css-modules.spec.ts @@ -0,0 +1,77 @@ +import { expect } from '@playwright/test'; +import { execSync, exec, ChildProcess } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +import waitPort from 'wait-port'; +import { debugChildProcess, getFreePort, terminate, test } from './utils.js'; +import { rm } from 'node:fs/promises'; + +const waku = fileURLToPath( + new URL('../packages/waku/dist/cli.js', import.meta.url), +); + +const commands = [ + { + command: 'dev', + }, + { + build: 'build', + command: 'start', + }, +]; + +const cwd = fileURLToPath( + new URL('./fixtures/rsc-css-modules', import.meta.url), +); + +for (const { build, command } of commands) { + test.describe(`rsc-css-modules: ${command}`, () => { + let cp: ChildProcess; + let port: number; + test.beforeAll('remove cache', async () => { + await rm(`${cwd}/dist`, { + recursive: true, + force: true, + }); + }); + + test.beforeAll(async () => { + if (build) { + execSync(`node ${waku} ${build}`, { cwd }); + } + port = await getFreePort(); + cp = exec(`node ${waku} ${command} --port ${port}`, { cwd }); + debugChildProcess(cp, fileURLToPath(import.meta.url), [ + /ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time/, + ]); + await waitPort({ port }); + }); + + test.afterAll(async () => { + await terminate(cp.pid!); + }); + + test('css-modules classes', async ({ page }) => { + await page.goto(`http://localhost:${port}/`); + + const wrapperClass = await page + .getByTestId('app-wrapper') + .getAttribute('class'); + expect(wrapperClass).toContain('wrapper'); + + const appNameClass = await page + .getByTestId('app-name') + .getAttribute('class'); + expect(appNameClass).toContain('text'); + + const clientcounterClass = await page + .getByTestId('client-counter') + .getAttribute('class'); + expect(clientcounterClass).toContain('counterWrapper'); + + const incrementClass = await page + .getByTestId('increment') + .getAttribute('class'); + expect(incrementClass).toContain('counterButton'); + }); + }); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 063b273f9..823374f12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -125,6 +125,31 @@ importers: specifier: 5.4.4 version: 5.4.4 + e2e/fixtures/rsc-css-modules: + dependencies: + react: + specifier: 19.0.0-beta-4508873393-20240430 + version: 19.0.0-beta-4508873393-20240430 + react-dom: + specifier: 19.0.0-beta-4508873393-20240430 + version: 19.0.0-beta-4508873393-20240430(react@19.0.0-beta-4508873393-20240430) + react-server-dom-webpack: + specifier: 19.0.0-beta-4508873393-20240430 + version: 19.0.0-beta-4508873393-20240430(react-dom@19.0.0-beta-4508873393-20240430)(react@19.0.0-beta-4508873393-20240430)(webpack@5.91.0) + waku: + specifier: workspace:* + version: link:../../../packages/waku + devDependencies: + '@types/react': + specifier: 18.3.1 + version: 18.3.1 + '@types/react-dom': + specifier: 18.3.0 + version: 18.3.0 + typescript: + specifier: 5.4.4 + version: 5.4.4 + e2e/fixtures/rsc-router: dependencies: react: diff --git a/tsconfig.e2e.json b/tsconfig.e2e.json index 0bdf1a825..9a4b6f30c 100644 --- a/tsconfig.e2e.json +++ b/tsconfig.e2e.json @@ -9,6 +9,9 @@ { "path": "./e2e/fixtures/rsc-basic/tsconfig.json" }, + { + "path": "./e2e/fixtures/rsc-css-modules/tsconfig.json" + }, { "path": "./e2e/fixtures/ssr-basic/tsconfig.json" },