Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@ on:

jobs:
build:
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: sudo apt-get install -y xorg-dev mesa-utils xvfb libgl1 freeglut3-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev
- uses: actions/checkout@v4
- run: sudo apt-get update && sudo apt-get install -y xorg-dev mesa-utils xvfb libgl1 freeglut3-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libxext-dev
- name: Setup Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 14
cache: 'yarn'
- run: yarn
- run: yarn build
node-version: 24
- run: corepack enable
- run: yarn install
- name: Build gl-react packages
run: |
for d in packages/gl-react packages/gl-react-dom packages/gl-react-headless; do
yarn workspace gl-react-dev exec babel --root-mode upward --source-maps -d "$d/lib" "$d/src"
yarn workspace gl-react-dev exec flow-copy-source "$d/src" "$d/lib"
done
- run: xvfb-run -s "-ac -screen 0 1280x1024x24" yarn test
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@ packages/gl-react-dom/gl-react-dom.js
packages/tests/node_modules_to_test/
packages/cookbook/src/API.json
examples/*/shared/
.yarn
.yarn
.pnp.cjs
.pnp.loader.mjs
test-results
packages/cookbook-v2/test-results
10 changes: 10 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}


4 changes: 4 additions & 0 deletions .prototools
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node = "18.20.8"
npm = "10.8.2"


51 changes: 51 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

gl-react is a universal React library for writing and composing WebGL shaders. It uses Flow for type checking and is structured as a Yarn workspaces monorepo managed with Lerna.

## Commands

**Must use Yarn** (npm is blocked by a preinstall check).

```bash
yarn # Install dependencies
yarn build # Babel-compile all packages to lib/
yarn watch # Watch mode for development
yarn test # Run Jest tests (packages/tests)
yarn test-rewrite-snapshots # Regenerate test snapshots
yarn prettier # Format source files
yarn cookbook-v2 # Start modern cookbook dev server (Vite)
```

Tests run via `packages/tests/test.sh`, which executes Jest on each `__tests__/*.js` file individually. On CI (Linux), tests require `xvfb-run` for headless OpenGL.

## Monorepo Package Structure

- **`packages/gl-react/`** — Core library. Defines `Node`, `Bus`, `Shaders`, `GLSL`, `createSurface`, `Visitor`, uniforms, and the rendering pipeline. All other packages depend on this.
- **`packages/gl-react-dom/`** — WebGL implementation for React DOM (browser).
- **`packages/gl-react-native/`** — React Native standalone implementation.
- **`packages/gl-react-expo/`** — React Native via Expo GLView.
- **`packages/gl-react-headless/`** — Node.js implementation using headless-gl.
- **`packages/tests/`** — Shared Jest test suite using `gl-react-headless` + `react-test-renderer`.
- **`packages/cookbook-v2/`** — Modern examples (Vite + TypeScript + Tailwind).

## Architecture

**Build pipeline:** Babel compiles `src/` → `lib/` for each `gl-react*` package. `flow-copy-source` copies Flow type definitions alongside compiled output. Lerna runs per-package build steps after.

**Rendering model:** Two-phase: `redraw()` marks nodes dirty, `flush()` performs actual GL draws. Async flushing at 60fps by default; synchronous mode available via `sync` prop on Surface.

**Surface/Node tree:** `createSurface` is a factory that produces platform-specific Surface components (used by gl-react-dom, gl-react-native, etc.). Each `Node` component manages one framebuffer. The root Node draws directly to the Surface canvas. Nodes communicate via React context (`SurfaceContext`).

**Shader composition:** `Node` components can be nested — a child Node's output texture becomes a uniform input to its parent. `Bus` enables sharing a computation across multiple consumers without re-rendering.

**Texture loading:** Extensible via `webgltexture-loader` packages. Platform implementations register their own loaders (e.g., `webgltexture-loader-dom` for browser image/video/canvas).

## Code Conventions

- Source uses **Flow** type annotations (`// @flow`). The `packages/cookbook-v2/` is the exception (TypeScript).
- Prettier config: semicolons, double quotes, trailing commas (es5), 80 char width.
- Node 18+ required (see `.prototools`).
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,22 @@
"packages/*"
],
"private": true,
"packageManager": "yarn@4.10.3",
"license": "MIT",
"scripts": {
"preinstall": "node -e \"if (process.env.npm_execpath.indexOf('yarn') === -1) { console.log('\u001b[31mPlease use yarn\u001b[0m'); process.exit(1); }\"",
"build": "export NODE_OPTIONS=--openssl-legacy-provider && ./scripts/build.sh",
"watch": "./scripts/watch.sh",
"prettier": "prettier --write 'packages/{gl-react,gl-react-dom,gl-react-expo,gl-react-headless,gl-react-native}/src/*.js'",
"prettier": "prettier --write 'packages/{gl-react,gl-react-dom,gl-react-expo,gl-react-headless,gl-react-native}/src/*.js' 'packages/cookbook-v2/src/**/*.{ts,tsx}'",
"cookbook-v2": "cd packages/cookbook-v2 && yarn dev",
"test": "cd packages/tests && yarn test",
"test-rewrite-snapshots": "cd packages/tests && yarn test -- -u",
"clean": "rm -rf node_modules packages/*/node_modules/ packages/*/lib",
"publish": "yarn && lerna run clean && yarn build && lerna publish --registry=https://registry.npmjs.org/"
},
"devDependencies": {
"@babel/cli": "^7.13.10",
"@babel/plugin-proposal-class-properties": "^7.13.0",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-object-rest-spread": "^7.13.8",
"@babel/preset-env": "^7.13.10",
"@babel/preset-flow": "^7.12.13",
Expand All @@ -28,6 +30,6 @@
"documentation": "13.2.0",
"flow-copy-source": "^2.0.9",
"lerna": "^3.22.1",
"prettier": "^2.2.1"
"prettier": "^3.3.3"
}
}
21 changes: 21 additions & 0 deletions packages/cookbook-v2/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
plugins: ['react-refresh'],
rules: {
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
},
}


