Import, inline (and minify) GLSL/WGSL/Slang shader files
Inspired by threejs-glsl-loader and vite-plugin-string, compatible with Babylon.js, three.js and lygia.
npm i vite-plugin-glsl --save-dev
# or
yarn add vite-plugin-glsl --dev
# or
pnpm add -D vite-plugin-glsl
# or
bun add vite-plugin-glsl --dev// vite.config.js
import { defineConfig } from 'vite';
import glsl from 'vite-plugin-glsl';
export default defineConfig({
plugins: [glsl()]
});Add extension declarations to your types in tsconfig.json:
{
"compilerOptions": {
"types": [
"vite-plugin-glsl/ext"
]
}
}or as a package dependency directive to your global types:
/// <reference types="vite-plugin-glsl/ext" />Slang shaders are not supported out of the box; however, starting from version 1.6.0, it's quite easy to use them with this plugin. You'll need some minimal setup you can find here to copy-paste in your vite.config file. The idea is to use multiple importKeywords to include Slang shader chunks and convert the output shader to a web-friendly format (like WGSL) by using the onComplete option and the Slang compiler.
glsl({
include: [ // Glob pattern, or array of glob patterns to import
'**/*.glsl', '**/*.wgsl',
'**/*.vert', '**/*.frag',
'**/*.vs', '**/*.fs'
],
exclude: undefined, // Glob pattern, or array of glob patterns to ignore
defaultExtension: 'glsl', // Shader suffix to use when no extension is specified
warnDuplicatedImports: true, // Warn if the same chunk was imported multiple times
removeDuplicatedImports: false, // Automatically remove an already imported chunk
importKeywords: ['#include'], // Keywords used to import shader chunks
onComplete: undefined, // Function to call with output shader
minify: false, // Minify/optimize output shader code
watch: true, // Recompile shader on change
root: '/' // Directory for root imports
})root
├── src/
│ ├── glsl/
│ │ ├── chunk0.frag
│ │ ├── chunk3.frag
│ │ ├── main.frag
│ │ ├── main.vert
│ │ └── utils/
│ │ ├── chunk1.glsl
│ │ └── chunk2.frag
│ └── main.js
├── vite.config.js
└── package.json
// main.js
import fragment from './glsl/main.frag';// main.frag
#version 300 es
#ifndef GL_FRAGMENT_PRECISION_HIGH
precision mediump float;
#else
precision highp float;
#endif
out vec4 fragColor;
#include chunk0.frag;
void main (void) {
fragColor = chunkFn();
}// chunk0.frag
// ".glsl" extension will be added automatically:
#include utils/chunk1;
vec4 chunkFn () {
return vec4(chunkRGB(), 1.0);
}// utils/chunk1.glsl
#include chunk2.frag;
#include ../chunk3.frag;
vec3 chunkRGB () {
return vec3(chunkRed(), chunkGreen(), 0.0);
}// utils/chunk2.frag
float chunkRed () {
return 0.0;
}// chunk3.frag
float chunkGreen () {
return 0.8;
}Will result in:
// main.frag
#version 300 es
#ifndef GL_FRAGMENT_PRECISION_HIGH
precision mediump float;
#else
precision highp float;
#endif
out vec4 fragColor;
float chunkRed () {
return 0.0;
}
float chunkGreen () {
return 0.8;
}
vec3 chunkRGB () {
return vec3(chunkRed(), chunkGreen(), 0.0);
}
vec4 chunkFn () {
return vec4(chunkRGB(), 1.0);
}
void main (void) {
fragColor = chunkFn();
}Shader chunks can also be imported from third-party packages installed in node_modules. Any import path that is neither relative (./, ../) nor absolute from the project root (/) is treated as a package specifier once no matching file is found relative to the importing shader:
npm install glsl-noise// main.frag
#version 300 es
precision highp float;
in vec2 vUv;
out vec4 fragColor;
// Resolved from "node_modules/glsl-noise/simplex/2d.glsl":
#include glsl-noise/simplex/2d.glsl
void main (void) {
fragColor = vec4(vec3(snoise(vUv) * 0.5 + 0.5), 1.0);
}Scoped packages (#include @scope/package/chunk.glsl) are supported too and defaultExtension is appended when the specifier has no extension. Packages are looked up by walking up all node_modules directories starting from the importing shader's location, falling back to the node_modules directory of the current working one. Files relative to the importing shader always take precedence over packages with the same name.
If the package defines an exports field in its package.json, the import subpath is mapped through it before falling back to the raw file path inside the package. This allows shader libraries to expose a public layout that differs from their source tree:
// node_modules/@field/shaderlib/package.json
{
"name": "@field/shaderlib",
"exports": {
"./noise": {
"default": "./src/noise"
}
}
}// Resolved from "node_modules/@field/shaderlib/src/noise/2d.glsl":
#include @field/shaderlib/noise/2d.glslThe following exports forms are supported (the longest matching key wins):
- Exact subpaths:
"./noise/2d.glsl": "./src/noise/2d.glsl" - Subpath patterns:
"./noise/*": "./src/noise/*" - Directory prefixes:
"./noise": "./src/noise"(the rest of the subpath is appended to the target)
Targets can be plain strings, arrays (first resolvable entry wins) or condition objects. Conditions are matched in the order they are declared and only glsl, import and default are considered:
{
"exports": {
"./noise/*": {
"glsl": "./src/noise/*",
"default": "./dist/noise/*"
}
}
}Packages without an exports field keep resolving against their raw file layout, and so do subpaths that don't match any exports key.
-
Starting from the next release (unreleased) this plugin supports importing shader chunks from packages installed in
node_modules(e.g.#include glsl-noise/simplex/2d.glsl), honoring the package.jsonexportsmap when present (e.g.#include @field/shaderlib/noise/2d.glsl). Check "Importing fromnode_modules" for more info. -
Starting from
v1.6.0this plugin supportsonCompletecallback function to customize output shaders. -
Starting from
v1.5.2this plugin usesvite.transformWithOxcfunction when available. -
Starting from
v1.5.1this plugin is fully compatible withvite^7.0.0. -
Starting from
v1.5.0this plugin supports a customimportKeywordto include shader chunks. -
Starting from
v1.4.0compressoption was renamed tominifyand now it allows a promise callback. -
Starting from
v1.3.2this plugin allows to automatically remove already imported chunks with theremoveDuplicatedImportsoption set totrue. -
Starting from
v1.3.1this plugin is fully compatible withvite^6.0.0. -
Starting from
v1.3.0this plugin will not remove comments starting with///, unlesscompressoption is set totrue. -
Starting from
v1.2.0this plugin is fully compatible withvite^5.0.0. -
Starting from
v1.1.1this plugin has a complete TypeScript support. Check "Usage" > "With TypeScript" for more info. -
Starting from
v1.0.0this plugin is fully compatible withvite^4.0.0. -
Starting from
v0.5.4this plugin supports customcompresscallback function to optimize output shader length after all shader chunks have been included. -
Starting from
v0.5.0this plugin supports shaders hot reloading whenwatchoption is set totrue. -
Starting from
v0.4.0this plugin supports chunk imports from project root androotoption to override the default root directory. -
Starting from
v0.3.0this plugin is pure ESM. Consider updating your project to an ESM module by adding"type": "module"in yourpackage.jsonor consult this issue for possible workarounds. -
Starting from
v0.2.2this plugin supportscompressoption to optimize output shader length. You might consider setting this totruein production environment. -
Starting from
v0.2.0this plugin uses a config object as a single argument toglslfunction and allows to disable import warnings with thewarnDuplicatedImportsparam set tofalse. -
Starting from
v0.1.5this plugin warns about duplicated chunks imports and throws an error when a recursive loop occurres. -
Starting from
v0.1.2this plugin generates sourcemaps using vite esbuild when thesourcemapoption is set totrue. -
Starting from
v0.1.0this plugin supports WebGPU shaders with.wgslextension. -
Starting from
v0.0.9this plugin supports optional semicolons at the end of#includestatements. -
Starting from
v0.0.7this plugin supports optional single and double quotation marks around file names.
When used with three.js r0.99 and higher, it's possible to include shader chunks as specified in the documentation, those imports will be ignored by vite-plugin-glsl since they are handled internally by the library itself:
#include <common>
vec3 randVec3 (const in vec2 uv) {
return vec3(
rand(uv * 0.1), rand(uv * 2.5), rand(uv)
);
}