loadTslMaterials() is the only place that imports
rendering/tsl/registry, and it does so with await import().
Because that is the sole path from the entry point to the *-tsl modules,
rolldown emits them — and the three-webgpu chunk they pull in — as a lazy
chunk instead of a static dependency of index-*.js. A WebGL session never
calls this, so it never pays for ~182 kB gzipped of the three.js node system
(issue #1679).
The nine TSL material classes deliberately keep their ordinary
extends NodeMaterial shape. A class body cannot be evaluated before an
await resolves, which makes it tempting to convert them all into
createXTSLMaterialClass() factories — but that is unnecessary here and
would push async through ~20 unit test files for no payload benefit. What
matters is that nothing reaches those modules except through this one
dynamic import; their class bodies then evaluate when the lazy chunk loads,
which is after the await.
Ordering contract: loadTslMaterials must resolve before the first
material is constructed on the WebGPU path. SceneManager.init already
satisfies this — it awaits setupRenderer() (where the install happens)
before setupPostProcessing(), which builds the first material of the whole
app. That is why buildMaterial can stay synchronous.
Synchronous consumers read the loaded registry from ./slot, which is a
zero-import leaf so the GLSL shader modules can reach it without closing a
dependency cycle — see that module's header.
The one dynamic edge above the TSL / WebGPU cone.
loadTslMaterials()is the only place that importsrendering/tsl/registry, and it does so withawait import(). Because that is the sole path from the entry point to the*-tslmodules, rolldown emits them — and thethree-webgpuchunk they pull in — as a lazy chunk instead of a static dependency ofindex-*.js. A WebGL session never calls this, so it never pays for ~182 kB gzipped of the three.js node system (issue #1679).The nine TSL material classes deliberately keep their ordinary
extends NodeMaterialshape. A class body cannot be evaluated before anawaitresolves, which makes it tempting to convert them all intocreateXTSLMaterialClass()factories — but that is unnecessary here and would pushasyncthrough ~20 unit test files for no payload benefit. What matters is that nothing reaches those modules except through this one dynamic import; their class bodies then evaluate when the lazy chunk loads, which is after the await.Ordering contract: loadTslMaterials must resolve before the first material is constructed on the WebGPU path.
SceneManager.initalready satisfies this — it awaitssetupRenderer()(where the install happens) beforesetupPostProcessing(), which builds the first material of the whole app. That is whybuildMaterialcan stay synchronous.Synchronous consumers read the loaded registry from
./slot, which is a zero-import leaf so the GLSL shader modules can reach it without closing a dependency cycle — see that module's header.