21 changes: 21 additions & 0 deletions packages/cookbook-v2/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
node_modules
dist
build
.next
.nuxt
.vuepress/dist
.serverless
.fusebox
.dynamodb
.tern-port
coverage
.nyc_output
.cache
.parcel-cache
.vscode
.idea
*.log
yarn.lock
package-lock.json


10 changes: 10 additions & 0 deletions packages/cookbook-v2/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": true,
"trailingComma": "es5",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"useTabs": false
}


113 changes: 113 additions & 0 deletions packages/cookbook-v2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# GL React Cookbook v2

A modern, TypeScript-first cookbook for building stunning WebGL experiences with React.

## 🚀 Features

- **Modern React 18** with hooks and modern patterns
- **TypeScript** with strict configuration
- **Vite** for lightning-fast development
- **Tailwind CSS** for beautiful, responsive UI
- **WebGL Inspector** for debugging shaders and uniforms
- **Live Examples** with real-time WebGL rendering

## 🛠️ Tech Stack

- React 18.3.1
- TypeScript 5.6.3
- Vite 6.0.1
- Tailwind CSS 3.4.15
- React Router v6
- Heroicons
- GL React & GL React DOM

## 🏃‍♂️ Getting Started

From the root of the monorepo:

```bash
# Install dependencies
yarn install

# Start the development server
yarn cookbook-v2
```

Or directly from this package:

```bash
cd packages/cookbook-v2
yarn dev
```

The cookbook will be available at http://localhost:3001

## 📁 Structure

```
src/
├── components/ # Reusable components
│ ├── Layout.tsx # Main layout with navigation
│ ├── Inspector.tsx # WebGL inspector for debugging
│ ├── WebGLExample.tsx # Basic WebGL examples
│ └── AdvancedWebGLExample.tsx # Advanced shader examples
├── pages/ # Page components
│ ├── HomePage.tsx # Landing page with live example
│ ├── ExamplesPage.tsx # Examples listing
│ ├── ExampleDetailPage.tsx # Individual example view
│ └── ApiPage.tsx # API documentation
├── types/ # TypeScript type definitions
└── utils/ # Utility functions
```

## 🎨 Examples

The cookbook includes several WebGL examples:

1. **Hello World** - Basic animated color shader
2. **Color Gradient** - Animated gradient with custom colors
3. **Procedural Noise** - Mathematical noise generation
4. **Wave Animation** - Smooth wave animations

Each example includes:
- Live WebGL preview
- Source code display
- Inspector for debugging uniforms and shaders
- Performance metrics

## 🔧 Inspector

The WebGL Inspector provides:
- Real-time uniform values
- Shader source code display
- Performance metrics (FPS, draw calls, vertices)
- Toggle visibility for debugging

## 🎯 Development

### Scripts

- `yarn dev` - Start development server
- `yarn build` - Build for production
- `yarn preview` - Preview production build
- `yarn type-check` - Run TypeScript checks
- `yarn lint` - Run ESLint
- `yarn lint:fix` - Fix ESLint issues

### Code Style

- Prettier configured with double quotes (monorepo standard)
- ESLint with TypeScript and React rules
- Tailwind CSS for styling

## 🌟 Key Improvements over v1

- ✅ No React 18 warnings (uses createRoot)
- ✅ No string refs (uses createRef)
- ✅ No legacy context API (uses modern React context)
- ✅ TypeScript for better developer experience
- ✅ Modern build system with Vite
- ✅ Beautiful, responsive UI with Tailwind
- ✅ Better code organization and structure


79 changes: 79 additions & 0 deletions packages/cookbook-v2/e2e/examples.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { test, expect } from "@playwright/test";

// All example IDs from the registry
const exampleIds = [
"hellogl",
"helloblue",
"helloblueanim",
"colordisc",
"gradients",
"heart",
"saturation",
"colorscale",
"mergechannels",
"diamondcrop",
"diamondhello",
"diamondanim",
"blurxy",
"blurxydownscale",
"blurmulti",
"blurmap",
"blurmapdyn",
"blurmapmouse",
"distortion",
"demotunnel",
"demodesert",
"sdf1",
"gol",
"golglider",
"golrot",
"glsledit",
"transitions",
"textanimated",
"textfunky",
"animated",
"reactmotion",
];

test("examples page loads with all examples listed", async ({ page }) => {
const errors: string[] = [];
page.on("pageerror", (err) => errors.push(err.message));

await page.goto("/examples");
await page.waitForLoadState("networkidle");

// Check page loaded
await expect(page.locator("h1")).toHaveText(/Examples/);

// No JS errors
expect(errors).toEqual([]);
});

for (const id of exampleIds) {
test(`example "${id}" loads without JS errors`, async ({ page }) => {
const errors: string[] = [];
page.on("pageerror", (err) => errors.push(err.message));

await page.goto(`/examples/${id}`);
await page.waitForLoadState("networkidle");

// Wait for lazy-loaded component
await page.waitForTimeout(2000);

// Title should be visible
await expect(page.locator("h1")).toBeVisible();

// Report errors but don't fail on WebGL context issues (CI may not have GPU)
const criticalErrors = errors.filter(
(e) =>
!e.includes("WebGL") &&
!e.includes("GL_") &&
!e.includes("getUserMedia")
);

if (criticalErrors.length > 0) {
console.log(`[${id}] JS errors:`, criticalErrors);
}
expect(criticalErrors).toEqual([]);
});
}
Loading
Loading