diff --git a/.changeset/config.json b/.changeset/config.json index e6bade14a9..a793a7b12d 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -4,7 +4,7 @@ "commit": true, "linked": [], "access": "public", - "baseBranch": "master", + "baseBranch": "v10", "updateInternalDependencies": "minor", "ignore": [], "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 0000000000..6b55b96e72 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,11 @@ +{ + "mode": "pre", + "tag": "alpha", + "initialVersions": { + "example": "0.0.0", + "@react-three/eslint-plugin": "0.1.2", + "@react-three/fiber": "9.4.2", + "@react-three/test-renderer": "9.1.0" + }, + "changesets": ["v10-alpha-release"] +} diff --git a/.changeset/v10-alpha-release.md b/.changeset/v10-alpha-release.md new file mode 100644 index 0000000000..7687110325 --- /dev/null +++ b/.changeset/v10-alpha-release.md @@ -0,0 +1,35 @@ +--- +'@react-three/fiber': major +'@react-three/test-renderer': major +'@react-three/eslint-plugin': minor +--- + +## R3F v10 - WebGPU Support & React 19 + +### Breaking Changes + +- **React 19 required** - Minimum React version is now 19.0 +- **Three.js 0.181+ required** - Minimum Three.js version is now 0.181.2 +- **New entry points** - Bundle structure reorganized with dedicated WebGPU support + +### New Features + +- **WebGPU Renderer Support** - New `@react-three/fiber/webgpu` entry point with full WebGPU and TSL (Three.js Shading Language) support +- **Legacy Entry Point** - `@react-three/fiber/legacy` for WebGL-only environments +- **Improved Frame Loop** - Enhanced `useFrame` with better priority scheduling and `runOnce` support +- **Build System Migration** - Moved from Preconstruct to Unbuild for better per-entry-point optimization + +### Entry Points + +```js +// Default - WebGL + WebGPU support +import { Canvas } from '@react-three/fiber' + +// WebGPU only - smaller bundle, TSL support +import { Canvas } from '@react-three/fiber/webgpu' + +// Legacy WebGL only - maximum compatibility +import { Canvas } from '@react-three/fiber/legacy' +``` + +See the full migration guide in the documentation. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b8a9477a71..f82525e419 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,9 +6,16 @@ on: pull_request: {} jobs: build: - name: Build, lint, and test + name: Build, lint, and test (React ${{ matrix.react-version }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + react-version: + - 19.0.0 + - latest + steps: - name: Checkout repo uses: actions/checkout@v4 @@ -18,11 +25,26 @@ jobs: with: node-version: 22 + - name: Cache node_modules and Yarn cache + uses: actions/cache@v4 + with: + path: | + **/node_modules + .yarn/cache + key: > + ${{ runner.os }}-node22-react-${{ matrix.react-version }}-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node22-react-${{ matrix.react-version }}- + - name: Install deps and build (with cache) uses: bahmutov/npm-install@v1 with: install-command: yarn --immutable --silent + - name: Override React version (${{ matrix.react-version }}) + run: | + yarn add @types/react@${{ matrix.react-version }} react@${{ matrix.react-version }} @types/react-dom@${{ matrix.react-version }} react-dom@${{ matrix.react-version }} --dev -W + - name: Build run: yarn run build diff --git a/.gitignore b/.gitignore index f06d7b07b9..3ae913a828 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ node_modules/ coverage/ dist/ build/ +packages/fiber/react-reconciler/ Thumbs.db ehthumbs.db Desktop.ini diff --git a/.prettierignore b/.prettierignore index de2a8a33dc..086543aa7b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,7 @@ dist/ coverage/ node_modules/ +packages/fiber/react-reconciler/ .yarn/ *.gltf *.mdx \ No newline at end of file diff --git a/ALPHA-RELEASE.md b/ALPHA-RELEASE.md new file mode 100644 index 0000000000..596dc8b215 --- /dev/null +++ b/ALPHA-RELEASE.md @@ -0,0 +1,351 @@ +# Alpha Release Guide (v10) + +This guide covers how to create and publish alpha releases for the v10 branch to npm. + +## Understanding the Release Structure + +| Branch | npm Tag | Version Format | Install Command | +| -------- | -------- | ---------------- | -------------------------------- | +| `master` | `latest` | `9.x.x` | `npm i @react-three/fiber` | +| `v10` | `alpha` | `10.0.0-alpha.x` | `npm i @react-three/fiber@alpha` | + +**Important**: The `latest` tag is what users get by default when running `npm install`. We must ensure alpha releases are tagged as `alpha`, NOT `latest`. + +--- + +## Prerequisites + +Before releasing, ensure: + +1. You are on the `v10` branch +2. All tests pass: `yarn test` +3. Build succeeds: `yarn build` +4. Bundle verification passes: `yarn verify-bundles` + +```bash +# Verify you're on the correct branch +git branch --show-current # Should output: v10 + +# Run the full CI check +yarn ci +``` + +--- + +## Step-by-Step Alpha Release Process + +### 1. Verify Prerelease Mode + +Check that prerelease mode is active: + +```bash +# Should show: { "mode": "pre", "tag": "alpha", ... } +cat .changeset/pre.json +``` + +If the file doesn't exist, enter prerelease mode: + +```bash +yarn changeset pre enter alpha +``` + +This creates `.changeset/pre.json` which tells changesets to version packages as `X.X.X-alpha.X`. + +### 2. Create a Changeset + +Describe what changed in this release: + +```bash +yarn changeset:add +``` + +You'll be prompted to: + +- Select which packages changed (use spacebar to select) +- Choose the bump type (major/minor/patch) +- Write a summary of changes + +**Example changeset summary:** + +``` +Added WebGPU support with new entry point @react-three/fiber/webgpu +``` + +### 3. Version the Packages + +Apply the changesets to update package versions: + +```bash +yarn vers +``` + +This will: + +- Update `package.json` versions to something like `10.0.0-alpha.0` +- Generate/update `CHANGELOG.md` files +- Consume the changeset files + +**Review the changes before committing:** + +```bash +git diff +``` + +### 4. Commit the Version Bump + +```bash +git add . +git commit -m "chore: version packages for alpha release" +git push origin v10 +``` + +### 5. Build and Verify with Dry Run + +Before publishing, build and verify what will be published: + +```bash +# Build all packages +yarn build + +# Dry run to see exactly what will be published (without actually publishing) +cd packages/fiber && npm publish --dry-run --tag alpha +cd ../test-renderer && npm publish --dry-run --tag alpha +cd ../eslint-plugin && npm publish --dry-run --tag alpha +cd ../../ +``` + +**What to check in dry-run output:** + +- `dist/` folder is included (should see `dist/index.mjs`, `dist/legacy.mjs`, `dist/webgpu/index.mjs`, etc.) +- Package size looks reasonable (~1 MB for fiber, not ~100 KB) +- Version shows `-alpha.X` suffix + +> **Why this matters**: The root `.gitignore` excludes `dist/`, which can cause npm to skip it. +> The `files` field in `package.json` whitelists what to include. If dist is missing, +> users will get a broken package! + +### 6. Publish to npm with Alpha Tag + +⚠️ **CRITICAL: Use the `--tag alpha` flag to avoid publishing to `latest`** + +```bash +# Publish with alpha tag +yarn changeset publish --tag alpha +``` + +Or manually for each package: + +```bash +cd packages/fiber +npm publish --tag alpha --access public + +cd ../test-renderer +npm publish --tag alpha --access public + +cd ../eslint-plugin +npm publish --tag alpha --access public +``` + +### 7. Verify the Release + +After publishing, verify the tags are correct: + +```bash +# Check what version is on each tag +npm dist-tag ls @react-three/fiber +``` + +Expected output: + +``` +alpha: 10.0.0-alpha.0 +latest: 9.x.x +``` + +**If you accidentally published to `latest`**, see the Emergency Recovery section below. + +--- + +## Quick Reference Commands + +```bash +# Enter alpha prerelease mode (first time only) +yarn changeset pre enter alpha + +# Add a changeset describing your changes +yarn changeset:add + +# Version packages (applies changesets) +yarn vers + +# Build packages +yarn build + +# Dry-run to verify package contents (IMPORTANT!) +cd packages/fiber && npm pack --dry-run + +# Publish with alpha tag +yarn changeset publish --tag alpha + +# Exit prerelease mode (when ready for stable v10) +yarn changeset pre exit + +# Check npm tags +npm dist-tag ls @react-three/fiber +``` + +--- + +## Safety Checklist Before Publishing + +Before running `yarn changeset publish`: + +- [ ] Verified on `v10` branch: `git branch --show-current` +- [ ] Prerelease mode is active: Check `.changeset/pre.json` exists +- [ ] Version looks correct: `grep version packages/fiber/package.json` shows `-alpha.X` +- [ ] Tests pass: `yarn test` +- [ ] Build passes: `yarn build` +- [ ] **Dry-run verified**: `npm publish --dry-run --tag alpha` shows `dist/` files included (~1 MB package size) +- [ ] Will use `--tag alpha` flag + +--- + +## Emergency Recovery + +### If You Accidentally Published to `latest` + +If an alpha version was accidentally published as `latest`: + +```bash +# 1. Move the alpha version to the alpha tag +npm dist-tag add @react-three/fiber@10.0.0-alpha.0 alpha + +# 2. Restore the correct latest version +npm dist-tag add @react-three/fiber@9.x.x latest + +# Replace 9.x.x with the actual last stable version number +``` + +Verify the fix: + +```bash +npm dist-tag ls @react-three/fiber +``` + +### If You Need to Unpublish + +npm has a 72-hour window for unpublishing: + +```bash +npm unpublish @react-three/fiber@10.0.0-alpha.0 +``` + +> **Warning**: Unpublishing can break users who have already installed that version. + +--- + +## When Ready for Stable v10 Release + +When v10 is ready to become the new stable version: + +### 1. Exit Prerelease Mode + +```bash +yarn changeset pre exit +``` + +### 2. Create Final Changeset + +```bash +yarn changeset:add +``` + +### 3. Version as Stable + +```bash +yarn vers +``` + +This will produce `10.0.0` (no alpha suffix). + +### 4. Publish to Latest + +```bash +yarn build +yarn changeset publish # No --tag flag = publishes to latest +``` + +### 5. Update Branch Strategy + +After stable v10 release: + +1. Merge `v10` into `master` or make `v10` the new default branch +2. Update `.changeset/config.json` baseBranch back to `master` if needed + +--- + +## Troubleshooting + +### "Cannot publish over existing version" + +You're trying to publish a version that already exists. Either: + +- Create a new changeset and run `yarn vers` again +- Or increment the prerelease number manually + +### Changesets not detecting changes + +Make sure you're comparing against the correct base branch. Check `.changeset/config.json`: + +```json +{ + "baseBranch": "v10" +} +``` + +### Version shows stable instead of alpha + +Prerelease mode may not be active. Check for `.changeset/pre.json`: + +```bash +cat .changeset/pre.json +``` + +If missing, re-enter prerelease mode: + +```bash +yarn changeset pre enter alpha +``` + +### Dry-run shows package is too small / missing dist/ + +If `npm pack --dry-run` shows ~100-200 KB instead of ~1 MB, the `dist/` folder is being excluded. + +**Cause**: The root `.gitignore` has `dist/` which npm respects by default. + +**Fix**: Ensure the package's `package.json` has a `files` field that explicitly includes `dist`: + +```json +"files": [ + "dist", + "types", + "react-reconciler", + "readme.md" +] +``` + +The `files` field acts as a whitelist and overrides `.gitignore` for publishing. + +--- + +## NPM Tags Explained + +| Tag | Purpose | Command to Install | +| -------- | ---------------------------------------- | -------------------------------- | +| `latest` | Default stable release | `npm i @react-three/fiber` | +| `alpha` | Early preview, may have breaking changes | `npm i @react-three/fiber@alpha` | +| `beta` | Feature complete, testing stability | `npm i @react-three/fiber@beta` | +| `rc` | Release candidate, final testing | `npm i @react-three/fiber@rc` | +| `next` | Alternative for bleeding edge | `npm i @react-three/fiber@next` | + +Users on `latest` will **never** automatically get alpha versions - they must explicitly opt-in with `@alpha`. diff --git a/docs/BUILD.md b/docs/BUILD.md index f29305176e..9f523b8940 100644 --- a/docs/BUILD.md +++ b/docs/BUILD.md @@ -102,8 +102,48 @@ yarn build # Development mode with stub files (faster iteration) yarn stub + +# Patch react-reconciler to ESM (runs automatically during postinstall) +yarn patch-react-reconciler +``` + +## React Reconciler Patching + +The package includes a patching system for `react-reconciler` to convert it from CJS to ESM format. This is necessary because the official `react-reconciler` package has ESM compatibility issues. + +### How it works: + +1. **Automatic patching**: During `yarn install`, the `postinstall` script runs `yarn patch-react-reconciler` +2. **Vite transformation**: Uses `vite build` (configured in root `vite.config.ts`) to: + - Bundle `react-reconciler` from `node_modules` + - Convert CJS constants to ESM exports + - Copy TypeScript definitions + - Output to `packages/fiber/react-reconciler/` +3. **Build aliasing**: `build.config.ts` aliases `react-reconciler` imports to the patched version +4. **Test integration**: `jest.config.js` also uses the patched version for tests + +### Output: + +``` +packages/fiber/react-reconciler/ +├── index.js # Main reconciler bundle (ESM) +├── index.d.ts # TypeScript definitions +├── constants.js # Constants bundle (ESM) +└── constants.d.ts # Constants TypeScript definitions ``` +This directory is gitignored and regenerated on install. + +### Testing and Building: + +Both tests and builds use the **same patched reconciler** to ensure consistency: + +- **Source code** imports via relative paths: `../../react-reconciler/index.js` +- **Build time** (unbuild): Bundles the patched ESM version directly into `dist/` files +- **Test time** (Jest): Transforms the patched ESM files to CJS on-the-fly using babel-jest + +This ensures you test exactly what you ship. + ## Output Structure After building, the `dist/` folder contains: diff --git a/docs/getting-started/examples.mdx b/docs/getting-started/examples.mdx index 124bc4fae2..da03ced745 100644 --- a/docs/getting-started/examples.mdx +++ b/docs/getting-started/examples.mdx @@ -8,332 +8,332 @@ nav: 3
  • - +

    [![shopping demo](https://pmndrs.github.io/examples/shopping/thumbnail.webp)](https://pmndrs.github.io/examples/shopping) -- selection, tiltshift

  • - +

    [![cards-with-border-radius demo](https://pmndrs.github.io/examples/cards-with-border-radius/thumbnail.webp)](https://pmndrs.github.io/examples/cards-with-border-radius) -- border-radius

  • - +

    [![threejs-journey-lv-1-fisheye demo](https://pmndrs.github.io/examples/threejs-journey-lv-1-fisheye/thumbnail.webp)](https://pmndrs.github.io/examples/threejs-journey-lv-1-fisheye) -- bruno, simon, threejs-journey, fisheye

  • - +

    [![lusion-connectors demo](https://pmndrs.github.io/examples/lusion-connectors/thumbnail.webp)](https://pmndrs.github.io/examples/lusion-connectors) -- lusion, n8ao

  • - +

    [![ecctrl-fisheye demo](https://pmndrs.github.io/examples/ecctrl-fisheye/thumbnail.webp)](https://pmndrs.github.io/examples/ecctrl-fisheye) -- ecctrl, character-controller

  • - +

    [![monitors demo](https://pmndrs.github.io/examples/monitors/thumbnail.webp)](https://pmndrs.github.io/examples/monitors) -- effects, bloom, dof, reflections

  • - +

    [![flying-bananas demo](https://pmndrs.github.io/examples/flying-bananas/thumbnail.webp)](https://pmndrs.github.io/examples/flying-bananas) -- effects, dof, bananas

  • - +

    [![t-shirt-configurator demo](https://pmndrs.github.io/examples/t-shirt-configurator/thumbnail.webp)](https://pmndrs.github.io/examples/t-shirt-configurator) -- configurator, t-shirt, soft-shadows

  • - +

    [![caustics demo](https://pmndrs.github.io/examples/caustics/thumbnail.webp)](https://pmndrs.github.io/examples/caustics) -- caustics, effects, soft-shadows

  • - +

    [![ssgi-spheres-with-rapier-physics demo](https://pmndrs.github.io/examples/ssgi-spheres-with-rapier-physics/thumbnail.webp)](https://pmndrs.github.io/examples/ssgi-spheres-with-rapier-physics) -- ssgi, rapier

  • - +

    [![thunder-clouds demo](https://pmndrs.github.io/examples/thunder-clouds/thumbnail.webp)](https://pmndrs.github.io/examples/thunder-clouds) -- clouds

  • - +

    [![motionpathcontrols demo](https://pmndrs.github.io/examples/motionpathcontrols/thumbnail.webp)](https://pmndrs.github.io/examples/motionpathcontrols) -- motion-path, clouds

  • - +

    [![room-with-soft-shadows demo](https://pmndrs.github.io/examples/room-with-soft-shadows/thumbnail.webp)](https://pmndrs.github.io/examples/room-with-soft-shadows) -- caustics, effects, soft-shadows

  • - +

    [![volumetric-light-godray demo](https://pmndrs.github.io/examples/volumetric-light-godray/thumbnail.webp)](https://pmndrs.github.io/examples/volumetric-light-godray) -- godrays, reflections

  • - +

    [![enter-portals demo](https://pmndrs.github.io/examples/enter-portals/thumbnail.webp)](https://pmndrs.github.io/examples/enter-portals) -- portals

  • - +

    [![pass-through-portals demo](https://pmndrs.github.io/examples/pass-through-portals/thumbnail.webp)](https://pmndrs.github.io/examples/pass-through-portals) -- portals

  • - +

    [![magic-box demo](https://pmndrs.github.io/examples/magic-box/thumbnail.webp)](https://pmndrs.github.io/examples/magic-box) -- portals

  • - +

    [![video-cookies demo](https://pmndrs.github.io/examples/video-cookies/thumbnail.webp)](https://pmndrs.github.io/examples/video-cookies) -- video, cookies, caustics

  • - +

    [![glass-flower demo](https://pmndrs.github.io/examples/glass-flower/thumbnail.webp)](https://pmndrs.github.io/examples/glass-flower) -- glass, transmission, bloom

  • - +

    [![cards demo](https://pmndrs.github.io/examples/cards/thumbnail.webp)](https://pmndrs.github.io/examples/cards) -- cards, image

  • - +

    [![image-gallery demo](https://pmndrs.github.io/examples/image-gallery/thumbnail.webp)](https://pmndrs.github.io/examples/image-gallery) -- reflections, annotations

  • - +

    [![horizontal-tiles demo](https://pmndrs.github.io/examples/horizontal-tiles/thumbnail.webp)](https://pmndrs.github.io/examples/horizontal-tiles) -- scroll, controls

  • - +

    [![bestservedbold-christmas-baubles demo](https://pmndrs.github.io/examples/bestservedbold-christmas-baubles/thumbnail.webp)](https://pmndrs.github.io/examples/bestservedbold-christmas-baubles) -- physics

  • - +

    [![the-three-graces demo](https://pmndrs.github.io/examples/the-three-graces/thumbnail.webp)](https://pmndrs.github.io/examples/the-three-graces) -- html, annotations

  • - +

    [![frosted-glass demo](https://pmndrs.github.io/examples/frosted-glass/thumbnail.webp)](https://pmndrs.github.io/examples/frosted-glass) -- frosted, glass, transmission

  • - +

    [![gltfjsx-400kb-drone demo](https://pmndrs.github.io/examples/gltfjsx-400kb-drone/thumbnail.webp)](https://pmndrs.github.io/examples/gltfjsx-400kb-drone) -- gltfjsx, effects, bloom, soft-shadows

  • - +

    [![starwars demo](https://pmndrs.github.io/examples/starwars/thumbnail.webp)](https://pmndrs.github.io/examples/starwars) -- effects, reflections, ssr, bloom

  • - +

    [![bruno-simons-20k-challenge demo](https://pmndrs.github.io/examples/bruno-simons-20k-challenge/thumbnail.webp)](https://pmndrs.github.io/examples/bruno-simons-20k-challenge) -- rapier, physics, soft-shadows

  • - +

    [![scrollcontrols-and-lens-refraction demo](https://pmndrs.github.io/examples/scrollcontrols-and-lens-refraction/thumbnail.webp)](https://pmndrs.github.io/examples/scrollcontrols-and-lens-refraction) -- scroll, refraction, lens

  • - +

    [![building-dynamic-envmaps demo](https://pmndrs.github.io/examples/building-dynamic-envmaps/thumbnail.webp)](https://pmndrs.github.io/examples/building-dynamic-envmaps) -- effects, bloom, reflections

  • - +

    [![nextjs-prism demo](https://pmndrs.github.io/examples/nextjs-prism/thumbnail.webp)](https://pmndrs.github.io/examples/nextjs-prism) -- effects, bloom

  • - +

    [![building-live-envmaps demo](https://pmndrs.github.io/examples/building-live-envmaps/thumbnail.webp)](https://pmndrs.github.io/examples/building-live-envmaps) -- custom, environments

  • - +

    [![shoe-configurator demo](https://pmndrs.github.io/examples/shoe-configurator/thumbnail.webp)](https://pmndrs.github.io/examples/shoe-configurator) -- gltfjsx, configurator

  • - +

    [![audio-analyser demo](https://pmndrs.github.io/examples/audio-analyser/thumbnail.webp)](https://pmndrs.github.io/examples/audio-analyser) -- audio, analyser

  • - +

    [![ground-reflections-and-video-textures demo](https://pmndrs.github.io/examples/ground-reflections-and-video-textures/thumbnail.webp)](https://pmndrs.github.io/examples/ground-reflections-and-video-textures) -- ground, reflections, video-texture

  • - +

    [![threejs-journey-portal demo](https://pmndrs.github.io/examples/threejs-journey-portal/thumbnail.webp)](https://pmndrs.github.io/examples/threejs-journey-portal) -- bruno-simon, threejs-journey

  • - +

    [![mixing-html-and-webgl-w-occlusion demo](https://pmndrs.github.io/examples/mixing-html-and-webgl-w-occlusion/thumbnail.webp)](https://pmndrs.github.io/examples/mixing-html-and-webgl-w-occlusion) -- html, iframe

  • - +

    [![interactive-spline-scene-live-html demo](https://pmndrs.github.io/examples/interactive-spline-scene-live-html/thumbnail.webp)](https://pmndrs.github.io/examples/interactive-spline-scene-live-html) -- splinetool, iframe

  • - +

    [![diamond-refraction demo](https://pmndrs.github.io/examples/diamond-refraction/thumbnail.webp)](https://pmndrs.github.io/examples/diamond-refraction) -- refraction

  • - +

    [![diamond-ring demo](https://pmndrs.github.io/examples/diamond-ring/thumbnail.webp)](https://pmndrs.github.io/examples/diamond-ring) -- refraction, instanced

  • - +

    [![envmap-ground-projection demo](https://pmndrs.github.io/examples/envmap-ground-projection/thumbnail.webp)](https://pmndrs.github.io/examples/envmap-ground-projection) -- ground-projected-env

  • - +

    [![spline-glass-shapes demo](https://pmndrs.github.io/examples/spline-glass-shapes/thumbnail.webp)](https://pmndrs.github.io/examples/spline-glass-shapes) -- splinetool, transmission

  • - +

    [![csg-bunny-usegroups demo](https://pmndrs.github.io/examples/csg-bunny-usegroups/thumbnail.webp)](https://pmndrs.github.io/examples/csg-bunny-usegroups) -- transmission, csg

  • - +

    [![csg-house demo](https://pmndrs.github.io/examples/csg-house/thumbnail.webp)](https://pmndrs.github.io/examples/csg-house) -- csg

  • - +

    [![gltf-animations-tied-to-scroll demo](https://pmndrs.github.io/examples/gltf-animations-tied-to-scroll/thumbnail.webp)](https://pmndrs.github.io/examples/gltf-animations-tied-to-scroll) -- scroll, animation, effects, tiltshift

  • - +

    [![object-clump demo](https://pmndrs.github.io/examples/object-clump/thumbnail.webp)](https://pmndrs.github.io/examples/object-clump) -- physics, effects, n8ao

  • - +

    [![html-input-fields demo](https://pmndrs.github.io/examples/html-input-fields/thumbnail.webp)](https://pmndrs.github.io/examples/html-input-fields) -- html, input

  • - +

    [![useintersect-and-scrollcontrols demo](https://pmndrs.github.io/examples/useintersect-and-scrollcontrols/thumbnail.webp)](https://pmndrs.github.io/examples/useintersect-and-scrollcontrols) -- scroll

  • - +

    [![infinite-scroll demo](https://pmndrs.github.io/examples/infinite-scroll/thumbnail.webp)](https://pmndrs.github.io/examples/infinite-scroll) -- scroll

  • - +

    [![scrollcontrols-with-minimap demo](https://pmndrs.github.io/examples/scrollcontrols-with-minimap/thumbnail.webp)](https://pmndrs.github.io/examples/scrollcontrols-with-minimap) -- scroll

  • - +

    [![instanced-particles-effects demo](https://pmndrs.github.io/examples/instanced-particles-effects/thumbnail.webp)](https://pmndrs.github.io/examples/instanced-particles-effects) -- effects, particles

  • - +

    [![dbismut-furniture demo](https://pmndrs.github.io/examples/dbismut-furniture/thumbnail.webp)](https://pmndrs.github.io/examples/dbismut-furniture) -- cross-fade, transitions

  • - +

    [![portal-shapes demo](https://pmndrs.github.io/examples/portal-shapes/thumbnail.webp)](https://pmndrs.github.io/examples/portal-shapes) -- transmission, portals, physics

  • - +

    [![aquarium demo](https://pmndrs.github.io/examples/aquarium/thumbnail.webp)](https://pmndrs.github.io/examples/aquarium) -- transmission, portals

  • - +

    [![portals demo](https://pmndrs.github.io/examples/portals/thumbnail.webp)](https://pmndrs.github.io/examples/portals) -- portals, blend

  • - +

    [![inter-epoxy-resin demo](https://pmndrs.github.io/examples/inter-epoxy-resin/thumbnail.webp)](https://pmndrs.github.io/examples/inter-epoxy-resin)

  • - +

    [![stage-presets-gltfjsx demo](https://pmndrs.github.io/examples/stage-presets-gltfjsx/thumbnail.webp)](https://pmndrs.github.io/examples/stage-presets-gltfjsx)

  • - +

    [![react-ellipsecurve demo](https://pmndrs.github.io/examples/react-ellipsecurve/thumbnail.webp)](https://pmndrs.github.io/examples/react-ellipsecurve)

  • - +

    [![iridescent-decals demo](https://pmndrs.github.io/examples/iridescent-decals/thumbnail.webp)](https://pmndrs.github.io/examples/iridescent-decals)

  • - +

    [![baking-soft-shadows demo](https://pmndrs.github.io/examples/baking-soft-shadows/thumbnail.webp)](https://pmndrs.github.io/examples/baking-soft-shadows)

  • - +

    [![ssr-test demo](https://pmndrs.github.io/examples/ssr-test/thumbnail.webp)](https://pmndrs.github.io/examples/ssr-test)

  • - +

    [![csg-operations-rapier-physics demo](https://pmndrs.github.io/examples/csg-operations-rapier-physics/thumbnail.webp)](https://pmndrs.github.io/examples/csg-operations-rapier-physics)

  • - +

    [![faucets-select-highlight demo](https://pmndrs.github.io/examples/faucets-select-highlight/thumbnail.webp)](https://pmndrs.github.io/examples/faucets-select-highlight)

  • - +

    [![rapier-physics demo](https://pmndrs.github.io/examples/rapier-physics/thumbnail.webp)](https://pmndrs.github.io/examples/rapier-physics)

  • - +

    [![bloom-hdr-workflow-gltf demo](https://pmndrs.github.io/examples/bloom-hdr-workflow-gltf/thumbnail.webp)](https://pmndrs.github.io/examples/bloom-hdr-workflow-gltf)

  • - +

    [![ground-projected-envmaps-lamina demo](https://pmndrs.github.io/examples/ground-projected-envmaps-lamina/thumbnail.webp)](https://pmndrs.github.io/examples/ground-projected-envmaps-lamina)

  • - +

    [![basic-ballpit demo](https://pmndrs.github.io/examples/basic-ballpit/thumbnail.webp)](https://pmndrs.github.io/examples/basic-ballpit)

  • - +

    [![backdrop-and-cables demo](https://pmndrs.github.io/examples/backdrop-and-cables/thumbnail.webp)](https://pmndrs.github.io/examples/backdrop-and-cables)

  • - +

    [![clones demo](https://pmndrs.github.io/examples/clones/thumbnail.webp)](https://pmndrs.github.io/examples/clones)

  • - +

    [![lamina-1x demo](https://pmndrs.github.io/examples/lamina-1x/thumbnail.webp)](https://pmndrs.github.io/examples/lamina-1x)

  • - +

    [![react-pp-outlines demo](https://pmndrs.github.io/examples/react-pp-outlines/thumbnail.webp)](https://pmndrs.github.io/examples/react-pp-outlines)

  • - +

    [![gatsby-stars demo](https://pmndrs.github.io/examples/gatsby-stars/thumbnail.webp)](https://pmndrs.github.io/examples/gatsby-stars)

  • - +

    [![pmndrs-vercel demo](https://pmndrs.github.io/examples/pmndrs-vercel/thumbnail.webp)](https://pmndrs.github.io/examples/pmndrs-vercel)

  • - +

    [![sport-hall demo](https://pmndrs.github.io/examples/sport-hall/thumbnail.webp)](https://pmndrs.github.io/examples/sport-hall)

  • - +

    [![night-train demo](https://pmndrs.github.io/examples/night-train/thumbnail.webp)](https://pmndrs.github.io/examples/night-train)

  • - +

    [![bouncy-watch demo](https://pmndrs.github.io/examples/bouncy-watch/thumbnail.webp)](https://pmndrs.github.io/examples/bouncy-watch)

  • - +

    [![transparent-aesop-bottles demo](https://pmndrs.github.io/examples/transparent-aesop-bottles/thumbnail.webp)](https://pmndrs.github.io/examples/transparent-aesop-bottles)

  • - +

    [![raycast-cycling demo](https://pmndrs.github.io/examples/raycast-cycling/thumbnail.webp)](https://pmndrs.github.io/examples/raycast-cycling)

  • - +

    [![landing-page demo](https://pmndrs.github.io/examples/landing-page/thumbnail.webp)](https://pmndrs.github.io/examples/landing-page)

  • - +

    [![scrollcontrols-gltf demo](https://pmndrs.github.io/examples/scrollcontrols-gltf/thumbnail.webp)](https://pmndrs.github.io/examples/scrollcontrols-gltf)

  • - +

    [![merged-instance demo](https://pmndrs.github.io/examples/merged-instance/thumbnail.webp)](https://pmndrs.github.io/examples/merged-instance)

  • - +

    [![gpgpu-curl-noise-dof demo](https://pmndrs.github.io/examples/gpgpu-curl-noise-dof/thumbnail.webp)](https://pmndrs.github.io/examples/gpgpu-curl-noise-dof)

  • - +

    [![hi-key-bubbles demo](https://pmndrs.github.io/examples/hi-key-bubbles/thumbnail.webp)](https://pmndrs.github.io/examples/hi-key-bubbles)

  • - +

    [![floating-instanced-shoes demo](https://pmndrs.github.io/examples/floating-instanced-shoes/thumbnail.webp)](https://pmndrs.github.io/examples/floating-instanced-shoes)

  • - +

    [![simple-audio-analyser demo](https://pmndrs.github.io/examples/simple-audio-analyser/thumbnail.webp)](https://pmndrs.github.io/examples/simple-audio-analyser)

  • - +

    [![camera-scroll demo](https://pmndrs.github.io/examples/camera-scroll/thumbnail.webp)](https://pmndrs.github.io/examples/camera-scroll)

  • - +

    [![springy-boxes demo](https://pmndrs.github.io/examples/springy-boxes/thumbnail.webp)](https://pmndrs.github.io/examples/springy-boxes)

  • - +

    [![floating-diamonds demo](https://pmndrs.github.io/examples/floating-diamonds/thumbnail.webp)](https://pmndrs.github.io/examples/floating-diamonds)

  • - +

    [![gltf-animations demo](https://pmndrs.github.io/examples/gltf-animations/thumbnail.webp)](https://pmndrs.github.io/examples/gltf-animations)

  • - +

    [![sparks-and-effects demo](https://pmndrs.github.io/examples/sparks-and-effects/thumbnail.webp)](https://pmndrs.github.io/examples/sparks-and-effects)

  • - +

    [![camera-shake demo](https://pmndrs.github.io/examples/camera-shake/thumbnail.webp)](https://pmndrs.github.io/examples/camera-shake)

  • - +

    [![ragdoll-physics demo](https://pmndrs.github.io/examples/ragdoll-physics/thumbnail.webp)](https://pmndrs.github.io/examples/ragdoll-physics)

  • - +

    [![react-spring-animations demo](https://pmndrs.github.io/examples/react-spring-animations/thumbnail.webp)](https://pmndrs.github.io/examples/react-spring-animations)

  • - +

    [![mount-transitions demo](https://pmndrs.github.io/examples/mount-transitions/thumbnail.webp)](https://pmndrs.github.io/examples/mount-transitions)

  • - +

    [![floating-laptop demo](https://pmndrs.github.io/examples/floating-laptop/thumbnail.webp)](https://pmndrs.github.io/examples/floating-laptop)

  • - +

    [![zustand-site demo](https://pmndrs.github.io/examples/zustand-site/thumbnail.webp)](https://pmndrs.github.io/examples/zustand-site)

  • - +

    [![cell-fracture demo](https://pmndrs.github.io/examples/cell-fracture/thumbnail.webp)](https://pmndrs.github.io/examples/cell-fracture)

  • - +

    [![router-transitions demo](https://pmndrs.github.io/examples/router-transitions/thumbnail.webp)](https://pmndrs.github.io/examples/router-transitions)

  • - +

    [![soft-shadows demo](https://pmndrs.github.io/examples/soft-shadows/thumbnail.webp)](https://pmndrs.github.io/examples/soft-shadows)

  • - +

    [![lulaby-city demo](https://pmndrs.github.io/examples/lulaby-city/thumbnail.webp)](https://pmndrs.github.io/examples/lulaby-city)

  • - +

    [![viking-ship demo](https://pmndrs.github.io/examples/viking-ship/thumbnail.webp)](https://pmndrs.github.io/examples/viking-ship)

  • - +

    [![wobbling-sphere demo](https://pmndrs.github.io/examples/wobbling-sphere/thumbnail.webp)](https://pmndrs.github.io/examples/wobbling-sphere)

  • - +

    [![moksha demo](https://pmndrs.github.io/examples/moksha/thumbnail.webp)](https://pmndrs.github.io/examples/moksha)

  • - +

    [![flexbox-yoga-in-webgl demo](https://pmndrs.github.io/examples/flexbox-yoga-in-webgl/thumbnail.webp)](https://pmndrs.github.io/examples/flexbox-yoga-in-webgl)

  • - +

    [![confetti demo](https://pmndrs.github.io/examples/confetti/thumbnail.webp)](https://pmndrs.github.io/examples/confetti)

  • - +

    [![learn-with-jason demo](https://pmndrs.github.io/examples/learn-with-jason/thumbnail.webp)](https://pmndrs.github.io/examples/learn-with-jason)

  • - +

    [![volumetric-spotlight demo](https://pmndrs.github.io/examples/volumetric-spotlight/thumbnail.webp)](https://pmndrs.github.io/examples/volumetric-spotlight)

  • @@ -341,25 +341,25 @@ nav: 3
  • - +

    [![racing-game demo](https://pmndrs.github.io/examples/racing-game/thumbnail.webp)](https://pmndrs.github.io/examples/racing-game)

  • - +

    [![pinball-in-70-lines demo](https://pmndrs.github.io/examples/pinball-in-70-lines/thumbnail.webp)](https://pmndrs.github.io/examples/pinball-in-70-lines)

  • - +

    [![space-game demo](https://pmndrs.github.io/examples/space-game/thumbnail.webp)](https://pmndrs.github.io/examples/space-game)

  • - +

    [![minecraft demo](https://pmndrs.github.io/examples/minecraft/thumbnail.webp)](https://pmndrs.github.io/examples/minecraft)

  • - +

    [![arkanoid demo](https://pmndrs.github.io/examples/arkanoid/thumbnail.webp)](https://pmndrs.github.io/examples/arkanoid)

  • - +

    [![rapier-ping-pong demo](https://pmndrs.github.io/examples/rapier-ping-pong/thumbnail.webp)](https://pmndrs.github.io/examples/rapier-ping-pong)

  • - +

    [![arkanoid-under-60-loc demo](https://pmndrs.github.io/examples/arkanoid-under-60-loc/thumbnail.webp)](https://pmndrs.github.io/examples/arkanoid-under-60-loc)

  • @@ -367,135 +367,135 @@ nav: 3
  • - +

    [![basic-demo demo](https://pmndrs.github.io/examples/basic-demo/thumbnail.webp)](https://pmndrs.github.io/examples/basic-demo)

  • - +

    [![drei-rendertexture demo](https://pmndrs.github.io/examples/drei-rendertexture/thumbnail.webp)](https://pmndrs.github.io/examples/drei-rendertexture)

  • - +

    [![bvh demo](https://pmndrs.github.io/examples/bvh/thumbnail.webp)](https://pmndrs.github.io/examples/bvh)

  • - +

    [![environment-blur-and-transitions demo](https://pmndrs.github.io/examples/environment-blur-and-transitions/thumbnail.webp)](https://pmndrs.github.io/examples/environment-blur-and-transitions)

  • - +

    [![pairing-threejs-to-ui demo](https://pmndrs.github.io/examples/pairing-threejs-to-ui/thumbnail.webp)](https://pmndrs.github.io/examples/pairing-threejs-to-ui)

  • - +

    [![inverted-stencil-buffer demo](https://pmndrs.github.io/examples/inverted-stencil-buffer/thumbnail.webp)](https://pmndrs.github.io/examples/inverted-stencil-buffer)

  • - +

    [![stencil-mask demo](https://pmndrs.github.io/examples/stencil-mask/thumbnail.webp)](https://pmndrs.github.io/examples/stencil-mask)

  • - +

    [![transformcontrols-and-makedefault demo](https://pmndrs.github.io/examples/transformcontrols-and-makedefault/thumbnail.webp)](https://pmndrs.github.io/examples/transformcontrols-and-makedefault)

  • - +

    [![bounds-and-makedefault demo](https://pmndrs.github.io/examples/bounds-and-makedefault/thumbnail.webp)](https://pmndrs.github.io/examples/bounds-and-makedefault)

  • - +

    [![instanced-vertex-colors demo](https://pmndrs.github.io/examples/instanced-vertex-colors/thumbnail.webp)](https://pmndrs.github.io/examples/instanced-vertex-colors)

  • - +

    [![progressive-loading-states-with-suspense demo](https://pmndrs.github.io/examples/progressive-loading-states-with-suspense/thumbnail.webp)](https://pmndrs.github.io/examples/progressive-loading-states-with-suspense)

  • - +

    [![view-tracking demo](https://pmndrs.github.io/examples/view-tracking/thumbnail.webp)](https://pmndrs.github.io/examples/view-tracking) -- views, portals

  • - +

    [![multiple-views-with-uniform-controls demo](https://pmndrs.github.io/examples/multiple-views-with-uniform-controls/thumbnail.webp)](https://pmndrs.github.io/examples/multiple-views-with-uniform-controls) -- views, portals

  • - +

    [![canvas-text demo](https://pmndrs.github.io/examples/canvas-text/thumbnail.webp)](https://pmndrs.github.io/examples/canvas-text) -- html, scroll

  • - +

    [![gltf-animations-re-used demo](https://pmndrs.github.io/examples/gltf-animations-re-used/thumbnail.webp)](https://pmndrs.github.io/examples/gltf-animations-re-used)

  • - +

    [![re-using-gltfs demo](https://pmndrs.github.io/examples/re-using-gltfs/thumbnail.webp)](https://pmndrs.github.io/examples/re-using-gltfs)

  • - +

    [![svg-renderer demo](https://pmndrs.github.io/examples/svg-renderer/thumbnail.webp)](https://pmndrs.github.io/examples/svg-renderer)

  • - +

    [![mixing-html-and-webgl demo](https://pmndrs.github.io/examples/mixing-html-and-webgl/thumbnail.webp)](https://pmndrs.github.io/examples/mixing-html-and-webgl)

  • - +

    [![viewcube demo](https://pmndrs.github.io/examples/viewcube/thumbnail.webp)](https://pmndrs.github.io/examples/viewcube)

  • - +

    [![mixing-controls demo](https://pmndrs.github.io/examples/mixing-controls/thumbnail.webp)](https://pmndrs.github.io/examples/mixing-controls)

  • - +

    [![video-textures demo](https://pmndrs.github.io/examples/video-textures/thumbnail.webp)](https://pmndrs.github.io/examples/video-textures)

  • - +

    [![sky-dome-with-annotations demo](https://pmndrs.github.io/examples/sky-dome-with-annotations/thumbnail.webp)](https://pmndrs.github.io/examples/sky-dome-with-annotations)

  • - +

    [![tying-canvas-to-scroll-offset demo](https://pmndrs.github.io/examples/tying-canvas-to-scroll-offset/thumbnail.webp)](https://pmndrs.github.io/examples/tying-canvas-to-scroll-offset)

  • - +

    [![edgesgeometry demo](https://pmndrs.github.io/examples/edgesgeometry/thumbnail.webp)](https://pmndrs.github.io/examples/edgesgeometry)

  • - +

    [![html-annotations demo](https://pmndrs.github.io/examples/html-annotations/thumbnail.webp)](https://pmndrs.github.io/examples/html-annotations)

  • - +

    [![shadermaterials demo](https://pmndrs.github.io/examples/shadermaterials/thumbnail.webp)](https://pmndrs.github.io/examples/shadermaterials)

  • - +

    [![simple-physics-demo demo](https://pmndrs.github.io/examples/simple-physics-demo/thumbnail.webp)](https://pmndrs.github.io/examples/simple-physics-demo)

  • - +

    [![trigger-meshes demo](https://pmndrs.github.io/examples/trigger-meshes/thumbnail.webp)](https://pmndrs.github.io/examples/trigger-meshes)

  • - +

    [![simple-physics-demo-with-debug-bounds demo](https://pmndrs.github.io/examples/simple-physics-demo-with-debug-bounds/thumbnail.webp)](https://pmndrs.github.io/examples/simple-physics-demo-with-debug-bounds)

  • - +

    [![selective-outlines demo](https://pmndrs.github.io/examples/selective-outlines/thumbnail.webp)](https://pmndrs.github.io/examples/selective-outlines)

  • - +

    [![instances demo](https://pmndrs.github.io/examples/instances/thumbnail.webp)](https://pmndrs.github.io/examples/instances)

  • - +

    [![physics-with-convex-polyhedrons demo](https://pmndrs.github.io/examples/physics-with-convex-polyhedrons/thumbnail.webp)](https://pmndrs.github.io/examples/physics-with-convex-polyhedrons)

  • - +

    [![color-grading demo](https://pmndrs.github.io/examples/color-grading/thumbnail.webp)](https://pmndrs.github.io/examples/color-grading)

  • - +

    [![grass-shader demo](https://pmndrs.github.io/examples/grass-shader/thumbnail.webp)](https://pmndrs.github.io/examples/grass-shader)

  • - +

    [![clouds demo](https://pmndrs.github.io/examples/clouds/thumbnail.webp)](https://pmndrs.github.io/examples/clouds)

  • - +

    [![svg-maps-with-html-annotations demo](https://pmndrs.github.io/examples/svg-maps-with-html-annotations/thumbnail.webp)](https://pmndrs.github.io/examples/svg-maps-with-html-annotations)

  • - +

    [![re-using-geometry-and-level-of-detail demo](https://pmndrs.github.io/examples/re-using-geometry-and-level-of-detail/thumbnail.webp)](https://pmndrs.github.io/examples/re-using-geometry-and-level-of-detail)

  • - +

    [![html-markers demo](https://pmndrs.github.io/examples/html-markers/thumbnail.webp)](https://pmndrs.github.io/examples/html-markers)

  • - +

    [![bezier-curves-and-nodes demo](https://pmndrs.github.io/examples/bezier-curves-and-nodes/thumbnail.webp)](https://pmndrs.github.io/examples/bezier-curves-and-nodes)

  • - +

    [![shader-fire demo](https://pmndrs.github.io/examples/shader-fire/thumbnail.webp)](https://pmndrs.github.io/examples/shader-fire)

  • - +

    [![water-shader demo](https://pmndrs.github.io/examples/water-shader/thumbnail.webp)](https://pmndrs.github.io/examples/water-shader)

  • - +

    [![staging-and-camerashake demo](https://pmndrs.github.io/examples/staging-and-camerashake/thumbnail.webp)](https://pmndrs.github.io/examples/staging-and-camerashake)

  • - +

    [![shader-hmr demo](https://pmndrs.github.io/examples/shader-hmr/thumbnail.webp)](https://pmndrs.github.io/examples/shader-hmr)

  • diff --git a/example/package.json b/example/package.json index dbd54955af..8adf192fe1 100644 --- a/example/package.json +++ b/example/package.json @@ -2,6 +2,7 @@ "name": "example", "version": "0.0.0", "type": "module", + "license": "MIT", "scripts": { "dev": "vite", "build": "vite build", @@ -11,8 +12,8 @@ "@react-three/drei": "^10.7.7", "@use-gesture/react": "^10.3.1", "leva": "^0.10.1", - "react": "19.0.0", - "react-dom": "19.0.0", + "react": "19.2.0", + "react-dom": "19.2.0", "react-use-refs": "^1.0.1", "three": "^0.181.0", "use-error-boundary": "^2.0.6", @@ -20,8 +21,8 @@ "zustand": "^4.4.7" }, "devDependencies": { - "@types/react": "^19.0.1", - "@types/react-dom": "^19.0.1", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "@types/three": "^0.181.0", "@vitejs/plugin-react": "^4.2.1", "@vitejs/plugin-react-refresh": "^1.3.6", diff --git a/example/src/demos/default/Activity.tsx b/example/src/demos/default/Activity.tsx new file mode 100644 index 0000000000..99b90f3650 --- /dev/null +++ b/example/src/demos/default/Activity.tsx @@ -0,0 +1,92 @@ +import { useRef, useEffectEvent, Suspense, use, useState, Activity } from 'react' +import { useFrame, Canvas } from '@react-three/fiber' +import { Mesh, Group } from 'three' +import { DRACOLoader, GLTFLoader } from 'three-stdlib' +import { Environment } from '@react-three/drei' + +const colors = ['orange', 'hotpink', 'cyan', 'lime', 'yellow', 'red', 'blue', 'purple', 'green', 'coral'] + +function SceneA({ onSelect }: { onSelect: Function }) { + const ref = useRef(null) + const [scale, setScale] = useState(1) + const [color, setColor] = useState(colors[0]) + + // Stable event handler using the new React 19.2 API + const handleSelect = useEffectEvent(() => onSelect()) + + useFrame((_, dt) => { + if (ref.current) ref.current.rotation.y += dt * 1.2 + }) + + return ( + { + setScale(1.2) + setColor(colors[Math.floor(Math.random() * colors.length)]) + }} + onPointerOut={() => setScale(1)}> + + + + ) +} + +const dracoLoader = new DRACOLoader() +dracoLoader.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.5/') + +const gltfLoader = new GLTFLoader() +gltfLoader.setDRACOLoader(dracoLoader) + +const modelPromise = gltfLoader.loadAsync('/models/apple.gltf') + +function SceneB({ onSelect }: { onSelect: Function }) { + const gltf = use(modelPromise) + const ref = useRef(null) + + const [scale, setScale] = useState(5) + const handleSelect = useEffectEvent(() => onSelect()) + + useFrame((_, dt) => { + if (ref.current) ref.current.rotation.y -= dt * 0.8 + }) + + return ( + setScale(6)} + onPointerOut={() => setScale(5)} + /> + ) +} + +export default function App() { + const [active, setActive] = useState('A') + + return ( + + + + + + + + setActive('B')} /> + + + + + + setActive('A')} /> + + + + ) +} diff --git a/example/src/demos/index.tsx b/example/src/demos/index.tsx index 21363e071d..7ec81e86eb 100644 --- a/example/src/demos/index.tsx +++ b/example/src/demos/index.tsx @@ -2,6 +2,7 @@ import { lazy } from 'react' //* Default Examples ============================== // Examples that work with both WebGL and WebGPU renderers +const Activity = { Component: lazy(() => import('./default/Activity')) } const AutoDispose = { Component: lazy(() => import('./default/AutoDispose')) } const CanvasDebounceFixed = { Component: lazy(() => import('./default/CanvasDebounceFixed')) } const ChangeTexture = { Component: lazy(() => import('./default/ChangeTexture')) } @@ -50,6 +51,7 @@ const UseFrameNextControls = { Component: lazy(() => import('./webgpu/UseFrameNe export { // Default + Activity, AutoDispose, CanvasDebounceFixed, ChangeTexture, diff --git a/jest.config.js b/jest.config.js index 1e00ace5cd..6e0910dd02 100644 --- a/jest.config.js +++ b/jest.config.js @@ -42,9 +42,16 @@ module.exports = { presets: [['@babel/preset-env', { modules: 'commonjs' }]], }, ], + // Transform patched react-reconciler ESM files to CJS + 'packages[\\\\/]fiber[\\\\/]react-reconciler[\\\\/].+\\.js$': [ + 'babel-jest', + { + presets: [['@babel/preset-env', { modules: 'commonjs' }]], + }, + ], }, - // Allow transforming three (override default ignore) - works on both Windows and Unix - transformIgnorePatterns: ['node_modules[\\\\/](?!(three)[\\\\/])'], + // Allow transforming three and patched react-reconciler - works on both Windows and Unix + transformIgnorePatterns: ['node_modules[\\\\/](?!(three)[\\\\/])', '!packages[\\\\/]fiber[\\\\/]react-reconciler'], coveragePathIgnorePatterns: [ '/node_modules/', '/packages/fiber/dist', diff --git a/package.json b/package.json index 4626776288..ad751b2fa3 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ }, "scripts": { "changeset:add": "changeset add", - "postinstall": "yarn stub", + "postinstall": "yarn stub && yarn patch-react-reconciler", + "patch-react-reconciler": "vite build", "build": "yarn workspace @react-three/fiber build && yarn workspace @react-three/eslint-plugin build", "examples": "yarn workspace example dev", "stub": "yarn workspace @react-three/fiber stub && yarn workspace @react-three/eslint-plugin stub", @@ -57,6 +58,7 @@ "@types/node": "^22.0.0", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.1", + "@types/react-reconciler": "^0.32.0", "@types/scheduler": "0.23.0", "@types/three": "^0.181.0", "@typescript-eslint/eslint-plugin": "^5.17.0", @@ -80,10 +82,12 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-nil": "^2.0.0", + "react-reconciler": "^0.33.0", "three": "^0.181.0", "ts-jest": "^29.1.2", "typescript": "^5.9.3", - "unbuild": "^3.6.1" + "unbuild": "^3.6.1", + "vite": "^6.0.0" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 78b39f38a6..c028282275 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,5 +1,39 @@ # @react-three/eslint-plugin +## 0.2.0-alpha.0 + +### Minor Changes + +- ## R3F v10 - WebGPU Support & React 19 + + ### Breaking Changes + + - **React 19 required** - Minimum React version is now 19.0 + - **Three.js 0.181+ required** - Minimum Three.js version is now 0.181.2 + - **New entry points** - Bundle structure reorganized with dedicated WebGPU support + + ### New Features + + - **WebGPU Renderer Support** - New `@react-three/fiber/webgpu` entry point with full WebGPU and TSL (Three.js Shading Language) support + - **Legacy Entry Point** - `@react-three/fiber/legacy` for WebGL-only environments + - **Improved Frame Loop** - Enhanced `useFrame` with better priority scheduling and `runOnce` support + - **Build System Migration** - Moved from Preconstruct to Unbuild for better per-entry-point optimization + + ### Entry Points + + ```js + // Default - WebGL + WebGPU support + import { Canvas } from '@react-three/fiber' + + // WebGPU only - smaller bundle, TSL support + import { Canvas } from '@react-three/fiber/webgpu' + + // Legacy WebGL only - maximum compatibility + import { Canvas } from '@react-three/fiber/legacy' + ``` + + See the full migration guide in the documentation. + ## 0.1.2 ### Patch Changes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index c4ca454ba1..940f76a4ce 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@react-three/eslint-plugin", - "version": "0.1.2", + "version": "0.2.0-alpha.0", "description": "An ESLint plugin which provides lint rules for @react-three/fiber.", "keywords": [ "react", diff --git a/packages/fiber/CHANGELOG.md b/packages/fiber/CHANGELOG.md index 19193d48c7..df8d7eebc8 100644 --- a/packages/fiber/CHANGELOG.md +++ b/packages/fiber/CHANGELOG.md @@ -1,5 +1,39 @@ # @react-three/fiber +## 10.0.0-alpha.0 + +### Major Changes + +- ## R3F v10 - WebGPU Support & React 19 + + ### Breaking Changes + + - **React 19 required** - Minimum React version is now 19.0 + - **Three.js 0.181+ required** - Minimum Three.js version is now 0.181.2 + - **New entry points** - Bundle structure reorganized with dedicated WebGPU support + + ### New Features + + - **WebGPU Renderer Support** - New `@react-three/fiber/webgpu` entry point with full WebGPU and TSL (Three.js Shading Language) support + - **Legacy Entry Point** - `@react-three/fiber/legacy` for WebGL-only environments + - **Improved Frame Loop** - Enhanced `useFrame` with better priority scheduling and `runOnce` support + - **Build System Migration** - Moved from Preconstruct to Unbuild for better per-entry-point optimization + + ### Entry Points + + ```js + // Default - WebGL + WebGPU support + import { Canvas } from '@react-three/fiber' + + // WebGPU only - smaller bundle, TSL support + import { Canvas } from '@react-three/fiber/webgpu' + + // Legacy WebGL only - maximum compatibility + import { Canvas } from '@react-three/fiber/legacy' + ``` + + See the full migration guide in the documentation. + ## 9.4.2 ### Patch Changes diff --git a/packages/fiber/build.config.ts b/packages/fiber/build.config.ts index 209dcaded5..b21bc3f9bb 100644 --- a/packages/fiber/build.config.ts +++ b/packages/fiber/build.config.ts @@ -11,6 +11,11 @@ import { dirname } from 'path' * - Default: #three → three/index.ts (both WebGL + WebGPU) * - Legacy: #three → three/legacy.ts (WebGL only) * - WebGPU: #three → three/webgpu.ts (WebGPU only) + * + * React Reconciler: + * - Source code imports from ./react-reconciler/* using relative paths + * - The patched ESM version is built via vite during postinstall (see root vite.config.ts) + * - Gets bundled into dist files (not externalized) */ const __filename = fileURLToPath(import.meta.url) @@ -36,10 +41,10 @@ const sharedRollup = { } //* Shared externals ============================== +// Note: react-reconciler is NOT external - we bundle our patched ESM version const baseExternals = [ 'react', 'react-dom', - 'react-reconciler', 'scheduler', 'zustand', 'zustand/shallow', diff --git a/packages/fiber/package.json b/packages/fiber/package.json index 89e830db90..662799547b 100644 --- a/packages/fiber/package.json +++ b/packages/fiber/package.json @@ -1,6 +1,6 @@ { "name": "@react-three/fiber", - "version": "9.4.2", + "version": "10.0.0-alpha.0", "description": "A React renderer for Threejs", "keywords": [ "react", @@ -33,6 +33,12 @@ "module": "./dist/index.mjs", "types": "./src/index.tsx", "sideEffects": false, + "files": [ + "dist", + "types", + "react-reconciler", + "readme.md" + ], "exports": { ".": { "types": "./src/index.tsx", @@ -59,21 +65,21 @@ "@babel/runtime": "^7.17.8", "dequal": "^2.0.3", "its-fine": "^2.0.0", - "react-reconciler": "^0.31.0", "react-use-measure": "^2.1.7", - "scheduler": "^0.25.0", + "scheduler": "^0.27.0", "suspend-react": "^0.1.3", "use-sync-external-store": "^1.4.0", "zustand": "^5.0.3" }, "devDependencies": { "@types/react-reconciler": "^0.32.0", + "react-reconciler": "^0.33.0", "@types/three": "^0.181.0", "@types/webxr": "*" }, "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0", + "react": ">=19.0 <19.3", + "react-dom": ">=19.0 <19.3", "three": ">=0.181.2" }, "peerDependenciesMeta": { diff --git a/packages/fiber/src/core/reconciler.tsx b/packages/fiber/src/core/reconciler.tsx index 9830780011..6913d6d4bd 100644 --- a/packages/fiber/src/core/reconciler.tsx +++ b/packages/fiber/src/core/reconciler.tsx @@ -2,8 +2,12 @@ import type { Scene, Color, ColorRepresentation } from '#three' import packageData from '../../package.json' import * as React from 'react' -import Reconciler from 'react-reconciler' -import { ContinuousEventPriority, DiscreteEventPriority, DefaultEventPriority } from 'react-reconciler/constants' +import Reconciler from '../../react-reconciler/index.js' +import { + ContinuousEventPriority, + DiscreteEventPriority, + DefaultEventPriority, +} from '../../react-reconciler/constants.js' import { unstable_IdlePriority as idlePriority, unstable_scheduleCallback as scheduleCallback } from 'scheduler' import { diffProps, @@ -537,7 +541,6 @@ export const reconciler = /* @__PURE__ */ createReconciler< requestPostPaintCallback() {}, maySuspendCommit: () => false, preloadInstance: () => true, // true indicates already loaded - startSuspendingCommit() {}, suspendInstance() {}, waitForCommitToBeReady: () => null, NotPendingTransition: null, @@ -582,4 +585,71 @@ export const reconciler = /* @__PURE__ */ createReconciler< // @ts-ignore DefinitelyTyped is not up to date rendererPackageName: '@react-three/fiber', rendererVersion: packageData.version, + + // 19.2 Reconciler + + // https://github.com/facebook/react/pull/31975 + // https://github.com/facebook/react/pull/31999 + applyViewTransitionName(_instance: any, _name: any, _className: any) {}, + restoreViewTransitionName(_instance: any, _props: any) {}, + cancelViewTransitionName(_instance: any, _name: any, _props: any) {}, + cancelRootViewTransitionName(_rootContainer: any) {}, + restoreRootViewTransitionName(_rootContainer: any) {}, + InstanceMeasurement: null, + measureInstance: (_instance: any) => null, + wasInstanceInViewport: (_measurement: any): boolean => true, + hasInstanceChanged: (_oldMeasurement: any, _newMeasurement: any): boolean => false, + hasInstanceAffectedParent: (_oldMeasurement: any, _newMeasurement: any): boolean => false, + + // https://github.com/facebook/react/pull/32002 + // https://github.com/facebook/react/pull/34486 + suspendOnActiveViewTransition(_state: any, _container: any) {}, + + // https://github.com/facebook/react/pull/32451 + // https://github.com/facebook/react/pull/32760 + startGestureTransition: () => null, + startViewTransition: () => null, + stopViewTransition(_transition: null) {}, + + // https://github.com/facebook/react/pull/32038 + createViewTransitionInstance: (_name: string): null => null, + + // https://github.com/facebook/react/pull/32379 + // https://github.com/facebook/react/pull/32786 + getCurrentGestureOffset(_provider: null): number { + throw new Error('startGestureTransition is not yet supported in react-three-fiber.') + }, + + // https://github.com/facebook/react/pull/32500 + cloneMutableInstance(instance: any, _keepChildren: any) { + return instance + }, + cloneMutableTextInstance(textInstance: any) { + return textInstance + }, + cloneRootViewTransitionContainer(_rootContainer: any) { + throw new Error('Not implemented.') + }, + removeRootViewTransitionClone(_rootContainer: any, _clone: any) { + throw new Error('Not implemented.') + }, + + // https://github.com/facebook/react/pull/32465 + createFragmentInstance: (_fiber: any): null => null, + updateFragmentInstanceFiber(_fiber: any, _instance: any): void {}, + commitNewChildToFragmentInstance(_child: any, _fragmentInstance: any): void {}, + deleteChildFromFragmentInstance(_child: any, _fragmentInstance: any): void {}, + + // https://github.com/facebook/react/pull/32653 + measureClonedInstance: (_instance: any) => null, + + // https://github.com/facebook/react/pull/32819 + maySuspendCommitOnUpdate: (_type: any, _oldProps: any, _newProps: any) => false, + maySuspendCommitInSyncRender: (_type: any, _props: any) => false, + + // https://github.com/facebook/react/pull/34486 + startSuspendingCommit: () => null, + + // https://github.com/facebook/react/pull/34522 + getSuspendedCommitReason: (_state: any, _rootContainer: any) => null, }) diff --git a/packages/fiber/src/core/renderer.tsx b/packages/fiber/src/core/renderer.tsx index 2ebb747ebc..fbae52c4d2 100644 --- a/packages/fiber/src/core/renderer.tsx +++ b/packages/fiber/src/core/renderer.tsx @@ -4,7 +4,7 @@ import { R3F_BUILD_LEGACY, R3F_BUILD_WEBGPU, WebGLRenderer, WebGPURenderer, Insp import type { Object3D } from '#three' import type { JSX, ReactNode, RefObject } from 'react' import { useCallback, useMemo, useState } from 'react' -import { ConcurrentRoot } from 'react-reconciler/constants' +import { ConcurrentRoot } from '../../react-reconciler/constants.js' import { createWithEqualityFn } from 'zustand/traditional' import { useStore } from './hooks' diff --git a/packages/fiber/src/core/utils/instance.ts b/packages/fiber/src/core/utils/instance.ts index 2dc092ebef..ffb8b7a18b 100644 --- a/packages/fiber/src/core/utils/instance.ts +++ b/packages/fiber/src/core/utils/instance.ts @@ -1,6 +1,5 @@ import * as THREE from '#three' import type { RootStore, Instance, ObjectMap, Disposable } from '#types' -import type { Fiber } from 'react-reconciler' import { getUuidPrefix } from './three' //* Instance & Scene Graph Management ============================== @@ -101,11 +100,11 @@ export function dispose(obj: T): void { * @param queue - Pending props from reconciler fiber * @returns Props object without React-internal keys */ -export function getInstanceProps(queue: Fiber['pendingProps']): Instance['props'] { +export function getInstanceProps(pendingProps: Record): Instance['props'] { const props: Instance['props'] = {} - for (const key in queue) { - if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key] + for (const key in pendingProps) { + if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = pendingProps[key] } return props diff --git a/packages/fiber/tests/canvas.test.tsx b/packages/fiber/tests/canvas.test.tsx index 6abc9f5c4a..0f5138135e 100644 --- a/packages/fiber/tests/canvas.test.tsx +++ b/packages/fiber/tests/canvas.test.tsx @@ -85,6 +85,33 @@ describe('web Canvas', () => { const errorSpy = jest.fn() console.error = errorSpy + //* Track what the error boundary catches ============================== + let caughtError: Error | null = null + let errorInfo: React.ErrorInfo | null = null + + // User-provided error boundary component + class TestErrorBoundary extends React.Component< + { children: React.ReactNode; fallback: React.ReactNode }, + { hasError: boolean } + > { + state = { hasError: false } + + static getDerivedStateFromError() { + return { hasError: true } + } + + componentDidCatch(error: Error, info: React.ErrorInfo) { + caughtError = error + errorInfo = info + } + + render() { + if (this.state.hasError) return this.props.fallback + return this.props.children + } + } + + // Component that throws in useFrame const ErrorComponent = () => { useFrame(() => { throw testError @@ -93,32 +120,28 @@ describe('web Canvas', () => { } try { - // Test that error is set in store and propagated to Canvas error boundary const renderer = await act(async () => render( - - - , + Error caught}> + + + + , ), ) - // Wait for frames to execute and error to be thrown - // Wrap in try-catch since React may re-throw after error boundary catches - try { - await act(async () => { - await new Promise((resolve) => setTimeout(resolve, 150)) - }) - } catch (err) { - // Expected - error may be re-thrown by React after boundary catches it - } + // Wait for frames to execute and error to propagate to boundary + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 150)) + }) - // Verify the scheduler error handler was called - expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('[Scheduler] Error in job'), testError) + // Verify the error was caught by our boundary + expect(caughtError).toBe(testError) + expect(errorInfo).not.toBeNull() - // Canvas should still be mounted (error boundary prevents unmount) - expect(renderer.container).toBeTruthy() + // Verify fallback UI is rendered + expect(renderer.getByTestId('error-fallback')).toBeTruthy() } finally { - // Restore console.error console.error = originalError } }) diff --git a/packages/fiber/tests/hooks.test.tsx b/packages/fiber/tests/hooks.test.tsx index 0b07f889ab..deec0f2d99 100644 --- a/packages/fiber/tests/hooks.test.tsx +++ b/packages/fiber/tests/hooks.test.tsx @@ -109,6 +109,15 @@ describe('hooks', () => { }) }) + it('can handle future (19.x) hooks without crashing', async () => { + function Component() { + // @ts-ignore + React.useEffectEvent?.(() => {}) + return null + } + expect(async () => await act(async () => root.render())).not.toThrow() + }) + it('can handle useInstanceHandle hook', async () => { const ref = React.createRef() let instance!: React.RefObject diff --git a/packages/fiber/tests/reconciler.test.ts b/packages/fiber/tests/reconciler.test.ts new file mode 100644 index 0000000000..a9786dd6b6 --- /dev/null +++ b/packages/fiber/tests/reconciler.test.ts @@ -0,0 +1,69 @@ +import * as THREE from 'three' +import { createCanvas } from '../../test-renderer/src/createTestCanvas' + +async function act(fn: () => Promise) { + // Silence act warning since we have a custom act implementation + const error = console.error + console.error = function (message) { + if (/was not wrapped in act/.test(message)) return + return error.call(this, arguments) + } + + const value = fn() + + return new Promise((res) => { + requestAnimationFrame(() => requestAnimationFrame(() => requestAnimationFrame(() => res(value)))) + }).finally(() => { + console.error = error + }) +} + +describe('reconciler', () => { + const NODE_ENV = process.env.NODE_ENV + + for (const env of ['development', 'production']) { + it(`should work with ${env} builds of React`, async () => { + jest.resetModules() + + // @ts-ignore + if (typeof window !== 'undefined') delete window.__THREE__ + process.env.NODE_ENV = env + + const React = await import('react') + const R3F = await import('../src/index') + + // Ensure that the correct build was loaded + expect(typeof React.act === (env === 'production' ? 'undefined' : 'function')) + + R3F.extend(THREE as any) + const canvas = createCanvas() + const root = R3F.createRoot(canvas) + + const lifecycle: string[] = [] + + const object = {} + const ref = React.createRef<{}>() + + function Test() { + lifecycle.push('render') + React.useImperativeHandle(React.useRef(undefined), () => void lifecycle.push('ref')) + React.useInsertionEffect(() => void lifecycle.push('useInsertionEffect'), []) + React.useLayoutEffect(() => void lifecycle.push('useLayoutEffect'), []) + React.useEffect(() => void lifecycle.push('useEffect'), []) + return React.createElement('primitive', { ref, object }) + } + await act(async () => root.render(React.createElement(Test))) + + expect(lifecycle).toStrictEqual(['render', 'useInsertionEffect', 'ref', 'useLayoutEffect', 'useEffect']) + expect(ref.current).toBe(object) + + await act(async () => root.unmount()) + expect(ref.current).toBe(null) + }) + } + + // @ts-ignore + if (typeof window !== 'undefined') delete window.__THREE__ + process.env.NODE_ENV = NODE_ENV + jest.resetModules() +}) diff --git a/packages/fiber/tests/useLoader.test.tsx b/packages/fiber/tests/useLoader.test.tsx index 849d5cfcea..1e9ec6479b 100644 --- a/packages/fiber/tests/useLoader.test.tsx +++ b/packages/fiber/tests/useLoader.test.tsx @@ -228,26 +228,22 @@ describe('useLoader', () => { } // Start rendering (will suspend and trigger loadAsync) - let renderError: any = null - act(() => { - try { - root.render( - }> - - , - ) - } catch (err) { - renderError = err - } + // Use async act to properly handle Suspense + await act(async () => { + root.render( + }> + + , + ) + // Give time for suspend-react to start the load + await new Promise((resolve) => setTimeout(resolve, 50)) }) - // Wait for loadAsync to be called by suspend-react - await new Promise((resolve) => setTimeout(resolve, 100)) + // Verify loadAsync was called (component suspended and triggered the loader) expect(loadAsyncCalled).toBe(true) expect(loadCompleted).toBe(false) - // Get the loader instance and abort after 1 second - await new Promise((resolve) => setTimeout(resolve, 1000)) + // Get the loader instance and abort const loaderInstance = useLoader.loader(AbortableLoader) loaderInstance.abort() @@ -255,13 +251,10 @@ describe('useLoader', () => { expect(abortCalled).toBe(true) expect(loadCompleted).toBe(false) - // Wait a bit for the abort to propagate - await new Promise((resolve) => setTimeout(resolve, 100)) - // Clear the cache to ensure suspend is cleared useLoader.clear(AbortableLoader, URL_SLOW) - // Reset flags for next test + // Reset flags for second load attempt loadAsyncCalled = false abortCalled = false @@ -273,21 +266,18 @@ describe('useLoader', () => { } // This should trigger a new load since cache was cleared - act(() => { - try { - root.render( - }> - - , - ) - } catch (err) { - // Expected to suspend again - } + await act(async () => { + root.render( + }> + + , + ) + // Give time for suspend-react to start the new load + await new Promise((resolve) => setTimeout(resolve, 50)) }) - // Wait to let the new load attempt start - await new Promise((resolve) => setTimeout(resolve, 100)) - expect(loadAsyncCalled).toBe(true) // New load started + // Verify a new load started + expect(loadAsyncCalled).toBe(true) expect(abortCalled).toBe(false) // No abort on this new attempt yet // Clean up diff --git a/packages/test-renderer/CHANGELOG.md b/packages/test-renderer/CHANGELOG.md index 1f263a7170..5791f9059d 100644 --- a/packages/test-renderer/CHANGELOG.md +++ b/packages/test-renderer/CHANGELOG.md @@ -1,5 +1,44 @@ # @react-three/test-renderer +## 10.0.0-alpha.0 + +### Major Changes + +- ## R3F v10 - WebGPU Support & React 19 + + ### Breaking Changes + + - **React 19 required** - Minimum React version is now 19.0 + - **Three.js 0.181+ required** - Minimum Three.js version is now 0.181.2 + - **New entry points** - Bundle structure reorganized with dedicated WebGPU support + + ### New Features + + - **WebGPU Renderer Support** - New `@react-three/fiber/webgpu` entry point with full WebGPU and TSL (Three.js Shading Language) support + - **Legacy Entry Point** - `@react-three/fiber/legacy` for WebGL-only environments + - **Improved Frame Loop** - Enhanced `useFrame` with better priority scheduling and `runOnce` support + - **Build System Migration** - Moved from Preconstruct to Unbuild for better per-entry-point optimization + + ### Entry Points + + ```js + // Default - WebGL + WebGPU support + import { Canvas } from '@react-three/fiber' + + // WebGPU only - smaller bundle, TSL support + import { Canvas } from '@react-three/fiber/webgpu' + + // Legacy WebGL only - maximum compatibility + import { Canvas } from '@react-three/fiber/legacy' + ``` + + See the full migration guide in the documentation. + +### Patch Changes + +- Updated dependencies + - @react-three/fiber@10.0.0-alpha.0 + ## 9.1.0 ### Minor Changes diff --git a/packages/test-renderer/package.json b/packages/test-renderer/package.json index b4ad3bb50b..67769edabf 100644 --- a/packages/test-renderer/package.json +++ b/packages/test-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@react-three/test-renderer", - "version": "9.1.0", + "version": "10.0.0-alpha.0", "description": "Test Renderer for react-three-fiber", "author": "Josh Ellis", "license": "MIT", @@ -43,7 +43,7 @@ }, "peerDependencies": { "react": "^19.0.0", - "@react-three/fiber": ">=9.0.0", + "@react-three/fiber": ">=10.0.0-alpha.0", "three": ">=0.181" }, "peerDependenciesMeta": { diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000000..d685bb42c0 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,72 @@ +import { defineConfig, transformWithEsbuild } from 'vite' +import * as path from 'node:path' +import * as fs from 'node:fs' + +/** + * Vite configuration for patching react-reconciler + * + * This builds react-reconciler from node_modules into a local ESM-compatible version + * since the official package is CJS-only or has ESM issues. + * + * Runs automatically during postinstall via `yarn patch-react-reconciler` + */ +export default defineConfig({ + build: { + outDir: 'packages/fiber/react-reconciler', + target: 'esnext', + lib: { + formats: ['es'], + entry: { + index: 'node_modules/react-reconciler/index.js', + constants: 'node_modules/react-reconciler/cjs/react-reconciler-constants.production.js', + }, + fileName: '[name]', + }, + rollupOptions: { + treeshake: false, + external: (id: string) => !id.startsWith('.') && !path.isAbsolute(id), + output: { + entryFileNames: '[name].js', + chunkFileNames: '[name].js', + }, + }, + }, + plugins: [ + //* CJS to ESM Transformation ============================== + { + name: 'vite-ts', + enforce: 'pre', + transform(code, id) { + // Convert CJS constants to ESM format + if (id.includes('react-reconciler-constants')) { + return code.replace(/exports\.(\w+)\s*=\s*(.*?);/g, 'export const $1 = $2;').replace(/"use strict";\s*/g, '') + } + }, + generateBundle(_options, bundle) { + // Copy TypeScript definitions for each entry point + for (const key in bundle) { + if (!('isEntry' in bundle[key]) || !bundle[key].isEntry) continue + + const name = bundle[key].name + const typePath = `node_modules/@types/react-reconciler/${name}.d.ts` + + if (fs.existsSync(typePath)) { + const source = fs.readFileSync(typePath, 'utf-8') + this.emitFile({ type: 'asset', fileName: `${name}.d.ts`, source }) + } + } + }, + }, + + //* Minification ============================== + { + name: 'vite-minify', + renderChunk: { + order: 'post', + handler(code, { fileName }) { + return transformWithEsbuild(code, fileName, { minify: true }) + }, + }, + }, + ], +}) diff --git a/yarn.lock b/yarn.lock index dce69a07f6..d7c06b74d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2653,7 +2653,7 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== -"@types/react-dom@^19.0.1": +"@types/react-dom@^19.0.1", "@types/react-dom@^19.2.3": version "19.2.3" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.2.3.tgz#c1e305d15a52a3e508d54dca770d202cb63abf2c" integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== @@ -2668,7 +2668,7 @@ resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.32.3.tgz#eb4b346f367f29f07628032934d30a4f3f9eaba7" integrity sha512-cMi5ZrLG7UtbL7LTK6hq9w/EZIRk4Mf1Z5qHoI+qBh7/WkYkFXQ7gOto2yfUvPzF5ERMAhaXS5eTQ2SAnHjLzA== -"@types/react@^19.0.1": +"@types/react@^19.0.1", "@types/react@^19.2.7": version "19.2.7" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.2.7.tgz#84e62c0f23e8e4e5ac2cadcea1ffeacccae7f62f" integrity sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg== @@ -4229,7 +4229,7 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" -esbuild@^0.25.9: +esbuild@^0.25.0, esbuild@^0.25.9: version "0.25.12" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.12.tgz#97a1d041f4ab00c2fce2f838d2b9969a2d2a97a5" integrity sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg== @@ -4620,7 +4620,7 @@ fb-watchman@^2.0.0, fb-watchman@^2.0.2: dependencies: bser "2.1.1" -fdir@^6.2.0, fdir@^6.5.0: +fdir@^6.2.0, fdir@^6.4.4, fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== @@ -7344,7 +7344,7 @@ postcss-value-parser@^4.2.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.43, postcss@^8.5.6: +postcss@^8.4.43, postcss@^8.5.3, postcss@^8.5.6: version "8.5.6" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== @@ -7481,12 +7481,12 @@ react-colorful@^5.5.1: resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b" integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw== -react-dom@19.0.0: - version "19.0.0" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.0.0.tgz#43446f1f01c65a4cd7f7588083e686a6726cfb57" - integrity sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ== +react-dom@19.2.0: + version "19.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.2.0.tgz#00ed1e959c365e9a9d48f8918377465466ec3af8" + integrity sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ== dependencies: - scheduler "^0.25.0" + scheduler "^0.27.0" react-dom@^19.0.0: version "19.2.3" @@ -7534,6 +7534,13 @@ react-reconciler@^0.31.0: dependencies: scheduler "^0.25.0" +react-reconciler@^0.33.0: + version "0.33.0" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.33.0.tgz#9dd20208d45baa5b0b4701781f858236657f15e1" + integrity sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA== + dependencies: + scheduler "^0.27.0" + react-refresh@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.10.0.tgz#2f536c9660c0b9b1d500684d9e52a65e7404f7e3" @@ -7554,10 +7561,10 @@ react-use-refs@^1.0.1: resolved "https://registry.yarnpkg.com/react-use-refs/-/react-use-refs-1.0.1.tgz#44cab5f4764b3fa4a112189c0058fc8752d1eb2c" integrity sha512-zVmPRY5DJhzjGgmlIWw9pkdCNlIdrfsEXgdzcSau3MSpKPVuwRQU6DoviwH5f9n5Hc+M2HWW7mkRWbX+4eyC8w== -react@19.0.0: - version "19.0.0" - resolved "https://registry.yarnpkg.com/react/-/react-19.0.0.tgz#6e1969251b9f108870aa4bff37a0ce9ddfaaabdd" - integrity sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ== +react@19.2.0: + version "19.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-19.2.0.tgz#d33dd1721698f4376ae57a54098cb47fc75d93a5" + integrity sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ== react@^19.0.0: version "19.2.3" @@ -7735,7 +7742,7 @@ rollup-plugin-dts@^6.2.1: optionalDependencies: "@babel/code-frame" "^7.27.1" -rollup@^4.20.0, rollup@^4.34.8, rollup@^4.46.2: +rollup@^4.20.0, rollup@^4.34.8, rollup@^4.34.9, rollup@^4.46.2: version "4.54.0" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.54.0.tgz#930f4dfc41ff94d720006f9f62503612a6c319b8" integrity sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw== @@ -8319,7 +8326,7 @@ through@^2.3.8: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -tinyglobby@^0.2.14, tinyglobby@^0.2.15: +tinyglobby@^0.2.13, tinyglobby@^0.2.14, tinyglobby@^0.2.15: version "0.2.15" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== @@ -8714,6 +8721,20 @@ vite@^5.2.10: optionalDependencies: fsevents "~2.3.3" +vite@^6.0.0: + version "6.4.1" + resolved "https://registry.yarnpkg.com/vite/-/vite-6.4.1.tgz#afbe14518cdd6887e240a4b0221ab6d0ce733f96" + integrity sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g== + dependencies: + esbuild "^0.25.0" + fdir "^6.4.4" + picomatch "^4.0.2" + postcss "^8.5.3" + rollup "^4.34.9" + tinyglobby "^0.2.13" + optionalDependencies: + fsevents "~2.3.3" + w3c-hr-time@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"