@fluentui/react-icons-atomic-webpack-loader 2.0.324-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @fluentui/react-icons-atomic-webpack-loader
2
+
3
+ > **⚠️ Alpha** — this package is available as an alpha prerelease only.
4
+ > ⚠️ The API may change before the first stable release.
5
+
6
+ > Install via `npm install @fluentui/react-icons-atomic-webpack-loader@alpha --save-dev`
7
+
8
+ Webpack loader that transforms barrel imports and re-exports from `@fluentui/react-icons` into atomic deep paths for better tree-shaking and smaller bundles.
9
+
10
+ ## Before / After
11
+
12
+ ```js
13
+ // Before — barrel import pulls in the entire icon set
14
+ import { AddFilled, bundleIcon, useIconContext } from '@fluentui/react-icons';
15
+ export { ArrowLeftRegular } from '@fluentui/react-icons';
16
+
17
+ // After — each reference resolves to a small, isolated module
18
+ import { AddFilled } from '@fluentui/react-icons/svg/add';
19
+ import { bundleIcon } from '@fluentui/react-icons/utils';
20
+ import { useIconContext } from '@fluentui/react-icons/providers';
21
+ export { ArrowLeftRegular } from '@fluentui/react-icons/svg/arrow-left';
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Add the loader to your webpack config as an [`enforce: 'pre'`](https://webpack.js.org/configuration/module/#ruleenforce) rule so it runs on the original source before any other loaders:
27
+
28
+ NOTE: Unlike most loaders, this one should NOT exclude `node_modules`. It needs to process files inside `node_modules` as well to transform barrel imports from `@fluentui/react-icons` in your third-party dependencies. Files that don't reference `@fluentui/react-icons` are skipped via a fast regex pre-check, so there is no meaningful overhead.
29
+
30
+ ```js
31
+ // webpack.config.js
32
+ module.exports = {
33
+ module: {
34
+ rules: [
35
+ {
36
+ test: /\.[mc]?[jt]sx?$/,
37
+ enforce: 'pre',
38
+ use: ['@fluentui/react-icons-atomic-webpack-loader'],
39
+ },
40
+ // … your other rules (babel-loader, ts-loader, etc.)
41
+ ],
42
+ },
43
+ };
44
+ ```
45
+
46
+ If your existing rules exclude `node_modules`, add a separate rule to cover dependencies:
47
+
48
+ ```js
49
+ module.exports = {
50
+ module: {
51
+ rules: [
52
+ {
53
+ test: /\.[mc]?[jt]sx?$/,
54
+ include: /[\\/]node_modules[\\/]/,
55
+ enforce: 'pre',
56
+ use: ['@fluentui/react-icons-atomic-webpack-loader'],
57
+ },
58
+ // … your other rules (babel-loader, ts-loader, etc.)
59
+ ],
60
+ },
61
+ };
62
+ ```
63
+
64
+ ## Options
65
+
66
+ | Option | Type | Default | Description |
67
+ | ------------- | -------------------- | ------- | ----------------------------------------------------- |
68
+ | `iconVariant` | `'svg'` \| `'fonts'` | `'svg'` | Whether icons resolve to SVG or font-based components |
69
+
70
+ ### Using font icons
71
+
72
+ ```js
73
+ {
74
+ test: /\.[mc]?[jt]sx?$/,
75
+ enforce: 'pre',
76
+ use: [
77
+ {
78
+ loader: '@fluentui/react-icons-atomic-webpack-loader',
79
+ options: {
80
+ iconVariant: 'fonts',
81
+ },
82
+ },
83
+ ],
84
+ }
85
+ ```
86
+
87
+ This changes icon resolution from `@fluentui/react-icons/svg/*` to `@fluentui/react-icons/fonts/*`. Non-icon exports (`utils`, `providers`) are unaffected.
88
+
89
+ ## How it works
90
+
91
+ The loader uses a Babel transform to rewrite import and re-export declarations that reference `@fluentui/react-icons`. Each named specifier is routed to an atomic subpath based on its name:
92
+
93
+ | Export type | Example | Resolved path |
94
+ | -------------- | ------------------------------------------------ | ------------------------------------------------- |
95
+ | Icon component | `AddFilled`, `ArrowLeftRegular` | `@fluentui/react-icons/svg/add` (or `/fonts/add`) |
96
+ | Context / hook | `useIconContext`, `IconDirectionContextProvider` | `@fluentui/react-icons/providers` |
97
+ | Utility | `bundleIcon`, `createFluentIcon` | `@fluentui/react-icons/utils` |
98
+
99
+ Files that don't reference `@fluentui/react-icons` are passed through untouched (fast regex pre-check).
100
+
101
+ ## Requirements
102
+
103
+ - `webpack` >= 5
104
+ - `@fluentui/react-icons` >= 2 (with atomic subpath exports)
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { LoaderContext } from 'webpack';
2
+ export interface FluentIconsAtomicImportLoaderOptions {
3
+ iconVariant?: 'svg' | 'fonts';
4
+ }
5
+ export default function fluentIconsAtomicImportLoader(this: LoaderContext<FluentIconsAtomicImportLoaderOptions>, sourceCode: string): void;
package/lib/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const transform_1 = require("./transform");
4
+ const REACT_ICONS_IMPORT_REGEX = /['"]@fluentui\/react-icons['";\s]/;
5
+ function fluentIconsAtomicImportLoader(sourceCode) {
6
+ if (!REACT_ICONS_IMPORT_REGEX.test(sourceCode)) {
7
+ this.callback(null, sourceCode);
8
+ return;
9
+ }
10
+ const options = this.getOptions();
11
+ const iconVariant = options.iconVariant ?? 'svg';
12
+ const isTsx = this.resourcePath.endsWith('.tsx');
13
+ const isTypescript = /\.[mc]?tsx?$/.test(this.resourcePath);
14
+ try {
15
+ const { code, map } = (0, transform_1.transformSource)(sourceCode, { iconVariant, isTypescript, isTsx });
16
+ this.callback(null, code, map);
17
+ }
18
+ catch {
19
+ this.emitWarning(new Error(`FluentIconsAtomicImportLoader: Failed to transform "${this.resourcePath}"`));
20
+ this.callback(null, sourceCode);
21
+ }
22
+ }
23
+ exports.default = fluentIconsAtomicImportLoader;
@@ -0,0 +1,12 @@
1
+ import MagicString from 'magic-string';
2
+ interface TransformOptions {
3
+ iconVariant: 'svg' | 'fonts';
4
+ isTypescript: boolean;
5
+ isTsx: boolean;
6
+ }
7
+ export interface TransformResult {
8
+ code: string;
9
+ map: ReturnType<MagicString['generateMap']>;
10
+ }
11
+ export declare function transformSource(source: string, options: TransformOptions): TransformResult;
12
+ export {};
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.transformSource = void 0;
30
+ const acorn = __importStar(require("acorn"));
31
+ const acorn_typescript_1 = __importDefault(require("acorn-typescript"));
32
+ const magic_string_1 = __importDefault(require("magic-string"));
33
+ const MODULE_NAME = '@fluentui/react-icons';
34
+ const ICON_SUFFIX_REGEX = /(\d*)?(Regular|Filled|Light|Color)$/;
35
+ function getAtomicImportPath(importName, iconVariant) {
36
+ if (importName === 'useIconContext' || importName === 'IconDirectionContextProvider') {
37
+ return '@fluentui/react-icons/providers';
38
+ }
39
+ const isIcon = importName.match(ICON_SUFFIX_REGEX);
40
+ if (!isIcon) {
41
+ return '@fluentui/react-icons/utils';
42
+ }
43
+ const withoutSuffix = importName.replace(ICON_SUFFIX_REGEX, '');
44
+ const kebabCase = withoutSuffix.replace(/[a-z\d](?=[A-Z])|[a-zA-Z](?=\d)|[A-Z](?=[A-Z][a-z])/g, '$&-').toLowerCase();
45
+ return `@fluentui/react-icons/${iconVariant}/${kebabCase}`;
46
+ }
47
+ function getName(node) {
48
+ return (node.name ?? String(node.value));
49
+ }
50
+ function getParser(options) {
51
+ if (!options.isTypescript) {
52
+ return acorn.Parser;
53
+ }
54
+ const plugin = (0, acorn_typescript_1.default)(options.isTsx ? { jsx: {} } : undefined);
55
+ return acorn.Parser.extend(plugin);
56
+ }
57
+ function transformSource(source, options) {
58
+ const parser = getParser(options);
59
+ const ast = parser.parse(source, {
60
+ sourceType: 'module',
61
+ ecmaVersion: 'latest',
62
+ locations: false,
63
+ });
64
+ const src = new magic_string_1.default(source);
65
+ for (const node of ast.body) {
66
+ if (node.type === 'ImportDeclaration') {
67
+ const n = node;
68
+ if (n.source.value !== MODULE_NAME) {
69
+ continue;
70
+ }
71
+ const memberImports = n.specifiers.filter((s) => s.type === 'ImportSpecifier');
72
+ if (memberImports.length === 0) {
73
+ continue;
74
+ }
75
+ const fullImports = n.specifiers.filter((s) => s.type !== 'ImportSpecifier');
76
+ const lines = [];
77
+ if (fullImports.length > 0) {
78
+ const names = fullImports
79
+ .map((s) => (s.type === 'ImportDefaultSpecifier' ? s.local.name : `* as ${s.local.name}`))
80
+ .join(', ');
81
+ lines.push(`import ${names} from '${MODULE_NAME}';`);
82
+ }
83
+ for (const specifier of memberImports) {
84
+ const importedName = getName(specifier.imported);
85
+ const localName = specifier.local.name;
86
+ const newSource = getAtomicImportPath(importedName, options.iconVariant);
87
+ const spec = importedName === localName ? importedName : `${importedName} as ${localName}`;
88
+ lines.push(`import { ${spec} } from '${newSource}';`);
89
+ }
90
+ src.overwrite(n.start, n.end, lines.join('\n'));
91
+ }
92
+ if (node.type === 'ExportNamedDeclaration') {
93
+ const n = node;
94
+ if (!n.source || n.source.value !== MODULE_NAME) {
95
+ continue;
96
+ }
97
+ const specifiers = n.specifiers.filter((s) => s.type === 'ExportSpecifier');
98
+ if (specifiers.length === 0) {
99
+ continue;
100
+ }
101
+ const lines = [];
102
+ for (const specifier of specifiers) {
103
+ const localName = getName(specifier.local);
104
+ const exportedName = getName(specifier.exported);
105
+ const newSource = getAtomicImportPath(localName, options.iconVariant);
106
+ const spec = localName === exportedName ? localName : `${localName} as ${exportedName}`;
107
+ lines.push(`export { ${spec} } from '${newSource}';`);
108
+ }
109
+ src.overwrite(n.start, n.end, lines.join('\n'));
110
+ }
111
+ }
112
+ return {
113
+ code: src.toString(),
114
+ map: src.generateMap({ hires: true }),
115
+ };
116
+ }
117
+ exports.transformSource = transformSource;
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@fluentui/react-icons-atomic-webpack-loader",
3
+ "version": "2.0.324-alpha.1",
4
+ "description": "Webpack loader that transforms barrel imports and re-exports from @fluentui/react-icons into atomic deep paths",
5
+ "main": "lib/index.js",
6
+ "scripts": {
7
+ "build": "tsc -p .",
8
+ "test": "webpack -c test/webpack.config.js"
9
+ },
10
+ "engines": {
11
+ "node": ">=20.0.0"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/microsoft/fluentui-system-icons.git",
16
+ "directory": "packages/react-icons-atomic-webpack-loader"
17
+ },
18
+ "license": "MIT",
19
+ "bugs": {
20
+ "url": "https://github.com/microsoft/fluentui-system-icons/issues"
21
+ },
22
+ "dependencies": {
23
+ "acorn-typescript": "^1.4.13",
24
+ "magic-string": "^0.30.0"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "5.0.4",
28
+ "webpack": "^5.72.0",
29
+ "@types/node": "22"
30
+ },
31
+ "peerDependencies": {
32
+ "acorn": ">=8.9.0",
33
+ "webpack": ">=5.0.0"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
38
+ "files": [
39
+ "lib/*"
40
+ ]
41
+ }