Skip to content

CAT-1335: tree shake phosphor and chunk#16160

Closed
benjiaming wants to merge 9 commits into
masterfrom
CAT-1335-chunk-and-phosphor
Closed

CAT-1335: tree shake phosphor and chunk#16160
benjiaming wants to merge 9 commits into
masterfrom
CAT-1335-chunk-and-phosphor

Conversation

@benjiaming

@benjiaming benjiaming commented Feb 10, 2026

Copy link
Copy Markdown
Contributor

Summary: Phase 1 of FE performance optimizations

The DataHub frontend currently produces around 20MB of minified JavaScript across 5 chunks, compressing to approximately 5 MB when gzipped. This is a substantial bundle size that impacts initial load time and performance, particularly on slower networks. Phase 1 addresses some of the issues:

  • ported chunking from SaaS to vite.config.ts
  • added antd to chunks
  • tree-shaking Phosphor icons (we were bundling all 4,539 icons despite using < 200)

Results

Metric Before After Improvement
Total Bundle 20.45 MB 12.9 MB -36.5% (-7.55 MB)
Gzipped 4.89 MB 3.28 MB -32.9% (-1.61 MB)
Phosphor Icons 6 MB (4,539 icons) 372 KB (133 icons) -95.7%
Page Load (3G) ~27s ~18s -9 seconds

Before

  • giant monolithic bundle: index-B3Suu7YI.js = 19,395 KB (~19 MB)
  • no chunking (everything bundled together)
  • all 4,539 Phosphor icons included (a large block)
  • no vendor separation
Screenshot 2026-02-10 at 12 12 24 PM

After

  1. vendor-yJ7kOkOV.js: 8.6 MB (8,999.72 KB) [KEY CHUNK]

    • React + ReactDOM (bundled together)
    • Ant Design + rc-* components (bundled together)
    • Material UI + @emotion (bundled together)
    • Apollo Client + GraphQL
    • Other dependencies (lodash, moment, etc.)
    • This is the FIX: React + Ant Design + MUI together = no load order issues
  2. source-CbhsHxku.js: 3.9 MB (4,042.65 KB)

    • Application source code
    • Custom components, pages, features
  3. phosphor-vendor-D8fS90Jx.js: 372 KB (408.51 KB) [OPTIMIZED]

    • Custom Phosphor icons bundle (133 icons only)
    • Down from 6 MB originally (95.7% reduction)
  4. index-CxYYPpoy.js: 4 KB (0.74 KB)

    • Entry point

Total Size:

  • Minified: 12.9 MB
  • Gzipped: 3.28 MB (what users actually download)
Screenshot 2026-02-12 at 11 28 05 AM

Bundle Analyzer

  • rollup-plugin-visualize installed as a dev dependency
  • run yarn build:analyze and open dist/stats.html

Risks

  • certain Phosphor icons not loading
    → Mitigation: npm run validate-bundle
  • chunking fails
    → Mitigation: added datahub-web-react/src/__tests__/bundle-optimization.test.ts
  • merge issues (acryl-main does chunking already but a bit differently)

Reference

https://www.notion.so/acryldata/DataHub-Frontend-Performance-Tech-Plan-246fc6a64277803e9976fdde565602d8

@alwaysmeticulous

alwaysmeticulous Bot commented Feb 10, 2026

Copy link
Copy Markdown

✅ Meticulous spotted 0 visual differences across 981 screens tested: view results.

Meticulous evaluated ~8 hours of user flows against your PR.

Expected differences? Click here. Last updated for commit 758ce55. This comment will update as new commits are pushed.

@datahub-cyborg datahub-cyborg Bot added the needs-review Label for PRs that need review from a maintainer. label Feb 10, 2026
@codecov

codecov Bot commented Feb 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@codecov

codecov Bot commented Feb 11, 2026

Copy link
Copy Markdown

Bundle Report

