Compatibility

Does obfuscation break audio and paint worklets?

A worklet does not run in the window. It runs in its own global scope with its own furniture, and several of the objects a protection guard expects to find are simply not there. Three guards react to that in three different ways, and only one of the three does what most people would guess.

A worklet is a separate global, not just a separate file

Both the audio worklet and the CSS paint worklet are loaded the same way: you call addModule() with a URL and the browser evaluates that file in a scope built for the job. The point of the arrangement is isolation. Audio processing runs on a real-time thread that must never wait for the main thread, and paint callbacks run where layout can drive them, so neither scope gets the objects that would let you reach back into the page.

What is missing matters more than what is present. There is no document and no DOM. There is no location. There is no navigator. In the audio worklet scope there is no setTimeout or setInterval either, because a real-time audio callback is driven by the audio clock rather than by timers. What you do get is the language itself, a small amount of scope-specific API, and console.

That produces two practical consequences before any protection question comes up. The first is that the worklet file is a separate build input. If your pipeline protects the bundle your page loads and nothing else, the worklet module is going out untouched, which is easy to miss because nothing fails when it happens. The second is that worklet modules are always ES modules, so the export-name rules apply to them exactly as the ES modules article describes.

Three guards, three different behaviours

This is the part worth knowing, because the three outcomes are genuinely different and only one of them is loud. Every runtime guard starts by resolving the global the same way, preferring globalThis and falling back to a function-constructed reference, and then each one feature-tests for what it needs. What happens next depends entirely on which test it runs.

Console suppression applies. The guard reads console off the global and returns early only if it is absent. A worklet scope has a console, so the guard proceeds and replaces the same thirteen methods with empty functions: log, info, warn, error, debug, trace, dir, table, group, groupCollapsed, groupEnd, time and timeEnd. In a worklet that is not one diagnostic channel among several, it is the only one you had, because you cannot set a breakpoint on an audio thread the way you would in page code.

The debug-protection timer does nothing at all. The emitted guard is wrapped in a test for setInterval together with Date.now, and an audio worklet scope has neither timer function. The test fails, the guard installs nothing, and no error is raised. This is a well-behaved outcome rather than a bug, but it is worth being clear about what it means: in that scope the option is inert, so you are not getting the protection you enabled and you will see no sign of that either way.

The domain, browser and operating-system locks fire their failure action. This is the one to know about. The domain guard reads location.hostname and, when it cannot get a string out of it, treats the empty result as a failure rather than as an absence, running the configured failure action and returning. The browser and OS locks are shaped identically around navigator. A worklet scope has neither object, so all three take the failure branch every time the module is evaluated, on a completely legitimate load, on the machine of a customer who did nothing wrong.

The default failure action is to throw, which means a domain-locked build with a worklet in it produces a worklet that fails to initialise on every load rather than one that misbehaves occasionally. If you have ever seen an audio feature die instantly in a protected build while the rest of the page carried on, this is the first thing to check.

The members the browser calls, and the members you call

Worklets have an unusual property: you write a class, but you never call most of it. You hand the class to the browser and the browser calls into it on its own schedule. That inverts the usual renaming question, because a member that is never referenced from your own code looks unused to any tool reasoning about your code alone.

In an audio worklet you register a processor by name and implement process(), optionally exposing a static parameterDescriptors getter. In a paint worklet you register a painter by name and implement paint(), optionally exposing static inputProperties and inputArguments. Every one of those names is a contract with the browser, not an internal detail.

The registration name itself is safe. It is an ordinary string literal, and string handling preserves values even when literals are collected into a table and assembled at run time, so the name you register and the name you construct the node with still match. The member names are the exposed part. If member renaming is enabled and these are not on the reserved list, the browser looks for a method that no longer exists under that name, and you get silence rather than a build error. That is the same failure shape the member protection documentation describes for serialised data whose keys match your object shape.

