├── .gitignore
├── .npmignore
├── .npmrc
├── LICENSE
├── README.md
├── bun.lock
├── ext.d.ts
├── favicon.svg
├── index.html
├── package.json
├── src
├── index.d.ts
├── index.js
├── loadShader.d.ts
├── loadShader.js
└── types.d.ts
├── test
├── glsl
│ ├── chunk0.frag
│ ├── chunk3.frag
│ ├── main.frag
│ └── utils
│ │ ├── chunk1.glsl
│ │ └── chunk2.frag
├── index.js
└── wgsl
│ ├── chunk0.wgsl
│ ├── chunk3.wgsl
│ ├── main.wgsl
│ └── utils
│ ├── chunk1.wgsl
│ └── chunk2.wgsl
├── vite-plugin-glsl.code-workspace
└── vite.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Folders:
2 | node_modules/
3 | test/glsl/lygia
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # Folders:
2 | test
3 | node_modules
4 |
5 | # Files:
6 | .gitignore
7 | index.html
8 | favicon.svg
9 | vite.config.js
10 | *.code-workspace
11 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | @ustym:registry=https://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2022 - 2025 Ustym Ukhman
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vite Plugin GLSL #
2 |
3 | > Import, inline (and minify) GLSL/WGSL shader files
4 |
5 | 
6 | 
7 | 
8 |
9 | _Inspired by [threejs-glsl-loader](https://github.com/MONOGRID/threejs-glsl-loader) and [vite-plugin-string](https://github.com/aweikalee/vite-plugin-string), compatible with [Babylon.js](https://www.babylonjs.com/), [three.js](https://threejs.org/) and [lygia](https://lygia.xyz/)._
10 |
11 | ## Installation ##
12 |
13 | ```sh
14 | npm i vite-plugin-glsl --save-dev
15 | # or
16 | yarn add vite-plugin-glsl --dev
17 | # or
18 | pnpm add -D vite-plugin-glsl
19 | # or
20 | bun add vite-plugin-glsl --dev
21 | ```
22 |
23 | ## Usage ##
24 |
25 | ```js
26 | // vite.config.js
27 | import glsl from 'vite-plugin-glsl';
28 | import { defineConfig } from 'vite';
29 |
30 | export default defineConfig({
31 | plugins: [glsl()]
32 | });
33 | ```
34 |
35 | ### With TypeScript ###
36 |
37 | Add extension declarations to your [`types`](https://www.typescriptlang.org/tsconfig#types) in `tsconfig.json`:
38 |
39 | ```json
40 | {
41 | "compilerOptions": {
42 | "types": [
43 | "vite-plugin-glsl/ext"
44 | ]
45 | }
46 | }
47 | ```
48 |
49 | or as a [package dependency directive](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) to your global types:
50 |
51 | ```ts
52 | ///
53 | ```
54 |
55 | ## Default Options ##
56 |
57 | ```js
58 | glsl({
59 | include: [ // Glob pattern, or array of glob patterns to import
60 | '**/*.glsl', '**/*.wgsl',
61 | '**/*.vert', '**/*.frag',
62 | '**/*.vs', '**/*.fs'
63 | ],
64 | exclude: undefined, // Glob pattern, or array of glob patterns to ignore
65 | warnDuplicatedImports: true, // Warn if the same chunk was imported multiple times
66 | removeDuplicatedImports: false, // Automatically remove an already imported chunk
67 | defaultExtension: 'glsl', // Shader suffix when no extension is specified
68 | minify: false, // Minify/optimize output shader code
69 | watch: true, // Recompile shader on change
70 | root: '/' // Directory for root imports
71 | })
72 | ```
73 |
74 | ## Example ##
75 |
76 | ```
77 | root
78 | ├── src/
79 | │ ├── glsl/
80 | │ │ ├── chunk0.frag
81 | │ │ ├── chunk3.frag
82 | │ │ ├── main.frag
83 | │ │ ├── main.vert
84 | │ │ └── utils/
85 | │ │ ├── chunk1.glsl
86 | │ │ └── chunk2.frag
87 | │ └── main.js
88 | ├── vite.config.js
89 | └── package.json
90 | ```
91 |
92 | ```js
93 | // main.js
94 | import fragment from './glsl/main.frag';
95 | ```
96 |
97 | ```glsl
98 | // main.frag
99 | #version 300 es
100 |
101 | #ifndef GL_FRAGMENT_PRECISION_HIGH
102 | precision mediump float;
103 | #else
104 | precision highp float;
105 | #endif
106 |
107 | out vec4 fragColor;
108 |
109 | #include chunk0.frag;
110 |
111 | void main (void) {
112 | fragColor = chunkFn();
113 | }
114 | ```
115 |
116 | ```glsl
117 | // chunk0.frag
118 |
119 | // ".glsl" extension will be added automatically:
120 | #include utils/chunk1;
121 |
122 | vec4 chunkFn () {
123 | return vec4(chunkRGB(), 1.0);
124 | }
125 | ```
126 |
127 | ```glsl
128 | // utils/chunk1.glsl
129 |
130 | #include chunk2.frag;
131 | #include ../chunk3.frag;
132 |
133 | vec3 chunkRGB () {
134 | return vec3(chunkRed(), chunkGreen(), 0.0);
135 | }
136 | ```
137 |
138 | ```glsl
139 | // utils/chunk2.frag
140 |
141 | float chunkRed () {
142 | return 0.0;
143 | }
144 | ```
145 |
146 | ```glsl
147 | // chunk3.frag
148 |
149 | float chunkGreen () {
150 | return 0.8;
151 | }
152 | ```
153 |
154 | Will result in:
155 |
156 | ```glsl
157 | // main.frag
158 | #version 300 es
159 |
160 | #ifndef GL_FRAGMENT_PRECISION_HIGH
161 | precision mediump float;
162 | #else
163 | precision highp float;
164 | #endif
165 |
166 | out vec4 fragColor;
167 |
168 | float chunkRed () {
169 | return 0.0;
170 | }
171 |
172 | float chunkGreen () {
173 | return 0.8;
174 | }
175 |
176 | vec3 chunkRGB () {
177 | return vec3(chunkRed(), chunkGreen(), 0.0);
178 | }
179 |
180 | vec4 chunkFn () {
181 | return vec4(chunkRGB(), 1.0);
182 | }
183 |
184 | void main (void) {
185 | fragColor = chunkFn();
186 | }
187 | ```
188 |
189 | ## Change Log ##
190 |
191 | - Starting from `v1.4.0` `compress` option was renamed to `minify` and now it allows a promise callback.
192 |
193 | - Starting from `v1.3.2` this plugin allows to automatically remove already imported chunks with the `removeDuplicatedImports` option set to `true`.
194 |
195 | - Starting from `v1.3.1` this plugin is fully compatible with `vite^6.0.0`.
196 |
197 | - Starting from `v1.3.0` this plugin will not remove comments starting with `///`, unless `compress` option is set to `true`.
198 |
199 | - Starting from `v1.2.0` this plugin is fully compatible with `vite^5.0.0`.
200 |
201 | - Starting from `v1.1.1` this plugin has a complete TypeScript support. Check "Usage" > "With TypeScript" for more info.
202 |
203 | - Starting from `v1.0.0` this plugin is fully compatible with `vite^4.0.0`.
204 |
205 | - Starting from `v0.5.4` this plugin supports custom `compress` callback function to optimize output shader length after all shader chunks have been included.
206 |
207 | - Starting from `v0.5.0` this plugin supports shaders hot reloading when `watch` option is set to `true`.
208 |
209 | - Starting from `v0.4.0` this plugin supports chunk imports from project root and `root` option to override the default root directory.
210 |
211 | - Starting from `v0.3.0` this plugin is pure [ESM](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). Consider updating your project to an ESM module by adding `"type": "module"` in your `package.json` or consult [this](https://github.com/UstymUkhman/vite-plugin-glsl/issues/16) issue for possible workarounds.
212 |
213 | - Starting from `v0.2.2` this plugin supports `compress` option to optimize output shader length. You might consider setting this to `true` in production environment.
214 |
215 | - Starting from `v0.2.0` this plugin uses a config object as a single argument to `glsl` function and allows to disable import warnings with the `warnDuplicatedImports` param set to `false`.
216 |
217 | - Starting from `v0.1.5` this plugin warns about duplicated chunks imports and throws an error when a recursive loop occurres.
218 |
219 | - Starting from `v0.1.2` this plugin generates sourcemaps using vite esbuild when the `sourcemap` [option](https://github.com/UstymUkhman/vite-plugin-glsl/blob/main/vite.config.js#L5) is set to `true`.
220 |
221 | - Starting from `v0.1.0` this plugin supports WebGPU shaders with `.wgsl` extension.
222 |
223 | - Starting from `v0.0.9` this plugin supports optional semicolons at the end of `#include` statements.
224 |
225 | - Starting from `v0.0.7` this plugin supports optional single and double quotation marks around file names.
226 |
227 | ### Note: ###
228 |
229 | When used with [three.js](https://github.com/mrdoob/three.js) r0.99 and higher, it's possible to include shader chunks as specified in the [documentation](https://threejs.org/docs/index.html?q=Shader#api/en/materials/ShaderMaterial), those imports will be ignored by `vite-plugin-glsl` since they are handled internally by the library itself:
230 |
231 | ```glsl
232 | #include
233 |
234 | vec3 randVec3 (const in vec2 uv) {
235 | return vec3(
236 | rand(uv * 0.1), rand(uv * 2.5), rand(uv)
237 | );
238 | }
239 | ```
240 |
--------------------------------------------------------------------------------
/bun.lock:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1,
3 | "workspaces": {
4 | "": {
5 | "name": "vite-plugin-glsl",
6 | "dependencies": {
7 | "@rollup/pluginutils": "^5.1.4",
8 | },
9 | "devDependencies": {
10 | "vite": "^6.1.0",
11 | },
12 | "peerDependencies": {
13 | "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0",
14 | },
15 | },
16 | },
17 | "packages": {
18 | "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.24.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA=="],
19 |
20 | "@esbuild/android-arm": ["@esbuild/android-arm@0.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q=="],
21 |
22 | "@esbuild/android-arm64": ["@esbuild/android-arm64@0.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg=="],
23 |
24 | "@esbuild/android-x64": ["@esbuild/android-x64@0.24.2", "", { "os": "android", "cpu": "x64" }, "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw=="],
25 |
26 | "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA=="],
27 |
28 | "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA=="],
29 |
30 | "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.24.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg=="],
31 |
32 | "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q=="],
33 |
34 | "@esbuild/linux-arm": ["@esbuild/linux-arm@0.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA=="],
35 |
36 | "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg=="],
37 |
38 | "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.24.2", "", { "os": "linux", "cpu": "ia32" }, "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw=="],
39 |
40 | "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ=="],
41 |
42 | "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw=="],
43 |
44 | "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw=="],
45 |
46 | "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q=="],
47 |
48 | "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw=="],
49 |
50 | "@esbuild/linux-x64": ["@esbuild/linux-x64@0.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q=="],
51 |
52 | "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw=="],
53 |
54 | "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.24.2", "", { "os": "none", "cpu": "x64" }, "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw=="],
55 |
56 | "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.24.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A=="],
57 |
58 | "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.24.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA=="],
59 |
60 | "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.24.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig=="],
61 |
62 | "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ=="],
63 |
64 | "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.24.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA=="],
65 |
66 | "@esbuild/win32-x64": ["@esbuild/win32-x64@0.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg=="],
67 |
68 | "@rollup/pluginutils": ["@rollup/pluginutils@5.1.4", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ=="],
69 |
70 | "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.6", "", { "os": "android", "cpu": "arm" }, "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg=="],
71 |
72 | "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.6", "", { "os": "android", "cpu": "arm64" }, "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA=="],
73 |
74 | "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg=="],
75 |
76 | "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg=="],
77 |
78 | "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ=="],
79 |
80 | "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ=="],
81 |
82 | "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg=="],
83 |
84 | "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg=="],
85 |
86 | "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA=="],
87 |
88 | "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q=="],
89 |
90 | "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw=="],
91 |
92 | "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.6", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ=="],
93 |
94 | "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg=="],
95 |
96 | "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.6", "", { "os": "linux", "cpu": "s390x" }, "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw=="],
97 |
98 | "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw=="],
99 |
100 | "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A=="],
101 |
102 | "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA=="],
103 |
104 | "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA=="],
105 |
106 | "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.6", "", { "os": "win32", "cpu": "x64" }, "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w=="],
107 |
108 | "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
109 |
110 | "esbuild": ["esbuild@0.24.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.24.2", "@esbuild/android-arm": "0.24.2", "@esbuild/android-arm64": "0.24.2", "@esbuild/android-x64": "0.24.2", "@esbuild/darwin-arm64": "0.24.2", "@esbuild/darwin-x64": "0.24.2", "@esbuild/freebsd-arm64": "0.24.2", "@esbuild/freebsd-x64": "0.24.2", "@esbuild/linux-arm": "0.24.2", "@esbuild/linux-arm64": "0.24.2", "@esbuild/linux-ia32": "0.24.2", "@esbuild/linux-loong64": "0.24.2", "@esbuild/linux-mips64el": "0.24.2", "@esbuild/linux-ppc64": "0.24.2", "@esbuild/linux-riscv64": "0.24.2", "@esbuild/linux-s390x": "0.24.2", "@esbuild/linux-x64": "0.24.2", "@esbuild/netbsd-arm64": "0.24.2", "@esbuild/netbsd-x64": "0.24.2", "@esbuild/openbsd-arm64": "0.24.2", "@esbuild/openbsd-x64": "0.24.2", "@esbuild/sunos-x64": "0.24.2", "@esbuild/win32-arm64": "0.24.2", "@esbuild/win32-ia32": "0.24.2", "@esbuild/win32-x64": "0.24.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA=="],
111 |
112 | "estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
113 |
114 | "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
115 |
116 | "nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
117 |
118 | "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
119 |
120 | "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
121 |
122 | "postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="],
123 |
124 | "rollup": ["rollup@4.34.6", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.6", "@rollup/rollup-android-arm64": "4.34.6", "@rollup/rollup-darwin-arm64": "4.34.6", "@rollup/rollup-darwin-x64": "4.34.6", "@rollup/rollup-freebsd-arm64": "4.34.6", "@rollup/rollup-freebsd-x64": "4.34.6", "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", "@rollup/rollup-linux-arm-musleabihf": "4.34.6", "@rollup/rollup-linux-arm64-gnu": "4.34.6", "@rollup/rollup-linux-arm64-musl": "4.34.6", "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", "@rollup/rollup-linux-riscv64-gnu": "4.34.6", "@rollup/rollup-linux-s390x-gnu": "4.34.6", "@rollup/rollup-linux-x64-gnu": "4.34.6", "@rollup/rollup-linux-x64-musl": "4.34.6", "@rollup/rollup-win32-arm64-msvc": "4.34.6", "@rollup/rollup-win32-ia32-msvc": "4.34.6", "@rollup/rollup-win32-x64-msvc": "4.34.6", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ=="],
125 |
126 | "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
127 |
128 | "vite": ["vite@6.1.0", "", { "dependencies": { "esbuild": "^0.24.2", "postcss": "^8.5.1", "rollup": "^4.30.1" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ=="],
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/ext.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * @const
3 | * @readonly
4 | * @kind module
5 | * @description Generic shaders
6 | */
7 | declare module '*.glsl' {
8 | const shader: string;
9 | export default shader;
10 | }
11 |
12 | /**
13 | * @const
14 | * @readonly
15 | * @kind module
16 | * @description WebGPU shaders
17 | */
18 | declare module '*.wgsl' {
19 | const shader: string;
20 | export default shader;
21 | }
22 |
23 | /**
24 | * @const
25 | * @readonly
26 | * @kind module
27 | * @description Vertex shaders
28 | */
29 | declare module '*.vert' {
30 | const shader: string;
31 | export default shader;
32 | }
33 |
34 | /**
35 | * @const
36 | * @readonly
37 | * @kind module
38 | * @description Fragment shaders
39 | */
40 | declare module '*.frag' {
41 | const shader: string;
42 | export default shader;
43 | }
44 |
45 | /**
46 | * @const
47 | * @readonly
48 | * @kind module
49 | * @description Vertex shaders
50 | */
51 | declare module '*.vs' {
52 | const shader: string;
53 | export default shader;
54 | }
55 |
56 | /**
57 | * @const
58 | * @readonly
59 | * @kind module
60 | * @description Fragment shaders
61 | */
62 | declare module '*.fs' {
63 | const shader: string;
64 | export default shader;
65 | }
66 |
--------------------------------------------------------------------------------
/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Vite Plugin GLSL
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vite-plugin-glsl",
3 | "description": "Import, inline (and minify) GLSL/WGSL shader files",
4 | "homepage": "https://github.com/UstymUkhman/vite-plugin-glsl#readme",
5 | "packageManager": "^npm@10.8.3",
6 | "types": "./src/index.d.ts",
7 | "module": "./src/index.js",
8 | "main": "./src/index.js",
9 | "version": "1.4.2",
10 | "private": false,
11 | "license": "MIT",
12 | "type": "module",
13 | "author": {
14 | "name": "Ustym Ukhman",
15 | "email": "ustym.ukhman@gmail.com",
16 | "url": "https://github.com/UstymUkhman/"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/UstymUkhman/vite-plugin-glsl.git"
21 | },
22 | "bugs": {
23 | "url": "https://github.com/UstymUkhman/vite-plugin-glsl/issues",
24 | "email": "ustym.ukhman@gmail.com"
25 | },
26 | "publishConfig": {
27 | "save-dev": true,
28 | "access": "public",
29 | "registry": "https://registry.npmjs.org/"
30 | },
31 | "exports": {
32 | ".": "./src/index.js",
33 | "./ext": {
34 | "types": "./ext.d.ts"
35 | }
36 | },
37 | "files": [
38 | "package.json",
39 | "README.md",
40 | "bun.lock",
41 | "ext.d.ts",
42 | "LICENSE",
43 | "src"
44 | ],
45 | "keywords": [
46 | "vite",
47 | "glsl",
48 | "wgsl",
49 | "lygia",
50 | "webgl",
51 | "webgpu",
52 | "vitejs",
53 | "plugin",
54 | "threejs",
55 | "shaders",
56 | "babylonjs",
57 | "vite-plugin",
58 | "glsl-shaders",
59 | "wgsl-shaders",
60 | "webgl-shaders",
61 | "webgpu-shaders"
62 | ],
63 | "scripts": {
64 | "test": "vite"
65 | },
66 | "peerDependencies": {
67 | "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0"
68 | },
69 | "dependencies": {
70 | "@rollup/pluginutils": "^5.1.4"
71 | },
72 | "devDependencies": {
73 | "vite": "^6.1.0"
74 | },
75 | "engines": {
76 | "node": ">= 20.17.0",
77 | "npm": ">= 10.8.3"
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | import type { PluginOptions } from './types.d';
2 | import type { Plugin } from 'vite';
3 | export type { PluginOptions };
4 |
5 | /**
6 | * @function
7 | * @name glsl
8 | * @description Plugin entry point to import,
9 | * inline, (and minify) GLSL/WGSL shader files
10 | *
11 | * @see {@link https://vitejs.dev/guide/api-plugin.html}
12 | * @link https://github.com/UstymUkhman/vite-plugin-glsl
13 | *
14 | * @param {PluginOptions} options Plugin config object
15 | *
16 | * @returns {Plugin} Vite plugin that converts shader code
17 | */
18 | export default function ({
19 | include, exclude,
20 | warnDuplicatedImports,
21 | removeDuplicatedImports,
22 | defaultExtension,
23 | minify,
24 | watch,
25 | root
26 | }?: PluginOptions): Plugin;
27 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @module vite-plugin-glsl
3 | * @author Ustym Ukhman
4 | * @description Import, inline (and minify) GLSL/WGSL shader files
5 | * @version 1.4.2
6 | * @license MIT
7 | */
8 |
9 | import { createFilter } from '@rollup/pluginutils';
10 | import { transformWithEsbuild } from 'vite';
11 | import loadShader from './loadShader.js';
12 |
13 | /**
14 | * @const
15 | * @default
16 | * @readonly
17 | * @type {string}
18 | */
19 | const DEFAULT_EXTENSION = 'glsl';
20 |
21 | /**
22 | * @const
23 | * @default
24 | * @readonly
25 | * @type {readonly RegExp[]}
26 | */
27 | const DEFAULT_SHADERS = Object.freeze([
28 | '**/*.glsl', '**/*.wgsl',
29 | '**/*.vert', '**/*.frag',
30 | '**/*.vs', '**/*.fs'
31 | ]);
32 |
33 | /**
34 | * @function
35 | * @name glsl
36 | * @description Plugin entry point to import,
37 | * inline, (and minify) GLSL/WGSL shader files
38 | *
39 | * @see {@link https://vitejs.dev/guide/api-plugin.html}
40 | * @link https://github.com/UstymUkhman/vite-plugin-glsl
41 | *
42 | * @param {import('./types').PluginOptions} options Plugin config object
43 | *
44 | * @returns {import('vite').Plugin} Vite plugin that converts shader code
45 | */
46 | export default function ({
47 | include = DEFAULT_SHADERS,
48 | exclude = undefined,
49 | warnDuplicatedImports = true,
50 | removeDuplicatedImports = false,
51 | defaultExtension = DEFAULT_EXTENSION,
52 | minify = false,
53 | watch = true,
54 | root = '/'
55 | } = {}
56 | ) {
57 | let sourcemap = false;
58 | const filter = createFilter(include, exclude);
59 | const prod = process.env.NODE_ENV === 'production';
60 |
61 | return {
62 | enforce: 'pre',
63 | name: 'vite-plugin-glsl',
64 |
65 | configResolved (resolvedConfig) {
66 | sourcemap = resolvedConfig.build.sourcemap;
67 | },
68 |
69 | async transform (source, shader) {
70 | if (!filter(shader)) return;
71 |
72 | const { dependentChunks, outputShader } = await loadShader(source, shader, {
73 | removeDuplicatedImports,
74 | warnDuplicatedImports,
75 | defaultExtension,
76 | minify,
77 | root
78 | });
79 |
80 | watch && !prod && Array.from(dependentChunks.values())
81 | .flat().forEach(chunk => this.addWatchFile(chunk));
82 |
83 | return await transformWithEsbuild(outputShader, shader, {
84 | sourcemap: sourcemap && 'external',
85 | loader: 'text', format: 'esm',
86 | minifyWhitespace: prod
87 | });
88 | }
89 | };
90 | }
91 |
--------------------------------------------------------------------------------
/src/loadShader.d.ts:
--------------------------------------------------------------------------------
1 | import type { LoadingOptions, LoadingOutput } from './types.d';
2 |
3 | /**
4 | * @function
5 | * @name loadShader
6 | * @description Iterates through all external chunks, includes them
7 | * into the shader's source code and optionally minifies the output
8 | *
9 | * @param {string} source Shader's source code
10 | * @param {string} shader Shader's absolute path
11 | * @param {LoadingOptions} options Configuration object to define:
12 | *
13 | * - Warn if the same chunk was imported multiple times
14 | * - Automatically remove an already imported chunk
15 | * - Shader suffix when no extension is specified
16 | * - Directory for root imports
17 | * - Minify output shader code
18 | *
19 | * @returns {Promise} Loaded, parsed (and minified)
20 | * shader output and Map of shaders that import other chunks
21 | */
22 | export default async function (
23 | source: string, shader: string,
24 | options: LoadingOptions
25 | ): Promise;
26 |
--------------------------------------------------------------------------------
/src/loadShader.js:
--------------------------------------------------------------------------------
1 | import { dirname, resolve, extname, posix, sep } from 'path';
2 | import { emitWarning, cwd } from 'process';
3 | import { readFileSync } from 'fs';
4 | import { platform } from 'os';
5 |
6 | /**
7 | * @name recursiveChunk
8 | * @type {string}
9 | *
10 | * @description Shader chunk path
11 | * that caused a recursion error
12 | */
13 | let recursiveChunk = '';
14 |
15 | /**
16 | * @const
17 | * @name allChunks
18 | * @type {readonly Set}
19 | *
20 | * @description List of all shader chunks,
21 | * it's used to track included files
22 | */
23 | const allChunks = new Set();
24 |
25 | /**
26 | * @const
27 | * @name dependentChunks
28 | * @type {readonly Map}
29 | *
30 | * @description Map of shaders that import other chunks, it's
31 | * used to track included files in order to avoid recursion
32 | * - Key: shader path that uses other chunks as dependencies
33 | * - Value: list of chunk paths included within the shader
34 | */
35 | const dependentChunks = new Map();
36 |
37 | /**
38 | * @const
39 | * @name duplicatedChunks
40 | * @type {readonly Map}
41 | *
42 | * @description Map of duplicated shader
43 | * imports, used by warning messages
44 | */
45 | const duplicatedChunks = new Map();
46 |
47 | /**
48 | * @const
49 | * @name include
50 | * @type {readonly RegExp}
51 | *
52 | * @description RegEx to match GLSL
53 | * `#include` preprocessor instruction
54 | */
55 | const include = /#include(\s+([^\s<>]+));?/gi;
56 |
57 | /**
58 | * @function
59 | * @name resetSavedChunks
60 | * @description Clears all lists of saved chunks
61 | * and resets "recursiveChunk" path to empty
62 | *
63 | * @returns {string} Copy of "recursiveChunk" path
64 | */
65 | function resetSavedChunks () {
66 | const chunk = recursiveChunk;
67 | duplicatedChunks.clear();
68 | dependentChunks.clear();
69 |
70 | recursiveChunk = '';
71 | allChunks.clear();
72 | return chunk;
73 | }
74 |
75 | /**
76 | * @function
77 | * @name getRecursionCaller
78 | * @description Gets last chunk that caused a
79 | * recursion error from the "dependentChunks" list
80 | *
81 | * @returns {string} Chunk path that started a recursion
82 | */
83 | function getRecursionCaller () {
84 | const dependencies = [...dependentChunks.keys()];
85 | return dependencies[dependencies.length - 1];
86 | }
87 |
88 | /**
89 | * @function
90 | * @name checkDuplicatedImports
91 | * @description Checks if shader chunk was already included
92 | * and adds it to the "duplicatedChunks" list if yes
93 | *
94 | * @param {string} path Shader's absolute path
95 | *
96 | * @throws {Warning} If shader chunk was already included
97 | */
98 | function checkDuplicatedImports (path) {
99 | const caller = getRecursionCaller();
100 |
101 | const chunks = duplicatedChunks.get(caller) ?? [];
102 | if (chunks.includes(path)) return;
103 |
104 | chunks.push(path);
105 | duplicatedChunks.set(caller, chunks);
106 |
107 | emitWarning(`'${path}' was included multiple times.`, {
108 | code: 'vite-plugin-glsl',
109 | detail: 'Please avoid multiple imports of the same chunk in order to avoid' +
110 | ` recursions and optimize your shader length.\nDuplicated import found in file '${caller}'.`
111 | });
112 | }
113 |
114 | /**
115 | * @function
116 | * @name removeSourceComments
117 | * @description Removes comments from shader source
118 | * code in order to avoid including commented chunks
119 | *
120 | * @param {string} source Shader's source code
121 | * @param {boolean} triple Remove comments starting with `///`
122 | *
123 | * @returns {string} Shader's source code without comments
124 | */
125 | function removeSourceComments (source, triple = false) {
126 | if (source.includes('/*') && source.includes('*/')) {
127 | source = source.slice(0, source.indexOf('/*')) +
128 | source.slice(source.indexOf('*/') + 2, source.length);
129 | }
130 |
131 | const lines = source.split('\n');
132 |
133 | for (let l = lines.length; l--; ) {
134 | const index = lines[l].indexOf('//');
135 |
136 | if (index > -1) {
137 | if (lines[l][index + 2] === '/' && !include.test(lines[l]) && !triple) continue;
138 | lines[l] = lines[l].slice(0, lines[l].indexOf('//'));
139 | }
140 | }
141 |
142 | return lines.join('\n');
143 | }
144 |
145 | /**
146 | * @function
147 | * @name checkRecursiveImports
148 | * @description Checks if shader dependencies
149 | * have caused a recursion error or warning
150 | * ignoring duplicate chunks if required
151 | *
152 | * @param {string} path Shader's absolute path
153 | * @param {string} lowPath Shader's lowercase path
154 | * @param {boolean} warn Check already included chunks
155 | * @param {boolean} ignore Ignore already included chunks
156 | *
157 | * @returns {boolean | null} Import recursion has occurred
158 | * or chunk was ignored because of `ignore` argument
159 | */
160 | function checkRecursiveImports (path, lowPath, warn, ignore) {
161 | if (allChunks.has(lowPath)) {
162 | if (ignore) return null;
163 | warn && checkDuplicatedImports(path);
164 | }
165 |
166 | return checkIncludedDependencies(path, path);
167 | }
168 |
169 | /**
170 | * @function
171 | * @name checkIncludedDependencies
172 | * @description Checks if included
173 | * chunks caused a recursion error
174 | *
175 | * @param {string} path Current chunk absolute path
176 | * @param {string} root Main shader path that imports chunks
177 | *
178 | * @returns {boolean} Included chunk started a recursion
179 | */
180 | function checkIncludedDependencies (path, root) {
181 | const dependencies = dependentChunks.get(path);
182 | let recursiveDependency = false;
183 |
184 | if (dependencies?.includes(root)) {
185 | recursiveChunk = root;
186 | return true;
187 | }
188 |
189 | dependencies?.forEach(dependency => recursiveDependency ||=
190 | checkIncludedDependencies(dependency, root)
191 | );
192 |
193 | return recursiveDependency;
194 | }
195 |
196 | /**
197 | * @function
198 | * @name minifyShader
199 | * @description Minifies shader source code by
200 | * removing unnecessary whitespace and empty lines
201 | *
202 | * @param {string} shader Shader code with included chunks
203 | * @param {boolean} newLine Flag to require a new line for the code
204 | *
205 | * @returns {string} Minified shader's source code
206 | */
207 | function minifyShader (shader, newLine = false) {
208 | return shader.replace(/\\(?:\r\n|\n\r|\n|\r)|\/\*.*?\*\/|\/\/(?:\\(?:\r\n|\n\r|\n|\r)|[^\n\r])*/g, '')
209 | .split(/\n+/).reduce((result, line) => {
210 | line = line.trim().replace(/\s{2,}|\t/, ' ');
211 |
212 | if (/@(vertex|fragment|compute)/.test(line) || line.endsWith('return')) line += ' ';
213 |
214 | if (line[0] === '#') {
215 | newLine && result.push('\n');
216 | result.push(line, '\n');
217 | newLine = false;
218 | }
219 |
220 | else {
221 | !line.startsWith('{') && result.length && result[result.length - 1].endsWith('else') && result.push(' ');
222 | result.push(line.replace(/\s*({|}|=|\*|,|\+|\/|>|<|&|\||\[|\]|\(|\)|\-|!|;)\s*/g, '$1'));
223 | newLine = true;
224 | }
225 |
226 | return result;
227 | }, []).join('').replace(/\n+/g, '\n');
228 | }
229 |
230 | /**
231 | * @function
232 | * @name loadChunks
233 | * @description Includes shader's dependencies
234 | * and removes comments from the source code
235 | *
236 | * @param {string} source Shader's source code
237 | * @param {string} path Shader's absolute path
238 | * @param {Options} options Shader loading config object
239 | *
240 | * @throws {Error} If shader chunks started a recursion loop
241 | *
242 | * @returns {string} Shader's source code without external chunks
243 | */
244 | function loadChunks (source, path, options) {
245 | const { warnDuplicatedImports, removeDuplicatedImports } = options;
246 | const unixPath = path.split(sep).join(posix.sep);
247 |
248 | const chunkPath = platform() === 'win32' &&
249 | unixPath.toLocaleLowerCase() || unixPath;
250 |
251 | const recursion = checkRecursiveImports(
252 | unixPath, chunkPath,
253 | warnDuplicatedImports,
254 | removeDuplicatedImports
255 | );
256 |
257 | if (recursion) return recursiveChunk;
258 | else if (recursion === null) return '';
259 |
260 | source = removeSourceComments(source);
261 | let directory = dirname(unixPath);
262 | allChunks.add(chunkPath);
263 |
264 | if (include.test(source)) {
265 | dependentChunks.set(unixPath, []);
266 | const currentDirectory = directory;
267 | const ext = options.defaultExtension;
268 |
269 | source = source.replace(include, (_, chunkPath) => {
270 | chunkPath = chunkPath.trim().replace(/^(?:"|')?|(?:"|')?;?$/gi, '');
271 |
272 | if (!chunkPath.indexOf('/')) {
273 | const base = cwd().split(sep).join(posix.sep);
274 | chunkPath = base + options.root + chunkPath;
275 | }
276 |
277 | const directoryIndex = chunkPath.lastIndexOf('/');
278 | directory = currentDirectory;
279 |
280 | if (directoryIndex !== -1) {
281 | directory = resolve(directory, chunkPath.slice(0, directoryIndex + 1));
282 | chunkPath = chunkPath.slice(directoryIndex + 1, chunkPath.length);
283 | }
284 |
285 | let shader = resolve(directory, chunkPath);
286 | if (!extname(shader)) shader = `${shader}.${ext}`;
287 |
288 | const shaderPath = shader.split(sep).join(posix.sep);
289 | dependentChunks.get(unixPath)?.push(shaderPath);
290 |
291 | return loadChunks(
292 | readFileSync(shader, 'utf8'),
293 | shader, options
294 | );
295 | });
296 | }
297 |
298 | if (recursiveChunk) {
299 | const caller = getRecursionCaller();
300 | const recursiveChunk = resetSavedChunks();
301 |
302 | throw new Error(
303 | `Recursion detected when importing "${recursiveChunk}" in "${caller}".`
304 | );
305 | }
306 |
307 | return source.trim().replace(/(\r\n|\r|\n){3,}/g, '$1\n');
308 | }
309 |
310 | /**
311 | * @function
312 | * @name loadShader
313 | * @description Iterates through all external chunks, includes them
314 | * into the shader's source code and optionally minifies the output
315 | *
316 | * @typedef {import('./types').LoadingOptions} Options
317 | * @typedef {import('./types').LoadingOutput} Output
318 | *
319 | * @param {string} source Shader's source code
320 | * @param {string} shader Shader's absolute path
321 | * @param {Options} options Configuration object to define:
322 | *
323 | * - Warn if the same chunk was imported multiple times
324 | * - Automatically remove an already imported chunk
325 | * - Shader suffix when no extension is specified
326 | * - Directory for root imports
327 | * - Minify output shader code
328 | *
329 | * @returns {Promise