loadTslMaterials() imports rendering/tsl/registry with await import().
The small standalone prefetch entry also imports it dynamically, before
the main viewer bundle runs. Both edges keep the three-webgpu cone lazy
for WebGL, while the browser deduplicates their shared module fetch.
Rolldown emits the TSL modules and three-webgpu as lazy chunks instead
of static dependencies of index-*.js. A session that selects
WebGLRenderer at the outset never calls this, so it never pays for
~182 kB gzipped of the three.js node system (issue #1679). A WebGPU
request that later falls back may already have started the fetch.
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 normal dynamic load of the TSL / WebGPU cone.
loadTslMaterials()importsrendering/tsl/registrywithawait import(). The small standalone prefetch entry also imports it dynamically, before the main viewer bundle runs. Both edges keep thethree-webgpucone lazy for WebGL, while the browser deduplicates their shared module fetch. Rolldown emits the TSL modules andthree-webgpuas lazy chunks instead of static dependencies ofindex-*.js. A session that selectsWebGLRendererat the outset never calls this, so it never pays for ~182 kB gzipped of the three.js node system (issue #1679). A WebGPU request that later falls back may already have started the fetch.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.