If you’ve ever written complex WebGL shaders or procedural raymarching scenes, you already know the pain of raw GLSL: everything has to live in one giant, monolithic file.
Want to reuse your favorite math utilities, signed distance field (SDF) shapes, or lighting helpers across different projects? Good luck copying and pasting code blocks across files, or wrestling with hacky string-injection scripts in your build setup.
That frustration led me to build glslh—a lightweight, C-style #include preprocessor and live VS Code extension designed to bring true modularity to shader development. I recently hit 570+ installs in its first week, so I wanted to share the architecture behind it and how it solves the modularity problem for WebGL developers.
🛑 The Problem with Raw GLSL
Unlike C, C++, or modern web languages, standard GLSL has no native file inclusion system. When graphics drivers compile a shader, they expect a single continuous block of source code.
Developers usually end up solving this with custom Node scripts or bundler plugins that blindly concatenate files. But that approach breaks down quickly when you run into:
- Circular Dependencies: File A includes File B, which accidentally includes File A back.
- Order-Dependent Compilation: If a helper function or struct is used before it's declared in the merged output, the GPU compiler throws a cryptic error.
- Web vs. Desktop Discrepancies: Tools that work locally often fail when run inside browser-based editors like GitHub.dev or VS Code for the Web.
🤔 "Why Not Just Use Existing Alternatives or Bundlers?"
A common question I get is: Why build a custom preprocessor when there are already bundler plugins or general script runners?
That brings up two massive pain points with other tools that glslh specifically solves:
Q: What if you try to use standard bundler imports or other tools?
Answer: Most existing alternatives rely on heavy bundler plugins (like Webpack/Vite loaders). The moment you try to take a multi-file modular shader out of your local bundler environment and view it standalone—or test it quickly in an online editor—it breaks completely because the raw file has unresolved #include statements. glslh solves this by preprocessing files dynamically right at the loader level, keeping your source portable.
Q: Why do we even need built-in library constants like Math.PI?
Answer: In native GLSL, you have to manually define constants like PI (#define PI 3.14159265359) at the top of every single file. If you forget one, your math breaks. By injecting common math library properties (Math.PI, Math.TAU, etc.) automatically during preprocessing, glslh gives you a cleaner developer experience out of the box without boilerplate clutter.
🛠️ Under the Hood of glslh
To make shader modularity seamless, glslh handles the heavy lifting before the shader ever hits the GPU:
- Intelligent Multi-Candidate Path Resolver: It scans for
#include "./utils.glsl"directives and resolves relative paths, absolute paths, and nested workspace includes safely across both desktop and web environments. - Circular Dependency Protection: Tracks import history recursively to instantly catch and prevent infinite loops.
- Topological Sorting (Kahn’s Algorithm): Automatically analyzes function and variable dependencies across included files, sorting them into the correct compilation order so you never have to worry about forward declaration errors again.
- Overload-Aware Static Collision Detection: Warns you if two included files try to declare conflicting function signatures, while fully supporting valid GLSL function overloading.
- Dynamic Macro & Uniform Injection: Automatically injects useful constants like
Math.PIandMath.TAUduring preprocessing while binding default uniforms (u_time,u_resolution,u_intensity).
⚡ Meet glslh-previewer: The VS Code Companion
A preprocessor is only as good as its developer experience. Alongside the core npm package, I built the glslh-previewer extension for VS Code (fully optimized for both Desktop and Web browsers).
GitHub Repository: glsl-header
Core npm Package: glslh
Vscode extension: glslh previewer
💬 Let's Chat: What Features Should Come Next?
I want to keep glslh lightweight and focused on what actually improves your daily workflow without over-engineering it.
I’d love to hear from you in the comments:
- What is your biggest pain point when writing modular shaders?
- Are there any specific preprocessor features, macros, or tooling integrations you wish existed?
Let me know what you think! ⚡
