interstage

Import WGSL shaders into TypeScript as typed TypeGPU modules. A TypeScript 7.1 content mapper reads your .wgsl files, so tsc and your editor check every place where host code meets shader code.

One file, two languages

The shader stays plain WGSL. interstage turns each declaration into its TypeGPU counterpart: structs into schemas, each @group into a bind group layout, functions into typed functions and entry points into pipeline stages.

Because tsc reads the shader itself, there is nothing to generate and nothing to keep in sync. Rename a field in the shader and the TypeScript that writes it stops compiling.

slime.wgsl, as written

          
slime.wgsl, as tsc sees it

          

Examples

Each one runs in your browser on WebGPU. Open the contract drawer on any of them to see the shader next to the declarations tsc emits for it.

What tsc tells you

Real output from building these examples.

With the validate option, Tint, the WGSL compiler inside Chrome, checks every shader during the type check. One of the first drafts of the slime mold used a reserved word:
$ tsc --noEmit --runExternalCode
src/slime/slime.wgsl(116,9): error wgsl100: 'target' is a reserved keyword
Changing speed: f32 to speed: vec2f in slime.wgsl breaks the TypeScript that writes it:
$ tsc --noEmit --runExternalCode
src/slime/main.ts(40,66): error TS2345: Argument of type '{ decay: number; sensorAngle: number;
  sensorDistance: number; speciesCount: number; speed: number; turnSpeed: number; }' is not
  assignable to parameter of type 'Behaviour'.
  Types of property 'speed' are incompatible.
    Type 'number' is not assignable to type 'v2f'.

Set it up

  1. Install interstage next to TypeScript 7.1 and TypeGPU.

    pnpm add -D interstage typescript@beta
    pnpm add typegpu
  2. Register the content mapper in tsconfig.json. validate is optional and needs the webgpu package (pnpm add -D webgpu).

    {
      "compilerOptions": { "skipLibCheck": true },
      "contentMappers": [
        { "package": "interstage", "extensions": [".wgsl"], "options": { "validate": true } }
      ]
    }
  3. Let Vite load .wgsl files as modules.

    import interstage from 'interstage/vite'
    
    export default defineConfig({ plugins: [interstage()] })
  4. Type-check with permission to run the mapper. In VS Code, the TypeScript 7 extension does the same in trusted workspaces.

    tsc --noEmit --runExternalCode