-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable tree shaking #10
Comments
Due to the sync nature of The main problem of this approach is that useEffect(() => {
ref.current.then((instance) => {
// access here in your instance
});
}, [ref.current]); and i want to avoid this. So a solution could be to dynamic import the required Babylon modules before the rendering process started by secondary Reactylon render and then serve these modules to How can i dynamic import the required modules if i don't know what kind of Babylon entities will be generated? Traversing the Fiber node generated by primary React reconciler and getting the proper tag names (JSX intrinsic elements). In this way i can get the right import from the lookup table previously created and then dynamic import the specific module. |
The solution provided is not entirely correct because the root Fiber node does not encompass all components of the tree. For example, in the following scenario, only one between the box and the sphere will be included: condition ? <box /> : <sphere /> |
Perhaps do not allow users to write syntax like that, and always render both, but have the condition dictate which is currently used by the scene? WebGPU WGSL operates in a similar manner where conditional texture look-ups are not allowed since that would alter the structure and flow of instructions sent to the GPU. Alternatively, could you perhaps use react suspense?
|
If we always rendered both, they would both belong to the Fiber root, and consequently, the secondary rendering process would start for each of them. Maybe the rendering process could be skipped (e.g. by a prop) but I think the developer experience would suffer (imagining to replace the conditional rendering with a specific prop, it would be a mess finding the prop in a nested component!). Regarding Suspense, it could be a great idea. However, I don't think it is possible with |
I guess I don't know enough to grasp how it's really any different; React dropdown components use show/hide props all the time. But also, could you maybe have the transpiler do that for you while keeping the syntax sugar of I thought React Suspense allows one you to async import actual code modules only when you need them thereby allowing you to cut down on the bundle size. |
This is true, but only for a single render! When we change the value of
for dynamic module imports is still a valid option. Where does the problem lie? Updating the state from the secondary renderer won't trigger an update in the primary renderer because the renderers are independent by design. Therefore, if the update in the secondary renderer does not propagate to the primary renderer (into the Possible alternatives:
|
This would be interesting, by any means if you can provide a react context-like interface to both renderers then that would be an elegant solution. |
As of now, you can share context between two renderers using the Context Bridge (https://github.com/simonedevit/reactylon/blob/main/packages/library/src/core/Scene.tsx#L142) so implementing the solution it shouldn't take long. I’m wondering whether this approach is worth it, as it introduces the overhead of re-rendering every time the state changes. For instance, if you change the color of a material, there’s no need to trigger the entire primary renderer because you don't need to dynamic import a different module. This extra cost might also explain why frameworks like |
I've been thinking about this a little and it's occurred to me that dynamically injecting new babylon.js mesh materials that each likely use their own textures would cause a stutter in the rendering frame rate, so I personally wouldn't do this for any game-like application. That being said, here's the results of a claude chat that I used to help visualize two approaches I've been considering: 1. Module Preloading with Chunking: const Box = React.lazy(() => import('./chunks/Box'));
const Sphere = React.lazy(() => import('./chunks/Sphere'));
function Scene({ condition }) {
useEffect(() => {
// Preload alternate component
const chunk = condition ? './chunks/Sphere' : './chunks/Box';
import(chunk);
}, []);
return (
<Suspense fallback={<LoadingMesh />}>
{condition ? <Box /> : <Sphere />}
</Suspense>
);
} Key characteristics:
2. State-Based Module Registry: const moduleRegistry = new Map();
function registerModule(key, importPath) {
if (!moduleRegistry.has(key)) {
moduleRegistry.set(key, import(importPath));
}
return moduleRegistry.get(key);
}
function Scene({ condition }) {
const [BoxModule, setBoxModule] = useState(null);
const [SphereModule, setSphereModule] = useState(null);
useEffect(() => {
// Register and cache both modules
registerModule('box', '@babylonjs/core/Meshes/box')
.then(module => setBoxModule(module));
registerModule('sphere', '@babylonjs/core/Meshes/sphere')
.then(module => setSphereModule(module));
}, []);
return condition
? BoxModule && <BoxModule.Box />
: SphereModule && <SphereModule.Sphere />;
} Key characteristics:
Main Differences:
Example of Combined Benefits: const moduleRegistry = new Map();
const LazyComponent = React.lazy(() => {
const modulePromise = registerModule('component', './path');
return modulePromise.then(module => ({
default: props => <module.Component {...props} />
}));
});
function Scene() {
// Get benefits of both approaches
return (
<Suspense fallback={<Loading />}>
<LazyComponent />
</Suspense>
);
} To my knowledge there's no real reason we can't use react suspense with pure js modules - is this assumption wrong? EDIT: I just realized you more or less have already refuted the above approaches in your 2nd comment of this issue:
Unless you meant specifically not wanting developers to use the react refs api. ===============
|
As today tree shaking is not working because all packages are importing whole Babylon modules. Let's dive in:
1. Import a well known module
Import the module directly from index.js Babylon entry point (e.g
import { Scene } from '@babylonjs/core'
)This would be the right approach if Babylon was tree shakeable but it isn't due to side effects (https://doc.babylonjs.com/setup/frameworkPackages/es6Support#side-effects) and retro compatibility.
Solution: import a module from specific path (e.g.
import { Scene } from '@babylonjs/core/scene'
)Be careful: Babylon.js modules are not always entirely self-contained, and one module may depend on others so understanding the module dependencies is key.
2. Import an unknown module
Import all from Babylon module entry point (e.g.
import * as BabylonCore from '@babylonjs/core'
)This wouldn't be the right approach also if Babylon was tree shakeable because you are importing all modules exported from index.js by the way. The reason why it has been used this approach is that you don't know the module name (and his import path) until the execution time when the Babylon entity is created on demand by custom React reconciler.
Solution:
Pre-Execution Phase: during the props generation phase, build a map that associates each Babylon entity with its corresponding import path. This map will serve as a lookup table for efficient dynamic imports later during execution.
Execution Phase: when the application is executing and you need to dynamically import a Babylon entity, use the map generated in the props generation phase. Look up the import path associated with the requested entity and use dynamic import to load the required module at runtime.
Be careful: it can introduce complexity and potential runtime overhead. Is the deepest exported module a safe warranty to avoid to import a file containing side effects? Handle corner cases.
The text was updated successfully, but these errors were encountered: