Skip to content

Optimize @primer/octicons-react for codesplitting and tree-shaking - #1245

Merged
mattcosta7 merged 6 commits into
mainfrom
copilot/optimize-react-octicons
Jul 23, 2026
Merged

Optimize @primer/octicons-react for codesplitting and tree-shaking#1245
mattcosta7 merged 6 commits into
mainfrom
copilot/optimize-react-octicons

Conversation

Copilot AI commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Description

@primer/octicons-react shipped a single bundled ESM file built from one entry point. It tree-shook static named imports, but there was no per-icon module (so icons couldn't be dynamically imported/code-split), and every icon was constructed at module-eval time via a createIconComponent factory call.

This splits output to one module per icon, pre-transforms icons into finished components, and adds a subpath export — all additive and backward compatible.

Output: one module per icon + pure re-export barrel

  • script/build.js emits src/__generated__/icons/<Icon>.js per icon plus a barrel icons/index.js.
  • rollup.config.js uses a multi-entry ESM build so dist/ mirrors the tree: dist/index.esm.mjs (barrel), dist/icons/AlertIcon.mjs, and a shared renderOcticon chunk. UMD build retained.
  • package.json exports adds "./*" (per-icon .mjs + .d.ts) alongside the "." barrel.

Pre-transformed components (no runtime factory)

  • Extracted a shared renderOcticon runtime from createIconComponent's render body; createIconComponent now delegates to it.
  • Generated icons are direct /*#__PURE__*/ forwardRef components with statically-known heights/svgDataByHeight — no factory work on import - this is a minor startup cost win, on the order of .1ms - since we do't need to evaluate a bunch of duplicative work

Types

  • Per-icon .d.ts + shared types.d.ts generated and copied into dist/icons (script/types.js), so subpath imports resolve types.

Consumers get both access patterns from the same package:

import {AlertIcon} from '@primer/octicons-react'          // barrel, tree-shakes to one icon
import * as Icons from '@primer/octicons-react'           // whole barrel (inherent to import *)
const Icon = lazy(() => import('@primer/octicons-react/AlertIcon'))  // code-split chunk

Generated per-icon module:

const heights = ["16", "24"]
const svgDataByHeight = { "16": { width: 16, path: <path d="…" /> }, /* … */ }

export const AlertIcon = /*#__PURE__*/ React.forwardRef((props, ref) =>
  renderOcticon(props, ref, "octicon octicon-alert", svgDataByHeight, heights)
)
AlertIcon.displayName = "AlertIcon"

Bundle size (rollup, unminified):

Scenario Result
single named import import {AlertIcon} 6.24 kB → one icon
10 dynamic subpath imports 24.82 kB across 12 code-split chunks
import * 597.66 kB (inherent to import *)

Spritesheet/<symbol> exploration is intentionally out of scope.

@changeset-bot

changeset-bot Bot commented Jul 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c6db8e1

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copilot AI changed the title [WIP] Optimize React octicons for codesplitting and sprite usage Optimize @primer/octicons-react for codesplitting and tree-shaking Jul 22, 2026
Copilot AI requested a review from mattcosta7 July 22, 2026 21:46
@mattcosta7
mattcosta7 marked this pull request as ready for review July 22, 2026 21:54
@mattcosta7
mattcosta7 requested a review from a team as a code owner July 22, 2026 21:54
@mattcosta7
mattcosta7 requested review from Copilot and liuliu-dev July 22, 2026 21:54

Copilot AI 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.

Pull request overview

This PR restructures @primer/octicons-react’s build output to enable per-icon subpath imports for code-splitting while keeping the existing barrel export tree-shakeable, and it removes per-icon runtime factory work by generating finished forwardRef components that share a renderOcticon runtime.

Changes:

  • Added a shared renderOcticon runtime and updated createIconComponent to delegate rendering to it.
  • Updated the generator + Rollup build to emit one module per icon (plus a pure re-export barrel) and exposed per-icon subpath exports via package.json.
  • Added tests/fixtures to validate codesplitting behavior and updated the tree-shaking snapshot; added a changeset for a minor release.
Show a summary per file
File Description
lib/octicons_react/src/renderOcticon.js Introduces shared SVG render/runtime logic used by generated per-icon components.
lib/octicons_react/src/createIconComponent.js Refactors the factory to delegate rendering to renderOcticon.
lib/octicons_react/script/build.js Generates per-icon JS modules + barrel and per-icon .d.ts modules + barrel.
lib/octicons_react/script/types.js Copies per-icon declaration files into dist/icons and writes dist/index.d.ts/index.d.mts.
lib/octicons_react/rollup.config.js Switches to multi-entry ESM output so dist/ mirrors per-icon modules.
lib/octicons_react/package.json Adds exports["./*"] for per-icon subpath imports; preserves . barrel.
lib/octicons_react/src/index.d.ts Updates type barrel to point at the generated per-icon barrel.
lib/octicons_react/tests/tree-shaking.test.js Updates snapshot to reflect new output size.
lib/octicons_react/tests/codesplitting.test.js Adds regression tests for per-icon modules, pure barrel, exports, and code-splitting.
lib/octicons_react/tests/fixtures/dynamic-imports.mjs Adds a fixture that triggers separate dynamic import chunks.
.changeset/octicons-react-codesplitting.md Declares a minor release with the new codesplitting/tree-shaking behavior.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 10/13 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment thread lib/octicons_react/rollup.config.js
)

* Initial plan

* Remove stray npm lockfile and restore yarn.lock to match main

- Delete lib/octicons_react/package-lock.json (added by accident when npm install was run in a Yarn repo)
- Restore lib/octicons_react/yarn.lock to match main (no dependency changes in this PR, so yarn.lock churn was unintended side effect of npm install)

yarn install --frozen-lockfile succeeds with the restored lockfile.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

@joshblack joshblack left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks great! Thanks for putting this up. Just had a couple of comments 👀

Comment thread lib/octicons_react/package.json Outdated
Comment thread lib/octicons_react/src/renderOcticon.js
Comment thread lib/octicons_react/package.json Outdated
Co-authored-by: Matthew Costabile <mattcosta7@github.com>
@mattcosta7
mattcosta7 requested a review from joshblack July 23, 2026 17:15
@mattcosta7
mattcosta7 enabled auto-merge (squash) July 23, 2026 17:23

@joshblack joshblack left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's try it out! @mattcosta7 did you want to test this out in github-ui first or do you feel like it's good to go in a release and we can go from there?

@mattcosta7
mattcosta7 merged commit b6bfb8b into main Jul 23, 2026
11 checks passed
@mattcosta7
mattcosta7 deleted the copilot/optimize-react-octicons branch July 23, 2026 20:18
@primer primer Bot mentioned this pull request Jul 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimize React octicons for codesplitting / Treeshaking, and explore spritesheet usage

4 participants