diff --git a/e2e/fixtures/exports-condition/README.md b/e2e/fixtures/exports-condition/README.md new file mode 100644 index 000000000..019239843 --- /dev/null +++ b/e2e/fixtures/exports-condition/README.md @@ -0,0 +1 @@ +# Exports Condition diff --git a/e2e/fixtures/exports-condition/modules/my-module/index.browser.js b/e2e/fixtures/exports-condition/modules/my-module/index.browser.js new file mode 100644 index 000000000..fa3d07eec --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/index.browser.js @@ -0,0 +1 @@ +export const runtime = 'browser' diff --git a/e2e/fixtures/exports-condition/modules/my-module/index.d.ts b/e2e/fixtures/exports-condition/modules/my-module/index.d.ts new file mode 100644 index 000000000..3d89aa203 --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/index.d.ts @@ -0,0 +1 @@ +export const runtime: string; diff --git a/e2e/fixtures/exports-condition/modules/my-module/index.js b/e2e/fixtures/exports-condition/modules/my-module/index.js new file mode 100644 index 000000000..a57f3df9f --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/index.js @@ -0,0 +1 @@ +export const runtime = 'server' diff --git a/e2e/fixtures/exports-condition/modules/my-module/package.json b/e2e/fixtures/exports-condition/modules/my-module/package.json new file mode 100644 index 000000000..ddb684747 --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/package.json @@ -0,0 +1,12 @@ +{ + "name": "my-module", + "private": true, + "type": "module", + "types": "./index.d.ts", + "exports": { + ".": { + "browser": "./index.browser.js", + "import": "./index.js" + } + } +} diff --git a/e2e/fixtures/exports-condition/package.json b/e2e/fixtures/exports-condition/package.json new file mode 100644 index 000000000..20e16e221 --- /dev/null +++ b/e2e/fixtures/exports-condition/package.json @@ -0,0 +1,23 @@ +{ + "name": "exports-condition", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "dev": "waku dev", + "build": "waku build", + "start": "waku start" + }, + "dependencies": { + "react": "19.0.0-rc-d6cb4e77-20240911", + "react-dom": "19.0.0-rc-d6cb4e77-20240911", + "react-server-dom-webpack": "19.0.0-rc-d6cb4e77-20240911", + "waku": "workspace:*", + "my-module": "link:./modules/my-module" + }, + "devDependencies": { + "@types/react": "^18.3.5", + "@types/react-dom": "^18.3.0", + "typescript": "^5.6.2" + } +} diff --git a/e2e/fixtures/exports-condition/src/components/App.tsx b/e2e/fixtures/exports-condition/src/components/App.tsx new file mode 100644 index 000000000..37289a3eb --- /dev/null +++ b/e2e/fixtures/exports-condition/src/components/App.tsx @@ -0,0 +1,23 @@ +import { runtime } from 'my-module'; +import { Client } from './Client.js'; + +const App = ({ name }: { name: string }) => { + return ( + + + Waku example + + +
+

{name}

+
{runtime}
+ +
+ + + ); +}; + +export default App; diff --git a/e2e/fixtures/exports-condition/src/components/Client.tsx b/e2e/fixtures/exports-condition/src/components/Client.tsx new file mode 100644 index 000000000..ec0660eb7 --- /dev/null +++ b/e2e/fixtures/exports-condition/src/components/Client.tsx @@ -0,0 +1,15 @@ +'use client'; +import { runtime } from 'my-module'; +import { useEffect, useState } from 'react'; + +export const Client = () => { + // avoid hydration error, since runtime is different between client/server + const [mount, setMount] = useState(false); + useEffect(() => { + setMount(true); + }, []); + if (mount) { + return
{runtime}
; + } + return null; +}; diff --git a/e2e/fixtures/exports-condition/src/entries.tsx b/e2e/fixtures/exports-condition/src/entries.tsx new file mode 100644 index 000000000..d674a6f06 --- /dev/null +++ b/e2e/fixtures/exports-condition/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: '', + html: , + }; + default: + return null; + } + }, +); diff --git a/e2e/fixtures/exports-condition/src/main.tsx b/e2e/fixtures/exports-condition/src/main.tsx new file mode 100644 index 000000000..101c2e3fc --- /dev/null +++ b/e2e/fixtures/exports-condition/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 ((globalThis as any).__WAKU_HYDRATE__) { + hydrateRoot(document, rootElement); +} else { + createRoot(document as any).render(rootElement); +} diff --git a/e2e/fixtures/exports-condition/tsconfig.json b/e2e/fixtures/exports-condition/tsconfig.json new file mode 100644 index 000000000..8e8eddf8c --- /dev/null +++ b/e2e/fixtures/exports-condition/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "composite": true, + "strict": true, + "target": "esnext", + "downlevelIteration": true, + "esModuleInterop": true, + "module": "nodenext", + "skipLibCheck": true, + "noUncheckedIndexedAccess": true, + "exactOptionalPropertyTypes": true, + "types": ["react/experimental"], + "jsx": "react-jsx", + "outDir": "./dist" + }, + "include": ["./src", "./vite.config.ts"] +} diff --git a/packages/waku/src/lib/builder/build.ts b/packages/waku/src/lib/builder/build.ts index dda65be6d..bb5098c1f 100644 --- a/packages/waku/src/lib/builder/build.ts +++ b/packages/waku/src/lib/builder/build.ts @@ -251,6 +251,9 @@ const buildServerBundle = async ( esbuild: { jsx: 'automatic', }, + resolve: { + conditions: ['import', 'module', 'default'], + }, define: { 'process.env.NODE_ENV': JSON.stringify('production'), }, @@ -311,6 +314,9 @@ const buildSsrBundle = async ( esbuild: { jsx: 'automatic', }, + resolve: { + conditions: ['import', 'module', 'default'], + }, define: { 'process.env.NODE_ENV': JSON.stringify('production'), }, diff --git a/packages/waku/src/lib/middleware/dev-server-impl.ts b/packages/waku/src/lib/middleware/dev-server-impl.ts index 9c21e2e9a..5e3090e10 100644 --- a/packages/waku/src/lib/middleware/dev-server-impl.ts +++ b/packages/waku/src/lib/middleware/dev-server-impl.ts @@ -112,6 +112,9 @@ const createMainViteServer = ( rscHmrPlugin(), fsRouterTypegenPlugin(config), ], + resolve: { + conditions: ['import', 'module', 'default'], + }, optimizeDeps: { include: ['react-server-dom-webpack/client', 'react-dom'], exclude: ['waku', 'rsc-html-stream/server'], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e421eab02..acbf4cc53 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -97,6 +97,34 @@ importers: specifier: ^5.6.2 version: 5.6.2 + e2e/fixtures/exports-condition: + dependencies: + my-module: + specifier: link:./modules/my-module + version: link:modules/my-module + react: + specifier: 19.0.0-rc-d6cb4e77-20240911 + version: 19.0.0-rc-d6cb4e77-20240911 + react-dom: + specifier: 19.0.0-rc-d6cb4e77-20240911 + version: 19.0.0-rc-d6cb4e77-20240911(react@19.0.0-rc-d6cb4e77-20240911) + react-server-dom-webpack: + specifier: 19.0.0-rc-d6cb4e77-20240911 + version: 19.0.0-rc-d6cb4e77-20240911(react-dom@19.0.0-rc-d6cb4e77-20240911(react@19.0.0-rc-d6cb4e77-20240911))(react@19.0.0-rc-d6cb4e77-20240911)(webpack@5.94.0) + waku: + specifier: workspace:* + version: link:../../../packages/waku + devDependencies: + '@types/react': + specifier: ^18.3.5 + version: 18.3.5 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + typescript: + specifier: ^5.6.2 + version: 5.6.2 + e2e/fixtures/partial-build: dependencies: react: diff --git a/tsconfig.e2e.json b/tsconfig.e2e.json index 9b2e8c2ea..fc159ffc2 100644 --- a/tsconfig.e2e.json +++ b/tsconfig.e2e.json @@ -47,6 +47,9 @@ }, { "path": "./e2e/fixtures/broken-links/tsconfig.json" + }, + { + "path": "./e2e/fixtures/exports-condition/tsconfig.json" } ] }