Package Exports
- @vgpu/wgsl
- @vgpu/wgsl/loader-vite
- @vgpu/wgsl/loader-webpack
- @vgpu/wgsl/runtime
- @vgpu/wgsl/wgsl-types
Readme
@vgpu/wgsl
@vgpu/wgsl turns WGSL files into JavaScript string modules and resolves WGSL-to-WGSL imports before bundling. It includes a resolver/runtime, webpack and Vite integrations, and a TypeScript ambient-types sub-export for import shader from "./shader.wgsl".
The resolver preserves shader entry-point names for WebGPU pipeline creation while mangling non-entry helpers and imports to avoid cross-module symbol collisions. Loader integrations also wire transitive .wgsl imports into bundler watch/HMR systems.
Installation
pnpm add @vgpu/wgsl
npm install @vgpu/wgsl
yarn add @vgpu/wgslTypeScript setup
Add a project .d.ts file (for example wgsl-env.d.ts):
/// <reference types="@vgpu/wgsl/wgsl-types" />Or add the package type to tsconfig.json:
{
"compilerOptions": {
"types": ["@vgpu/wgsl/wgsl-types"]
}
}This is required for TypeScript to accept .wgsl imports. If you cannot use the sub-export, a local fallback is:
declare module "*.wgsl" {
const source: string;
export default source;
}Bundler integrations
Webpack 5
Recommended rule:
module.exports = { module: { rules: [{ test: /\.wgsl$/, loader: "@vgpu/wgsl/loader-webpack" }] } };Verbose/options form (advanced):
module.exports = {
module: {
rules: [
{
test: /\.wgsl$/,
loader: require.resolve("@vgpu/wgsl/loader-webpack"),
options: {},
},
],
},
};Vite 5+
import { wgslVitePlugin } from "@vgpu/wgsl/loader-vite";
export default { plugins: [wgslVitePlugin()] };Next.js / Turbopack (Next >= 15.5)
Recommended string form:
import type { NextConfig } from "next";
const config: NextConfig = {
turbopack: {
rules: {
"*.wgsl": {
loaders: ["@vgpu/wgsl/loader-webpack"],
as: "*.js",
},
},
},
};
export default config;Verbose/options form (advanced; object loaders require options by the Next TypeScript type):
import type { NextConfig } from "next";
const config: NextConfig = {
turbopack: {
rules: {
"*.wgsl": {
loaders: [{ loader: "@vgpu/wgsl/loader-webpack", options: {} }],
as: "*.js",
},
},
},
};
export default config;Use legacy experimental.turbo.rules on older Next 15.0-15.2 apps. The as: "*.js" mapping is required because Turbopack's webpack-loader bridge expects loaders to return JavaScript.
WGSL syntax
WGSL modules can import named exports from other WGSL files:
import { helper_color } from "./helper.wgsl";
@fragment
fn fs_main() -> @location(0) vec4f {
return helper_color();
}Imported files export normal WGSL declarations:
export fn helper_color() -> vec4f { return vec4f(0.1, 0.2, 0.3, 1.0); }See examples/next-wgsl/app/*.wgsl and packages/wgsl/tests/*.test.ts for more examples.
Mangling and entry-point preservation
Non-entry helpers and imported symbols may be renamed to _vgsl_<hash>__<name> to avoid collisions between modules. WebGPU entry-point functions annotated with @vertex, @fragment, or @compute are preserved, so pipeline creation keeps working:
device.createRenderPipeline({
layout: "auto",
vertex: { module, entryPoint: "vs_main" },
fragment: { module, entryPoint: "fs_main", targets: [{ format }] },
});Bindings and override constants are likewise exposed by their shader-visible names; when a reflected item needs a generated name, use its mangledName field.
Reflection API
import { resolveShader } from "@vgpu/wgsl/runtime";
const resolved = await resolveShader({ entry: "./shader.wgsl" });
const fragment = resolved.reflection.entryPoints.find((entry) => entry.stage === "fragment");resolveShader() returns { wgsl, deps, cacheKey, ast, sourceMap, diagnostics, reflection }. Reflection currently exposes:
| Field | Description |
|---|---|
reflection.entryPoints |
{ name, mangledName, stage } for @vertex, @fragment, and @compute functions. |
reflection.bindings |
{ group, binding, name } for discovered resource bindings. |
reflection.overrides |
{ name, mangledName, defaultValue? } for override constants. |
reflection.featuresRequired |
Feature names from enable ...; directives. |
Workgroup sizes, binding access types, and struct layouts are not yet exposed.
HMR behavior
Transitive .wgsl imports are registered with each bundler's watch graph:
- Webpack: the loader calls
this.addDependency()for resolved dependencies. - Vite/Rollup: the plugin calls
this.addWatchFile(). - Turbopack: the webpack-loader bridge tracks patched async
fs.readFilecalls; the resolver usesfs/promises.readFileso transitive imports are intercepted.resolveShader()does not keep a stale entry-levelresolveCache, so reloads re-read changed imports.
API reference
| Export | Purpose |
|---|---|
@vgpu/wgsl |
compile() for plain WGSL source strings plus shared public types. |
@vgpu/wgsl/runtime |
resolveShader() for file/module graph resolution and reflection. |
@vgpu/wgsl/loader-webpack |
Default webpack-compatible .wgsl loader. |
@vgpu/wgsl/loader-vite |
Default/named wgslVitePlugin() and transformWgsl(). |
@vgpu/wgsl/wgsl-types |
Ambient declare module "*.wgsl" types for TypeScript apps. |
License
MIT.