diff --git a/apps/typegpu-docs/src/examples/react/monkey/index.html b/apps/typegpu-docs/src/examples/react/monkey/index.html
new file mode 100644
index 0000000000..974ed97c0a
--- /dev/null
+++ b/apps/typegpu-docs/src/examples/react/monkey/index.html
@@ -0,0 +1 @@
+
diff --git a/apps/typegpu-docs/src/examples/react/monkey/index.tsx b/apps/typegpu-docs/src/examples/react/monkey/index.tsx
new file mode 100644
index 0000000000..bac8b33d40
--- /dev/null
+++ b/apps/typegpu-docs/src/examples/react/monkey/index.tsx
@@ -0,0 +1,86 @@
+import { Canvas, Pass } from '@typegpu/react';
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+
+const meshLayout = tgpu.bindGroupLayout({
+ modelMatrix: { uniform: d.mat4x4f },
+ albedo: { uniform: d.vec3f },
+ tint: { uniform: d.vec3f },
+});
+
+const vertex = ({ pos }: { pos: d.v3f }) => {
+ 'use gpu';
+ return meshLayout.$.modelMatrix.mul(d.vec4f(pos, 1));
+};
+
+const fragment = () => {
+ 'use gpu';
+ return d.vec4f(meshLayout.$.tint, 1);
+};
+
+export function Monkey({ albedo, pos }: { albedo: d.v3f; pos: d.v3f }) {
+ // const monkeyMesh = useMonkeyMesh();
+ // const modelMatrix = useMemo(() => mat4.translation(pos, d.mat4x4f()), []);
+
+ // Optional bindings
+ // const bindings = useMemo(() => ([
+ // [fooSlot, 123],
+ // [(cfg: Configurable) => /* ... */],
+ // // ...
+ // ]), []);
+
+ return (
+ //
+ //
+ // {/* things provided after the pipeline is created */}
+
+ // {/* the entries are passed into the shader as automatically created resources */}
+ //
+ // {/* monkeyMesh has 'layout' and 'buffer' properties, which fit this component */}
+ //
+ //
+ //
+
+ Monkey at {`(${pos.x}, ${pos.y}, ${pos.z})`}
+
+ );
+}
+
+export function App() {
+ return (
+
+ );
+}
+
+// #region Example controls and cleanup
+
+import { createRoot } from 'react-dom/client';
+
+const reactRoot = createRoot(
+ document.getElementById('example-app') as HTMLDivElement,
+);
+reactRoot.render();
+
+export function onCleanup() {
+ setTimeout(() => reactRoot.unmount(), 0);
+}
+
+// #endregion
diff --git a/apps/typegpu-docs/src/examples/react/monkey/meta.json b/apps/typegpu-docs/src/examples/react/monkey/meta.json
new file mode 100644
index 0000000000..976f106c6c
--- /dev/null
+++ b/apps/typegpu-docs/src/examples/react/monkey/meta.json
@@ -0,0 +1,5 @@
+{
+ "title": "React: 3D Monkey",
+ "category": "react",
+ "tags": ["experimental"]
+}
diff --git a/apps/typegpu-docs/src/examples/react/monkey/thumbnail.png b/apps/typegpu-docs/src/examples/react/monkey/thumbnail.png
new file mode 100644
index 0000000000..889b2f297b
Binary files /dev/null and b/apps/typegpu-docs/src/examples/react/monkey/thumbnail.png differ
diff --git a/apps/typegpu-docs/src/examples/react/monkey/use-monkey-mesh.ts b/apps/typegpu-docs/src/examples/react/monkey/use-monkey-mesh.ts
new file mode 100644
index 0000000000..8873955537
--- /dev/null
+++ b/apps/typegpu-docs/src/examples/react/monkey/use-monkey-mesh.ts
@@ -0,0 +1,65 @@
+import { load } from '@loaders.gl/core';
+import { OBJLoader } from '@loaders.gl/obj';
+import * as d from 'typegpu/data';
+import tgpu from 'typegpu';
+import { useRoot, type VertexBufferProps } from '@typegpu/react';
+
+const MONKEY_MODEL_PATH = '/TypeGPU/assets/3d-monkey/monkey.obj';
+
+const MeshVertexInput = {
+ modelPosition: d.vec3f,
+ modelNormal: d.vec3f,
+ textureUV: d.vec2f,
+} as const;
+
+const meshVertexLayout = tgpu.vertexLayout((n: number) =>
+ d.arrayOf(d.struct(MeshVertexInput), n)
+);
+
+export type MeshVertex = d.WgslArray<
+ d.WgslStruct<{
+ readonly modelPosition: d.Vec3f;
+ readonly modelNormal: d.Vec3f;
+ readonly textureUV: d.Vec2f;
+ }>
+>;
+
+export async function useMonkeyMesh(): Promise> {
+ const root = useRoot();
+
+ const modelMesh = await load(MONKEY_MODEL_PATH, OBJLoader);
+ const polygonCount = modelMesh.attributes.POSITION.value.length / 3;
+
+ const vertexBuffer = root
+ .createBuffer(meshVertexLayout.schemaForCount(polygonCount))
+ .$usage('vertex')
+ .$name(`model vertices of ${MONKEY_MODEL_PATH}`);
+
+ const modelVertices = [];
+ for (let i = 0; i < polygonCount; i++) {
+ modelVertices.push({
+ modelPosition: d.vec3f(
+ modelMesh.attributes.POSITION.value[3 * i],
+ modelMesh.attributes.POSITION.value[3 * i + 1],
+ modelMesh.attributes.POSITION.value[3 * i + 2],
+ ),
+ modelNormal: d.vec3f(
+ modelMesh.attributes.NORMAL.value[3 * i],
+ modelMesh.attributes.NORMAL.value[3 * i + 1],
+ modelMesh.attributes.NORMAL.value[3 * i + 2],
+ ),
+ textureUV: d.vec2f(
+ modelMesh.attributes.TEXCOORD_0.value[2 * i],
+ 1 - modelMesh.attributes.TEXCOORD_0.value[2 * i + 1],
+ ),
+ });
+ }
+ modelVertices.reverse();
+
+ vertexBuffer.write(modelVertices);
+
+ return {
+ layout: meshVertexLayout,
+ buffer: vertexBuffer,
+ };
+}
\ No newline at end of file
diff --git a/packages/typegpu-react/src/components/BindGroup.tsx b/packages/typegpu-react/src/components/BindGroup.tsx
new file mode 100644
index 0000000000..2bd0f16f4b
--- /dev/null
+++ b/packages/typegpu-react/src/components/BindGroup.tsx
@@ -0,0 +1,47 @@
+import { useEffect, useMemo } from 'react';
+import type { TgpuBindGroupLayout } from 'typegpu';
+import type { AnyData } from 'typegpu/data';
+import { useCanvas } from '../hooks/use-canvas.ts';
+import { usePass } from '../hooks/use-pass.ts';
+
+type Entries> = {
+ [K in keyof T]: any; // Using 'any' to match the expected value types for buffers, textures, etc.
+};
+
+interface BindGroupProps> {
+ /**
+ * The layout for the bind group.
+ */
+ layout: TgpuBindGroupLayout;
+ /**
+ * An object containing the resources to be bound.
+ * The keys must match the keys in the layout definition.
+ */
+ entries: Entries;
+}
+
+export function BindGroup>({
+ layout,
+ entries,
+}: BindGroupProps) {
+ const { root } = useCanvas();
+ const { addDrawCall } = usePass();
+
+ const bindGroup = useMemo(() => {
+ // It's important that the values in `entries` are stable (e.g., memoized)
+ // to avoid recreating the bind group on every render.
+ return root.createBindGroup(layout, entries);
+ }, [root, layout, entries]);
+
+ useEffect(() => {
+ const removeDrawCall = addDrawCall((pass) => {
+ // The group index is derived from the layout object itself.
+ pass.setBindGroup(layout.groupIndex, bindGroup);
+ });
+
+ return removeDrawCall;
+ }, [addDrawCall, layout.groupIndex, bindGroup]);
+
+ // This component does not render anything to the DOM.
+ return null;
+}
\ No newline at end of file
diff --git a/packages/typegpu-react/src/components/Canvas.tsx b/packages/typegpu-react/src/components/Canvas.tsx
new file mode 100644
index 0000000000..ca0979cc89
--- /dev/null
+++ b/packages/typegpu-react/src/components/Canvas.tsx
@@ -0,0 +1,69 @@
+import type React from 'react';
+import { useEffect, useRef, useState } from 'react';
+import {
+ CanvasContext,
+ type CanvasContextValue,
+} from '../context/canvas-context.ts';
+import { useRoot } from '../hooks/use-root.ts';
+
+export function Canvas({ children }: { children: React.ReactNode }) {
+ const root = useRoot();
+ const canvasRef = useRef(null);
+ const canvasCtxRef = useRef(null);
+
+ const frameCallbacksRef = useRef(new Set<(time: number) => void>());
+
+ const [contextValue] = useState(() => ({
+ get context() {
+ return canvasCtxRef.current;
+ },
+ addFrameCallback(cb: (time: number) => void) {
+ frameCallbacksRef.current.add(cb);
+ return () => frameCallbacksRef.current.delete(cb);
+ },
+ }));
+
+ useEffect(() => {
+ if (!canvasRef.current) return;
+
+ let disposed = false;
+ const canvas = canvasRef.current;
+ const context = canvas.getContext('webgpu');
+ if (!context) {
+ console.error('WebGPU not supported');
+ return;
+ }
+
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
+ context.configure({
+ device: root.device,
+ format: presentationFormat,
+ alphaMode: 'premultiplied',
+ });
+ canvasCtxRef.current = context;
+
+ const frame = (time: number) => {
+ if (disposed) return;
+ requestAnimationFrame(frame);
+
+ frameCallbacksRef.current.forEach((cb) => {
+ cb(time);
+ });
+
+ root['~unstable'].flush();
+ };
+ requestAnimationFrame(frame);
+
+ return () => {
+ disposed = true;
+ };
+ }, [root]);
+
+ return (
+
+ );
+}
diff --git a/packages/typegpu-react/src/components/Config.tsx b/packages/typegpu-react/src/components/Config.tsx
new file mode 100644
index 0000000000..be56e29a30
--- /dev/null
+++ b/packages/typegpu-react/src/components/Config.tsx
@@ -0,0 +1,39 @@
+import type React from 'react';
+import { useMemo } from 'react';
+import type { Configurable, TgpuRenderPipeline, TgpuSlot } from 'typegpu';
+import { PipelineContext } from '../context/pipeline-context.ts';
+import { useRenderPipeline } from '../hooks/use-render-pipeline.ts';
+
+type Binding =
+ | [slot: TgpuSlot, value: any]
+ | ((cfg: Configurable) => Configurable);
+
+interface ConfigProps {
+ bindings: Binding[];
+ children: React.ReactNode;
+}
+
+export function Config({ bindings, children }: ConfigProps) {
+ const pipeline = useRenderPipeline();
+
+ const configuredPipeline = useMemo(() => {
+ if (!pipeline) return null;
+ return bindings.reduce((p, binding) => {
+ if (Array.isArray(binding)) {
+ return p.with(binding[0], binding[1]);
+ }
+ return p.pipe(binding);
+ }, pipeline as Configurable) as TgpuRenderPipeline;
+ }, [pipeline, bindings]);
+
+ if (!pipeline) {
+ return <>{children}>;
+ }
+
+ // This re-provides the configured pipeline to children
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/typegpu-react/src/components/Pass.tsx b/packages/typegpu-react/src/components/Pass.tsx
new file mode 100644
index 0000000000..d3f313e303
--- /dev/null
+++ b/packages/typegpu-react/src/components/Pass.tsx
@@ -0,0 +1,80 @@
+import type React from 'react';
+import { useCallback, useEffect, useRef } from 'react';
+import type { RenderPass } from '../../../typegpu/src/core/root/rootTypes.ts'; // TODO: Expose it in typegpu
+import { PassContext } from '../context/pass-context.tsx';
+import { useCanvas } from '../hooks/use-canvas.ts';
+import { useRoot } from '../hooks/use-root.ts';
+
+export function Pass(
+ { children }: { children: React.ReactNode; schedule: 'frame' },
+) {
+ const root = useRoot();
+ const ctx = useCanvas();
+ const drawCalls = useRef(new Set<(pass: RenderPass) => void>()).current;
+ const depthTextureRef = useRef(null);
+
+ const addDrawCall = useCallback(
+ (cb: (pass: RenderPass) => void) => {
+ drawCalls.add(cb);
+ return () => drawCalls.delete(cb);
+ },
+ [drawCalls],
+ );
+
+ useEffect(() => {
+ const removeFrameCallback = ctx.addFrameCallback(() => {
+ const canvas = ctx.context?.canvas as HTMLCanvasElement;
+ let depthTexture = depthTextureRef.current;
+ if (
+ !depthTexture ||
+ depthTexture.width !== canvas.width ||
+ depthTexture.height !== canvas.height
+ ) {
+ depthTexture?.destroy();
+ const newDepthTexture = root.device.createTexture({
+ size: [canvas.width, canvas.height],
+ format: 'depth24plus',
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+ depthTexture = depthTextureRef.current = newDepthTexture;
+ }
+
+ root['~unstable'].beginRenderPass(
+ {
+ colorAttachments: [
+ {
+ view: ctx.context?.getCurrentTexture()
+ .createView() as GPUTextureView,
+ clearValue: { r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
+ loadOp: 'clear',
+ storeOp: 'store',
+ },
+ ],
+ depthStencilAttachment: {
+ view: depthTexture.createView(),
+ depthClearValue: 1.0,
+ depthLoadOp: 'clear',
+ depthStoreOp: 'store',
+ },
+ },
+ (pass) => {
+ drawCalls.forEach((draw) => {
+ draw(pass);
+ });
+ },
+ );
+ });
+
+ return () => {
+ removeFrameCallback();
+ depthTextureRef.current?.destroy();
+ depthTextureRef.current = null;
+ };
+ }, [ctx, root, drawCalls]);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/typegpu-react/src/components/RenderPipeline.tsx b/packages/typegpu-react/src/components/RenderPipeline.tsx
new file mode 100644
index 0000000000..c7f9ea33ce
--- /dev/null
+++ b/packages/typegpu-react/src/components/RenderPipeline.tsx
@@ -0,0 +1,126 @@
+import type React from 'react';
+import { useEffect, useMemo, useRef } from 'react';
+import tgpu from 'typegpu';
+import type * as d from 'typegpu/data';
+// TODO: Export these types in typegpu
+import type {
+ VertexInConstrained,
+ VertexOutConstrained,
+} from '../../../typegpu/src/core/function/tgpuVertexFn.ts';
+import type { OmitBuiltins } from '../../../typegpu/src/builtin.ts';
+import type {
+ FragmentInConstrained,
+ FragmentOutConstrained,
+} from '../../../typegpu/src/core/function/tgpuFragmentFn.ts';
+import type { RenderPass } from '../../../typegpu/src/core/root/rootTypes.ts';
+import type { LayoutToAllowedAttribs } from '../../../typegpu/src/core/vertexLayout/vertexAttribute.ts';
+// TODO:
+import { usePass } from '../hooks/use-pass.ts';
+import { useCanvas } from '../hooks/use-canvas.ts';
+import { PipelineContext } from '../context/pipeline-context.ts';
+import { useRoot } from '../hooks/use-root.ts';
+
+type InferRecord = { [K in keyof T]: d.Infer };
+
+interface VertexOptions<
+ VIn extends VertexInConstrained,
+ VOut extends VertexOutConstrained,
+> {
+ body: (input: InferRecord) => InferRecord;
+ in: VIn;
+ out: VOut;
+ attributes: LayoutToAllowedAttribs>;
+}
+
+interface FragmentOptions<
+ FIn extends FragmentInConstrained,
+ FOut extends FragmentOutConstrained,
+> {
+ body: (input: InferRecord) => d.Infer;
+ in: FIn;
+ out: FOut;
+}
+
+interface RenderPipelineProps<
+ VIn extends VertexInConstrained,
+ VOut extends VertexOutConstrained,
+ FIn extends FragmentInConstrained,
+ FOut extends FragmentOutConstrained,
+> {
+ vertex: VertexOptions;
+ fragment: FragmentOptions;
+ vertexCount: number;
+ instanceCount?: number;
+ children?: React.ReactNode;
+}
+
+// TODO: Alter this hook when .withVertex and .withFragment will be simplified
+export function RenderPipeline<
+ VIn extends VertexInConstrained,
+ VOut extends VertexOutConstrained,
+ FIn extends VertexOutConstrained & FragmentInConstrained,
+ FOut extends FragmentOutConstrained,
+>({
+ vertex,
+ fragment,
+ vertexCount,
+ instanceCount,
+ children,
+}: RenderPipelineProps & {
+ fragmentIn?: OmitBuiltins;
+}) {
+ const root = useRoot();
+ const ctx = useCanvas();
+ const { addDrawCall } = usePass();
+ const drawCommand = useRef<(pass: RenderPass) => void>(() => {});
+
+ const vertexRef = useRef(vertex.body);
+ const fragmentRef = useRef(fragment.body);
+
+ const pipeline = useMemo(() => {
+ const vertexFn = tgpu['~unstable'].vertexFn({
+ in: vertex.in,
+ out: vertex.out,
+ })(vertexRef.current);
+ const fragmentFn = tgpu['~unstable'].fragmentFn({
+ in: fragment.in,
+ out: fragment.out,
+ })(
+ fragmentRef.current,
+ );
+
+ const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
+ return root['~unstable']
+ .withVertex(vertexFn, vertex.attributes)
+ .withFragment(fragmentFn, { format: presentationFormat })
+ .withDepthStencil({
+ format: 'depth24plus',
+ depthWriteEnabled: true,
+ depthCompare: 'less',
+ })
+ .createPipeline();
+ }, [root, vertex, fragment]);
+
+ useEffect(() => {
+ const removeDrawCall = addDrawCall((pass) => {
+ pass.setPipeline(pipeline);
+ // Children will set buffers and bind groups here before drawing.
+ // This function is captured by the closure and will be executed
+ // with the latest context from its children.
+ drawCommand.current(pass);
+ });
+ return removeDrawCall;
+ }, [addDrawCall, pipeline]);
+
+ // This function will be updated by children (like VertexBuffer, BindGroup)
+ // to set their resources on the pass.
+ drawCommand.current = (pass: RenderPass) => {
+ pass.draw(vertexCount, instanceCount);
+ };
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/packages/typegpu-react/src/components/VertexBuffer.tsx b/packages/typegpu-react/src/components/VertexBuffer.tsx
new file mode 100644
index 0000000000..b19242a727
--- /dev/null
+++ b/packages/typegpu-react/src/components/VertexBuffer.tsx
@@ -0,0 +1,28 @@
+import { useEffect } from 'react';
+import type {
+ TgpuBuffer,
+ TgpuVertexLayout,
+ VertexFlag,
+} from 'typegpu';
+import type { Disarray, WgslArray } from 'typegpu/data';
+import { usePass } from '../hooks/use-pass.ts';
+
+export type VertexBufferProps ={
+ layout: TgpuVertexLayout;
+ buffer: TgpuBuffer & VertexFlag;
+}
+
+export function VertexBuffer({
+ layout,
+ buffer,
+}: VertexBufferProps) {
+ const { addDrawCall } = usePass();
+
+ useEffect(() => {
+ return addDrawCall((pass) => {
+ pass.setVertexBuffer(layout, buffer);
+ });
+ }, [addDrawCall, layout, buffer]);
+
+ return null;
+}
\ No newline at end of file
diff --git a/packages/typegpu-react/src/components/index.ts b/packages/typegpu-react/src/components/index.ts
new file mode 100644
index 0000000000..32117246c3
--- /dev/null
+++ b/packages/typegpu-react/src/components/index.ts
@@ -0,0 +1,6 @@
+export { BindGroup } from './BindGroup.tsx';
+export { Pass } from './Pass.tsx';
+export { Canvas } from './Canvas.tsx';
+export { RenderPipeline } from './RenderPipeline.tsx';
+export { Config } from './Config.tsx';
+export { VertexBuffer, type VertexBufferProps } from './VertexBuffer.tsx';
diff --git a/packages/typegpu-react/src/context/canvas-context.ts b/packages/typegpu-react/src/context/canvas-context.ts
new file mode 100644
index 0000000000..01009c3c87
--- /dev/null
+++ b/packages/typegpu-react/src/context/canvas-context.ts
@@ -0,0 +1,8 @@
+import { createContext } from 'react';
+
+export interface CanvasContextValue {
+ readonly context: GPUCanvasContext | null;
+ addFrameCallback: (cb: (time: number) => void) => () => void;
+}
+
+export const CanvasContext = createContext(null);
diff --git a/packages/typegpu-react/src/context/pass-context.tsx b/packages/typegpu-react/src/context/pass-context.tsx
new file mode 100644
index 0000000000..9e455878c2
--- /dev/null
+++ b/packages/typegpu-react/src/context/pass-context.tsx
@@ -0,0 +1,8 @@
+import { createContext } from 'react';
+import type { RenderPass } from '../../../typegpu/src/core/root/rootTypes.ts'; // TODO: Expose it in typegpu
+
+export interface PassContextValue {
+ addDrawCall: (cb: (pass: RenderPass) => void) => () => void;
+}
+
+export const PassContext = createContext(null);
diff --git a/packages/typegpu-react/src/context/pipeline-context.ts b/packages/typegpu-react/src/context/pipeline-context.ts
new file mode 100644
index 0000000000..f642d2c3bf
--- /dev/null
+++ b/packages/typegpu-react/src/context/pipeline-context.ts
@@ -0,0 +1,4 @@
+import { createContext } from 'react';
+import type { TgpuRenderPipeline } from 'typegpu';
+
+export const PipelineContext = createContext(null);
diff --git a/packages/typegpu-react/src/context/root-context.tsx b/packages/typegpu-react/src/context/root-context.tsx
new file mode 100644
index 0000000000..dba6308e36
--- /dev/null
+++ b/packages/typegpu-react/src/context/root-context.tsx
@@ -0,0 +1,29 @@
+import { createContext } from 'react';
+import tgpu, { type TgpuRoot } from 'typegpu';
+
+export class RootContext {
+ #root: TgpuRoot | undefined;
+ #rootPromise: Promise | undefined;
+
+ initOrGetRoot(): Promise | TgpuRoot {
+ if (this.#root) {
+ return this.#root;
+ }
+
+ if (!this.#rootPromise) {
+ this.#rootPromise = tgpu.init().then((root) => {
+ this.#root = root;
+ return root;
+ });
+ }
+
+ return this.#rootPromise;
+ }
+}
+
+/**
+ * Used in case no provider is mounted
+ */
+export const globalRootContextValue = new RootContext();
+
+export const rootContext = createContext(null);
\ No newline at end of file
diff --git a/packages/typegpu-react/src/hooks/index.ts b/packages/typegpu-react/src/hooks/index.ts
new file mode 100644
index 0000000000..5f50074ffe
--- /dev/null
+++ b/packages/typegpu-react/src/hooks/index.ts
@@ -0,0 +1,4 @@
+export { useFrame } from './use-frame.ts';
+export { useRoot } from './use-root.ts';
+export { useUniform } from './use-uniform.ts';
+export { useUniformRef } from './use-uniform-ref.ts';
diff --git a/packages/typegpu-react/src/hooks/use-canvas.ts b/packages/typegpu-react/src/hooks/use-canvas.ts
new file mode 100644
index 0000000000..be40f6ac67
--- /dev/null
+++ b/packages/typegpu-react/src/hooks/use-canvas.ts
@@ -0,0 +1,10 @@
+import { useContext } from 'react';
+import { CanvasContext } from '../context/canvas-context.ts';
+
+export const useCanvas = () => {
+ const context = useContext(CanvasContext);
+ if (!context) {
+ throw new Error('useCanvas must be used within a Canvas component');
+ }
+ return context;
+};
diff --git a/packages/typegpu-react/src/use-frame.ts b/packages/typegpu-react/src/hooks/use-frame.ts
similarity index 100%
rename from packages/typegpu-react/src/use-frame.ts
rename to packages/typegpu-react/src/hooks/use-frame.ts
diff --git a/packages/typegpu-react/src/hooks/use-pass.ts b/packages/typegpu-react/src/hooks/use-pass.ts
new file mode 100644
index 0000000000..e0af752dac
--- /dev/null
+++ b/packages/typegpu-react/src/hooks/use-pass.ts
@@ -0,0 +1,10 @@
+import { useContext } from "react";
+import { PassContext, type PassContextValue } from "../context/pass-context.tsx";
+
+export function usePass(): PassContextValue {
+ const context = useContext(PassContext);
+ if (!context) {
+ throw new Error('This component must be a child of a Pass component');
+ }
+ return context;
+};
diff --git a/packages/typegpu-react/src/hooks/use-render-pipeline.ts b/packages/typegpu-react/src/hooks/use-render-pipeline.ts
new file mode 100644
index 0000000000..a38ac98055
--- /dev/null
+++ b/packages/typegpu-react/src/hooks/use-render-pipeline.ts
@@ -0,0 +1,4 @@
+import { useContext } from 'react';
+import { PipelineContext } from '../context/pipeline-context.ts';
+
+export const useRenderPipeline = () => useContext(PipelineContext);
diff --git a/packages/typegpu-react/src/hooks/use-root.ts b/packages/typegpu-react/src/hooks/use-root.ts
new file mode 100644
index 0000000000..cec7d82ba9
--- /dev/null
+++ b/packages/typegpu-react/src/hooks/use-root.ts
@@ -0,0 +1,10 @@
+import { use, useContext } from 'react';
+import type { TgpuRoot } from 'typegpu';
+import { globalRootContextValue, rootContext } from '../context/root-context.tsx';
+
+export function useRoot(): TgpuRoot {
+ const context = useContext(rootContext) ?? globalRootContextValue;
+
+ const maybeRoot = context.initOrGetRoot();
+ return maybeRoot instanceof Promise ? use(maybeRoot) : maybeRoot;
+}
diff --git a/packages/typegpu-react/src/use-uniform-value.ts b/packages/typegpu-react/src/hooks/use-uniform-ref.ts
similarity index 95%
rename from packages/typegpu-react/src/use-uniform-value.ts
rename to packages/typegpu-react/src/hooks/use-uniform-ref.ts
index 9330c47f36..847ae76595 100644
--- a/packages/typegpu-react/src/use-uniform-value.ts
+++ b/packages/typegpu-react/src/hooks/use-uniform-ref.ts
@@ -1,7 +1,7 @@
import type * as d from 'typegpu/data';
-import { useRoot } from './root-context.tsx';
import { useEffect, useMemo, useRef, useState } from 'react';
import type { ValidateUniformSchema } from 'typegpu';
+import { useRoot } from './use-root.ts';
interface UniformValue> {
schema: TSchema;
@@ -19,7 +19,7 @@ function initialValueFromSchema(
return schema() as d.Infer;
}
-export function useUniformValue<
+export function useUniformRef<
TSchema extends d.AnyWgslData,
TValue extends d.Infer,
>(
diff --git a/packages/typegpu-react/src/hooks/use-uniform.ts b/packages/typegpu-react/src/hooks/use-uniform.ts
new file mode 100644
index 0000000000..6a0951563f
--- /dev/null
+++ b/packages/typegpu-react/src/hooks/use-uniform.ts
@@ -0,0 +1,69 @@
+import * as d from 'typegpu/data';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import type { ValidateUniformSchema } from 'typegpu';
+import { useRoot } from './use-root.ts';
+
+interface Uniform {
+ schema: TSchema;
+ readonly $: d.InferGPU;
+}
+
+export function useUniform<
+ TSchema extends d.AnyWgslData,
+ TValue extends d.Infer,
+>(
+ schema: ValidateUniformSchema,
+ value: TValue,
+): Uniform {
+ const root = useRoot();
+ const [uniformBuffer, setUniformBuffer] = useState(() => {
+ return root.createUniform(schema, value);
+ });
+ const prevSchemaRef = useRef(schema);
+ const currentSchemaRef = useRef(schema);
+ const cleanupRef = useRef | null>(null);
+
+ useEffect(() => {
+ if (cleanupRef.current) {
+ clearTimeout(cleanupRef.current);
+ }
+
+ return () => {
+ cleanupRef.current = setTimeout(() => {
+ uniformBuffer.buffer.destroy();
+ }, 200);
+ };
+ }, [uniformBuffer]);
+
+ useEffect(() => {
+ if (!d.deepEqual(prevSchemaRef.current as d.AnyData, schema as d.AnyData)) {
+ uniformBuffer.buffer.destroy();
+ setUniformBuffer(root.createUniform(schema, value));
+ prevSchemaRef.current = schema;
+ } else {
+ uniformBuffer.write(value);
+ }
+ }, [schema, value, root, uniformBuffer]);
+
+ if (
+ !d.deepEqual(currentSchemaRef.current as d.AnyData, schema as d.AnyData)
+ ) {
+ currentSchemaRef.current = schema;
+ }
+
+ // Using current schema ref instead of schema directly
+ // to prevent unnecessary re-memoization when schema object
+ // reference changes but content is structurally equivalent.
+ // biome-ignore lint/correctness/useExhaustiveDependencies: This value needs to be stable
+ const uniformValue = useMemo(
+ () => ({
+ schema,
+ get $() {
+ return uniformBuffer.$;
+ },
+ }),
+ [currentSchemaRef.current, uniformBuffer],
+ );
+
+ return uniformValue as Uniform;
+}
\ No newline at end of file
diff --git a/packages/typegpu-react/src/index.ts b/packages/typegpu-react/src/index.ts
index f92b8da8f8..b404274210 100644
--- a/packages/typegpu-react/src/index.ts
+++ b/packages/typegpu-react/src/index.ts
@@ -1,3 +1,10 @@
-export { useFrame } from './use-frame.ts';
-export { useRender } from './use-render.ts';
-export { useUniformValue } from './use-uniform-value.ts';
+export {
+ BindGroup,
+ Canvas,
+ Config,
+ Pass,
+ RenderPipeline,
+ VertexBuffer,
+ type VertexBufferProps,
+} from './components/index.ts';
+export { useFrame, useRoot, useUniform, useUniformRef } from './hooks/index.ts';
diff --git a/packages/typegpu-react/src/root-context.tsx b/packages/typegpu-react/src/root-context.tsx
deleted file mode 100644
index e0443f3cf7..0000000000
--- a/packages/typegpu-react/src/root-context.tsx
+++ /dev/null
@@ -1,56 +0,0 @@
-import {
- createContext,
- type ReactNode,
- use,
- useContext,
- useState,
-} from 'react';
-import tgpu, { type TgpuRoot } from 'typegpu';
-
-class RootContext {
- #root: TgpuRoot | undefined;
- #rootPromise: Promise | undefined;
-
- initOrGetRoot(): Promise | TgpuRoot {
- if (this.#root) {
- return this.#root;
- }
-
- if (!this.#rootPromise) {
- this.#rootPromise = tgpu.init().then((root) => {
- this.#root = root;
- return root;
- });
- }
-
- return this.#rootPromise;
- }
-}
-
-/**
- * Used in case no provider is mounted
- */
-const globalRootContextValue = new RootContext();
-
-const rootContext = createContext(null);
-
-export interface RootProps {
- children?: ReactNode | undefined;
-}
-
-export const Root = ({ children }: RootProps) => {
- const [ctx] = useState(() => new RootContext());
-
- return (
-
- {children}
-
- );
-};
-
-export function useRoot(): TgpuRoot {
- const context = useContext(rootContext) ?? globalRootContextValue;
-
- const maybeRoot = context.initOrGetRoot();
- return maybeRoot instanceof Promise ? use(maybeRoot) : maybeRoot;
-}
diff --git a/packages/typegpu-react/src/use-render.ts b/packages/typegpu-react/src/use-render.ts
index e36dd7ee8f..de15be508b 100644
--- a/packages/typegpu-react/src/use-render.ts
+++ b/packages/typegpu-react/src/use-render.ts
@@ -1,8 +1,8 @@
-import * as d from 'typegpu/data';
-import tgpu from 'typegpu';
-import { useRoot } from './root-context.tsx';
import { useMemo, useRef } from 'react';
-import { useFrame } from './use-frame.ts';
+import tgpu from 'typegpu';
+import * as d from 'typegpu/data';
+import { useRoot } from './hooks/use-root.ts';
+import { useFrame } from './hooks/use-frame.ts';
type InferRecord = {
[K in keyof T]: d.Infer;
diff --git a/packages/typegpu/src/data/deepEqual.ts b/packages/typegpu/src/data/deepEqual.ts
index cb5f31e9a4..bd163497eb 100644
--- a/packages/typegpu/src/data/deepEqual.ts
+++ b/packages/typegpu/src/data/deepEqual.ts
@@ -1,5 +1,10 @@
import type { AnyAttribute } from './attributes.ts';
-import { isDisarray, isLooseDecorated, isUnstruct } from './dataTypes.ts';
+import {
+ isDisarray,
+ isLooseData,
+ isLooseDecorated,
+ isUnstruct,
+} from './dataTypes.ts';
import type { AnyData } from './dataTypes.ts';
import {
isAtomic,
@@ -107,8 +112,14 @@ export function deepEqual(a: AnyData, b: AnyData): boolean {
return false;
}
}
+
+ return true;
+ }
+
+ if (isLooseData(a) && isLooseData(b)) {
+ // TODO: This is a simplified check. A a more detailed comparison might be necessary.
+ return JSON.stringify(a) === JSON.stringify(b);
}
- // All other types have been checked for equality at the start
return true;
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c8e852e0e9..7d0589ee75 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -11,7 +11,7 @@ catalogs:
version: 2.6.0
tsdown:
specifier: ^0.15.0
- version: 0.15.7
+ version: 0.15.11
tsup:
specifier: ^8.5.0
version: 8.5.0
@@ -541,7 +541,7 @@ importers:
version: 0.1.66
tsdown:
specifier: catalog:build
- version: 0.15.7(typescript@5.8.3)
+ version: 0.15.11(typescript@5.8.3)
typegpu:
specifier: workspace:*
version: link:../typegpu
@@ -790,6 +790,10 @@ packages:
resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==}
engines: {node: '>=6.9.0'}
+ '@babel/generator@7.28.5':
+ resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-compilation-targets@7.27.2':
resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==}
engines: {node: '>=6.9.0'}
@@ -828,6 +832,10 @@ packages:
resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-validator-option@7.27.1':
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
@@ -856,6 +864,11 @@ packages:
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/parser@7.28.5':
+ resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
'@babel/plugin-transform-react-jsx-self@7.27.1':
resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
engines: {node: '>=6.9.0'}
@@ -904,6 +917,10 @@ packages:
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
engines: {node: '>=6.9.0'}
+ '@babel/types@7.28.5':
+ resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==}
+ engines: {node: '>=6.9.0'}
+
'@bcoe/v8-coverage@1.0.2':
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
engines: {node: '>=18'}
@@ -1720,6 +1737,10 @@ packages:
resolution: {integrity: sha512-cYxcj5CPn/vo5QSpCZcYzBiLidU5+GlFSqIeNaMgBDtcVRBsBJHZg3pHw999W6nHamFQ1EHuPPByB26tjaJiJw==}
engines: {node: '>=6.9.0'}
+ '@oxc-project/runtime@0.95.0':
+ resolution: {integrity: sha512-qJS5pNepwMGnafO9ayKGz7rfPQgUBuunHpnP1//9Qa0zK3oT3t1EhT+I+pV9MUA+ZKez//OFqxCxf1vijCKb2Q==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+
'@oxc-project/types@0.82.2':
resolution: {integrity: sha512-WMGSwd9FsNBs/WfqIOH0h3k1LBdjZJQGYjGnC+vla/fh6HUsu5HzGPerRljiq1hgMQ6gs031YJR12VyP57b/hQ==}
@@ -1758,6 +1779,10 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
+ '@pkgr/core@0.2.9':
+ resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==}
+ engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
+
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
@@ -3768,6 +3793,9 @@ packages:
get-tsconfig@4.10.1:
resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==}
+ get-tsconfig@4.13.0:
+ resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
+
github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
@@ -4299,6 +4327,9 @@ packages:
magic-string@0.30.19:
resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==}
+ magic-string@0.30.21:
+ resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
+
magicast@0.3.5:
resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
@@ -5342,13 +5373,13 @@ packages:
robust-predicates@3.0.2:
resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==}
- rolldown-plugin-dts@0.16.11:
- resolution: {integrity: sha512-9IQDaPvPqTx3RjG2eQCK5GYZITo203BxKunGI80AGYicu1ySFTUyugicAaTZWRzFWh9DSnzkgNeMNbDWBbSs0w==}
+ rolldown-plugin-dts@0.17.2:
+ resolution: {integrity: sha512-tbLm7FoDvZAhAY33wJbq0ACw+srToKZ5xFqwn/K4tayGloZPXQHyOEPEYi7whEfTCaMndZWaho9+oiQTlwIe6Q==}
engines: {node: '>=20.18.0'}
peerDependencies:
'@ts-macro/tsc': ^0.3.6
'@typescript/native-preview': '>=7.0.0-dev.20250601.1'
- rolldown: ^1.0.0-beta.9
+ rolldown: ^1.0.0-beta.44
typescript: ^5.0.0
vue-tsc: ~3.1.0
peerDependenciesMeta:
@@ -5623,6 +5654,10 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ synckit@0.11.11:
+ resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+
tailwindcss-motion@1.1.1:
resolution: {integrity: sha512-CeeQAc5o31BuEPMyWdq/786X7QWNeifa+8khfu74Fs8lGkgEwjNYv6dGv+lRFS8FWXV5dp7F3AU9JjBXjiaQfw==}
peerDependencies:
@@ -5756,8 +5791,8 @@ packages:
typescript:
optional: true
- tsdown@0.15.7:
- resolution: {integrity: sha512-uFaVgWAogjOMqjY+CQwrUt3C6wzy6ynt82CIoXymnbS17ipUZ8WDXUceJjkislUahF/BZc5+W44Ue3p2oWtqUg==}
+ tsdown@0.15.11:
+ resolution: {integrity: sha512-7k2OglWWt6LzvJKwEf1izbGvETvVfPYRBr9JgEYVRnz/R9LeJSp+B51FUMO46wUeEGtZ1jA3E3PtWWLlq3iygA==}
engines: {node: '>=20.19.0'}
hasBin: true
peerDependencies:
@@ -5950,6 +5985,11 @@ packages:
resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==}
engines: {node: '>=18.12.0'}
+ unrun@0.2.1:
+ resolution: {integrity: sha512-1HpwmlCKrAOP3jPxFisPR0sYpPuiNtyYKJbmKu9iugIdvCte3DH1uJ1p1DBxUWkxW2pjvkUguJoK9aduK8ak3Q==}
+ engines: {node: '>=20.19.0'}
+ hasBin: true
+
unstorage@1.17.1:
resolution: {integrity: sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ==}
peerDependencies:
@@ -6785,6 +6825,14 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.31
jsesc: 3.1.0
+ '@babel/generator@7.28.5':
+ dependencies:
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
'@babel/helper-compilation-targets@7.27.2':
dependencies:
'@babel/compat-data': 7.28.4
@@ -6821,6 +6869,8 @@ snapshots:
'@babel/helper-validator-identifier@7.27.1': {}
+ '@babel/helper-validator-identifier@7.28.5': {}
+
'@babel/helper-validator-option@7.27.1': {}
'@babel/helpers@7.28.4':
@@ -6844,6 +6894,10 @@ snapshots:
dependencies:
'@babel/types': 7.28.4
+ '@babel/parser@7.28.5':
+ dependencies:
+ '@babel/types': 7.28.5
+
'@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)':
dependencies:
'@babel/core': 7.28.4
@@ -6903,6 +6957,11 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+ '@babel/types@7.28.5':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
'@bcoe/v8-coverage@1.0.2': {}
'@biomejs/biome@2.2.5':
@@ -7601,6 +7660,8 @@ snapshots:
'@oxc-project/runtime@0.82.2': {}
+ '@oxc-project/runtime@0.95.0': {}
+
'@oxc-project/types@0.82.2': {}
'@oxc-project/types@0.95.0': {}
@@ -7625,6 +7686,8 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
+ '@pkgr/core@0.2.9': {}
+
'@polka/url@1.0.0-next.29': {}
'@prettier/sync@0.5.5(prettier@3.5.3)':
@@ -7992,7 +8055,7 @@ snapshots:
estree-walker: 2.0.2
fdir: 6.5.0(picomatch@4.0.3)
is-reference: 1.2.1
- magic-string: 0.30.19
+ magic-string: 0.30.21
picomatch: 4.0.3
optionalDependencies:
rollup: 4.34.8
@@ -8016,7 +8079,7 @@ snapshots:
'@rollup/plugin-replace@6.0.2(rollup@4.34.8)':
dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.34.8)
- magic-string: 0.30.19
+ magic-string: 0.30.21
optionalDependencies:
rollup: 4.34.8
@@ -8501,7 +8564,7 @@ snapshots:
'@vitest/snapshot@3.2.4':
dependencies:
'@vitest/pretty-format': 3.2.4
- magic-string: 0.30.19
+ magic-string: 0.30.21
pathe: 2.0.3
'@vitest/spy@3.2.4':
@@ -8647,7 +8710,7 @@ snapshots:
ast-kit@2.1.3:
dependencies:
- '@babel/parser': 7.28.4
+ '@babel/parser': 7.28.5
pathe: 2.0.3
astring@1.9.0: {}
@@ -9638,7 +9701,7 @@ snapshots:
fix-dts-default-cjs-exports@1.0.0:
dependencies:
- magic-string: 0.30.19
+ magic-string: 0.30.21
mlly: 1.7.4
rollup: 4.34.8
@@ -9710,6 +9773,10 @@ snapshots:
dependencies:
resolve-pkg-maps: 1.0.0
+ get-tsconfig@4.13.0:
+ dependencies:
+ resolve-pkg-maps: 1.0.0
+
github-from-package@0.0.0:
optional: true
@@ -10304,6 +10371,10 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
+ magic-string@0.30.21:
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+
magicast@0.3.5:
dependencies:
'@babel/parser': 7.28.4
@@ -11739,17 +11810,17 @@ snapshots:
robust-predicates@3.0.2: {}
- rolldown-plugin-dts@0.16.11(rolldown@1.0.0-beta.45)(typescript@5.8.3):
+ rolldown-plugin-dts@0.17.2(rolldown@1.0.0-beta.45)(typescript@5.8.3):
dependencies:
- '@babel/generator': 7.28.3
- '@babel/parser': 7.28.4
- '@babel/types': 7.28.4
+ '@babel/generator': 7.28.5
+ '@babel/parser': 7.28.5
+ '@babel/types': 7.28.5
ast-kit: 2.1.3
birpc: 2.6.1
debug: 4.4.3
dts-resolver: 2.1.2
- get-tsconfig: 4.10.1
- magic-string: 0.30.19
+ get-tsconfig: 4.13.0
+ magic-string: 0.30.21
rolldown: 1.0.0-beta.45
optionalDependencies:
typescript: 5.8.3
@@ -11801,7 +11872,7 @@ snapshots:
rollup-plugin-dts@6.1.1(rollup@4.34.8)(typescript@5.8.3):
dependencies:
- magic-string: 0.30.19
+ magic-string: 0.30.21
rollup: 4.34.8
typescript: 5.8.3
optionalDependencies:
@@ -12109,6 +12180,10 @@ snapshots:
symbol-tree@3.2.4: {}
+ synckit@0.11.11:
+ dependencies:
+ '@pkgr/core': 0.2.9
+
tailwindcss-motion@1.1.1(tailwindcss@4.1.11):
dependencies:
tailwindcss: 4.1.11
@@ -12235,7 +12310,7 @@ snapshots:
optionalDependencies:
typescript: 5.8.3
- tsdown@0.15.7(typescript@5.8.3):
+ tsdown@0.15.11(typescript@5.8.3):
dependencies:
ansis: 4.2.0
cac: 6.7.14
@@ -12245,12 +12320,13 @@ snapshots:
empathic: 2.0.0
hookable: 5.5.3
rolldown: 1.0.0-beta.45
- rolldown-plugin-dts: 0.16.11(rolldown@1.0.0-beta.45)(typescript@5.8.3)
+ rolldown-plugin-dts: 0.17.2(rolldown@1.0.0-beta.45)(typescript@5.8.3)
semver: 7.7.3
tinyexec: 1.0.1
tinyglobby: 0.2.15
tree-kill: 1.2.2
unconfig: 7.3.3
+ unrun: 0.2.1
optionalDependencies:
typescript: 5.8.3
transitivePeerDependencies:
@@ -12362,7 +12438,7 @@ snapshots:
fix-dts-default-cjs-exports: 1.0.0
hookable: 5.5.3
jiti: 2.6.0
- magic-string: 0.30.19
+ magic-string: 0.30.21
mkdist: 2.2.0(typescript@5.8.3)
mlly: 1.7.4
pathe: 2.0.3
@@ -12482,6 +12558,12 @@ snapshots:
picomatch: 4.0.3
webpack-virtual-modules: 0.6.2
+ unrun@0.2.1:
+ dependencies:
+ '@oxc-project/runtime': 0.95.0
+ rolldown: 1.0.0-beta.45
+ synckit: 0.11.11
+
unstorage@1.17.1:
dependencies:
anymatch: 3.1.3
@@ -12642,7 +12724,7 @@ snapshots:
chai: 5.2.0
debug: 4.4.3
expect-type: 1.2.1
- magic-string: 0.30.19
+ magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.3
std-env: 3.9.0