From f1d936dbbb20835755d8f136356af6236be747c0 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 1 Apr 2026 11:24:30 +0200 Subject: [PATCH 1/8] chore: remove react-icons from devDependencies as its handled by nx graph --- packages/react-icons-font-subsetting-webpack-plugin/package.json | 1 - .../package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/react-icons-font-subsetting-webpack-plugin/package.json b/packages/react-icons-font-subsetting-webpack-plugin/package.json index c2cfe1bcbdb..0c64bf5ecf2 100644 --- a/packages/react-icons-font-subsetting-webpack-plugin/package.json +++ b/packages/react-icons-font-subsetting-webpack-plugin/package.json @@ -25,7 +25,6 @@ "devDependencies": { "typescript": "5.0.4", "webpack": "^5.72.0", - "@fluentui/react-icons": "*", "@types/node": "14" }, "dependencies": { diff --git a/packages/react-icons-svg-sprite-subsetting-webpack-plugin/package.json b/packages/react-icons-svg-sprite-subsetting-webpack-plugin/package.json index 55704df3ff4..49860683bd0 100644 --- a/packages/react-icons-svg-sprite-subsetting-webpack-plugin/package.json +++ b/packages/react-icons-svg-sprite-subsetting-webpack-plugin/package.json @@ -31,7 +31,6 @@ "schema-utils": "^4.2.0" }, "devDependencies": { - "@fluentui/react-icons": "*", "@types/node": "22", "html-webpack-plugin": "^5.5.3", "typescript": "5.0.4", From 9754e28e259fe3ca65847c5447bee92c1a0a4944 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Thu, 2 Apr 2026 18:15:37 +0200 Subject: [PATCH 2/8] feat(demo-app): init --- package-lock.json | 93 +++++++++++- packages/icon-transform-demo/babel.config.js | 25 ++++ .../fluent-icons-transform.js | 28 ++++ packages/icon-transform-demo/package.json | 37 +++++ packages/icon-transform-demo/project.json | 8 + packages/icon-transform-demo/src/app.tsx | 140 ++++++++++++++++++ packages/icon-transform-demo/src/index.tsx | 13 ++ packages/icon-transform-demo/tsconfig.json | 13 ++ .../icon-transform-demo/webpack.config.js | 57 +++++++ 9 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 packages/icon-transform-demo/babel.config.js create mode 100644 packages/icon-transform-demo/fluent-icons-transform.js create mode 100644 packages/icon-transform-demo/package.json create mode 100644 packages/icon-transform-demo/project.json create mode 100644 packages/icon-transform-demo/src/app.tsx create mode 100644 packages/icon-transform-demo/src/index.tsx create mode 100644 packages/icon-transform-demo/tsconfig.json create mode 100644 packages/icon-transform-demo/webpack.config.js diff --git a/package-lock.json b/package-lock.json index 89fdaea2db5..03b592fbc62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11477,6 +11477,17 @@ "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", "license": "MIT" }, + "node_modules/babel-plugin-transform-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-imports/-/babel-plugin-transform-imports-2.0.0.tgz", + "integrity": "sha512-65ewumYJ85QiXdcB/jmiU0y0jg6eL6CdnDqQAqQ8JMOKh1E52VPG3NJzbVKWcgovUR5GBH8IWpCXQ7I8Q3wjgw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@babel/types": "^7.4", + "is-valid-path": "^0.1.1" + } + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -17194,6 +17205,10 @@ "resolved": "packages/icon-app", "link": true }, + "node_modules/icon-transform-demo": { + "resolved": "packages/icon-transform-demo", + "link": true + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -17799,6 +17814,42 @@ "node": ">=8" } }, + "node_modules/is-invalid-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", + "integrity": "sha512-aZMG0T3F34mTg4eTdszcGXx54oiZ4NtHSft3hWNJMGJXUUqdIj3cOZuHcU0nCWWcY3jd7yRe/3AEm3vSNTpBGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-glob": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-invalid-path/node_modules/is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-invalid-path/node_modules/is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -18030,6 +18081,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-valid-path": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-valid-path/-/is-valid-path-0.1.1.tgz", + "integrity": "sha512-+kwPrVDu9Ms03L90Qaml+79+6DZHqHyRoANI6IsZJ/g8frhnfchDOBCa0RbQ6/kdHt5CS5OeIEyrYznNuVN+8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-invalid-path": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -30385,6 +30449,33 @@ "webpack-dev-server": "^5.2.2" } }, + "packages/icon-transform-demo": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@fluentui/react-icons": "*", + "@griffel/react": "1.6.1", + "react": "19.2.3", + "react-dom": "19.2.3" + }, + "devDependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "@babel/preset-react": "^7.26.0", + "@babel/preset-typescript": "^7.26.0", + "@fluentui/react-icons-font-subsetting-webpack-plugin": "*", + "@fluentui/react-icons-svg-sprite-subsetting-webpack-plugin": "*", + "@types/react": "19.1.13", + "@types/react-dom": "19.1.9", + "babel-loader": "^9.2.1", + "babel-plugin-transform-imports": "^2.0.0", + "html-webpack-plugin": "^5.6.3", + "typescript": "5.0.4", + "webpack": "^5.100.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.2" + } + }, "packages/react-icons": { "name": "@fluentui/react-icons", "version": "2.0.325", @@ -30461,7 +30552,6 @@ "subset-font": "^1.4.0" }, "devDependencies": { - "@fluentui/react-icons": "*", "@types/node": "14", "typescript": "5.0.4", "webpack": "^5.72.0" @@ -30486,7 +30576,6 @@ "schema-utils": "^4.2.0" }, "devDependencies": { - "@fluentui/react-icons": "*", "@types/node": "22", "html-webpack-plugin": "^5.5.3", "typescript": "5.0.4", diff --git a/packages/icon-transform-demo/babel.config.js b/packages/icon-transform-demo/babel.config.js new file mode 100644 index 00000000000..d49ea5cfe45 --- /dev/null +++ b/packages/icon-transform-demo/babel.config.js @@ -0,0 +1,25 @@ +// @ts-check +const { resolveFluentIconImport } = require('./fluent-icons-transform'); + +module.exports = { + presets: [ + ['@babel/preset-env', { targets: { esmodules: true }, modules: false }], + ['@babel/preset-react', { runtime: 'classic' }], + '@babel/preset-typescript', + ], + plugins: [ + [ + 'transform-imports', + { + '@fluentui/react-icons': { + // Rewrites barrel imports to atomic SVG imports: + // import { AddRegular } from '@fluentui/react-icons' + // → import { AddRegular } from '@fluentui/react-icons/svg/add' + transform: (importName) => resolveFluentIconImport(importName, 'svg-sprite'), + preventFullImport: false, + skipDefaultConversion: true, + }, + }, + ], + ], +}; diff --git a/packages/icon-transform-demo/fluent-icons-transform.js b/packages/icon-transform-demo/fluent-icons-transform.js new file mode 100644 index 00000000000..6177fce83b2 --- /dev/null +++ b/packages/icon-transform-demo/fluent-icons-transform.js @@ -0,0 +1,28 @@ +// @ts-check + +/** + * Resolves a @fluentui/react-icons import name to its atomic module path. + * @param {string} importName - The named export being imported. + * @param {'svg' | 'svg-sprite' | 'fonts'} [target='svg'] - The target subpath (e.g. 'svg', 'svg-sprite', 'fonts', + * 'base/svg', 'base/svg-sprite', 'base/fonts'). + * @returns {string} The resolved module path. + */ +function resolveFluentIconImport(importName, target = 'svg') { + if (importName === 'useIconContext' || importName === 'IconDirectionContextProvider') { + return '@fluentui/react-icons/providers'; + } + + const match = importName.match(/^(.+?)(\d+)?(Regular|Filled|Light|Color)$/); + if (!match) { + return '@fluentui/react-icons/utils'; + } + + return `@fluentui/react-icons/${target}/${kebabCase(match[1])}`; +} + +/** Simplified lodash.kebabCase – handles PascalCase icon names with digits. */ +function kebabCase(/** @type {string} */ str) { + return str.replace(/[a-z\d](?=[A-Z])|[a-zA-Z](?=\d)|[A-Z](?=[A-Z][a-z])/g, '$&-').toLowerCase(); +} + +module.exports = { resolveFluentIconImport }; diff --git a/packages/icon-transform-demo/package.json b/packages/icon-transform-demo/package.json new file mode 100644 index 00000000000..1b7a2a815d2 --- /dev/null +++ b/packages/icon-transform-demo/package.json @@ -0,0 +1,37 @@ +{ + "name": "icon-transform-demo", + "version": "1.0.0", + "description": "Demo app for @fluentui/react-icons with babel-plugin-transform-imports showcase", + "main": "index.js", + "private": true, + "scripts": { + "clean": "git clean -fXd dist/", + "build": "webpack", + "dev": "webpack serve --open" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@babel/core": "^7.26.0", + "@babel/preset-env": "^7.26.0", + "@babel/preset-react": "^7.26.0", + "@babel/preset-typescript": "^7.26.0", + "@fluentui/react-icons-font-subsetting-webpack-plugin": "*", + "@fluentui/react-icons-svg-sprite-subsetting-webpack-plugin": "*", + "@types/react": "19.1.13", + "@types/react-dom": "19.1.9", + "babel-loader": "^9.2.1", + "babel-plugin-transform-imports": "^2.0.0", + "html-webpack-plugin": "^5.6.3", + "typescript": "5.0.4", + "webpack": "^5.100.2", + "webpack-cli": "^6.0.1", + "webpack-dev-server": "^5.2.2" + }, + "dependencies": { + "@fluentui/react-icons": "*", + "@griffel/react": "1.6.1", + "react": "19.2.3", + "react-dom": "19.2.3" + } +} diff --git a/packages/icon-transform-demo/project.json b/packages/icon-transform-demo/project.json new file mode 100644 index 00000000000..40ca138c67b --- /dev/null +++ b/packages/icon-transform-demo/project.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "name": "icon-transform-demo", + "projectType": "application", + "sourceRoot": "packages/icon-transform-demo/src", + "tags": [], + "targets": {} +} diff --git a/packages/icon-transform-demo/src/app.tsx b/packages/icon-transform-demo/src/app.tsx new file mode 100644 index 00000000000..13feb11d52a --- /dev/null +++ b/packages/icon-transform-demo/src/app.tsx @@ -0,0 +1,140 @@ +import * as React from 'react'; +import { makeStyles } from '@griffel/react'; + +// ---------------------------------------------------------------- +// All imports below use the barrel entry `@fluentui/react-icons`. +// At build time, `babel-plugin-transform-imports` rewrites each +// named import to its atomic module path, e.g.: +// import { AddRegular } from '@fluentui/react-icons' +// → import { AddRegular } from '@fluentui/react-icons/svg/add' +// ---------------------------------------------------------------- +import { + AccessTimeRegular, + AccessTimeFilled, + AccessibilityRegular, + AccessibilityFilled, + AddRegular, + AddFilled, + AddCircleRegular, + AddCircleFilled, + AgentsRegular, + AgentsFilled, + AirplaneRegular, + AirplaneFilled, + AlertRegular, + AlertFilled, + AlbumRegular, + AlbumFilled, + AlignLeftRegular, + AlignLeftFilled, + AnimalCatRegular, + AnimalCatFilled, + bundleIcon, + iconFilledClassName, + iconRegularClassName, +} from '@fluentui/react-icons'; + +// ---------- compound (bundled) icons ---------- +const AccessTime = bundleIcon(AccessTimeFilled, AccessTimeRegular); +const Accessibility = bundleIcon(AccessibilityFilled, AccessibilityRegular); +const Add = bundleIcon(AddFilled, AddRegular); +const AddCircle = bundleIcon(AddCircleFilled, AddCircleRegular); +const Agents = bundleIcon(AgentsFilled, AgentsRegular); +const Airplane = bundleIcon(AirplaneFilled, AirplaneRegular); +const Alert = bundleIcon(AlertFilled, AlertRegular); +const Album = bundleIcon(AlbumFilled, AlbumRegular); +const AlignLeft = bundleIcon(AlignLeftFilled, AlignLeftRegular); +const AnimalCat = bundleIcon(AnimalCatFilled, AnimalCatRegular); + +type IconEntry = { Icon: ReturnType; name: string }; + +const icons: IconEntry[] = [ + { Icon: AccessTime, name: 'AccessTime' }, + { Icon: Accessibility, name: 'Accessibility' }, + { Icon: Add, name: 'Add' }, + { Icon: AddCircle, name: 'AddCircle' }, + { Icon: Agents, name: 'Agents' }, + { Icon: Airplane, name: 'Airplane' }, + { Icon: Alert, name: 'Alert' }, + { Icon: Album, name: 'Album' }, + { Icon: AlignLeft, name: 'AlignLeft' }, + { Icon: AnimalCat, name: 'AnimalCat' }, +]; + +// ---------- styles ---------- +const useStyles = makeStyles({ + grid: { + display: 'flex', + flexWrap: 'wrap', + gap: '12px', + padding: '16px', + }, + card: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + width: '110px', + height: '110px', + backgroundColor: '#f5f5f5', + borderRadius: '8px', + fontSize: '36px', + cursor: 'pointer', + transitionProperty: 'background-color', + transitionDuration: '150ms', + [`&:hover .${iconFilledClassName}`]: { display: 'inline' }, + [`&:hover .${iconRegularClassName}`]: { display: 'none' }, + }, + label: { + fontSize: '11px', + marginTop: '6px', + textAlign: 'center', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '100px', + }, + header: { + padding: '16px 16px 0', + fontFamily: 'system-ui, sans-serif', + }, + subtitle: { + color: '#666', + fontSize: '14px', + fontWeight: 'normal', + marginTop: '4px', + }, +}); + +// ---------- components ---------- +function IconCard({ Icon, name }: IconEntry) { + const styles = useStyles(); + + return ( +
+ + {name} +
+ ); +} + +export function App() { + const styles = useStyles(); + + return ( +
+
+

Fluent Icons — Transform Demo

+

+ 20 icons (10 groups × Filled + Regular) imported via barrel entry. +
+ babel-plugin-transform-imports rewrites them to atomic paths at build time. +

+
+
+ {icons.map(({ Icon, name }) => ( + + ))} +
+
+ ); +} diff --git a/packages/icon-transform-demo/src/index.tsx b/packages/icon-transform-demo/src/index.tsx new file mode 100644 index 00000000000..82eb45c4e0e --- /dev/null +++ b/packages/icon-transform-demo/src/index.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './app'; + +main(); + +function main() { + const rootDiv = document.createElement('div'); + document.body.append(rootDiv); + + const root = createRoot(rootDiv); + root.render(); +} diff --git a/packages/icon-transform-demo/tsconfig.json b/packages/icon-transform-demo/tsconfig.json new file mode 100644 index 00000000000..2d6329cf906 --- /dev/null +++ b/packages/icon-transform-demo/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2020", + "jsx": "react", + "module": "esnext", + "moduleResolution": "bundler", + "outDir": "./lib", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/packages/icon-transform-demo/webpack.config.js b/packages/icon-transform-demo/webpack.config.js new file mode 100644 index 00000000000..0bf35f9cbae --- /dev/null +++ b/packages/icon-transform-demo/webpack.config.js @@ -0,0 +1,57 @@ +// @ts-check +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('node:path'); +const { + default: FluentUIReactIconsFontSubsettingPlugin, +} = require('@fluentui/react-icons-font-subsetting-webpack-plugin'); +const { + default: FluentUIReactIconsSvgSpriteSubsettingPlugin, +} = require('@fluentui/react-icons-svg-sprite-subsetting-webpack-plugin'); + +/** @type {import('webpack').Configuration} */ +module.exports = { + entry: './src/index.tsx', + output: { + path: path.resolve(__dirname, 'dist'), + filename: 'bundle.[contenthash].js', + clean: true, + }, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + mode: 'development', + module: { + rules: [ + { + test: /\.tsx?$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + }, + }, + { + test: /\.(ttf|woff2?)$/, + type: 'asset', + }, + { + test: /\.svg$/, + type: 'asset/resource', + generator: { + filename: '[name]-[contenthash][ext]', + }, + }, + ], + }, + optimization: { + usedExports: true, + }, + plugins: [ + new HtmlWebpackPlugin({ + title: 'Fluent React Icons — Transform Demo', + }), + new FluentUIReactIconsFontSubsettingPlugin(), + new FluentUIReactIconsSvgSpriteSubsettingPlugin({ + mode: 'merged', + }), + ], +}; From f3672e32057b91c5cb9b9d695958aed3f2ff3e94 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Tue, 14 Apr 2026 15:29:58 +0200 Subject: [PATCH 3/8] feat(react-icons): enable /headless and /svg-sprite api --- packages/react-icons/package.json | 41 ++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/react-icons/package.json b/packages/react-icons/package.json index 2b33b459677..c20631da646 100644 --- a/packages/react-icons/package.json +++ b/packages/react-icons/package.json @@ -1,7 +1,10 @@ { "name": "@fluentui/react-icons", "version": "2.0.325", - "sideEffects": false, + "sideEffects": [ + "**/headless/fonts/headless-fonts.css", + "**/headless/headless.css" + ], "main": "lib-cjs/index.js", "module": "lib/index.js", "typings": "lib/index.d.ts", @@ -14,8 +17,8 @@ "scripts": { "clean": "git clean -fXd lib/ lib-cjs/ intermediate/ src/ tmp/", "generate:assets-to-svg": "node ../../importer/generate.js --source=../../assets --dest=./intermediate --extension=svg --target=react", - "convert:svg": "node convert.js --source=./intermediate --dest=./src --perIconDest=./src/atoms/svg --spriteDest=./src/atoms/svg-sprite --headlessPerIconDest=./src/atoms/headless-svg --headlessSpriteDest=./src/atoms/headless-svg-sprite --rtl=./intermediate/rtl.json --metadata=./tmp/metadata-svg.json", - "convert:fonts": "node convert-font.js --source=./src/utils/fonts --dest=./src/fonts --perIconDest=./src/atoms/fonts --headlessPerIconDest=./src/atoms/headless-fonts --codepointDest=./src/utils/fonts --rtl=./intermediate/rtl.json --metadata=./tmp/metadata-font.json", + "convert:svg": "node convert.js --source=./intermediate --dest=./src --perIconDest=./src/atoms/svg --spriteDest=./src/atoms/svg-sprite --headlessPerIconDest=./src/atoms/headless-svg --headlessSpriteDest=./src/atoms/headless-svg-sprite --rtl=./intermediate/rtl.json --metadata=./tmp/metadata-svg.json --sprites --headless", + "convert:fonts": "node convert-font.js --source=./src/utils/fonts --dest=./src/fonts --perIconDest=./src/atoms/fonts --headlessPerIconDest=./src/atoms/headless-fonts --codepointDest=./src/utils/fonts --rtl=./intermediate/rtl.json --metadata=./tmp/metadata-font.json --headless", "convert:merge-metadata": "node merge-metadata.js --svgMetadata=./tmp/metadata-svg.json --fontMetadata=./tmp/metadata-font.json --output=./metadata.json", "generate:font-regular": "node ../../importer/generateFont.js --source=intermediate --dest=src/utils/fonts --iconType=Regular --codepoints=../../fonts/FluentSystemIcons-Regular.json", "generate:font-filled": "node ../../importer/generateFont.js --source=intermediate --dest=src/utils/fonts --iconType=Filled --codepoints=../../fonts/FluentSystemIcons-Filled.json", @@ -128,6 +131,38 @@ "types": "./lib/providers.d.ts", "import": "./lib/providers.js", "require": "./lib-cjs/providers.js" + }, + "./svg-sprite/*": { + "types": "./lib/atoms/svg-sprite/*.d.ts", + "import": "./lib/atoms/svg-sprite/*.js", + "require": "./lib-cjs/atoms/svg-sprite/*.js" + }, + "./headless": { + "types": "./lib/headless/index.d.ts", + "import": "./lib/headless/index.js", + "require": "./lib-cjs/headless/index.js" + }, + "./headless/fonts": { + "types": "./lib/headless/fonts/index.d.ts", + "import": "./lib/headless/fonts/index.js", + "require": "./lib-cjs/headless/fonts/index.js" + }, + "./headless/headless.css": "./lib/headless/headless.css", + "./headless/headless-fonts.css": "./lib/headless/fonts/headless-fonts.css", + "./headless/svg/*": { + "types": "./lib/atoms/headless-svg/*.d.ts", + "import": "./lib/atoms/headless-svg/*.js", + "require": "./lib-cjs/atoms/headless-svg/*.js" + }, + "./headless/svg-sprite/*": { + "types": "./lib/atoms/headless-svg-sprite/*.d.ts", + "import": "./lib/atoms/headless-svg-sprite/*.js", + "require": "./lib-cjs/atoms/headless-svg-sprite/*.js" + }, + "./headless/fonts/*": { + "types": "./lib/atoms/headless-fonts/*.d.ts", + "import": "./lib/atoms/headless-fonts/*.js", + "require": "./lib-cjs/atoms/headless-fonts/*.js" } } } From 58623efdf7617cb43909145e29348392803a9f03 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Tue, 14 Apr 2026 15:31:57 +0200 Subject: [PATCH 4/8] chore: enable svg-sprite webpack plugin for pre-release --- nx.json | 1 + 1 file changed, 1 insertion(+) diff --git a/nx.json b/nx.json index baf46ce3816..d1a572e482d 100644 --- a/nx.json +++ b/nx.json @@ -17,6 +17,7 @@ "projects": [ "packages/react-icons", "packages/react-icons-font-subsetting-webpack-plugin", + "packages/react-icons-svg-sprite-subsetting-webpack-plugin", "packages/react-native-icons" ], "projectsRelationship": "independent" From 310899a9c96ce7ce663891f6f9f9923b82641022 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Tue, 14 Apr 2026 16:19:13 +0200 Subject: [PATCH 5/8] feat(demo-app): add our new loader --- packages/icon-transform-demo/package.json | 1 + packages/icon-transform-demo/webpack.config.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/packages/icon-transform-demo/package.json b/packages/icon-transform-demo/package.json index 1b7a2a815d2..e4732536fea 100644 --- a/packages/icon-transform-demo/package.json +++ b/packages/icon-transform-demo/package.json @@ -18,6 +18,7 @@ "@babel/preset-typescript": "^7.26.0", "@fluentui/react-icons-font-subsetting-webpack-plugin": "*", "@fluentui/react-icons-svg-sprite-subsetting-webpack-plugin": "*", + "@fluentui/react-icons-atomic-webpack-loader": "*", "@types/react": "19.1.13", "@types/react-dom": "19.1.9", "babel-loader": "^9.2.1", diff --git a/packages/icon-transform-demo/webpack.config.js b/packages/icon-transform-demo/webpack.config.js index 0bf35f9cbae..f9b1ede9113 100644 --- a/packages/icon-transform-demo/webpack.config.js +++ b/packages/icon-transform-demo/webpack.config.js @@ -22,6 +22,11 @@ module.exports = { mode: 'development', module: { rules: [ + { + test: /\.[mc]?[jt]sx?$/, + enforce: 'pre', + use: ['@fluentui/react-icons-atomic-webpack-loader'], + }, { test: /\.tsx?$/, exclude: /node_modules/, From f5d9e993bce00b53f7ca798e910157ce0089ff3f Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Tue, 14 Apr 2026 16:25:15 +0200 Subject: [PATCH 6/8] feat(demo-app): use our atomic loader instead of babel transform for imports --- packages/icon-transform-demo/babel.config.js | 26 +++++++++---------- .../icon-transform-demo/webpack.config.js | 9 ++++++- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/icon-transform-demo/babel.config.js b/packages/icon-transform-demo/babel.config.js index d49ea5cfe45..87340610748 100644 --- a/packages/icon-transform-demo/babel.config.js +++ b/packages/icon-transform-demo/babel.config.js @@ -8,18 +8,18 @@ module.exports = { '@babel/preset-typescript', ], plugins: [ - [ - 'transform-imports', - { - '@fluentui/react-icons': { - // Rewrites barrel imports to atomic SVG imports: - // import { AddRegular } from '@fluentui/react-icons' - // → import { AddRegular } from '@fluentui/react-icons/svg/add' - transform: (importName) => resolveFluentIconImport(importName, 'svg-sprite'), - preventFullImport: false, - skipDefaultConversion: true, - }, - }, - ], + // [ + // 'transform-imports', + // { + // '@fluentui/react-icons': { + // // Rewrites barrel imports to atomic SVG imports: + // // import { AddRegular } from '@fluentui/react-icons' + // // → import { AddRegular } from '@fluentui/react-icons/svg/add' + // transform: (importName) => resolveFluentIconImport(importName, 'svg-sprite'), + // preventFullImport: false, + // skipDefaultConversion: true, + // }, + // }, + // ], ], }; diff --git a/packages/icon-transform-demo/webpack.config.js b/packages/icon-transform-demo/webpack.config.js index f9b1ede9113..318d8b220a5 100644 --- a/packages/icon-transform-demo/webpack.config.js +++ b/packages/icon-transform-demo/webpack.config.js @@ -25,7 +25,14 @@ module.exports = { { test: /\.[mc]?[jt]sx?$/, enforce: 'pre', - use: ['@fluentui/react-icons-atomic-webpack-loader'], + use: [ + { + loader: '@fluentui/react-icons-atomic-webpack-loader', + options: { + iconVariant: 'svg', + }, + }, + ], }, { test: /\.tsx?$/, From 59750ee6b3ce56c4fde223968f96a3d929da2d20 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Tue, 14 Apr 2026 17:06:12 +0200 Subject: [PATCH 7/8] chore(demo-app): add dummy headless showcase --- packages/icon-transform-demo/src/app.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/icon-transform-demo/src/app.tsx b/packages/icon-transform-demo/src/app.tsx index 13feb11d52a..efab1a481ec 100644 --- a/packages/icon-transform-demo/src/app.tsx +++ b/packages/icon-transform-demo/src/app.tsx @@ -34,6 +34,8 @@ import { iconRegularClassName, } from '@fluentui/react-icons'; +// import { AccessTimeRegular as AccessTimeRegularHeadless } from '@fluentui/react-icons/headless/svg/access-time'; + // ---------- compound (bundled) icons ---------- const AccessTime = bundleIcon(AccessTimeFilled, AccessTimeRegular); const Accessibility = bundleIcon(AccessibilityFilled, AccessibilityRegular); @@ -134,6 +136,7 @@ export function App() { {icons.map(({ Icon, name }) => ( ))} + {/* */} ); From 904c0309f0a73d038946c61495a85592b2fe2a91 Mon Sep 17 00:00:00 2001 From: Martin Hochel Date: Wed, 6 May 2026 13:58:10 +0200 Subject: [PATCH 8/8] chore(demo-app): update how we transform --- package-lock.json | 1 + packages/icon-transform-demo/fluent-icons-transform.js | 2 ++ packages/icon-transform-demo/src/app.tsx | 6 +++--- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 03b592fbc62..ecfd677aee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30463,6 +30463,7 @@ "@babel/preset-env": "^7.26.0", "@babel/preset-react": "^7.26.0", "@babel/preset-typescript": "^7.26.0", + "@fluentui/react-icons-atomic-webpack-loader": "*", "@fluentui/react-icons-font-subsetting-webpack-plugin": "*", "@fluentui/react-icons-svg-sprite-subsetting-webpack-plugin": "*", "@types/react": "19.1.13", diff --git a/packages/icon-transform-demo/fluent-icons-transform.js b/packages/icon-transform-demo/fluent-icons-transform.js index 6177fce83b2..da97b969fb1 100644 --- a/packages/icon-transform-demo/fluent-icons-transform.js +++ b/packages/icon-transform-demo/fluent-icons-transform.js @@ -1,5 +1,7 @@ // @ts-check +// NOTE: this is not used anymore - we use our new webpack loader + /** * Resolves a @fluentui/react-icons import name to its atomic module path. * @param {string} importName - The named export being imported. diff --git a/packages/icon-transform-demo/src/app.tsx b/packages/icon-transform-demo/src/app.tsx index efab1a481ec..f52290733bf 100644 --- a/packages/icon-transform-demo/src/app.tsx +++ b/packages/icon-transform-demo/src/app.tsx @@ -3,8 +3,8 @@ import { makeStyles } from '@griffel/react'; // ---------------------------------------------------------------- // All imports below use the barrel entry `@fluentui/react-icons`. -// At build time, `babel-plugin-transform-imports` rewrites each -// named import to its atomic module path, e.g.: +// At build time, `@fluentui/react-icons-atomic-webpack-loader` +// rewrites each named import to its atomic module path, e.g.: // import { AddRegular } from '@fluentui/react-icons' // → import { AddRegular } from '@fluentui/react-icons/svg/add' // ---------------------------------------------------------------- @@ -129,7 +129,7 @@ export function App() {

20 icons (10 groups × Filled + Regular) imported via barrel entry.
- babel-plugin-transform-imports rewrites them to atomic paths at build time. + @fluentui/react-icons-atomic-webpack-loader rewrites them to atomic paths at build time.