feat: Obfuscate variables in plugin - #2755
Conversation
|
pkg.pr.new packages benchmark commit |
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased (max 0.01%) | ❔ Unknown |
|---|---|---|---|
| 0 | 301 | 21 | 0 |
import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):
| Test | tsdown |
|---|---|
| tgpu_init.ts | 267.87 kB ( |
| tgpu_initFromDevice.ts | 267.34 kB ( |
| tgpu_resolve.ts | 168.44 kB ( |
| tgpu_resolveWithContext.ts | 168.38 kB ( |
| tgpu_bindGroupLayout.ts | 73.79 kB ( |
| tgpu_mutableAccessor.ts | 68.51 kB ( |
| tgpu_accessor.ts | 68.51 kB ( |
| tgpu_privateVar.ts | 67.20 kB ( |
| tgpu_workgroupVar.ts | 67.20 kB ( |
| tgpu_const.ts | 66.62 kB ( |
| tgpu_lazy.ts | 66.42 kB ( |
| tgpu_fragmentFn.ts | 38.92 kB ( |
| tgpu_fn.ts | 38.86 kB ( |
| tgpu_vertexFn.ts | 38.73 kB ( |
| tgpu_computeFn.ts | 38.44 kB ( |
| tgpu_vertexLayout.ts | 27.57 kB ( |
| tgpu_comptime.ts | 15.17 kB ( |
| tgpu_unroll.ts | 1.75 kB ( |
| tgpu_slot.ts | 1.70 kB ( |
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.66, 1.25, 2.64, 4.22, 5.40, 8.20, 14.26, 17.78]
line [0.68, 1.28, 2.74, 3.87, 4.93, 7.60, 14.76, 16.02]
line [0.68, 1.37, 3.19, 4.21, 5.43, 8.88, 15.90, 16.12]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.25, 0.37, 0.49, 0.56, 0.71, 0.73, 0.89, 0.98]
line [0.19, 0.33, 0.46, 0.55, 0.73, 0.78, 1.00, 1.09]
line [0.23, 0.37, 0.49, 0.58, 0.81, 0.84, 0.99, 1.12]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.56, 1.37, 2.54, 4.34, 8.13, 17.67, 36.44, 75.08]
line [0.60, 1.35, 2.97, 4.71, 8.11, 17.02, 36.34, 74.99]
line [0.71, 1.44, 2.99, 4.57, 8.74, 18.27, 38.12, 77.63]
|
| declaredNames: string[]; | ||
| }; | ||
|
|
||
| export type Externals = Set<string>; |
There was a problem hiding this comment.
Less work swapping it for a map here than in unplugin, and it needs to become a map eventually
|
@pullfrog review |
There was a problem hiding this comment.
ℹ️ No critical issues — minor suggestions inline.
Reviewed changes
This PR introduces an EXPERIMENTAL_obfuscate option for unplugin-typegpu that renames TGSL parameters, local variables, and external identifiers to short generated names before WGSL code generation. It also adds the supporting infrastructure: changing externalNames from a Set to a Map to carry original accessor chains, adding validation that prevents obfuscation and auto-naming from being enabled together, updating metadata serialization in the Babel and unplugin factories, and adding documentation and tests.
- Added
packages/unplugin-typegpu/src/core/obfuscate.tswith a tinyest-to-tinyest AST transformer that renames identifiers. - Added
EXPERIMENTAL_obfuscateplugin option with a conflict check againstautoNamingEnabled. - Changed
ExternalsfromSet<string>toMap<string, string>so obfuscated keys can map back to original accessor chains. - Updated
packages/typegpu/src/resolutionCtx.tsto resolveundefined/Infinity/NaNexternals correctly. - Added
packages/unplugin-typegpu/test/obfuscation.test.tscovering AST-level obfuscation for variables, parameters, externals, destructuring, shadowing, and name exhaustion. - Added docs page for shader minification/obfuscation.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
Pull request overview
Adds an experimental shader obfuscation/minification pathway by obfuscating identifiers captured in unplugin-typegpu metadata, supported by updates to the tinyest transpiler externals representation and expanded test/docs coverage.
Changes:
- Introduces
EXPERIMENTAL_obfuscateoption to obfuscate parameters/locals/externals stored in plugin metadata. - Changes tinyest-for-wgsl “externals” from
Set<string>toMap<string, string>to support renaming/aliasing of external chains. - Adds/updates tests and documentation for obfuscation behavior and collision/shadowing scenarios.
Reviewed changes
Copilot reviewed 15 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/unplugin-typegpu/test/obfuscation.test.ts | New plugin + core obfuscation test coverage and option-conflict checks. |
| packages/unplugin-typegpu/src/core/obfuscate.ts | New obfuscation pass over tinyest AST + externals mapping. |
| packages/unplugin-typegpu/src/core/common.ts | Plumbs EXPERIMENTAL_obfuscate through transpilation and adds option validation. |
| packages/unplugin-typegpu/src/core/factory.ts | Updates externals serialization and validates options in unplugin factory. |
| packages/unplugin-typegpu/src/babel.ts | Updates Babel plugin option validation and externals emission. |
| packages/unplugin-typegpu/src/bun.ts | Validates options for Bun plugin entrypoint. |
| packages/typegpu/src/resolutionCtx.ts | Adjusts external lookup to distinguish “missing” vs “present-but-undefined”. |
| packages/typegpu/src/internal.ts | Exposes stringifyNode via internal exports for tests/tooling. |
| packages/typegpu/tests/tgslFn.test.ts | Adds regression coverage for std shadowing behavior. |
| packages/typegpu/tests/tgsl/wgslGenerator.test.ts | Snapshot formatting updates. |
| packages/typegpu/tests/std/boolean/not.test.ts | Snapshot formatting updates. |
| packages/typegpu/tests/mutabilityTracking.test.ts | Snapshot formatting updates. |
| packages/tinyest-for-wgsl/src/types.ts | Switches Externals to Map<string, string>. |
| packages/tinyest-for-wgsl/src/parsers.ts | Emits externals into a Map instead of a Set. |
| packages/tinyest-for-wgsl/tests/helpers.ts | New shared helpers for dual-parser tests. |
| packages/tinyest-for-wgsl/tests/parsers.test.ts | Updates expectations for externals now being a Map. |
| apps/typegpu-docs/src/content/docs/advanced/minifying-shaders.mdx | New documentation page for obfuscation/minification. |
| apps/typegpu-docs/astro.config.mjs | Adds docs nav entry for the new page. |
Suppressed comments (1)
packages/tinyest-for-wgsl/src/types.ts:15
- The
Context.externalNamesdoc still says it “Holds a set…”, butExternalsis now aMap<string, string>. Updating the comment will prevent confusion for future maintainers.
export type Context = {
/** Holds a set of all identifiers that were used in code, but were not declared in code. */
externalNames: Externals;
/** Used to signal to identifiers that they should not treat their resolution as possible external uses. */
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Important
The obfuscation name generator can emit WGSL keywords, which will produce invalid shader code once enough identifiers are present. See the inline comment on packages/unplugin-typegpu/src/core/obfuscate.ts.
Reviewed changes
- Added an
EXPERIMENTAL_obfuscateplugin option that renames parameters, variables, and captured externals to short lowercase identifiers at build time. - Changed
tinyest-for-wgslexternalNamesfromSet<string>toMap<string, string>so obfuscated AST keys can still resolve to original external access chains at runtime. - Updated
resolutionCtx.tsto resolve externals by map membership (id in externalMap) rather than by truthiness, preserving null/undefined external values. - Added
stringifyNodeto the internal TypeGPU exports for test assertions. - Added docs page and parser/unplugin tests.
ℹ️ Nitpicks
- The options reference in
apps/typegpu-docs/src/content/docs/tooling/unplugin-typegpu.mdxwas not updated to listEXPERIMENTAL_obfuscate; consider adding it there (marked experimental) to keep the two docs pages consistent.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — one prior suggestion is still outstanding.
Reviewed changes
Reviewed the two new commits on PR #2755 since the prior Pullfrog review.
- Changed
resolutionCtx.tsto useObject.prototype.hasOwnProperty.call(layer.externalMap, id)instead ofid in layer.externalMap, so externals that are present-but-null/undefinedare resolved and prototype-named keys are no longer mistaken for externals. - Added
packages/typegpu/tests/internal/metadata.test.ts(replacingmetadataNormalization.test.ts) covering metadata normalization, deferred externals for v1 and v2, variable recapture, and invalid-identifier renaming. - Added parser coverage in
packages/tinyest-for-wgsl/tests/parsers.test.tsforundefined/Infinity/NaNbeing treated as external identifiers.
ℹ️ Outstanding prior suggestion
The earlier Pullfrog suggestion to add an end-to-end test that runs tgpu.resolve on an obfuscated function remains open. It's a minor follow-up rather than a merge blocker, but worth tracking if integration coverage for obfuscated WGSL output is planned.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the incremental delta on PR #2755 since the prior Pullfrog review.
- Merged
origin/maininto the feature branch; the only net change was restoring thedualImplexport inpackages/typegpu/src/internal.ts, matchingmain.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
iwoplaza
left a comment
There was a problem hiding this comment.
Awesome! Left one suggestion
Co-authored-by: Iwo Plaza <iwoplaza@gmail.com>
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
Reviewed the incremental delta on PR #2755 since the prior pullfrog evaluation (60756dd): a pure origin/main sync with no PR-authored changes.
- Merged
origin/main(commitd5b900c), folding #2809 (Simpler ShaderGenerator lifetime) and #2824 (@typegpu/three varyings handling) into the branch. - Confirmed all PR obfuscation files (
obfuscate.ts,common.ts, tinyest parsers/types, plugin entry points, docs) are byte-identical between60756ddandHEAD, and the merge is clean (d5b900cparents = PR head + current main). - Verified the PR's
Object.hasOwn(layer.externalMap, id)external lookup inresolutionCtx.ts:205survived intact alongside #2809's constructor-timeWgslGeneratorinit. - Re-ran post-merge:
obfuscation.test.ts(24),metadata.test.ts(9), tinyest parsers (17) all pass;typegpuandunplugin-typegputypechecks pass.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Blocked by #2788
We obfuscate variables, externals and parameters. Struct props are left as-is (at least for now).
Testing:
Further work: #2783, #2786.
Also, helpers like
fullScreenTrianglewill remain as is, they are implemented in raw wgsl.