compress-shader-literals 1.3.3 → 1.3.6

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
@@ -11,11 +11,11 @@
11
11
 
12
12
  A tiny build-time minifier for GLSL & WGSL shaders written as template literals in your JS/TS. Strips comments + whitespace — any bundler, no renaming, no toolchain, no runtime cost.
13
13
 
14
- ![Alt Preview](./assets/preview.gif)
14
+ ![Alt Preview](./assets/preview.png)
15
15
 
16
16
  ## Install
17
17
 
18
- [![NPM](https://nodei.co/npm/compress-shader-literals.png)](https://www.npmjs.com/package/compress-shader-literals)
18
+ <!-- [![NPM](https://nodei.co/npm/compress-shader-literals.png)](https://www.npmjs.com/package/compress-shader-literals) -->
19
19
 
20
20
  ```sh
21
21
  bun add -d compress-shader-literals
@@ -57,22 +57,32 @@ const frag = /* wgsl */ `
57
57
 
58
58
  **Options**
59
59
 
60
- | Option | Default | Description |
61
- | ------------- | ---------------------------- | --------------------------------------------------- |
62
- | `tags` | `['glsl', 'wgsl', 'shader']` | Tag names / comment markers to match |
63
- | `include` | `[/\.[mc]?[jt]sx?$/]` | Files to process — the JS/TS family Babel can parse |
64
- | `exclude` | `[/node_modules/, /dist/]` | Files to skipdependencies are skipped by default |
65
- | `outputRatio` | `false` | Print a bytes-saved summary after build |
66
- | `transform` | built-in `minifyShader` | Custom minifier `(shader: string) => string` |
67
- | `debug` | `false` | Log each file's discovered literals to the console |
60
+ | Option | Default | Description |
61
+ | ------------- | ---------------------------- | -------------------------------------------------------------------------- |
62
+ | `tags` | `['glsl', 'wgsl', 'shader']` | Tag names / comment markers to match |
63
+ | `scan` | `'ast'` | `'ast'` parses the file with Babel; `'loose'` matches by regex — see below |
64
+ | `include` | `[/\.[mc]?[jt]sx?$/]` | Files to processthe JS/TS family Babel can parse |
65
+ | `exclude` | `[/node_modules/, /dist/]` | Files to skip dependencies are skipped by default |
66
+ | `outputRatio` | `false` | Print a bytes-saved summary after build |
67
+ | `transform` | built-in `minifyShader` | Custom minifier `(shader: string) => string` |
68
+ | `debug` | `false` | Log each file's discovered literals to the console |
68
69
 
69
- **Programmatic API**the two core helpers are exported for tooling authors (validators, ESLint rules, CLIs), no plugin required:
70
+ **`scan: 'loose'`**for files Babel can't parse (anything that isn't plain JS/TS: Svelte components, Astro components, etc). Instead of a whole-file AST parse, it matches the same tagged/comment-prefixed literal shapes by regex. Opt in and point `include` at the files yourself — there's no whole-file syntax guarantee, so a match is only touched once its content also looks like a real shader:
70
71
 
71
72
  ```js
72
- import { extractShaderLiterals, minifyShader } from 'compress-shader-literals';
73
+ compressShaderLiterals.vite({ scan: 'loose', include: [/\.svelte$/] });
74
+ ```
75
+
76
+ **Programmatic API** — the core helpers are exported for tooling authors (validators, ESLint rules, CLIs), no plugin required:
77
+
78
+ ```js
79
+ import { extractShaderLiterals, extractShaderLiteralsLoose, minifyShader } from 'compress-shader-literals';
73
80
 
74
81
  extractShaderLiterals('const v = glsl`void main() {}`');
75
- // → [{ tag: 'glsl', value: 'void main() {}', start: 10, end: 26 }]
82
+ // → [{ tag: 'glsl', value: 'void main() {}', start: 14, end: 30 }]
83
+
84
+ extractShaderLiteralsLoose('<script>const v = glsl`void main() {}`</script>');
85
+ // → same shape, found by regex instead of a Babel parse
76
86
 
77
87
  minifyShader('// comment\nvoid main() {}'); // → 'void main() {}'
78
88
  ```
@@ -107,13 +117,13 @@ Real shaders shipped by popular libraries, run through the built-in minifier:
107
117
  | `postprocessing` | 136 | 179,705 B | 179,705 B | **0.0%** | — |
108
118
  | **Total** | 3323 | 4,976,645 B | 3,929,415 B | **21.0%** | — |
109
119
 
110
- _3323 shaders · 2477/3323 parseable shaders (GLSL + WGSL) verified valid after minify · [how this is measured](docs/stats.md) · 2026-07-09_
120
+ _3323 shaders · 2477/3323 parseable shaders (GLSL + WGSL) verified valid after minify · [how this is measured](docs/stats.md) · 2026-07-10_
111
121
 
112
122
  <!-- STATS:END -->
113
123
 
114
124
  ## How it works
115
125
 
116
- 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).
126
+ 1. Parses each matched file with Babel — `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts` by default. `scan: 'loose'` skips the parse and matches by regex instead, for files Babel can't parse.
117
127
  2. Finds tagged (`` glsl`…` ``) and comment-prefixed (`/* glsl */ \`…\``) literals, skipping any with `${…}` interpolation.
118
128
  3. Strips comments, collapses whitespace, and joins statements onto one line — keeping real newlines around `#` preprocessor directives and `\` line-continuations (which are newline-sensitive). Whitespace hugging a delimiter (`( ) { } ; ,`) is removed entirely; whitespace around operators (and `=`, to stay WGSL-generic-safe) is preserved, so adjacent tokens never merge.
119
129
  4. Rewrites the literal in place with [magic-string](https://github.com/Rich-Harris/magic-string), so sourcemaps stay intact.
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
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=/\s*([(){};,])\s*/g,C=p.default.default||p.default,w=(e,t=m)=>{let n=new Set(t),r=_(t),i=[];try{C((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},T=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(`
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=/\b(gl_FragColor|gl_Position|void\s+main|precision\s+(highp|mediump|lowp)|fn\s+main)\b/,y=/\r\n/g,b=/\/\*[\s\S]*?\*\//g,x=/\/\/.*$/gm,S=/[ \t]+/g,C=/\s*([(){};,])\s*/g,w=p.default.default||p.default,T=(e,t=m)=>{let n=new Set(t),r=_(t),i=[];try{w((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},E=(e,t)=>{for(let n=t;n<e.length;n++)if(e[n]===`\\`)n++;else if(e[n]==="`")return n;return-1},D=(e,t=m)=>{let n=t.map(e=>e.replace(/[.*+?^${}()|[\]\\]/g,`\\$&`)).join(`|`),r=RegExp(`\\b(${n})\\s*\`|/\\*\\s*(${n})\\s*\\*/\\s*\``,`g`),i=[],a;for(;a=r.exec(e);){let t=a.index+a[0].length,n=E(e,t);if(n===-1)continue;let o=e.slice(t,n);!o.includes("${")&&v.test(o)&&i.push({tag:a[1]??a[2],value:o,start:t-1,end:n+1}),r.lastIndex=n+1}return i},O=e=>{let t=e.replace(y,`
2
+ `).replace(b,``).replace(x,``).split(`
3
+ `).map(e=>e.replace(S,` `).trim()).filter(Boolean),n=``,r=!1;for(let e of t)e.startsWith(`#`)||r?n+=(n&&!n.endsWith(`
4
4
  `)?`
5
5
  `:``)+e+`
6
6
  `:n+=n===``||n.endsWith(`
7
7
  `)?e:` `+e,r=e.endsWith(`\\`);return r=!1,n=n.split(`
8
- `).map(e=>{let t=e.startsWith(`#`)||r?e:e.replace(S,`$1`);return r=e.endsWith(`\\`),t}).join(`
9
- `),n.trim()},E=(0,d.createUnplugin)((e={})=>{let t=e.tags||m,n=e.transform||T,r=(0,c.createFilter)(e.include||h,e.exclude||g),i=``,a=``,o=0;return{name:`compress-shader-literals`,enforce:`pre`,transform(s,c){if(!r(c)||!t.some(e=>s.includes(e)))return null;let l=w(s,t);if(l.length===0)return null;e.debug&&console.log(`[compress-shader-literals] ${c}: ${l.length} literal(s) — ${l.map(e=>e.tag).join(`, `)}`);let d=new u.default(s),f=!1;for(let t of l){let r=n(t.value);t.value!==r&&(d.overwrite(t.start,t.end,`\`${r}\``),f=!0),e.outputRatio&&(i+=t.value,a+=r,o++)}return f?{code:d.toString(),map:d.generateMap({hires:!0,source:c})}:null},buildEnd(){if(e.outputRatio&&i){let e=`compress-shader-literals: ${o} shader literal${o===1?``:`s`}`;(0,l.diff)(l.snap.text(i),l.snap.text(a)).print(e)}}}});exports.compressShaderLiterals=E,exports.extractShaderLiterals=w,exports.minifyShader=T;
8
+ `).map(e=>{let t=e.startsWith(`#`)||r?e:e.replace(C,`$1`);return r=e.endsWith(`\\`),t}).join(`
9
+ `),n.trim()},k=(0,d.createUnplugin)((e={})=>{let t=e.tags||m,n=e.transform||O,r=e.scan===`loose`?D:T,i=(0,c.createFilter)(e.include||h,e.exclude||g),a=``,o=``,s=0;return{name:`compress-shader-literals`,enforce:`pre`,transform(c,l){if(!i(l)||!t.some(e=>c.includes(e)))return null;let d=r(c,t);if(d.length===0)return null;e.debug&&console.log(`[compress-shader-literals] ${l}: ${d.length} literal(s) — ${d.map(e=>e.tag).join(`, `)}`);let f=new u.default(c),p=!1;for(let t of d){let r=n(t.value);t.value!==r&&(f.overwrite(t.start,t.end,`\`${r}\``),p=!0),e.outputRatio&&(a+=t.value,o+=r,s++)}return p?{code:f.toString(),map:f.generateMap({hires:!0,source:l})}:null},buildEnd(){if(e.outputRatio&&a){let e=`compress-shader-literals: ${s} shader literal${s===1?``:`s`}`;(0,l.diff)(l.snap.text(a),l.snap.text(o)).print(e)}}}});exports.compressShaderLiterals=k,exports.extractShaderLiterals=T,exports.minifyShader=O;
package/dist/index.d.ts CHANGED
@@ -6,6 +6,14 @@ 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
+ /**
10
+ * Extraction method. `'ast'` parses the whole file with Babel (JS/TS only).
11
+ * `'loose'` finds the same tagged/comment-prefixed literal shapes by regex
12
+ * instead, for files Babel can't parse — point `include` at them yourself.
13
+ * No whole-file parse means no syntax guarantee; only a match confirmed by a
14
+ * shader-content check is touched. Default: `'ast'`
15
+ */
16
+ scan?: 'ast' | 'loose';
9
17
  /** Files to process. Default: `[/\.[mc]?[jt]sx?$/]` (the JS/TS family Babel can parse). */
10
18
  include?: FilterPattern;
11
19
  /** Files to skip. Default: `[/node_modules/, /dist/]` */
@@ -37,5 +45,8 @@ export declare const compressShaderLiterals: UnpluginInstance<CompressShaderLite
37
45
  /** Find tagged (`glsl\`...\``) and comment-prefixed (`/* wgsl *\/ \`...\``) shader literals in source. */
38
46
  export declare function extractShaderLiterals(code: string, tags?: string[]): ShaderLiteral[];
39
47
 
48
+ /** Same as `extractShaderLiterals`, but by regex instead of a Babel parse — for source Babel can't parse. */
49
+ export declare function extractShaderLiteralsLoose(code: string, tags?: string[]): ShaderLiteral[];
50
+
40
51
  /** Strip comments and collapse whitespace in a shader source string. */
41
52
  export declare function minifyShader(src: string): string;
package/dist/index.js CHANGED
@@ -9,10 +9,10 @@ var s = [
9
9
  "glsl",
10
10
  "wgsl",
11
11
  "shader"
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 = /\s*([(){};,])\s*/g, g = o.default || o, _ = (e, t = s) => {
12
+ ], c = [/\.[mc]?[jt]sx?$/], l = [/node_modules/, /dist/], u = (e) => RegExp(`^\\s*(${e.join("|")})\\s*$`), d = /\b(gl_FragColor|gl_Position|void\s+main|precision\s+(highp|mediump|lowp)|fn\s+main)\b/, f = /\r\n/g, p = /\/\*[\s\S]*?\*\//g, m = /\/\/.*$/gm, h = /[ \t]+/g, g = /\s*([(){};,])\s*/g, _ = o.default || o, v = (e, t = s) => {
13
13
  let n = new Set(t), r = u(t), i = [];
14
14
  try {
15
- g(a(e, {
15
+ _(a(e, {
16
16
  sourceType: "module",
17
17
  plugins: [
18
18
  "typescript",
@@ -53,27 +53,45 @@ var s = [
53
53
  if (e.name !== "SyntaxError") throw e;
54
54
  }
55
55
  return i;
56
- }, v = (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;
56
+ }, y = (e, t) => {
57
+ for (let n = t; n < e.length; n++) if (e[n] === "\\") n++;
58
+ else if (e[n] === "`") return n;
59
+ return -1;
60
+ }, b = (e, t = s) => {
61
+ let n = t.map((e) => e.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|"), r = RegExp(`\\b(${n})\\s*\`|/\\*\\s*(${n})\\s*\\*/\\s*\``, "g"), i = [], a;
62
+ for (; a = r.exec(e);) {
63
+ let t = a.index + a[0].length, n = y(e, t);
64
+ if (n === -1) continue;
65
+ let o = e.slice(t, n);
66
+ !o.includes("${") && d.test(o) && i.push({
67
+ tag: a[1] ?? a[2],
68
+ value: o,
69
+ start: t - 1,
70
+ end: n + 1
71
+ }), r.lastIndex = n + 1;
72
+ }
73
+ return i;
74
+ }, x = (e) => {
75
+ let t = e.replace(f, "\n").replace(p, "").replace(m, "").split("\n").map((e) => e.replace(h, " ").trim()).filter(Boolean), n = "", r = !1;
58
76
  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
77
  return r = !1, n = n.split("\n").map((e) => {
60
- let t = e.startsWith("#") || r ? e : e.replace(h, "$1");
78
+ let t = e.startsWith("#") || r ? e : e.replace(g, "$1");
61
79
  return r = e.endsWith("\\"), t;
62
80
  }).join("\n"), n.trim();
63
- }, y = i((i = {}) => {
64
- let a = i.tags || s, o = i.transform || v, u = e(i.include || c, i.exclude || l), d = "", f = "", p = 0;
81
+ }, S = i((i = {}) => {
82
+ let a = i.tags || s, o = i.transform || x, u = i.scan === "loose" ? b : v, d = e(i.include || c, i.exclude || l), f = "", p = "", m = 0;
65
83
  return {
66
84
  name: "compress-shader-literals",
67
85
  enforce: "pre",
68
86
  transform(e, t) {
69
- if (!u(t) || !a.some((t) => e.includes(t))) return null;
70
- let n = _(e, a);
87
+ if (!d(t) || !a.some((t) => e.includes(t))) return null;
88
+ let n = u(e, a);
71
89
  if (n.length === 0) return null;
72
90
  i.debug && console.log(`[compress-shader-literals] ${t}: ${n.length} literal(s) — ${n.map((e) => e.tag).join(", ")}`);
73
91
  let s = new r(e), c = !1;
74
92
  for (let e of n) {
75
93
  let t = o(e.value);
76
- e.value !== t && (s.overwrite(e.start, e.end, `\`${t}\``), c = !0), i.outputRatio && (d += e.value, f += t, p++);
94
+ e.value !== t && (s.overwrite(e.start, e.end, `\`${t}\``), c = !0), i.outputRatio && (f += e.value, p += t, m++);
77
95
  }
78
96
  return c ? {
79
97
  code: s.toString(),
@@ -84,12 +102,12 @@ var s = [
84
102
  } : null;
85
103
  },
86
104
  buildEnd() {
87
- if (i.outputRatio && d) {
88
- let e = `compress-shader-literals: ${p} shader literal${p === 1 ? "" : "s"}`;
89
- t(n.text(d), n.text(f)).print(e);
105
+ if (i.outputRatio && f) {
106
+ let e = `compress-shader-literals: ${m} shader literal${m === 1 ? "" : "s"}`;
107
+ t(n.text(f), n.text(p)).print(e);
90
108
  }
91
109
  }
92
110
  };
93
111
  });
94
112
  //#endregion
95
- export { y as compressShaderLiterals, _ as extractShaderLiterals, v as minifyShader };
113
+ export { S as compressShaderLiterals, v as extractShaderLiterals, x as minifyShader };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compress-shader-literals",
3
- "version": "1.3.3",
3
+ "version": "1.3.6",
4
4
  "type": "module",
5
5
  "description": "✂️ Strip comments & whitespace from GLSL/WGSL shader literals in your JS/TS at build time — any bundler, no toolchain, no runtime cost.",
6
6
  "main": "./dist/index.cjs",