Skip to content

Conversation

hotzenklotz
Copy link
Member

@hotzenklotz hotzenklotz commented Jul 17, 2025

This PR updates the ThreeJs imports for smaller bundle size, better style points and maybe improved typing(?). The main change is to replace all the very general import * as THREE from "three" with more specific import, e.g. import {WebGLRenderer, Vector3} from "three".

In some files, the updated import lead to name conflicts for Vector3 from libs/constants and three, so renamed the Three.Vector3 as ThreeVector3 in these cases.

The ThreeJS yarn installation comes with prebuild "binaries" (see ./node_moudles/three/build/three.module.js) and the source files. I configure webpack to use the source files for bundling which resulted ~300kb smaller bundles (and likely less parsing time by browser).

Screenshot 2025-07-17 at 17 26 58 Screenshot 2025-07-17 at 17 27 12

Steps to test:

  • Linter, Typechecker etc should be enough

Issues:

  • fixes #Nada

(Please delete unneeded items, merge only when none are left open)

  • Added changelog entry (create a $PR_NUMBER.md file in unreleased_changes or use ./tools/create-changelog-entry.py)
  • Added migration guide entry if applicable (edit the same file as for the changelog)
  • Updated documentation if applicable
  • Adapted wk-libs python client if relevant API parts change
  • Removed dev-only changes like prints and application.conf edits
  • Considered common edge cases
  • Needs datastore update after deployment

Copy link
Contributor

coderabbitai bot commented Jul 17, 2025

📝 Walkthrough

"""

Walkthrough

This change refactors all imports of the "three" library throughout the codebase, replacing broad namespace imports with selective named imports of only the required classes, types, and constants. Corresponding type annotations and references are updated to use the imported names directly. No logic or control flow is altered.

Changes

Files/Groups Change Summary
frontend/javascripts/libs/UpdatableTexture.ts, .../compute_bvh_async.ts, .../cuckoo/*.ts, .../order_points_with_mst.ts, .../parse_stl_buffer.ts, .../stl_exporter.ts, .../trackball_controls.ts, .../visibility_aware_raycaster.ts, .../window.ts Refactored imports from full "three" namespace to selective named imports; updated all type annotations and usages accordingly.
frontend/javascripts/libs/mjs.ts Added a function overload for divide3 to support ThreeVector3 type from "three".
frontend/javascripts/test/**/* Updated test imports and references to use selective named imports from "three" instead of the namespace.
frontend/javascripts/viewer/controller/*, .../geometries/*, .../model/*, .../view/* Systematic replacement of namespace imports with named imports; updated all type annotations and instantiations.
frontend/javascripts/viewer/geometries/materials/*.ts Updated imports and type annotations for shader/material classes to use named imports from "three".
frontend/javascripts/viewer/model/bucket_data_handling/* Updated imports and type annotations for texture, color, and mesh handling to use named imports.
frontend/javascripts/viewer/model/helpers/rotation_helpers.ts Updated function signatures and type annotations to use named imports.
frontend/javascripts/viewer/model/reducers/flycam_reducer.ts Updated import style and instantiations to use named imports instead of namespace.
frontend/javascripts/viewer/model/sagas/meshes/common_mesh_saga.ts Updated type annotations to use named imports instead of namespace.
frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts Updated import style and usage of MathUtils and Matrix4 to named imports.
frontend/javascripts/viewer/view/arbitrary_view.ts Refactored imports to named imports; added reusable rotation matrix constant; updated type annotations.
webpack.config.js Added an alias for "three" to resolve to the source file in node_modules; added trailing blank lines.

Estimated code review effort

2 (~20 minutes)

Possibly related PRs

Suggested labels

enhancement

Poem

🐇
A hop, a skip, imports anew,
No more THREE as a namespace crew!
Named imports now, so crisp and light,
Types and classes shining bright.
Refactored code, so clean and clear—
A rabbit’s cheer for changes here!
🥕
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84d269c and 605998e.

📒 Files selected for processing (1)
  • webpack.config.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • webpack.config.js
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-smoketest-push
  • GitHub Check: frontend-tests
  • GitHub Check: backend-tests
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@hotzenklotz hotzenklotz self-assigned this Jul 17, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🔭 Outside diff range comments (2)
frontend/javascripts/viewer/controller/renderer.ts (1)

23-41: getRenderer() fallback returns plain {} – will explode on first use

When no #render-canvas element is found the ternary expression yields {} which is then cast to WebGLRenderer. Any subsequent call such as renderer.dispose() will throw.

-  renderer = (
-    renderCanvasElement != null
-      ? // Create a WebGL2 renderer
-        new WebGLRenderer({ … })
-      : {}
-  ) as WebGLRenderer;
+  if (renderCanvasElement == null) {
+    throw new Error("#render-canvas element not found – renderer cannot be created.");
+  }
+
+  renderer = new WebGLRenderer({
+    canvas: renderCanvasElement,
+    preserveDrawingBuffer: true,
+    antialias: Store.getState().userConfiguration.antialiasRendering,
+  });

Fail fast or return null and let callers handle it – either is safer than a masquerading plain object.

frontend/javascripts/viewer/geometries/cube.ts (1)

71-81: Material instances are leaked on every highlight toggle

getLineMaterial() creates a new LineBasicMaterial each call, and setIsHighlighted replaces the material without disposing the old one. This steadily increases GPU memory.

+disposeMaterial(mat: LineBasicMaterial) {
+  mat.dispose();
+}

setIsHighlighted(highlighted: boolean) {
  if (highlighted === this.isHighlighted) {
    return;
  }

  this.isHighlighted = highlighted;
-  this.getMeshes().forEach((mesh) => {
-    mesh.material = this.getLineMaterial();
-  });
+  this.getMeshes().forEach((mesh) => {
+    this.disposeMaterial(mesh.material as LineBasicMaterial);
+    mesh.material = this.getLineMaterial();
+  });
   app.vent.emit("rerender");
}

Consider caching one light & one dark material instead of re-creating every time.

Also applies to: 180-184

🧹 Nitpick comments (25)
frontend/javascripts/viewer/model/sagas/skeletontracing_saga.ts (1)

97-109: Minor mutability nitpick in matrix handling.

Matrix4.invert() mutates viewportRotationMatrix in-place; if the original matrix will ever be re-used later, a .clone().invert() would be safer.

-const viewportRotationMatrixInverted = viewportRotationMatrix.invert();
+const viewportRotationMatrixInverted = viewportRotationMatrix.clone().invert();

Currently the variable is not re-used, so the change is optional.

frontend/javascripts/libs/order_points_with_mst.ts (1)

66-94: Potential O(N²) scalability bottleneck.

computeMST builds every edge (≃ N²/2) and sorts them – fine for a few hundred points but quadratic for large data sets.

If point sets can grow to thousands, consider a spatial data structure (e.g., KD-tree) or Prim’s algorithm with a binary heap to get O(N²) ➝ O(E log N).

No change required if point counts stay small.

frontend/javascripts/viewer/model/bucket_data_handling/bucket.ts (1)

101-103: Uninitialised visualizationColor can be avoided.

The field triggers the TS 2564 suppression comment. Initialise it once in the constructor to drop the suppression and avoid undefined reads.

 constructor(
   elementClass: ElementClass,
   zoomedAddress: BucketAddress,
@@
   this.data = null;
+  this.visualizationColor = new Color(0x000000); // default black
frontend/javascripts/viewer/controller/combinations/skeleton_handlers.ts (3)

4-4: Consider a less ambiguous alias for the runtime Vector3

Vector3 is imported twice – once as type Vector3 (viewer constants) and once as the runtime Vector3 class from ThreeJS (aliased here as ThreeVector3).
The current alias works but can be confusing when scanning the file. Renaming the runtime import to something like ThreeJsVector3 (or the type alias to Vec3) would remove the cognitive overhead.

-import { Euler, Matrix4, Scene, Vector3 as ThreeVector3 } from "three";
+import { Euler, Matrix4, Scene, Vector3 as ThreeJsVector3 } from "three";

Follow-up: adjust the two occurrences (movementVector, new ThreeVector3()) below.


173-175: Shared mutable helper instances – safe but fragile

flycamRotationEuler, flycamRotationMatrix, and movementVector are reused across calls to moveNode.
While this avoids repeated allocations, the objects are mutated during the call. If moveNode is ever called re-entrantly (e.g. via async callbacks or event batching) the helpers will hold intermediate state and yield incorrect results.

If absolute performance is not critical here, instantiating the helpers inside the function keeps the implementation side-effect free.


428-430: Potential allocation hotspot

maybeGetNodeIdFromPosition creates a new Scene on every invocation. For rapid mouse-move-driven picking this can add measurable GC pressure.

If the scene contents are stateless between calls, consider pooling / reusing the scene object and only re-attaching the freshly built picking node:

-const pickingScene = new Scene();
-pickingScene.add(pickingNode);
+const pickingScene = _pickingScene ?? ( _pickingScene = new Scene() );
+pickingScene.clear();           // remove previous children
+pickingScene.add(pickingNode);

(Not critical – just something to watch for during profiling.)

frontend/javascripts/viewer/api/api_latest.ts (2)

1420-1432: Reduce per-frame allocations in tween

Inside the centerPositionAnimated tween the code allocates fresh Quaternion and Euler instances on every frame. For long animations this becomes a hot path.

A tiny optimisation is to hoist the scratch objects outside the onUpdate callback and just .copy() / .set() them:

-const interpolatedQuaternion = new Quaternion().slerpQuaternions(
-  startQuaternion,
-  endQuaternion,
-  t,
-);
-const interpolatedEuler = new Euler().setFromQuaternion(interpolatedQuaternion, "XYZ");
+interpolatedQuaternion.slerpQuaternions(startQuaternion, endQuaternion, t);
+interpolatedEuler.setFromQuaternion(interpolatedQuaternion, "XYZ");

You’d declare:

const interpolatedQuaternion = new Quaternion();
const interpolatedEuler = new Euler();

once before the tween is constructed.

This keeps GC pressure minimal without affecting readability.


1458-1464: Hoist scratch Quaternion / Euler objects (follow-up to previous comment)

Same allocation comment as above – these lines can reuse the cached helpers proposed earlier.

frontend/javascripts/test/model/accessors/view_mode_accessors.spec.ts (1)

233-236: Avoid per-iteration heap churn by re-using helper vectors

Inside the nested loops this line chain allocates three new ThreeVector3 instances for every offset-rotation combination. In tight test suites that’s fine, but in production code this pattern would create a lot of garbage.

-const rotatedAndScaledOffset = new ThreeVector3(offset[0], offset[1], 0)
-  .applyEuler(new Euler(...rotationInRadian, "ZYX"))
-  .multiply(new ThreeVector3(...scaleFactor))
-  .toArray();
+static _tmpVec = new ThreeVector3();
+static _tmpScale = new ThreeVector3();
+const rotatedAndScaledOffset = _tmpVec.set(offset[0], offset[1], 0)
+  .applyEuler(new Euler(...rotationInRadian, "ZYX"))
+  .multiply(_tmpScale.set(...scaleFactor))
+  .toArray();

Purely optional here (tests), but worth keeping in mind for hot code paths.

frontend/javascripts/libs/stl_exporter.ts (1)

37-48: Update error message to match new import style

The runtime check still complains about “THREE.BufferGeometry” although the namespace import was removed. Minor wording tweak keeps messages consistent.

-throw new Error("STLExporter: Geometry is not of type THREE.BufferGeometry.");
+throw new Error("STLExporter: Geometry is not of type BufferGeometry.");
frontend/javascripts/viewer/model/bucket_data_handling/layer_rendering_manager.ts (1)

11-12: Minor: prefer value-less import when the symbol is types-only

DataTexture is imported with type—good.
Double-check no runtime usage later; otherwise drop the type modifier.

frontend/javascripts/viewer/view/rendering_utils.ts (1)

66-86: Generic helper mutates its argument silently

adaptCameraToCurrentClippingDistance clones the camera but re-assigns it to the parameter, hiding side-effect from callers.

Not critical, but returning a new variable (const cloned = camera.clone() as T; … return cloned;) clarifies intent.

frontend/javascripts/test/api/api_skeleton_latest.spec.ts (1)

25-29: Tiny optimisation: use map for vector conversion

-const toRadian = (arr: Vector3): Vector3 => [
-  MathUtils.degToRad(arr[0]),
-  MathUtils.degToRad(arr[1]),
-  MathUtils.degToRad(arr[2]),
-];
+const toRadian = (arr: Vector3): Vector3 => arr.map(MathUtils.degToRad) as Vector3;

Reduces repetition; no functional change.

frontend/javascripts/viewer/view/arbitrary_view.ts (1)

160-171: Avoid per-frame heap allocations in hot loop

new Matrix4() is created twice for every render pass; in typical scenes that is every animation frame and quickly shows up in the GC profile.

-const rotY = new Matrix4().makeRotationY(Math.PI);
-
-camera.matrix.multiply(rotY);
-const translate = new Matrix4().makeTranslation(...this.cameraPosition);
-camera.matrix.multiply(translate);
+// Declare once at module scope or as private fields
+this.tmpRotY.makeRotationY(Math.PI);
+this.tmpTranslation.makeTranslation(...this.cameraPosition);
+camera.matrix.multiply(this.tmpRotY);
+camera.matrix.multiply(this.tmpTranslation);

Small change → measurable frame-time stability on mid-tier devices.

frontend/javascripts/viewer/geometries/arbitrary_plane.ts (1)

64-66: Cache the temporary Matrix4 to reduce GC churn

updateMesh executes every time the plane is re-rendered and instantiates two fresh Matrix4s. Re-use one instance instead:

-const meshMatrix = new Matrix4();
-
-mesh.matrix.multiply(new Matrix4().makeRotationY(Math.PI));
+this.tmpMatrix.identity();
+
+this.tmpMatrix.makeRotationY(Math.PI);
+mesh.matrix.multiply(this.tmpMatrix);

Minor, but this code is hit extremely often during navigation.

Also applies to: 85-86

frontend/javascripts/libs/trackball_controls.ts (1)

580-583: Restore constructor after prototype overwrite

After Object.create(EventDispatcher.prototype) the constructor property now points to EventDispatcher, which breaks instanceof checks and class-name based debugging.

 TrackballControls.prototype = Object.create(EventDispatcher.prototype);
+TrackballControls.prototype.constructor = TrackballControls;
frontend/javascripts/viewer/model/bucket_data_handling/data_cube.ts (1)

1077-1097: Consider object re-use in checkLineIntersection

Ray, Raycaster, and two Vector3s are allocated on every call. During flood-fill this may be executed thousands of times and becomes allocation-heavy. A tiny object pool or module-level singletons would cut the pressure without affecting readability.

Not blocking, but worth a follow-up if flood-fill is a performance hotspot.

frontend/javascripts/viewer/geometries/materials/edge_shader.ts (1)

17-18: material should be declared with definite-assignment to satisfy strictPropertyInitialization

-  material: RawShaderMaterial;
+  material!: RawShaderMaterial;

Avoids the need to disable strict in tsconfig and silences the initialisation warning.

frontend/javascripts/viewer/view/plane_view.ts (1)

86-94: Consider reusing the same Vector3 instances for the static up/lookAt vectors

These allocations happen once per view, so the impact is tiny, but caching constants avoids unnecessary GC churn:

- this.cameras[OrthoViews.PLANE_XY].up = new ThreeVector3(0, -1, 0);
-
- this.cameras[OrthoViews.TDView].up = new ThreeVector3(0, 0, -1);
+const UP_NEG_Y = new ThreeVector3(0, -1, 0);
+const UP_NEG_Z = new ThreeVector3(0, 0, -1);
+this.cameras[OrthoViews.PLANE_XY].up = UP_NEG_Y;
+
+this.cameras[OrthoViews.TDView].up = UP_NEG_Z;

Pure micro-optimisation—feel free to skip if you prefer current readability.

frontend/javascripts/viewer/geometries/crosshair.ts (1)

59-62: Avoid per-frame object allocations inside update()

update() is executed every render tick but allocates three fresh objects (Matrix4, Matrix4, Vector3) each call, creating avoidable GC pressure. Cache these as private fields and reuse them:

-    mesh.matrix.multiply(new Matrix4().makeRotationY(Math.PI));
-    mesh.matrix.multiply(new Matrix4().makeTranslation(0, 0, 0.5));
-    mesh.matrix.scale(new Vector3(this.scale, this.scale, this.scale));
+    // tmp matrices/vectors are declared once on the class (e.g. this.tmpRotY, this.tmpTranslate, this.tmpScale)
+    this.tmpRotY.makeRotationY(Math.PI);
+    this.tmpTranslate.makeTranslation(0, 0, 0.5);
+    this.tmpScale.set(this.scale, this.scale, this.scale);
+
+    mesh.matrix.multiply(this.tmpRotY);
+    mesh.matrix.multiply(this.tmpTranslate);
+    mesh.matrix.scale(this.tmpScale);

This cuts three allocations per frame.

frontend/javascripts/viewer/controller/segment_mesh_controller.ts (2)

36-37: Duplicate Mesh.prototype.raycast patch

Mesh.prototype.raycast = acceleratedRaycast is already set once in scene_controller.ts.
Keeping the patch in two places is harmless but noisy and risks diverging behaviour if changed later. Centralise it (e.g. a three-setup.ts) and import once.


213-216: Unnecessary allocation in _.get default

_.get(obj, path, new Group()) constructs the Group even when the value already exists.

-      new Group(),
+      undefined,

and afterwards:

let targetGroup = _.get(this.meshesGroupsPerSegmentId, keys) as Group | undefined;
if (!targetGroup) {
  targetGroup = new Group();
  _.set(this.meshesGroupsPerSegmentId, keys, targetGroup);
}

Avoids thousands of throw-away Group instances during bulk mesh loads.

frontend/javascripts/viewer/geometries/skeleton.ts (3)

40-42: Narrow mesh typing to the concrete ThreeJS classes

mesh is always created via either new Points or new LineSegments.
Keeping it as Object3D loses all compile-time safety (e.g. access to .geometry, .material, .isPoints, …).

Consider tightening the type:

-type Buffer = {
+type Buffer = {
   capacity: number;
   nextIndex: number;
   geometry: BufferGeometryWithBufferAttributes;
-  mesh: Object3D;
+  mesh: Points | LineSegments;
 };

If you’d like to stay fully generic, introduce <T extends Object3D> and thread that through Buffer / BufferCollection instead.
This is purely a typing improvement – no runtime impact.


74-76: Return a concrete class from buildMesh

Likewise, NodeBufferHelperType.buildMesh can return Points instead of Object3D:

-  buildMesh(geometry: BufferGeometry, material: RawShaderMaterial): Object3D {
-    return new Points(geometry, material);
-  },
+  buildMesh(geometry: BufferGeometry, material: RawShaderMaterial): Points {
+    return new Points(geometry, material);
+  },

Same for the edge helper (LineSegments). This cascades nicely into the stricter Buffer typing suggested above.


498-501: getAllNodes can expose Points[] for better type safety

buffer.mesh is guaranteed to be Points for the node collection, so exposing Points[] is safe and helps callers avoid manual casts:

-getAllNodes(): Object3D[] {
-  return this.nodes.buffers.map((buffer) => buffer.mesh);
-}
+getAllNodes(): Points[] {
+  return this.nodes.buffers.map((buffer) => buffer.mesh as Points);
+}

Optional, but makes downstream code clearer.

@MichaelBuessemeyer
Copy link
Contributor

@daniel-wer do you maybe have some time to review this?

@daniel-wer
Copy link
Member

Yes, I'll have a look later today or tomorrow

@daniel-wer daniel-wer requested review from daniel-wer and removed request for MichaelBuessemeyer July 22, 2025 11:04
@daniel-wer
Copy link
Member

@hotzenklotz I had a look at all the nitpick suggestions. Nothing earth-shattering, a lot of suggestions I'd classify as premature optimization, but there were some nice small improvements to type safety, GC churn and code readability in there which I've addressed.

Copy link
Member

@daniel-wer daniel-wer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

@hotzenklotz hotzenklotz merged commit 4b89c64 into master Jul 22, 2025
5 checks passed
@hotzenklotz hotzenklotz deleted the threejs-imports branch July 22, 2025 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants