@fluentui/react-icons-atomic-webpack-loader 0.0.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,121 @@
1
+ # @fluentui/react-icons-atomic-webpack-loader
2
+
3
+ > **⚠️ 0.x** — this package is in early development and follows [zero-based major semver](https://0ver.org/).
4
+ > Breaking changes may occur in minor releases until 1.0.
5
+
6
+ Webpack loader that transforms barrel imports and re-exports from `@fluentui/react-icons` into atomic deep paths for better tree-shaking and smaller bundles.
7
+
8
+ ## Before / After
9
+
10
+ ```js
11
+ // Before — barrel import pulls in the entire icon set
12
+ import { AddFilled, bundleIcon, useIconContext } from '@fluentui/react-icons';
13
+ export { ArrowLeftRegular } from '@fluentui/react-icons';
14
+
15
+ // After — each reference resolves to a small, isolated module
16
+ import { AddFilled } from '@fluentui/react-icons/svg/add';
17
+ import { bundleIcon } from '@fluentui/react-icons/utils';
18
+ import { useIconContext } from '@fluentui/react-icons/providers';
19
+ export { ArrowLeftRegular } from '@fluentui/react-icons/svg/arrow-left';
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ 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:
25
+
26
+ 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.
27
+
28
+ ```js
29
+ // webpack.config.js
30
+ module.exports = {
31
+ module: {
32
+ rules: [
33
+ {
34
+ test: /\.[mc]?[jt]sx?$/,
35
+ enforce: 'pre',
36
+ use: ['@fluentui/react-icons-atomic-webpack-loader'],
37
+ },
38
+ // … your other rules (babel-loader, ts-loader, etc.)
39
+ ],
40
+ },
41
+ };
42
+ ```
43
+
44
+ If your existing rules exclude `node_modules`, add a separate rule to cover dependencies:
45
+
46
+ ```js
47
+ module.exports = {
48
+ module: {
49
+ rules: [
50
+ {
51
+ test: /\.[mc]?[jt]sx?$/,
52
+ include: /[\\/]node_modules[\\/]/,
53
+ enforce: 'pre',
54
+ use: ['@fluentui/react-icons-atomic-webpack-loader'],
55
+ },
56
+ // … your other rules (babel-loader, ts-loader, etc.)
57
+ ],
58
+ },
59
+ };
60
+ ```
61
+
62
+ ## Options
63
+
64
+ | Option | Type | Default | Description |
65
+ | ------------- | -------------------------------------- | ------- | ------------------------------------------------------------------ |
66
+ | `iconVariant` | `'svg'` \| `'fonts'` \| `'svg-sprite'` | `'svg'` | Whether icons resolve to SVG, font-based, or SVG sprite components |
67
+
68
+ ### Using font icons
69
+
70
+ ```js
71
+ {
72
+ test: /\.[mc]?[jt]sx?$/,
73
+ enforce: 'pre',
74
+ use: [
75
+ {
76
+ loader: '@fluentui/react-icons-atomic-webpack-loader',
77
+ options: {
78
+ iconVariant: 'fonts',
79
+ },
80
+ },
81
+ ],
82
+ }
83
+ ```
84
+
85
+ This changes icon resolution from `@fluentui/react-icons/svg/*` to `@fluentui/react-icons/fonts/*`. Non-icon exports (`utils`, `providers`) are unaffected.
86
+
87
+ ### Using SVG sprite icons
88
+
89
+ ```js
90
+ {
91
+ test: /\.[mc]?[jt]sx?$/,
92
+ enforce: 'pre',
93
+ use: [
94
+ {
95
+ loader: '@fluentui/react-icons-atomic-webpack-loader',
96
+ options: {
97
+ iconVariant: 'svg-sprite',
98
+ },
99
+ },
100
+ ],
101
+ }
102
+ ```
103
+
104
+ This changes icon resolution from `@fluentui/react-icons/svg/*` to `@fluentui/react-icons/svg-sprite/*`. Non-icon exports (`utils`, `providers`) are unaffected.
105
+
106
+ ## How it works
107
+
108
+ 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:
109
+
110
+ | Export type | Example | Resolved path |
111
+ | -------------- | ------------------------------------------------ | -------------------------------------------------------------------- |
112
+ | Icon component | `AddFilled`, `ArrowLeftRegular` | `@fluentui/react-icons/svg/add` (or `/fonts/add`, `/svg-sprite/add`) |
113
+ | Context / hook | `useIconContext`, `IconDirectionContextProvider` | `@fluentui/react-icons/providers` |
114
+ | Utility | `bundleIcon`, `createFluentIcon` | `@fluentui/react-icons/utils` |
115
+
116
+ Files that don't reference `@fluentui/react-icons` are passed through untouched (fast regex pre-check).
117
+
118
+ ## Requirements
119
+
120
+ - `webpack` >= 5
121
+ - `@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' | 'svg-sprite';
4
+ }
5
+ export default function fluentIconsAtomicImportLoader(this: LoaderContext<FluentIconsAtomicImportLoaderOptions>, sourceCode: string): void;
package/lib/index.js ADDED
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const transform_1 = require("./transform");
4
+ function fluentIconsAtomicImportLoader(sourceCode) {
5
+ const { resourcePath } = this;
6
+ if (!sourceCode.includes('@fluentui/react-icons')) {
7
+ return this.callback(null, sourceCode);
8
+ }
9
+ const { iconVariant = 'svg' } = this.getOptions();
10
+ try {
11
+ const { code, map } = (0, transform_1.transformSource)(sourceCode, { iconVariant, path: resourcePath });
12
+ return this.callback(null, code, map);
13
+ }
14
+ catch {
15
+ return this.callback(new Error(`FluentIconsAtomicImportLoader: Failed to transform "${resourcePath}"`));
16
+ }
17
+ }
18
+ exports.default = fluentIconsAtomicImportLoader;
@@ -0,0 +1,11 @@
1
+ import MagicString from 'magic-string';
2
+ interface TransformOptions {
3
+ iconVariant: 'svg' | 'fonts' | 'svg-sprite';
4
+ path: string;
5
+ }
6
+ export interface TransformResult {
7
+ code: string;
8
+ map: ReturnType<MagicString['generateMap']>;
9
+ }
10
+ export declare function transformSource(source: string, options: TransformOptions): TransformResult;
11
+ export {};
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.transformSource = void 0;
7
+ const oxc_parser_1 = require("oxc-parser");
8
+ const magic_string_1 = __importDefault(require("magic-string"));
9
+ const MODULE_NAME = '@fluentui/react-icons';
10
+ const ICON_SUFFIX_REGEX = /(\d*)?(Regular|Filled|Light|Color)$/;
11
+ function getAtomicImportPath(importName, iconVariant) {
12
+ if (importName === 'useIconContext' || importName === 'IconDirectionContextProvider') {
13
+ return '@fluentui/react-icons/providers';
14
+ }
15
+ const isIcon = importName.match(ICON_SUFFIX_REGEX);
16
+ if (!isIcon) {
17
+ return '@fluentui/react-icons/utils';
18
+ }
19
+ const withoutSuffix = importName.replace(ICON_SUFFIX_REGEX, '');
20
+ const kebabCase = withoutSuffix.replace(/[a-z\d](?=[A-Z])|[a-zA-Z](?=\d)|[A-Z](?=[A-Z][a-z])/g, '$&-').toLowerCase();
21
+ return `@fluentui/react-icons/${iconVariant}/${kebabCase}`;
22
+ }
23
+ function transformSource(source, options) {
24
+ const { iconVariant, path } = options;
25
+ const result = (0, oxc_parser_1.parseSync)(path, source, {
26
+ sourceType: 'module',
27
+ });
28
+ if (result.errors.length > 0) {
29
+ throw new Error(result.errors[0].message);
30
+ }
31
+ const { staticImports, staticExports } = result.module;
32
+ const src = new magic_string_1.default(source);
33
+ for (const imp of staticImports) {
34
+ if (imp.moduleRequest.value !== MODULE_NAME)
35
+ continue;
36
+ const namedEntries = imp.entries.filter((e) => e.importName.kind === 'Name');
37
+ if (namedEntries.length === 0)
38
+ continue;
39
+ const otherEntries = imp.entries.filter((e) => e.importName.kind !== 'Name');
40
+ const lines = [];
41
+ if (otherEntries.length > 0) {
42
+ const names = otherEntries
43
+ .map((e) => (e.importName.kind === 'Default' ? e.localName.value : `* as ${e.localName.value}`))
44
+ .join(', ');
45
+ lines.push(`import ${names} from '${MODULE_NAME}';`);
46
+ }
47
+ for (const entry of namedEntries) {
48
+ const importedName = entry.importName.name;
49
+ const localName = entry.localName.value;
50
+ const newSource = getAtomicImportPath(importedName, iconVariant);
51
+ const spec = importedName === localName ? importedName : `${importedName} as ${localName}`;
52
+ lines.push(`import { ${spec} } from '${newSource}';`);
53
+ }
54
+ src.overwrite(imp.start, imp.end, lines.join('\n'));
55
+ }
56
+ for (const exp of staticExports) {
57
+ const relevantEntries = exp.entries.filter((e) => e.moduleRequest?.value === MODULE_NAME && e.exportName.kind === 'Name');
58
+ if (relevantEntries.length === 0)
59
+ continue;
60
+ const lines = [];
61
+ for (const entry of relevantEntries) {
62
+ const importedName = entry.importName.name;
63
+ const exportedName = entry.exportName.name;
64
+ const newSource = getAtomicImportPath(importedName, iconVariant);
65
+ const spec = importedName === exportedName ? importedName : `${importedName} as ${exportedName}`;
66
+ lines.push(`export { ${spec} } from '${newSource}';`);
67
+ }
68
+ src.overwrite(exp.start, exp.end, lines.join('\n'));
69
+ }
70
+ return {
71
+ code: src.toString(),
72
+ map: src.generateMap({ hires: true }),
73
+ };
74
+ }
75
+ exports.transformSource = transformSource;
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@fluentui/react-icons-atomic-webpack-loader",
3
+ "version": "0.0.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
+ "magic-string": "^0.30.0",
24
+ "oxc-parser": "^0.125.0"
25
+ },
26
+ "devDependencies": {
27
+ "@fluentui/react-icons": "*",
28
+ "ts-loader": "^9.5.0",
29
+ "typescript": "5.0.4",
30
+ "webpack": "^5.72.0",
31
+ "@types/node": "22"
32
+ },
33
+ "peerDependencies": {
34
+ "webpack": ">=5.0.0"
35
+ },
36
+ "files": [
37
+ "lib/*"
38
+ ]
39
+ }