The fix is the same as it is everywhere else in that family: put the contract names on the reserved list so the transform leaves them alone. It is a short list per worklet and it does not meaningfully reduce what the rest of the file gets.

The risks in the order you will actually hit them

A domain, browser or OS lock inside a worklet. Deterministic, immediate, and it looks like a broken feature rather than a triggered guard. Either exclude the worklet file from the locking configuration or accept that the guard cannot be evaluated in that scope.

Member renaming reaching a contract name. Silent at build time and specific to whichever member the browser wanted. Reserve process, paint, parameterDescriptors, inputProperties and inputArguments as applicable.

Forgetting the worklet file. Nothing breaks, which is why it survives. Confirm the file loaded by addModule() is in your input set, because it is often outside the entry point your bundler walks.

Console suppression removing your diagnostics. Not a break, a cost, and a larger one here than on the main thread.

The debug timer being inert. Not a break either, and not a cost. It is a gap, and the useful response is to stop counting that option as protection for worklet code.

The short version

Protection does not break worklets as a category, and the transform itself is rarely the problem. What causes trouble is the runtime options travelling beside it, because a worklet scope is missing the objects those guards inspect and the guards do not all treat absence the same way. Console suppression applies and costs you your only diagnostics. The debug timer quietly installs nothing. The domain, browser and OS locks read absence as failure and run their failure action on every load, which is the one that will page you.

Treat the worklet as its own build unit with its own configuration, reserve the handful of names the browser calls rather than your own code, and keep the locks on the page code where the objects they inspect actually exist.

Frequently asked questions

Does obfuscation break audio worklets?

Not by itself. The code transform produces valid JavaScript and a worklet evaluates it the same way any other module scope would. What breaks worklets is the runtime options often enabled alongside the transform, because a worklet global has no location, no navigator and no timer functions, and the guards that look for those objects do not all handle absence the same way. Check the domain, browser and OS locks first, because those treat a missing object as a failure and run their configured failure action on every legitimate load.

Why does my worklet fail immediately in a protected build?

The most common cause is a domain, browser or operating-system lock. The domain guard reads location.hostname and treats an empty result as a lock failure rather than as a scope that simply has no location, so it runs the failure action and returns. A worklet scope has no location object at all, so that branch is taken every time the module is evaluated. Because the default failure action is to throw, the worklet fails to initialise on every load rather than failing intermittently. Excluding the worklet file from the locking configuration resolves it.

Does debug protection work inside a worklet?

No, and it fails safe rather than loudly. The emitted timer guard is wrapped in a check for setInterval and Date.now before it installs anything, and an audio worklet scope provides neither. The test fails, nothing is installed, and no error is raised. The practical consequence is that the option is inert in that scope, so worklet code is not receiving the protection you enabled even though the build reported success. Treat it as a coverage gap rather than as a compatibility problem.

Will member renaming break registerProcessor or the paint callback?

The registration name is safe because it is an ordinary string literal and string handling preserves the value. The members are the exposure. Methods like process and paint, and static members like parameterDescriptors, inputProperties and inputArguments, are called by the browser rather than by your own code, so nothing in your source references them and they can be renamed as though unused. The browser then looks for a name that is no longer there and the failure is silent. Add those names to the reserved list.

Is the worklet file protected automatically with the rest of my bundle?

Only if it is in your input set, and it frequently is not. A worklet module is fetched at run time by addModule with a URL, so a bundler walking your entry point may never see it and a protection step configured against bundler output will not include it. Nothing fails when this happens, which is exactly why it persists. Check the built output directly and confirm the worklet file looks protected rather than assuming your pipeline reached it.

Should I use different protection settings for worklet code?

Yes, and treating the worklet as its own build unit is the cleanest way to do it. Keep the code transform, reserve the handful of member names the browser calls, and leave the domain, browser and OS locks off that file because the objects they inspect do not exist in the scope. Consider whether console suppression is worth it there, since console is the only diagnostic channel a worklet has and you cannot reach that thread with the tools you would normally use.

Related reading