compress-shader-literals 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,61 +9,41 @@
9
9
  [![npm downloads](https://img.shields.io/npm/dm/compress-shader-literals?color=success)](https://www.npmjs.com/package/compress-shader-literals)
10
10
  [![license](https://img.shields.io/npm/l/compress-shader-literals?color=blue)](./LICENSE)
11
11
 
12
- Your shaders ship to users as strings. This strips the comments and whitespace out of them at build time, before your bundler ever sees them — in any bundler.
12
+ A build-time minifier that strips comments and whitespace from GLSL & WGSL shaders written as template literals in your JS/TS — in any bundler, with no renaming, no toolchain, and no runtime cost.
13
13
 
14
14
  ![Alt Preview](./assets/preview.png)
15
15
 
16
- ## Stats
17
-
18
- Measured on the real shaders shipped by popular libraries:
19
-
20
- <!-- STATS:START -->
21
-
22
- | Package | Shaders | Before | After | Saved |
23
- | --------------------- | ------: | --------: | --------: | --------: |
24
- | `three` | 281 | 240,772 B | 203,428 B | **15.5%** |
25
- | `@jayf0x/fluidity-js` | 9 | 11,095 B | 7,788 B | **29.8%** |
26
- | `ogl` | 22 | 6,109 B | 5,335 B | **12.7%** |
27
- | `shader-park-core` | 18 | 10,794 B | 9,033 B | **16.3%** |
28
- | `curtainsjs` | 7 | 3,406 B | 2,563 B | **24.8%** |
29
- | **Total** | 337 | 272,176 B | 228,147 B | **16.2%** |
16
+ ## Install
30
17
 
31
- _Auto-generated by [`tests/e2e.js`](tests/e2e.js) · packages [verified](tests/validate.js) loadable · 2026-06-28_
18
+ [![NPM](https://nodei.co/npm/compress-shader-literals.png)](https://www.npmjs.com/package/compress-shader-literals)
32
19
 
33
- <!-- STATS:END -->
20
+ ```sh
21
+ bun add -d compress-shader-literals
22
+ # or: npm i -D compress-shader-literals
23
+ ```
34
24
 
35
- <!-- <a href="https://star-history.com/#jayf0x/compress-shader-literals&Date">
36
- <picture>
37
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=jayf0x/compress-shader-literals&type=Date&theme=dark&legend=top-left" />
38
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=jayf0x/compress-shader-literals&type=Date&legend=top-left" />
39
- <img alt="Star History Chart" src="https://api.star-history.com/chart?repos=jayf0x/compress-shader-literals&type=Date&legend=top-left" />
40
- </picture>
41
- </a> -->
25
+ ## About
42
26
 
43
- ## Install
27
+ Shader comments and indentation are dead weight in the bundle — the GPU ignores them, the browser still downloads them. This strips them at build time.
44
28
 
45
- ```sh
46
- npm i -D compress-shader-literals
47
- # or: bun add -d compress-shader-literals
48
- ```
29
+ - Removes comments, collapses whitespace. Nothing else.
30
+ - No renaming, no dead-code removal — output stays valid and readable.
31
+ - Runs before your bundler's own minifier.
49
32
 
50
33
  ## Usage
51
34
 
52
- Pick your bundler — same plugin, same options:
35
+ One [unplugin](https://github.com/unjs/unplugin) plugin, same API for every bundler:
53
36
 
54
37
  ```js
55
38
  import { compressShaderLiterals } from 'compress-shader-literals';
56
39
 
57
- // Vite vite.config.js
40
+ // vite.config.js
58
41
  export default { plugins: [compressShaderLiterals.vite({ outputRatio: true })] };
59
-
60
- // Rollup rollup.config.js → compressShaderLiterals.rollup({ ... })
61
- // webpack webpack.config.js → compressShaderLiterals.webpack({ ... })
62
- // esbuild build script → compressShaderLiterals.esbuild({ ... })
63
- // Rspack / Rolldown / Farm → .rspack() / .rolldown() / .farm()
64
42
  ```
65
43
 
66
- It finds tagged and comment-prefixed literals and yes, the comment form keeps your editor's syntax highlighting:
44
+ For Rollup, webpack, esbuild, Rspack, Rolldown or Farm, call the matching method `.rollup()`, `.webpack()`, `.esbuild()`, `.rspack()`, `.rolldown()`, `.farm()` — with the same options.
45
+
46
+ Finds tagged and comment-prefixed literals. The comment form keeps editor syntax highlighting:
67
47
 
68
48
  ```ts
69
49
  const vert = glsl`
@@ -78,18 +58,16 @@ const frag = /* wgsl */ `
78
58
  `;
79
59
  ```
80
60
 
81
- Both collapse to a single tight line. No comments, no padding, no touched source files.
82
-
83
61
  **Options**
84
62
 
85
- | Option | Default | Description |
86
- | ------------- | ---------------------------- | -------------------------------------------------- |
87
- | `tags` | `['glsl', 'wgsl', 'shader']` | Tag names / comment markers to match |
88
- | `include` | `[/\.[jt]sx?$/]` | Files to process |
89
- | `exclude` | `[/node_modules/, /dist/]` | Files to skip |
90
- | `outputRatio` | `false` | Print a bytes-saved summary after build |
91
- | `transform` | built-in `minifyShader` | Custom minifier — `(shader: string) => string` |
92
- | `debug` | `false` | Log each file's discovered literals to the console |
63
+ | Option | Default | Description |
64
+ | ------------- | ---------------------------- | --------------------------------------------------- |
65
+ | `tags` | `['glsl', 'wgsl', 'shader']` | Tag names / comment markers to match |
66
+ | `include` | `[/\.[mc]?[jt]sx?$/]` | Files to process — the JS/TS family Babel can parse |
67
+ | `exclude` | `[/node_modules/, /dist/]` | Files to skip — dependencies are skipped by default |
68
+ | `outputRatio` | `false` | Print a bytes-saved summary after build |
69
+ | `transform` | built-in `minifyShader` | Custom minifier — `(shader: string) => string` |
70
+ | `debug` | `false` | Log each file's discovered literals to the console |
93
71
 
94
72
  **Programmatic API** — the two core helpers are exported for tooling authors (validators, ESLint rules, CLIs), no plugin required:
95
73
 
@@ -102,16 +80,34 @@ extractShaderLiterals('const v = glsl`void main() {}`');
102
80
  minifyShader('// comment\nvoid main() {}'); // → 'void main() {}'
103
81
  ```
104
82
 
105
- ## How it works
83
+ ## Stats
84
+
85
+ Real shaders shipped by popular libraries, run through the built-in minifier:
106
86
 
107
- Nothing clever, which is the point:
87
+ <!-- STATS:START -->
88
+
89
+ | Package | Shaders | Before | After | Saved |
90
+ | ----------------------- | ------: | ----------: | ----------: | --------: |
91
+ | `curtainsjs` | 7 | 3,406 B | 2,485 B | **27.0%** |
92
+ | `pixi.js` | 162 | 75,768 B | 59,432 B | **21.6%** |
93
+ | `shader-park-core` | 18 | 10,794 B | 8,721 B | **19.2%** |
94
+ | `three` | 281 | 240,772 B | 197,399 B | **18.0%** |
95
+ | `ogl` | 22 | 6,109 B | 5,182 B | **15.2%** |
96
+ | `@paper-design/shaders` | 30 | 142,466 B | 129,723 B | **8.9%** |
97
+ | `@babylonjs/core` | 349 | 669,740 B | 664,617 B | **0.8%** |
98
+ | **Total** | 869 | 1,149,055 B | 1,067,559 B | **7.1%** |
99
+
100
+ _869 shaders · 558/558 parseable GLSL verified valid after minify · [how this is measured](docs/stats.md) · 2026-07-01_
108
101
 
109
- 1. Parses each matched file with Babel (TS/JSX aware).
110
- 2. Finds tagged and comment-prefixed shader literals.
111
- 3. Strips comments + collapses whitespace via [`magic-string`](https://github.com/Rich-Harris/magic-string), so **sourcemaps stay intact**.
112
- 4. Runs as a `pre` transform, so your bundler's own minifier still sees the result.
102
+ <!-- STATS:END -->
103
+
104
+ ## How it works
113
105
 
114
- One [unplugin](https://github.com/unjs/unplugin) covers **Vite, Rollup, webpack, esbuild, Rspack, Rolldown & Farm**.
106
+ 1. Parses each matched file with Babel `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts` (not `.vue`/`.svelte`/`.glsl`, which aren't whole-file JS).
107
+ 2. Finds tagged (`` glsl`…` ``) and comment-prefixed (`/* glsl */ \`…\``) literals, skipping any with `${…}` interpolation.
108
+ 3. Strips comments and collapses each run of whitespace to a single space or newline — never to nothing, so adjacent tokens stay separated and `#version`/`#define` lines survive.
109
+ 4. Rewrites the literal in place with [magic-string](https://github.com/Rich-Harris/magic-string), so sourcemaps stay intact.
110
+ 5. Runs as a `pre` transform, so your bundler's own minifier still sees the result.
115
111
 
116
112
  ## Issues & compatibility
117
113
 
package/dist/index.cjs CHANGED
@@ -1,3 +1,7 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("@rollup/pluginutils"),l=require("byte-snap"),u=require("magic-string");u=s(u,1);let d=require("unplugin"),f=require("@babel/parser"),p=require("@babel/traverse");p=s(p,1);var m=[`glsl`,`wgsl`,`shader`],h=[/\.[jt]sx?$/],g=[/node_modules/,/dist/],_=e=>RegExp(`^\\s*(${e.join(`|`)})\\s*$`),v=p.default.default||p.default,y=(e,t=m)=>{let n=new Set(t),r=_(t),i=[];try{v((0,f.parse)(e,{sourceType:`module`,plugins:[`typescript`,`jsx`,`decorators-legacy`],allowReturnOutsideFunction:!0}),{TaggedTemplateExpression(e){let{tag:t,quasi:r}=e.node,a=null;t.type===`Identifier`&&n.has(t.name)?a=t.name:t.type===`MemberExpression`&&t.property.type===`Identifier`&&n.has(t.property.name)&&(a=t.property.name),a&&r.expressions.length===0&&i.push({tag:a,value:r.quasis[0].value.raw,start:r.start,end:r.end})},TemplateLiteral(e){let t=e.node;if(t.expressions.length>0)return;let n=t.leadingComments||e.parentPath?.node?.leadingComments||[];for(let e of n){if(!e||e.type!==`CommentBlock`)continue;let n=e.value.match(r);if(n){i.push({tag:n[1],value:t.quasis[0].value.raw,start:t.start,end:t.end});break}}}})}catch(e){if(e.name!==`SyntaxError`)throw e}return i},b=e=>e.replace(/\r\n/g,`
2
- `).replace(/\/\*[\s\S]*?\*\//g,``).replace(/\/\/.*$/gm,``).replace(/[ \t]+/g,` `).replace(/\n{2,}/g,`
3
- `).trim(),x=(0,d.createUnplugin)((e={})=>{let t=e.tags||m,n=e.transform||b,r=(0,c.createFilter)(e.include||h,e.exclude||g),i=``,a=``;return{name:`compress-shader-literals`,enforce:`pre`,transform(o,s){if(!r(s)||!t.some(e=>o.includes(e)))return null;let c=y(o,t);if(c.length===0)return null;e.debug&&console.log(`[compress-shader-literals] ${s}: ${c.length} literal(s) — ${c.map(e=>e.tag).join(`, `)}`);let l=new u.default(o),d=!1;for(let t of c){let r=n(t.value);t.value!==r&&(l.overwrite(t.start,t.end,`\`${r}\``),d=!0),e.outputRatio&&(i+=t.value,a+=r)}return d?{code:l.toString(),map:l.generateMap({hires:!0,source:s})}:null},buildEnd(){e.outputRatio&&i&&(0,l.diff)(l.snap.text(i),l.snap.text(a)).print()}}});exports.compressShaderLiterals=x,exports.extractShaderLiterals=y,exports.minifyShader=b;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require("@rollup/pluginutils"),l=require("byte-snap"),u=require("magic-string");u=s(u,1);let d=require("unplugin"),f=require("@babel/parser"),p=require("@babel/traverse");p=s(p,1);var m=[`glsl`,`wgsl`,`shader`],h=[/\.[mc]?[jt]sx?$/],g=[/node_modules/,/dist/],_=e=>RegExp(`^\\s*(${e.join(`|`)})\\s*$`),v=/\r\n/g,y=/\/\*[\s\S]*?\*\//g,b=/\/\/.*$/gm,x=/[ \t]+/g,S=p.default.default||p.default,C=(e,t=m)=>{let n=new Set(t),r=_(t),i=[];try{S((0,f.parse)(e,{sourceType:`module`,plugins:[`typescript`,`jsx`,`decorators-legacy`],allowReturnOutsideFunction:!0}),{TaggedTemplateExpression(e){let{tag:t,quasi:r}=e.node,a=null;t.type===`Identifier`&&n.has(t.name)?a=t.name:t.type===`MemberExpression`&&t.property.type===`Identifier`&&n.has(t.property.name)&&(a=t.property.name),a&&r.expressions.length===0&&i.push({tag:a,value:r.quasis[0].value.raw,start:r.start,end:r.end})},TemplateLiteral(e){let t=e.node;if(t.expressions.length>0)return;let n=t.leadingComments||e.parentPath?.node?.leadingComments||[];for(let e of n){if(!e||e.type!==`CommentBlock`)continue;let n=e.value.match(r);if(n){i.push({tag:n[1],value:t.quasis[0].value.raw,start:t.start,end:t.end});break}}}})}catch(e){if(e.name!==`SyntaxError`)throw e}return i},w=e=>{let t=e.replace(v,`
2
+ `).replace(y,``).replace(b,``).split(`
3
+ `).map(e=>e.replace(x,` `).trim()).filter(Boolean),n=``,r=!1;for(let e of t)e.startsWith(`#`)||r?n+=(n&&!n.endsWith(`
4
+ `)?`
5
+ `:``)+e+`
6
+ `:n+=n===``||n.endsWith(`
7
+ `)?e:` `+e,r=e.endsWith(`\\`);return n.trim()},T=(0,d.createUnplugin)((e={})=>{let t=e.tags||m,n=e.transform||w,r=(0,c.createFilter)(e.include||h,e.exclude||g),i=``,a=``;return{name:`compress-shader-literals`,enforce:`pre`,transform(o,s){if(!r(s)||!t.some(e=>o.includes(e)))return null;let c=C(o,t);if(c.length===0)return null;e.debug&&console.log(`[compress-shader-literals] ${s}: ${c.length} literal(s) — ${c.map(e=>e.tag).join(`, `)}`);let l=new u.default(o),d=!1;for(let t of c){let r=n(t.value);t.value!==r&&(l.overwrite(t.start,t.end,`\`${r}\``),d=!0),e.outputRatio&&(i+=t.value,a+=r)}return d?{code:l.toString(),map:l.generateMap({hires:!0,source:s})}:null},buildEnd(){e.outputRatio&&i&&(0,l.diff)(l.snap.text(i),l.snap.text(a)).print()}}});exports.compressShaderLiterals=T,exports.extractShaderLiterals=C,exports.minifyShader=w;
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ type FilterPattern = string | RegExp | ReadonlyArray<string | RegExp> | null;
6
6
  export interface CompressShaderLiteralsOptions {
7
7
  /** Tag names / comment markers to match. Default: `['glsl', 'wgsl', 'shader']` */
8
8
  tags?: string[];
9
- /** Files to process. Default: `[/\.[jt]sx?$/]` */
9
+ /** Files to process. Default: `[/\.[mc]?[jt]sx?$/]` (the JS/TS family Babel can parse). */
10
10
  include?: FilterPattern;
11
11
  /** Files to skip. Default: `[/node_modules/, /dist/]` */
12
12
  exclude?: FilterPattern;
package/dist/index.js CHANGED
@@ -9,10 +9,10 @@ var s = [
9
9
  "glsl",
10
10
  "wgsl",
11
11
  "shader"
12
- ], c = [/\.[jt]sx?$/], l = [/node_modules/, /dist/], u = (e) => RegExp(`^\\s*(${e.join("|")})\\s*$`), d = o.default || o, f = (e, t = s) => {
12
+ ], c = [/\.[mc]?[jt]sx?$/], l = [/node_modules/, /dist/], u = (e) => RegExp(`^\\s*(${e.join("|")})\\s*$`), d = /\r\n/g, f = /\/\*[\s\S]*?\*\//g, p = /\/\/.*$/gm, m = /[ \t]+/g, h = o.default || o, g = (e, t = s) => {
13
13
  let n = new Set(t), r = u(t), i = [];
14
14
  try {
15
- d(a(e, {
15
+ h(a(e, {
16
16
  sourceType: "module",
17
17
  plugins: [
18
18
  "typescript",
@@ -53,20 +53,24 @@ var s = [
53
53
  if (e.name !== "SyntaxError") throw e;
54
54
  }
55
55
  return i;
56
- }, p = (e) => e.replace(/\r\n/g, "\n").replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/[ \t]+/g, " ").replace(/\n{2,}/g, "\n").trim(), m = i((i = {}) => {
57
- let a = i.tags || s, o = i.transform || p, u = e(i.include || c, i.exclude || l), d = "", m = "";
56
+ }, _ = (e) => {
57
+ let t = e.replace(d, "\n").replace(f, "").replace(p, "").split("\n").map((e) => e.replace(m, " ").trim()).filter(Boolean), n = "", r = !1;
58
+ for (let e of t) e.startsWith("#") || r ? n += (n && !n.endsWith("\n") ? "\n" : "") + e + "\n" : n += n === "" || n.endsWith("\n") ? e : " " + e, r = e.endsWith("\\");
59
+ return n.trim();
60
+ }, v = i((i = {}) => {
61
+ let a = i.tags || s, o = i.transform || _, u = e(i.include || c, i.exclude || l), d = "", f = "";
58
62
  return {
59
63
  name: "compress-shader-literals",
60
64
  enforce: "pre",
61
65
  transform(e, t) {
62
66
  if (!u(t) || !a.some((t) => e.includes(t))) return null;
63
- let n = f(e, a);
67
+ let n = g(e, a);
64
68
  if (n.length === 0) return null;
65
69
  i.debug && console.log(`[compress-shader-literals] ${t}: ${n.length} literal(s) — ${n.map((e) => e.tag).join(", ")}`);
66
70
  let s = new r(e), c = !1;
67
71
  for (let e of n) {
68
72
  let t = o(e.value);
69
- e.value !== t && (s.overwrite(e.start, e.end, `\`${t}\``), c = !0), i.outputRatio && (d += e.value, m += t);
73
+ e.value !== t && (s.overwrite(e.start, e.end, `\`${t}\``), c = !0), i.outputRatio && (d += e.value, f += t);
70
74
  }
71
75
  return c ? {
72
76
  code: s.toString(),
@@ -77,9 +81,9 @@ var s = [
77
81
  } : null;
78
82
  },
79
83
  buildEnd() {
80
- i.outputRatio && d && t(n.text(d), n.text(m)).print();
84
+ i.outputRatio && d && t(n.text(d), n.text(f)).print();
81
85
  }
82
86
  };
83
87
  });
84
88
  //#endregion
85
- export { m as compressShaderLiterals, f as extractShaderLiterals, p as minifyShader };
89
+ export { v as compressShaderLiterals, g as extractShaderLiterals, _ as minifyShader };
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "compress-shader-literals",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
- "description": "Size matters. Minify GLSL/WGSL shader template literals at build time universal plugin for Vite, Rollup, webpack, esbuild & more.",
5
+ "description": "Minify GLSL/WGSL shaders in your JS/TS at build time. Drop-in plugin for Vite, Rollup, webpack, esbuild, Rspack, Rolldown & Farm.",
6
6
  "keywords": [
7
7
  "shader",
8
8
  "glsl",
@@ -63,6 +63,8 @@
63
63
  "test:build": "bun run build && node tests/build-smoke.js",
64
64
  "test:validate": "cd tests && bun install && node validate.js",
65
65
  "test:e2e": "cd tests && bun install && node e2e.js",
66
+ "test:dump": "cd tests && bun install && node dump-chunks.js",
67
+ "tests:next": "cd tests && bun install && node experimental.js",
66
68
  "format": "prettier --write .",
67
69
  "format:check": "prettier --check .",
68
70
  "npm:deploy": "bash ./scripts/publish-npm.sh",
@@ -73,7 +75,7 @@
73
75
  "@babel/parser": "^8.0.0",
74
76
  "@babel/traverse": "^8.0.0",
75
77
  "@rollup/pluginutils": "^5.4.0",
76
- "byte-snap": "^1.0.6",
78
+ "byte-snap": "^1.1.0",
77
79
  "magic-string": "^0.30.21",
78
80
  "unplugin": "^3.0.0"
79
81
  },