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/dist/index.browser.js b/e2e/fixtures/exports-condition/modules/my-module/dist/index.browser.js new file mode 100644 index 000000000..1664dd456 --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/dist/index.browser.js @@ -0,0 +1,2 @@ +console.log('browser runtime called') +export const runtime = 'browser' \ No newline at end of file diff --git a/e2e/fixtures/exports-condition/modules/my-module/dist/index.d.ts b/e2e/fixtures/exports-condition/modules/my-module/dist/index.d.ts new file mode 100644 index 000000000..3d89aa203 --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/dist/index.d.ts @@ -0,0 +1 @@ +export const runtime: string; diff --git a/e2e/fixtures/exports-condition/modules/my-module/dist/index.js b/e2e/fixtures/exports-condition/modules/my-module/dist/index.js new file mode 100644 index 000000000..4c849e61f --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/dist/index.js @@ -0,0 +1,2 @@ +console.log('server runtime called') +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..b3331f78e --- /dev/null +++ b/e2e/fixtures/exports-condition/modules/my-module/package.json @@ -0,0 +1,12 @@ +{ + "name": "my-module", + "private": true, + "type": "module", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "browser": "./dist/index.browser.js", + "import": "./dist/index.js" + } + } +} diff --git a/e2e/fixtures/exports-condition/package.json b/e2e/fixtures/exports-condition/package.json new file mode 100644 index 000000000..0d4e56983 --- /dev/null +++ b/e2e/fixtures/exports-condition/package.json @@ -0,0 +1,23 @@ +{ + "name": "ssr-basic", + "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/e2e/fixtures/exports-condition/vite.config.ts b/e2e/fixtures/exports-condition/vite.config.ts new file mode 100644 index 000000000..294ec47b0 --- /dev/null +++ b/e2e/fixtures/exports-condition/vite.config.ts @@ -0,0 +1,10 @@ +export default ({ mode }: { mode: string }) => { + if (mode === 'development') { + return { + optimizeDeps: { + exclude: ['ai/rsc'], + }, + }; + } + return {}; +}; diff --git a/packages/waku/src/lib/builder/build.ts b/packages/waku/src/lib/builder/build.ts index 8c6ee8ac0..1add27557 100644 --- a/packages/waku/src/lib/builder/build.ts +++ b/packages/waku/src/lib/builder/build.ts @@ -252,6 +252,9 @@ const buildServerBundle = async ( esbuild: { jsx: 'automatic', }, + resolve: { + conditions: ['import', 'module', 'default', 'production'], + }, define: { 'process.env.NODE_ENV': JSON.stringify('production'), }, @@ -312,6 +315,9 @@ const buildSsrBundle = async ( esbuild: { jsx: 'automatic', }, + resolve: { + conditions: ['import', 'module', 'default', 'production'], + }, define: { 'process.env.NODE_ENV': JSON.stringify('production'), }, @@ -374,6 +380,9 @@ const buildClientBundle = async ( rscTransformPlugin({ isClient: true, isBuild: true, serverEntryFiles }), ...deployPlugins(config), ], + resolve: { + conditions: ['import', 'module', 'default', 'browser', 'production'], + }, build: { emptyOutDir: !partial, outDir: joinPath(rootDir, config.distDir, DIST_PUBLIC), diff --git a/packages/waku/src/lib/middleware/dev-server-impl.ts b/packages/waku/src/lib/middleware/dev-server-impl.ts index f0003abc5..d8d35df67 100644 --- a/packages/waku/src/lib/middleware/dev-server-impl.ts +++ b/packages/waku/src/lib/middleware/dev-server-impl.ts @@ -110,6 +110,9 @@ const createMainViteServer = ( rscTransformPlugin({ isClient: true, isBuild: false }), rscHmrPlugin(), ], + resolve: { + conditions: ['import', 'module', 'default', 'production'], + }, 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 620f38a8b..285a1a4eb 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: