GLSL Shader Loader for Webpack
glsl-shader-loader is a Webpack loader designed to bundle GLSL shader source code, enabling modular management of shaders for WebGL applications. It allows developers to organize GLSL functions into separate files and import them using a custom `#pragma loader: import` syntax directly within other `.glsl` files. The loader performs static analysis to resolve dependencies, remove unused functions, and ensure functions are imported only once, resulting in an optimized shader string ready for use with WebGL. As of version 0.1.6, it focuses on providing a preprocessor-like experience for GLSL, which is useful for complex shader graphs and code reuse. Its release cadence appears to be slow, with the latest version indicating an early stage or a stable, low-maintenance tool rather than rapid development. Key differentiators include its syntax tree analysis for dependency resolution and optimization, which goes beyond simple string concatenation.
Common errors
-
Module parse failed: Unexpected token (1:0) You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.
cause Webpack is trying to parse a `.glsl` file as a standard JavaScript module because glsl-shader-loader is not correctly configured or applied.fixAdd or verify the `glsl-shader-loader` configuration in your `webpack.config.js` under `module.rules`, ensuring the `test` regex matches your GLSL file extensions (e.g., `test: /\.(frag|vert|glsl)$/`). -
Error: Can't resolve './file.glsl' in 'path/to/shader/'
cause The `#pragma loader: import` path within a `.glsl` file is incorrect or cannot be resolved by the loader.fixDouble-check the relative path specified in the `#pragma loader: import` statement. If using the `root` option in the loader configuration, ensure absolute paths starting with `/` correctly map to your specified root directory. -
SyntaxError: Unexpected identifier 'functionName'
cause Attempting to use a standard JavaScript `import` statement for a GLSL file directly, or incorrect syntax within the GLSL `#pragma` directive.fixEnsure you are using `import myShaderSource from './myShader.glsl'` in your JavaScript files, and `glsl-shader-loader` is configured. Within GLSL files, strictly use `#pragma loader: import { functionName } from './file.glsl';` for function imports.
Warnings
- gotcha The package version 0.1.6 suggests it might be in an early development stage or is no longer actively maintained. While functional, it might not receive updates for newer Webpack versions or address new GLSL features/standards.
- gotcha The `#pragma loader: import` syntax is unique to this loader and not standard GLSL. It might not be compatible with other GLSL tooling or linting without specific configuration.
- gotcha When importing a single function from a GLSL file, you can rename it during import (e.g., `#pragma loader: import newName from './file.glsl';`). However, this only works if the source file contains *only one* function. If multiple functions exist, renaming fails, and you must use named imports.
- gotcha The loader performs static analysis and only includes imported functions if they are actually called within the consuming shader. While this is an optimization, it can lead to unexpected behavior if a function is imported but not explicitly called, as it will be omitted from the final output.
Install
-
npm install glsl-shader-loader -
yarn add glsl-shader-loader -
pnpm add glsl-shader-loader
Imports
- glsl-shader-loader
loader: require('glsl-shader-loader')loader: 'glsl-shader-loader'
- ShaderModule
const fragmentShaderSource = require('./fragmentShaderSource.glsl');import fragmentShaderSource from './fragmentShaderSource.glsl';
- GLSLFunctionImport
import { functionName } from './file.glsl';#pragma loader: import { functionName } from './file.glsl';
Quickstart
import path from 'path';
import webpack from 'webpack';
import MemoryFS from 'memory-fs';
// Basic webpack config to use glsl-shader-loader
const config = {
mode: 'development',
entry: './app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.(frag|vert|glsl)$/,
use: [
{
loader: 'glsl-shader-loader',
options: {
// Optional: specify a root path for absolute GLSL imports
// root: path.resolve(__dirname, 'src/shaders')
}
}
]
}
]
}
};
// Example application JS (app.js)
const appJsContent = `
import fragmentShaderSource from './fragmentShaderSource.glsl';
console.log('--- Compiled Fragment Shader Source ---');
console.log(fragmentShaderSource);
// In a real WebGL app, you'd use:
// const gl = canvas.getContext('webgl');
// const shader = gl.createShader(gl.FRAGMENT_SHADER);
// gl.shaderSource(shader, fragmentShaderSource);
// gl.compileShader(shader);
`;
// Example GLSL file (fragmentShaderSource.glsl)
const fragmentShaderContent = `
precision mediump float;
varying vec2 v_texCoord;
#pragma loader: import { randomColor } from './utils.glsl';
void main() {
vec3 color = randomColor(v_texCoord);
gl_FragColor = vec4(color, 1.0);
}
`;
// Example GLSL utility file (utils.glsl)
const utilsGlslContent = `
vec3 randomColor(vec2 coord) {
// Simple pseudo-random color based on coordinates
float r = fract(sin(dot(coord.xy, vec2(12.9898, 78.233))) * 43758.5453);
float g = fract(sin(dot(coord.xy, vec2(53.123, 19.345))) * 53758.9876);
float b = fract(sin(dot(coord.xy, vec2(87.654, 34.567))) * 63758.1234);
return vec3(r, g, b);
}
vec3 anotherFunction() {
return vec3(0.0);
}
`;
// Setup an in-memory file system for webpack to read from
const fs = new MemoryFS();
fs.mkdirpSync(path.resolve(__dirname, 'dist'));
fs.mkdirpSync(path.resolve(__dirname, 'src'));
fs.mkdirpSync(path.resolve(__dirname, 'utils'));
fs.writeFileSync('./app.js', appJsContent);
fs.writeFileSync('./fragmentShaderSource.glsl', fragmentShaderContent);
fs.writeFileSync('./utils.glsl', utilsGlslContent);
const compiler = webpack(config);
compiler.inputFileSystem = fs;
compiler.outputFileSystem = fs;
compiler.run((err, stats) => {
if (err) {
console.error(err.stack || err);
if (err.details) {
console.error(err.details);
}
return;
}
const info = stats.toJson();
if (stats.hasErrors()) {
console.error(info.errors);
}
if (stats.hasWarnings()) {
console.warn(info.warnings);
}
console.log('\nWebpack build completed.');
const bundlePath = path.resolve(__dirname, 'dist', 'bundle.js');
const bundleContent = fs.readFileSync(bundlePath, 'utf8');
// In a real scenario, you'd typically serve this bundle or inject it.
// For this quickstart, we'll just show the generated content.
// To demonstrate the *processed* GLSL, we'd need to run the bundle
// which is outside the scope of this quickstart directly.
// The console.log in app.js inside the bundle would show the processed shader.
});