feat: code splitting optimization - reduce initial bundle by 36% (#8542)

## Summary

Reduces initial module preload from 12.94 MB to 8.24 MB (-4.7 MB, -36%).

## Changes

- Split vendor chunks for better cache isolation (firebase, sentry,
i18n, zod, etc.)
- Exclude heavy optional chunks from initial preload: THREE.js, xterm,
tiptap, chart.js, yjs
- Lazy load 16 dialog components in dialogService
- Add \endor-yjs\ chunk for CRDT library

## Metrics

| Metric | Before | After |
|--------|--------|-------|
| Initial preload | 12.94 MB | 8.24 MB |
| vendor-other | 4,006 KB | 2,156 KB |
| dialogService | 1,860 KB | 1,304 KB |

All excluded chunks still load on-demand when their features are used.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8542-feat-code-splitting-optimization-reduce-initial-bundle-by-36-2fb6d73d36508146aaf7fdaed3274033)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Alexander Brown
2026-02-02 19:05:28 -08:00
committed by GitHub
parent 21492ecca5
commit cbdc7d030f
17 changed files with 423 additions and 100 deletions

View File

@@ -11,7 +11,9 @@ import './groupNodeManage'
import './groupOptions'
import './imageCompare'
import './imageCrop'
import './load3d'
// load3d and saveMesh are loaded on-demand to defer THREE.js (~1.8MB)
// The lazy loader triggers loading when a 3D node is used
import './load3dLazy'
import './maskeditor'
if (!isCloud) {
await import('./nodeTemplates')
@@ -20,7 +22,7 @@ import './noteNode'
import './previewAny'
import './rerouteNode'
import './saveImageExtraOutput'
import './saveMesh'
// saveMesh is loaded on-demand with load3d (see load3dLazy.ts)
import './selectionBorder'
import './simpleTouchSupport'
import './slotDefaults'

View File

@@ -0,0 +1,16 @@
/**
* Load3D constants that don't require THREE.js
* This file can be imported without pulling in the entire THREE.js bundle
*/
export const SUPPORTED_EXTENSIONS = new Set([
'.gltf',
'.glb',
'.obj',
'.fbx',
'.stl',
'.spz',
'.splat',
'.ply',
'.ksplat'
])

View File

@@ -1,11 +1,13 @@
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { type GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
import { type OBJLoader2Parallel } from 'wwobjloader2'
// Use type-only imports to avoid pulling THREE.js into the main bundle
// These imports are erased at compile time and don't create runtime dependencies
import type * as THREE from 'three'
import type { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import type { ViewHelper } from 'three/examples/jsm/helpers/ViewHelper'
import type { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import type { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import type { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import type { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
import type { OBJLoader2Parallel } from 'wwobjloader2'
export type MaterialMode =
| 'original'
@@ -192,15 +194,3 @@ export interface LoaderManagerInterface {
dispose(): void
loadModel(url: string, originalFileName?: string): Promise<void>
}
export const SUPPORTED_EXTENSIONS = new Set([
'.gltf',
'.glb',
'.obj',
'.fbx',
'.stl',
'.spz',
'.splat',
'.ply',
'.ksplat'
])

View File

@@ -0,0 +1,53 @@
/**
* Lazy loader for 3D extensions (Load3D, Preview3D, SaveGLB)
*
* This module defers loading of THREE.js (~1.8MB) until a 3D node is actually
* used in a workflow. The heavy imports are only loaded when:
* - A workflow containing 3D nodes is loaded
* - A user adds a 3D node from the node menu
*/
import { useExtensionService } from '@/services/extensionService'
const LOAD3D_NODE_TYPES = new Set(['Load3D', 'Preview3D', 'SaveGLB'])
let load3dExtensionsLoaded = false
let load3dExtensionsLoading: Promise<void> | null = null
/**
* Dynamically load the 3D extensions (and THREE.js) on demand
*/
async function loadLoad3dExtensions(): Promise<void> {
if (load3dExtensionsLoaded) return
if (load3dExtensionsLoading) {
return load3dExtensionsLoading
}
load3dExtensionsLoading = (async () => {
// Import both extensions - they will self-register via useExtensionService()
await Promise.all([import('./load3d'), import('./saveMesh')])
load3dExtensionsLoaded = true
})()
return load3dExtensionsLoading
}
/**
* Check if a node type is a 3D node that requires THREE.js
*/
function isLoad3dNodeType(nodeTypeName: string): boolean {
return LOAD3D_NODE_TYPES.has(nodeTypeName)
}
// Register a lightweight extension that triggers lazy loading
useExtensionService().registerExtension({
name: 'Comfy.Load3DLazy',
async beforeRegisterNodeDef(_nodeType, nodeData) {
// When a 3D node type is being registered, load the 3D extensions
if (isLoad3dNodeType(nodeData.name)) {
await loadLoad3dExtensions()
}
}
})