diff --git a/e2e/fixtures/ssr-target-bundle/README.md b/e2e/fixtures/ssr-target-bundle/README.md
new file mode 100644
index 000000000..183341d89
--- /dev/null
+++ b/e2e/fixtures/ssr-target-bundle/README.md
@@ -0,0 +1,3 @@
+# SSR Bundling for NPM packages with environment depended code parts
+
+Choosing the right code based on the `export` section of the `react-textarea-autosize` module based on the backend server deployment target ('node' | 'webworker') and the client browser. No browser only code should be bundled with server only code.
diff --git a/e2e/fixtures/ssr-target-bundle/package.json b/e2e/fixtures/ssr-target-bundle/package.json
new file mode 100644
index 000000000..daff6ef3c
--- /dev/null
+++ b/e2e/fixtures/ssr-target-bundle/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "ssr-target-bundle",
+ "version": "0.1.0",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "dev": "waku dev --with-ssr",
+ "build": "waku build --with-ssr",
+ "start": "waku start --with-ssr"
+ },
+ "dependencies": {
+ "react": "18.3.0-canary-4b2a1115a-20240202",
+ "react-dom": "18.3.0-canary-4b2a1115a-20240202",
+ "react-server-dom-webpack": "18.3.0-canary-4b2a1115a-20240202",
+ "react-textarea-autosize": "^8.5.3",
+ "waku": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/react": "18.2.55",
+ "@types/react-dom": "18.2.19",
+ "typescript": "5.3.3"
+ }
+}
diff --git a/e2e/fixtures/ssr-target-bundle/src/components/App.tsx b/e2e/fixtures/ssr-target-bundle/src/components/App.tsx
new file mode 100644
index 000000000..904e52da3
--- /dev/null
+++ b/e2e/fixtures/ssr-target-bundle/src/components/App.tsx
@@ -0,0 +1,13 @@
+import { Textarea } from './Textarea.js';
+
+const App = ({ name }: { name: string }) => {
+ return (
+
+
Waku example
+ {name}
+
+
+ );
+};
+
+export default App;
diff --git a/e2e/fixtures/ssr-target-bundle/src/components/Textarea.tsx b/e2e/fixtures/ssr-target-bundle/src/components/Textarea.tsx
new file mode 100644
index 000000000..c4343295a
--- /dev/null
+++ b/e2e/fixtures/ssr-target-bundle/src/components/Textarea.tsx
@@ -0,0 +1,10 @@
+'use client';
+import TextareaAutosize from 'react-textarea-autosize';
+
+export const Textarea = () => {
+ return (
+
+
+
+ );
+};
diff --git a/e2e/fixtures/ssr-target-bundle/src/entries.tsx b/e2e/fixtures/ssr-target-bundle/src/entries.tsx
new file mode 100644
index 000000000..3c2365ef1
--- /dev/null
+++ b/e2e/fixtures/ssr-target-bundle/src/entries.tsx
@@ -0,0 +1,28 @@
+import { lazy } from 'react';
+import { defineEntries } from 'waku/server';
+import { Slot } from 'waku/client';
+
+const App = lazy(() => import('./components/App.js'));
+
+export default defineEntries(
+ // renderEntries
+ async (input) => {
+ return {
+ App: ,
+ };
+ },
+ // getBuildConfig
+ async () => [{ pathname: '/', entries: [{ input: '' }] }],
+ // getSsrConfig
+ async (pathname) => {
+ switch (pathname) {
+ case '/':
+ return {
+ input: '',
+ body: ,
+ };
+ default:
+ return null;
+ }
+ },
+);
diff --git a/e2e/fixtures/ssr-target-bundle/src/main.tsx b/e2e/fixtures/ssr-target-bundle/src/main.tsx
new file mode 100644
index 000000000..bc436da93
--- /dev/null
+++ b/e2e/fixtures/ssr-target-bundle/src/main.tsx
@@ -0,0 +1,17 @@
+import { StrictMode } from 'react';
+import { createRoot, hydrateRoot } from 'react-dom/client';
+import { Root, Slot } from 'waku/client';
+
+const rootElement = (
+
+
+
+
+
+);
+
+if (import.meta.env.WAKU_HYDRATE) {
+ hydrateRoot(document.body, rootElement);
+} else {
+ createRoot(document.body).render(rootElement);
+}
diff --git a/e2e/fixtures/ssr-target-bundle/tsconfig.json b/e2e/fixtures/ssr-target-bundle/tsconfig.json
new file mode 100644
index 000000000..8a8505011
--- /dev/null
+++ b/e2e/fixtures/ssr-target-bundle/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "strict": true,
+ "target": "esnext",
+ "downlevelIteration": true,
+ "esModuleInterop": true,
+ "module": "nodenext",
+ "skipLibCheck": true,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+ "jsx": "react-jsx",
+ "rootDir": "./src",
+ "outDir": "./dist"
+ }
+}
diff --git a/e2e/ssr-target-bundle.spec.ts b/e2e/ssr-target-bundle.spec.ts
new file mode 100644
index 000000000..389478312
--- /dev/null
+++ b/e2e/ssr-target-bundle.spec.ts
@@ -0,0 +1,92 @@
+import { expect } from '@playwright/test';
+import { execSync, exec, ChildProcess } from 'node:child_process';
+import { fileURLToPath } from 'node:url';
+import waitPort from 'wait-port';
+import { getFreePort, 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 --with-ssr',
+ },
+ {
+ build: 'build --with-ssr',
+ command: 'start --with-ssr',
+ },
+];
+
+const cwd = fileURLToPath(
+ new URL('./fixtures/ssr-target-bundle', import.meta.url),
+);
+
+for (const { build, command } of commands) {
+ test.describe(`ssr-target-bundle: ${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}`, {
+ cwd,
+ env: {
+ ...process.env,
+ PORT: `${port}`,
+ },
+ });
+ cp.stdout?.on('data', (data) => {
+ console.log(`${port} stdout: `, `${data}`);
+ });
+ cp.stderr?.on('data', (data) => {
+ console.error(`${port} stderr: `, `${data}`);
+ });
+ await waitPort({
+ port,
+ });
+ });
+
+ test.afterAll(async () => {
+ cp.kill();
+ });
+
+ test('add text input', async ({ page }) => {
+ await page.goto(`http://localhost:${port}/`);
+ await expect(page.getByTestId('app-name')).toHaveText('Waku');
+ await expect(page.getByTestId('textarea')).toHaveValue('EMPTY');
+ const height = await page
+ .getByTestId('textarea')
+ .evaluate((el) => el.clientHeight);
+ await page.getByTestId('textarea').fill('Line1\nLine2\nLine3');
+ const heightChanged = await page
+ .getByTestId('textarea')
+ .evaluate((el) => el.clientHeight);
+ expect(heightChanged).toBeGreaterThan(height);
+ });
+
+ test('no js environment should have first screen', async ({ browser }) => {
+ const context = await browser.newContext({
+ javaScriptEnabled: false,
+ });
+ const page = await context.newPage();
+ await page.goto(`http://localhost:${port}/`);
+ await expect(page.getByTestId('app-name')).toHaveText('Waku');
+ await expect(page.getByTestId('textarea')).toHaveValue('EMPTY');
+ await page.close();
+ await context.close();
+ });
+ });
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 860d666fe..5988657d9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -150,6 +150,34 @@ importers:
specifier: 5.3.3
version: 5.3.3
+ e2e/fixtures/ssr-target-bundle:
+ dependencies:
+ react:
+ specifier: 18.3.0-canary-4b2a1115a-20240202
+ version: 18.3.0-canary-4b2a1115a-20240202
+ react-dom:
+ specifier: 18.3.0-canary-4b2a1115a-20240202
+ version: 18.3.0-canary-4b2a1115a-20240202(react@18.3.0-canary-4b2a1115a-20240202)
+ react-server-dom-webpack:
+ specifier: 18.3.0-canary-4b2a1115a-20240202
+ version: 18.3.0-canary-4b2a1115a-20240202(react-dom@18.3.0-canary-4b2a1115a-20240202)(react@18.3.0-canary-4b2a1115a-20240202)(webpack@5.90.1)
+ react-textarea-autosize:
+ specifier: ^8.5.3
+ version: 8.5.3(@types/react@18.2.55)(react@18.3.0-canary-4b2a1115a-20240202)
+ waku:
+ specifier: workspace:*
+ version: link:../../../packages/waku
+ devDependencies:
+ '@types/react':
+ specifier: 18.2.55
+ version: 18.2.55
+ '@types/react-dom':
+ specifier: 18.2.19
+ version: 18.2.19
+ typescript:
+ specifier: 5.3.3
+ version: 5.3.3
+
examples/01_template:
dependencies:
react:
@@ -5398,6 +5426,20 @@ packages:
react-dom: 18.3.0-canary-4b2a1115a-20240202(react@18.3.0-canary-4b2a1115a-20240202)
webpack: 5.90.1
+ /react-textarea-autosize@8.5.3(@types/react@18.2.55)(react@18.3.0-canary-4b2a1115a-20240202):
+ resolution: {integrity: sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ '@babel/runtime': 7.23.9
+ react: 18.3.0-canary-4b2a1115a-20240202
+ use-composed-ref: 1.3.0(react@18.3.0-canary-4b2a1115a-20240202)
+ use-latest: 1.2.1(@types/react@18.2.55)(react@18.3.0-canary-4b2a1115a-20240202)
+ transitivePeerDependencies:
+ - '@types/react'
+ dev: false
+
/react-wrap-balancer@1.1.0(react@18.3.0-canary-4b2a1115a-20240202):
resolution: {integrity: sha512-EhF3jOZm5Fjx+Cx41e423qOv2c2aOvXAtym2OHqrGeMUnwERIyNsRBgnfT3plB170JmuYvts8K2KSPEIerKr5A==}
peerDependencies:
@@ -6325,6 +6367,41 @@ packages:
resolution: {integrity: sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==}
dev: true
+ /use-composed-ref@1.3.0(react@18.3.0-canary-4b2a1115a-20240202):
+ resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ dependencies:
+ react: 18.3.0-canary-4b2a1115a-20240202
+ dev: false
+
+ /use-isomorphic-layout-effect@1.1.2(@types/react@18.2.55)(react@18.3.0-canary-4b2a1115a-20240202):
+ resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.55
+ react: 18.3.0-canary-4b2a1115a-20240202
+ dev: false
+
+ /use-latest@1.2.1(@types/react@18.2.55)(react@18.3.0-canary-4b2a1115a-20240202):
+ resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==}
+ peerDependencies:
+ '@types/react': '*'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+ peerDependenciesMeta:
+ '@types/react':
+ optional: true
+ dependencies:
+ '@types/react': 18.2.55
+ react: 18.3.0-canary-4b2a1115a-20240202
+ use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.55)(react@18.3.0-canary-4b2a1115a-20240202)
+ dev: false
+
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
diff --git a/tsconfig.e2e.json b/tsconfig.e2e.json
index 882b10f7e..cbffee426 100644
--- a/tsconfig.e2e.json
+++ b/tsconfig.e2e.json
@@ -14,6 +14,9 @@
},
{
"path": "./e2e/fixtures/rsc-router/tsconfig.json"
+ },
+ {
+ "path": "./e2e/fixtures/ssr-target-bundle/tsconfig.json"
}
]
}