diff --git a/e2e/fixtures/monorepo/package.json b/e2e/fixtures/monorepo/package.json new file mode 100644 index 000000000..1950a019e --- /dev/null +++ b/e2e/fixtures/monorepo/package.json @@ -0,0 +1,7 @@ +{ + "name": "monorepo", + "private": true, + "workspaces": [ + "packages/*" + ] +} diff --git a/e2e/fixtures/monorepo/packages/waku-project/package.json b/e2e/fixtures/monorepo/packages/waku-project/package.json new file mode 100644 index 000000000..644c9aa5b --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/package.json @@ -0,0 +1,23 @@ +{ + "name": "waku-project", + "version": "0.0.0", + "type": "module", + "private": true, + "scripts": { + "dev": "waku dev", + "build": "waku build", + "start": "waku start" + }, + "dependencies": { + "react": "19.0.0", + "react-dom": "19.0.0", + "react-server-dom-webpack": "19.0.0" + }, + "devDependencies": { + "@types/react": "19.0.1", + "@types/react-dom": "19.0.2", + "autoprefixer": "10.4.20", + "tailwindcss": "3.4.16", + "typescript": "5.7.2" + } +} diff --git a/e2e/fixtures/monorepo/packages/waku-project/postcss.config.js b/e2e/fixtures/monorepo/packages/waku-project/postcss.config.js new file mode 100644 index 000000000..709af5d83 --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/postcss.config.js @@ -0,0 +1,7 @@ +/** @type {import('postcss-load-config').Config} */ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/public/images/favicon.png b/e2e/fixtures/monorepo/packages/waku-project/public/images/favicon.png new file mode 100644 index 000000000..cd90d7908 Binary files /dev/null and b/e2e/fixtures/monorepo/packages/waku-project/public/images/favicon.png differ diff --git a/e2e/fixtures/monorepo/packages/waku-project/src/components/counter.tsx b/e2e/fixtures/monorepo/packages/waku-project/src/components/counter.tsx new file mode 100644 index 000000000..adb6b94e9 --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/src/components/counter.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { useState } from 'react'; // eslint-disable-line import/no-unresolved + +export const Counter = () => { + const [count, setCount] = useState(0); + + const handleIncrement = () => setCount((c) => c + 1); + + return ( +
+
Count: {count}
+ +
+ ); +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/src/components/footer.tsx b/e2e/fixtures/monorepo/packages/waku-project/src/components/footer.tsx new file mode 100644 index 000000000..8cfd9c897 --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/src/components/footer.tsx @@ -0,0 +1,18 @@ +export const Footer = () => { + return ( + + ); +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/src/components/header.tsx b/e2e/fixtures/monorepo/packages/waku-project/src/components/header.tsx new file mode 100644 index 000000000..1b03ba54d --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/src/components/header.tsx @@ -0,0 +1,11 @@ +import { Link } from 'waku'; + +export const Header = () => { + return ( +
+

+ Waku starter +

+
+ ); +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/src/pages/_layout.tsx b/e2e/fixtures/monorepo/packages/waku-project/src/pages/_layout.tsx new file mode 100644 index 000000000..6d227c9f6 --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/src/pages/_layout.tsx @@ -0,0 +1,39 @@ +import '../styles.css'; + +import type { ReactNode } from 'react'; + +import { Header } from '../components/header'; +import { Footer } from '../components/footer'; + +type RootLayoutProps = { children: ReactNode }; + +export default async function RootLayout({ children }: RootLayoutProps) { + const data = await getData(); + + return ( +
+ + +
+
+ {children} +
+
+ ); +} + +const getData = async () => { + const data = { + description: 'An internet website!', + icon: '/images/favicon.png', + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: 'static', + } as const; +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/src/pages/about.tsx b/e2e/fixtures/monorepo/packages/waku-project/src/pages/about.tsx new file mode 100644 index 000000000..15d4c90e1 --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/src/pages/about.tsx @@ -0,0 +1,32 @@ +import { Link } from 'waku'; + +export default async function AboutPage() { + const data = await getData(); + + return ( +
+ {data.title} +

{data.headline}

+

{data.body}

+ + Return home + +
+ ); +} + +const getData = async () => { + const data = { + title: 'About', + headline: 'About Waku', + body: 'The minimal React framework', + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: 'static', + } as const; +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/src/pages/index.tsx b/e2e/fixtures/monorepo/packages/waku-project/src/pages/index.tsx new file mode 100644 index 000000000..6a825dd7d --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/src/pages/index.tsx @@ -0,0 +1,37 @@ +import { Link } from 'waku'; + +import { Counter } from '../components/counter'; + +export default async function HomePage() { + const data = await getData(); + + return ( +
+ {data.title} +

+ {data.headline} +

+

{data.body}

+ + + About page + +
+ ); +} + +const getData = async () => { + const data = { + title: 'Waku', + headline: 'Waku', + body: 'Hello world!', + }; + + return data; +}; + +export const getConfig = async () => { + return { + render: 'static', + } as const; +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/src/styles.css b/e2e/fixtures/monorepo/packages/waku-project/src/styles.css new file mode 100644 index 000000000..4cb5445ab --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/src/styles.css @@ -0,0 +1,4 @@ +@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,400;0,700;1,400;1,700&display=swap'); +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/e2e/fixtures/monorepo/packages/waku-project/tailwind.config.js b/e2e/fixtures/monorepo/packages/waku-project/tailwind.config.js new file mode 100644 index 000000000..df5c92956 --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/tailwind.config.js @@ -0,0 +1,4 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.{js,jsx,ts,tsx}'], +}; diff --git a/e2e/fixtures/monorepo/packages/waku-project/tsconfig.json b/e2e/fixtures/monorepo/packages/waku-project/tsconfig.json new file mode 100644 index 000000000..0d0f8993d --- /dev/null +++ b/e2e/fixtures/monorepo/packages/waku-project/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "composite": true, + "strict": true, + "target": "esnext", + "downlevelIteration": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "types": ["react/experimental"], + "jsx": "react-jsx", + "rootDir": "./src", + "outDir": "./dist" + } +} diff --git a/e2e/monorepo.spec.ts b/e2e/monorepo.spec.ts new file mode 100644 index 000000000..dbeec7620 --- /dev/null +++ b/e2e/monorepo.spec.ts @@ -0,0 +1,29 @@ +import { expect } from '@playwright/test'; + +import { test, prepareStandaloneSetup } from './utils.js'; + +const startApp = prepareStandaloneSetup('monorepo'); + +for (const mode of ['DEV', 'PRD'] as const) { + for (const packageManager of ['npm', 'pnpm', 'yarn'] as const) { + test.describe(`${packageManager} monorepo: ${mode}`, () => { + let port: number; + let stopApp: () => Promise; + test.beforeAll(async () => { + ({ port, stopApp } = await startApp( + mode, + packageManager, + 'packages/waku-project', + )); + }); + test.afterAll(async () => { + await stopApp(); + }); + + test('renders the home page', async ({ page }) => { + await page.goto(`http://localhost:${port}`); + await expect(page.getByTestId('header')).toHaveText('Waku'); + }); + }); + } +} diff --git a/e2e/utils.ts b/e2e/utils.ts index 0b4df5fb8..f369eb0bf 100644 --- a/e2e/utils.ts +++ b/e2e/utils.ts @@ -138,6 +138,12 @@ export const prepareNormalSetup = (fixtureName: string) => { return startApp; }; +const PACKAGE_INSTALL = { + npm: (path: string) => `npm add ${path}`, + pnpm: (path: string) => `pnpm add ${path}`, + yarn: (path: string) => `yarn add ${path}`, +} as const; + export const prepareStandaloneSetup = (fixtureName: string) => { const wakuDir = fileURLToPath(new URL('../packages/waku', import.meta.url)); const { version } = createRequire(import.meta.url)( @@ -151,7 +157,11 @@ export const prepareStandaloneSetup = (fixtureName: string) => { const tmpDir = process.env.TEMP_DIR || tmpdir(); let standaloneDir: string | undefined; let built = false; - const startApp = async (mode: 'DEV' | 'PRD' | 'STATIC') => { + const startApp = async ( + mode: 'DEV' | 'PRD' | 'STATIC', + packageManager: 'npm' | 'pnpm' | 'yarn' = 'npm', + packageDir = '', + ) => { if (!standaloneDir) { standaloneDir = mkdtempSync(join(tmpDir, fixtureName)); cpSync(fixtureDir, standaloneDir, { @@ -164,16 +174,22 @@ export const prepareStandaloneSetup = (fixtureName: string) => { cwd: wakuDir, stdio: 'inherit', }); + const wakuPackageTgz = join(standaloneDir, `waku-${version}.tgz`); + const installScript = PACKAGE_INSTALL[packageManager](wakuPackageTgz); + execSync(installScript, { cwd: standaloneDir, stdio: 'inherit' }); execSync( `npm install --force ${join(standaloneDir, `waku-${version}.tgz`)}`, { cwd: standaloneDir, stdio: 'inherit' }, ); } if (mode !== 'DEV' && !built) { - rmSync(`${standaloneDir}/dist`, { recursive: true, force: true }); + rmSync(`${join(standaloneDir, packageDir, 'dist')}`, { + recursive: true, + force: true, + }); execSync( `node ${join(standaloneDir, './node_modules/waku/dist/cli.js')} build`, - { cwd: standaloneDir }, + { cwd: join(standaloneDir, packageDir) }, ); built = true; } @@ -190,7 +206,7 @@ export const prepareStandaloneSetup = (fixtureName: string) => { cmd = `node ${join(standaloneDir, './node_modules/serve/build/main.js')} dist/public -p ${port}`; break; } - const cp = exec(cmd, { cwd: standaloneDir }); + const cp = exec(cmd, { cwd: join(standaloneDir, packageDir) }); debugChildProcess(cp, fileURLToPath(import.meta.url), [ /ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time/, ]); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8699f989b..21408f93e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -175,6 +175,8 @@ importers: specifier: ^5.7.2 version: 5.7.2 + e2e/fixtures/monorepo: {} + e2e/fixtures/partial-build: dependencies: react: diff --git a/tsconfig.e2e.json b/tsconfig.e2e.json index 9ec7e2831..1845263d0 100644 --- a/tsconfig.e2e.json +++ b/tsconfig.e2e.json @@ -51,6 +51,9 @@ { "path": "./e2e/fixtures/hot-reload/tsconfig.json" }, + { + "path": "./e2e/fixtures/monorepo/packages/waku-project/tsconfig.json" + }, { "path": "./e2e/fixtures/create-pages/tsconfig.json" }