feat(react-icons-atomic-webpack-loader): add export-level atomization - #1246
Martin Hochel (Hotell) wants to merge 14 commits into
Conversation
Add versioned per-export selectors and dynamic grouping. Make font and sprite subsetting query-aware, with coordinated integration coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
📋 PR Validation SummaryCheck the Build react library job summary for detailed reports:
|
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
|
Split the general SVG-sprite per-entrypoint attribution fix into #1247. This PR now remains focused on icon granularity and the selector protocol handshake. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings affect RSC behavior, source maps, selector validation, and benchmark reliability.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds opt-in icon-level atomization for React icons while preserving family granularity by default, with coordinated font/SVG-sprite subsetting support.
Changes:
- Adds query-addressed ESM selection for static, direct, re-exported, and dynamic imports.
- Extends subsetting plugin selector handshakes.
- Adds source-map handling, integration tests, documentation, and benchmarking.
Final findings:
- Critical, 1 vote: Grouped dynamic-import modules drop the
"use client"directive. - Moderate, 2 votes: Pass-through processing can return source maps when maps are disabled.
- Moderate, 1 vote each: Benchmark setup lacks its generated dependency prerequisite; malformed selectors do not fail closed; an intermediate source map lacks source identity.
File summaries
| File | Reviewed change |
|---|---|
yarn.lock |
Locks the source-map dependency. |
packages/react-icons/build-verify.test.js |
Verifies independent generated declarations. |
packages/react-icons-svg-sprite-subsetting-webpack-plugin/test/webpack.config.js |
Adds icon-mode integration coverage. |
packages/react-icons-svg-sprite-subsetting-webpack-plugin/src/selector-protocol.ts |
Defines SVG-sprite selector handling. |
packages/react-icons-svg-sprite-subsetting-webpack-plugin/src/index.ts |
Supports queried sprite resources. |
packages/react-icons-svg-sprite-subsetting-webpack-plugin/README.md |
Documents selector integration. |
packages/react-icons-font-subsetting-webpack-plugin/test/plugin.test.ts |
Tests queried font resources and protocol errors. |
packages/react-icons-font-subsetting-webpack-plugin/test/make-configs.js |
Adds icon-mode font fixtures. |
packages/react-icons-font-subsetting-webpack-plugin/src/selector-protocol.ts |
Defines font selector handling. |
packages/react-icons-font-subsetting-webpack-plugin/src/index.ts |
Supports queried font modules. |
packages/react-icons-font-subsetting-webpack-plugin/README.md |
Documents query-aware subsetting. |
packages/react-icons-atomic-webpack-loader/test/transform.test.ts |
Tests icon-level transformations. |
packages/react-icons-atomic-webpack-loader/test/src/icon-granularity-svg.js |
Adds a static-import fixture. |
packages/react-icons-atomic-webpack-loader/test/src/icon-granularity-dynamic.js |
Adds a dynamic-import fixture. |
packages/react-icons-atomic-webpack-loader/test/selector-protocol.test.ts |
Tests selector parsing and emission. |
packages/react-icons-atomic-webpack-loader/test/make-configs.js |
Adds Webpack/Rspack assertions. |
packages/react-icons-atomic-webpack-loader/test/loader.test.ts |
Tests loader selection, handshakes, and maps. |
packages/react-icons-atomic-webpack-loader/test/benchmark.js |
Adds atomization benchmarking. |
packages/react-icons-atomic-webpack-loader/src/transform.ts |
Implements icon-level request rewriting. |
packages/react-icons-atomic-webpack-loader/src/source-maps.ts |
Composes source maps. |
packages/react-icons-atomic-webpack-loader/src/selector-protocol.ts |
Implements selector encoding and validation. |
packages/react-icons-atomic-webpack-loader/src/select-export.ts |
Emits selected ESM declarations. |
packages/react-icons-atomic-webpack-loader/src/modules.ts |
Provides icon and family resolution helpers. |
packages/react-icons-atomic-webpack-loader/src/loader-context.ts |
Extends loader context metadata. |
packages/react-icons-atomic-webpack-loader/src/index.ts |
Integrates selection and plugin checks. |
packages/react-icons-atomic-webpack-loader/src/direct-path.ts |
Handles explicit family imports. |
packages/react-icons-atomic-webpack-loader/README.md |
Documents icon granularity and configuration. |
packages/react-icons-atomic-webpack-loader/package.json |
Adds the remapping dependency and benchmark script. |
packages/docsite/stories/Icons/IconsBuildTransforms.mdx |
Documents icon-level chunk distribution. |
Review details
Suppressed comments (4)
packages/react-icons-atomic-webpack-loader/package.json:13
- This script builds only the loader, but
test/benchmark.jsimmediately reads../../react-icons/lib/atoms/svg; those generatedlibfiles are not present after a clean install. The documented standalone benchmark therefore fails with a missing-directory error unless the contributor manually buildsreact-iconsfirst; build that workspace target here (or document the prerequisite).
"benchmark": "yarn build && node test/benchmark.js"
packages/react-icons-atomic-webpack-loader/src/index.ts:113
- The no-op fast path has the same source-map leak: it returns
inputSourceMapregardless ofthis.sourceMap. With source maps disabled, this should passundefinedrather than preserve the incoming map.
return this.callback(null, sourceCode, inputSourceMap);
packages/react-icons-atomic-webpack-loader/src/selector-protocol.ts:11
- A malformed selector that contains the reserved key without
=(for example?__fluentIconor?foo=1&__fluentIcon) does not match this pattern, soparseSelectorQueryreturnsnulland the generated-resource bailout silently loads the full family. Match a bare key as well so the pair validation fails closed as documented.
const SELECTOR_QUERY_PATTERN = new RegExp(`(?:^\\?|&)${SELECTOR_QUERY_KEY}=`);
packages/react-icons-atomic-webpack-loader/src/transform.ts:513
- This new composition path generates the intermediate map without a
source(or embedded content), so wheninputSourceMapis present the remapper has no reliable source identity for these mappings and can leave importer positions unmapped or tied to an empty source. Generate the map with the currentpath(and content), as the selector-emission path already does.
map: sourceMap ? src.generateMap({ hires: true }) : undefined,
- Files reviewed: 28/29 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: eb2f6b74-f69e-472a-a562-355785a6ffe3
Summary
moduleGranularity: 'icon'while preserving the existing'family'default__fluentIcon=v1compilation handshake required by font and SVG-sprite subsetting pluginsWhy icon granularity?
Family mode already tree-shakes unused exports. Icon mode changes module identity so exports from the same physical icon family can be placed in different chunks.
For example:
With the default family granularity, both imports resolve to the same generated family module:
The bundler must assign that single module identity as a unit, which can move the family into a shared or otherwise less optimal chunk.
With
moduleGranularity: 'icon', the loader emits independent virtual module identities:Each selected module contains only its declaration and required imports, allowing the bundler to keep
AddRegularwith the main chunk andAddFilledwith the lazy chunk.Configuration
The rule must cover importers and the generated Fluent ESM atom directories because icon mode uses two loader passes:
Font and SVG-sprite variants require their matching query-aware subsetting plugin. The compilation handshake fails closed when the plugin is missing or supports a different selector key/version. Family mode does not use this handshake.
Validation
Notes
moduleGranularity: 'family'remains the default and rollback path.