Changes will decrease total bundle size by 5.94MB (-20.1%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
datahub-react-web-esm 23.63MB -5.94MB (-20.1%) ⬇️

Affected Assets, Files, and Routes:

view changes for bundle: datahub-react-web-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/vendor-*.js (New) 9.0MB 9.0MB 100.0% 🚀
assets/source-*.js (New) 4.04MB 4.04MB 100.0% 🚀
assets/source-*.css (New) 578.31kB 578.31kB 100.0% 🚀
assets/phosphor-*.js (New) 408.51kB 408.51kB 100.0% 🚀
assets/vendor-*.css (New) 30.3kB 30.3kB 100.0% 🚀
assets/index-*.js -19.39MB 740 bytes -100.0%
assets/index-*.css (Deleted) -608.59kB 0 bytes -100.0% 🗑️
assets/FTE-*.js (Deleted) -90 bytes 0 bytes -100.0% 🗑️
assets/FTE-*.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️
assets/FTE-*.js (Deleted) -89 bytes 0 bytes -100.0% 🗑️

Files in assets/source-*.js:

  • ./src/alchemy-components/components/Editor/toolbar/AddImageButtonV2.tsx → Total Size: 2.7kB

  • ./src/alchemy-components/components/IncidentStagePill/IncidentStagePill.tsx → Total Size: 1.64kB

  • ./src/alchemy-components/components/Editor/toolbar/AddImageButton.tsx → Total Size: 1.84kB

  • ./src/app/entityV2/document/DocumentEntity.tsx → Total Size: 4.13kB

  • ./src/app/entityV2/document/preview/Preview.tsx → Total Size: 1.03kB

  • ./src/app/entityV2/dataJob/DataJobEntity.tsx → Total Size: 7.74kB

  • ./src/app/entityV2/domain/DomainEntity.tsx → Total Size: 6.99kB

  • ./src/app/entity/application/ApplicationEntity.tsx → Total Size: 5.29kB

  • ./src/app/entityV2/dashboard/DashboardEntity.tsx → Total Size: 10.37kB

  • ./src/app/entity/shared/tabs/Dataset/Governance/TestResultsSummary.tsx → Total Size: 2.16kB

  • ./src/alchemy-components/components/Editor/toolbar/AddLinkButton.tsx → Total Size: 681 bytes

  • ./src/app/domainV2/DomainIcon.tsx → Total Size: 133 bytes

  • ./src/app/entityV2/dataProcessInstance/DataProcessInstanceEntity.tsx → Total Size: 4.77kB

  • ./src/alchemy-components/components/Icon/utils.ts → Total Size: 570 bytes

  • ./src/app/entityV2/dataProduct/DataProductEntity.tsx → Total Size: 6.21kB

  • ./src/app/entityV2/dataFlow/DataFlowEntity.tsx → Total Size: 6.0kB

  • ./src/alchemy-components/components/Icon/phosphor-icons.ts → Total Size: 0 bytes

  • ./src/app/entityV2/application/ApplicationEntity.tsx → Total Size: 5.21kB

  • ./src/app/entityV2/container/ContainerEntity.tsx → Total Size: 7.09kB

  • ./src/app/entityV2/dataset/DatasetEntity.tsx → Total Size: 13.75kB

  • ./src/alchemy-components/components/Icon/constants.ts → Total Size: 0 bytes

  • ./src/app/entityV2/chart/ChartEntity.tsx → Total Size: 9.13kB

  • ./src/alchemy-components/components/Editor/toolbar/Toolbar.tsx → Total Size: 4.8kB

  • ./src/app/entityV2/document/DocumentNativeProfile.tsx → Total Size: 3.94kB

@AdrianMachado AdrianMachado left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Take a look at my comment about the phosphor icons. Everything else seems to work based on your tests, but I think either @chriscollins3456 or @asikowitz should review it too. I'm not an expert on build tools 😅

'import/no-extraneous-dependencies': 'off',
'import/no-relative-packages': 'error',
'import/prefer-default-export': 'off', // TODO: remove this lint rule
'@typescript-eslint/no-restricted-imports': [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Excellent idea, I was about to suggest this while reading the PR

];

export const PHOSPHOR_ICONS = [
'Activity',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This was unused?

@@ -0,0 +1,150 @@
/**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't quite understand this change.
Phosphor says it supports tree-shaking out of the box.

Claude mentions that Vite should also should support tree shaking with named imports. So I am confused why we aren't getting the benefits of that without having to create this file?

This leads me to believe that either our build tools are out of date, or are misconfigured.

If we do get that working, we can simply rewrite icon imports to the dics/csr subdirectory?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Interesting that phosphor-icons does support tree shaking, but clearly the bundle size has reduced significantly based on the PR description. agreed that it's worth investigating

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, his approach will decrease it for sure. Like Claude basically reinvented tree shaking, but more restrictive.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But obviously the native implementation in our build tool is nicer because it doesn't require us to alias our imports

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, Phosphor does support tree-shaking but only when:
1. You import from individual CSR files:
As you said.
2. You don't use wildcard imports (import *):
We have 3 files that do import * as phosphorIcons from '@phosphor-icons/react';. Easy to fix.
3. You don't use dynamic icon resolution:
We actually have quite a lot of those.

// Icon name comes from props/API at runtime
<Icon icon="BookmarkSimple" source="phosphor" />

// Resolved dynamically from imported namespace
const IconComponent = phosphorIcons[iconName]; // Can't be tree-shaken

If we want to eliminate dynamic icon resolution, we'd need to refactor ~100 files using the Icon pattern to something like this:

// Old: 
<Icon icon="MagnifyingGlass" source="phosphor" />
<Icon icon="Search" source="material" /> // hypothetical, nobody actually uses source "material"

// New: 
import { MagnifyingGlass } from '@phosphor-icons/react'; // but what if the icon name comes from API?
import { Search } from '@mui/icons-material';

<Icon component={MagnifyingGlass} />
<Icon component={Search} />  

Not impossible but it would be a breaking change and quite a lot of effort.

The current solution is much simpler in that it creates a curated whitelist of icons and allows us to continue the same dynamic pattern. The disadvantage is that when we add an icon, we must update the list (but the new eslint rule and potentially unit test should catch that).

BTW mui-icons are also currently not tree shaken (~5 MB minified / 1.27 MB gzipped) but that's a follow-up PR.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems like what we should disallow is an import * from phosphor-icons, not all imports. Then we wouldn't have to include most of the import changes in this PR.

That being said, I'm not opposed to changing the Icon component entirely. I always thought it was weird we specified icon via string. I guess the idea was to limit what icons people could use via the component -- now you're limited to the ones provided by material + phosphor? But tbh that's an annoying restriction too -- we should be able to support custom icons as well. So I would be fine changing the 100+ files to pass the icon component in directly. AI should be able to make that change pretty easily.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Disallowing import * from phosphor-icons is not sufficient. The core issue is dynamic icon resolution, not wildcard imports and we would still be bundling all 4k icons with named imports because:

  1. Dynamic resolution requires all icons in scope:
// Icon component uses dynamic lookup:
const IconComponent = phosphorIcons[iconName]; // iconName from props
  1. Vite can't tree-shake dynamic property access - it must keep all icons since it doesn't know which iconName values will be used at runtime.

We have two options:

  1. Curated whitelist: Current PR. Ships faster with lower risk, no API changes needed but we would need to maintain the list (with automation).
  2. Native tree-shaking: Cleaner but would need to refactor the following:
  • ~85 simple static patterns and ~20 conditional patterns e.g. <Icon icon={linkIcon ?? 'ArrowRight'} .../>
  • 1 mapped Alchemy Icon pattern
export const ASSERTION_TYPE_TO_ICON_MAP: Record<AssertionType, JSX.Element> = {
    [AssertionType.Freshness]: <Clock size={20} />,  // Direct Phosphor component
    [AssertionType.Volume]: <Database size={20} />,
};
  • 13 maps MODULE_TYPE_TO_ICON and DATA_TYPE_ICON_MAP

Not impossible and AI will certainly help but it does add complexity and risk to the PR.

Both approaches should work and would deliver the same savings.

What is your preference?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think I'm with Andrew in saying option 2 may make this PR a bit bloated, but long term its less annoying.

const vendorSize = fs.statSync(path.join(distPath, vendorFile!)).size;

// Phosphor should be optimized (< 500 KB)
expect(phosphorSize).toBeLessThan(500 * 1024);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Will this break if we add more icons?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes - it's 372kb now. If we add many more, this would need to be updated.

Comment on lines +266 to +267
expect(vendorSize).toBeGreaterThan(7 * 1024 * 1024);
expect(vendorSize).toBeLessThan(10 * 1024 * 1024);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is a moving target as we update these libraries, no?

@datahub-cyborg datahub-cyborg Bot removed the needs-review Label for PRs that need review from a maintainer. label Feb 13, 2026
@datahub-cyborg datahub-cyborg Bot added the pending-submitter-response Issue/request has been reviewed but requires a response from the submitter label Feb 13, 2026
Comment on lines +33 to +35
'framework', // React core
'antd-vendor', // Ant Design
'mui-vendor', // Material UI

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

these aren't the chunks based on the new chunking strategy in vite.config

@@ -0,0 +1,147 @@
#!/usr/bin/env node

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

where and when do we plan to run this script?

Comment on lines +64 to +71
// Step 2: Verify all imported icons are exported
console.log('=== Verifying Icon Exports ===\n');

const phosphorIconsPath = path.join(__dirname, '..', 'src/alchemy-components/components/Icon/phosphor-icons.ts');
const phosphorIconsContent = fs.readFileSync(phosphorIconsPath, 'utf8');

// Extract exported icons
const exportedIcons = new Set();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Step 1 and 2 are redundant imo, and should be caught by ESLint and typescript.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also it depends on how frequently we run this script

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think we could turn this into a unit test.

@ani-malgari ani-malgari left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The performance gains are already amazing. Everything looks good to me except I just left some minor comments with regards to the validation scripts.

export { X } from '@phosphor-icons/react/dist/csr/X';
export { XCircle } from '@phosphor-icons/react/dist/csr/XCircle';

// Text formatting icons (for Editor toolbar)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I see no reason to distinguish between the two

@@ -0,0 +1,150 @@
/**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems like what we should disallow is an import * from phosphor-icons, not all imports. Then we wouldn't have to include most of the import changes in this PR.

That being said, I'm not opposed to changing the Icon component entirely. I always thought it was weird we specified icon via string. I guess the idea was to limit what icons people could use via the component -- now you're limited to the ones provided by material + phosphor? But tbh that's an annoying restriction too -- we should be able to support custom icons as well. So I would be fine changing the 100+ files to pass the icon component in directly. AI should be able to make that change pretty easily.

@benjiaming

Copy link
Copy Markdown
Contributor Author

Closed, this one got merged instead: #16338

@benjiaming benjiaming closed this May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-submitter-response Issue/request has been reviewed but requires a response from the submitter product PR or Issue related to the DataHub UI/UX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants