@fluentui/react-icons-atomic-webpack-loader 0.0.5 → 0.0.6
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 +86 -5
- package/lib/index.d.ts +18 -0
- package/lib/index.js +2 -1
- package/lib/transform.d.ts +6 -0
- package/lib/transform.js +165 -2
- package/package.json +5 -8
package/README.md
CHANGED
|
@@ -72,11 +72,12 @@ module.exports = {
|
|
|
72
72
|
|
|
73
73
|
## Options
|
|
74
74
|
|
|
75
|
-
| Option
|
|
76
|
-
|
|
|
77
|
-
| `iconVariant`
|
|
78
|
-
| `fallbackVariant`
|
|
79
|
-
| `headless`
|
|
75
|
+
| Option | Type | Default | Description |
|
|
76
|
+
| --------------------- | -------------------------------------- | ----------- | --------------------------------------------------------------------------------------- |
|
|
77
|
+
| `iconVariant` | `'svg'` \| `'fonts'` \| `'svg-sprite'` | `'svg'` | Variant icons resolve to. Applied to every supported module. |
|
|
78
|
+
| `fallbackVariant` | `'svg'` \| `'fonts'` \| `'svg-sprite'` | `undefined` | Variant used for a module that does not support `iconVariant` (see below). |
|
|
79
|
+
| `headless` | `boolean` | `false` | Resolve to the headless (Griffel-free) build where the module ships one. |
|
|
80
|
+
| `allowDynamicImports` | `boolean` | `false` | Atomize a narrow, statically-provable subset of dynamic `import()` barrels (see below). |
|
|
80
81
|
|
|
81
82
|
### Variant resolution & `fallbackVariant`
|
|
82
83
|
|
|
@@ -247,6 +248,86 @@ flowchart TD
|
|
|
247
248
|
|
|
248
249
|
Files that don't reference a supported module are passed through untouched (fast pre-check).
|
|
249
250
|
|
|
251
|
+
## Limitations
|
|
252
|
+
|
|
253
|
+
### Dynamic imports are not atomized
|
|
254
|
+
|
|
255
|
+
The loader only rewrites **static** `import` / `export … from` declarations. A dynamic `import()` of a barrel cannot be atomized, because the returned module-namespace object is a runtime value whose usage the loader cannot statically prove:
|
|
256
|
+
|
|
257
|
+
```js
|
|
258
|
+
// ⚠️ Not rewritten — the ENTIRE icon set is pulled into the async chunk.
|
|
259
|
+
const { AddFilled } = await import('@fluentui/react-icons');
|
|
260
|
+
React.lazy(() => import('@fluentui/react-icons'));
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
When it detects a dynamic import of a supported barrel, the loader emits a warning. Import the atomic path directly instead — then you lazy-load only the icons you use:
|
|
264
|
+
|
|
265
|
+
```js
|
|
266
|
+
// ✅ Only this icon lands in the async chunk.
|
|
267
|
+
const { AddFilled } = await import('@fluentui/react-icons/svg/add');
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Alternatively, move the icons behind a local module that statically imports them; the loader atomizes that module, and only your lazy chunk pays for what it uses.
|
|
271
|
+
|
|
272
|
+
> The same applies to full-barrel subpaths (`@fluentui/react-icons/svg`, `@fluentui/react-icons/fonts`) — dynamically importing those also bundles the whole set. Always target a per-icon atomic path.
|
|
273
|
+
|
|
274
|
+
### Opt-in: `allowDynamicImports`
|
|
275
|
+
|
|
276
|
+
> **Prefer a dedicated module of static imports.** The most robust pattern is a
|
|
277
|
+
> small module that statically imports the icons you need and is itself lazy-loaded
|
|
278
|
+
> (`const { AddFilled } = await import('./lazy-icons')`). Static imports are
|
|
279
|
+
> atomized unconditionally, tree-shake predictably, and avoid every gotcha below.
|
|
280
|
+
> Reach for `allowDynamicImports` only when refactoring to that pattern isn't
|
|
281
|
+
> practical.
|
|
282
|
+
|
|
283
|
+
With `allowDynamicImports: true`, the loader additionally rewrites a **narrow,
|
|
284
|
+
statically-provable** subset of dynamic barrel imports into atomic ones. Only two
|
|
285
|
+
call-site shapes qualify — where the imported names are literals at the `import()`:
|
|
286
|
+
|
|
287
|
+
```js
|
|
288
|
+
// await + object destructure
|
|
289
|
+
const { AddFilled } = await import('@fluentui/react-icons');
|
|
290
|
+
// → const { AddFilled } = await import('@fluentui/react-icons/svg/add');
|
|
291
|
+
|
|
292
|
+
// .then + object-pattern callback param
|
|
293
|
+
import('@fluentui/react-icons').then(({ AddFilled }) => …);
|
|
294
|
+
// → import('@fluentui/react-icons/svg/add').then(({ AddFilled }) => …);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Names from the **same** atom are grouped into one import; names from **different**
|
|
298
|
+
atoms become a positional `Promise.all([...])`:
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
const { AddFilled, ArrowLeftRegular } = await import('@fluentui/react-icons');
|
|
302
|
+
// →
|
|
303
|
+
const [{ AddFilled }, { ArrowLeftRegular }] = await Promise.all([
|
|
304
|
+
import('@fluentui/react-icons/svg/add'),
|
|
305
|
+
import('@fluentui/react-icons/svg/arrow-left'),
|
|
306
|
+
]);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
It honors `iconVariant` / `fallbackVariant` / `headless` and per-name color
|
|
310
|
+
rerouting exactly like static imports.
|
|
311
|
+
|
|
312
|
+
#### Gotchas — what is **not** rewritten (left untouched, still warns)
|
|
313
|
+
|
|
314
|
+
- **Namespace binding**: `const ns = await import('@fluentui/react-icons')` — `ns`
|
|
315
|
+
is a runtime object; usage isn't statically known, so the whole set ships.
|
|
316
|
+
- **`.then(m => m.AddFilled)`**: namespace parameter + member access — not a
|
|
317
|
+
destructure, so the names aren't visible at the call site.
|
|
318
|
+
- **Rest / computed / default / nested patterns**: `{ AddFilled, ...rest }`,
|
|
319
|
+
`{ [name]: icon }`, `{ AddFilled = fallback }`, `{ Add: { … } }`.
|
|
320
|
+
- **Non-literal specifiers**: `import(pkg)`, template interpolation, or a promise
|
|
321
|
+
stored in a variable and `.then`-ed elsewhere.
|
|
322
|
+
|
|
323
|
+
For any of these the loader leaves your code as-is and emits the standard
|
|
324
|
+
"cannot be atomized" warning — the safe default.
|
|
325
|
+
|
|
326
|
+
> ⚠️ The `Promise.all` rewrite changes the emitted runtime structure (parallel
|
|
327
|
+
> chunk loading, positional destructuring). It's semantically equivalent for the
|
|
328
|
+
> supported shapes, but if you depend on the exact expression shape, prefer the
|
|
329
|
+
> dedicated-module pattern above.
|
|
330
|
+
|
|
250
331
|
## Requirements
|
|
251
332
|
|
|
252
333
|
- `webpack` >= 5
|
package/lib/index.d.ts
CHANGED
|
@@ -39,5 +39,23 @@ export interface FluentIconsAtomicImportLoaderOptions {
|
|
|
39
39
|
* `headless/fonts/styles.css` for font icons) in your app entry point.
|
|
40
40
|
*/
|
|
41
41
|
headless?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Rewrite a **narrow, statically-provable** subset of dynamic `import()` barrel
|
|
44
|
+
* calls into atomic dynamic imports. Defaults to `false`.
|
|
45
|
+
*
|
|
46
|
+
* Only two shapes are rewritten, where the imported names are known literals at
|
|
47
|
+
* the call site:
|
|
48
|
+
* - `const { AddFilled } = await import('@fluentui/react-icons')`
|
|
49
|
+
* - `import('@fluentui/react-icons').then(({ AddFilled }) => …)`
|
|
50
|
+
*
|
|
51
|
+
* Names from the same atom are grouped into one import; names from different
|
|
52
|
+
* atoms become a positional `Promise.all([...])`. Anything else (namespace
|
|
53
|
+
* binding `const ns = await import(…)`, `.then(m => m.X)`, rest/computed/default
|
|
54
|
+
* patterns, non-literal specifiers) is left untouched and still warns.
|
|
55
|
+
*
|
|
56
|
+
* Prefer a dedicated module of **static** atomic imports that you lazy-load
|
|
57
|
+
* (`import('./icons')`) over relying on this; see the README for the gotchas.
|
|
58
|
+
*/
|
|
59
|
+
allowDynamicImports?: boolean;
|
|
42
60
|
}
|
|
43
61
|
export default function fluentIconsAtomicImportLoader(this: LoaderContext<FluentIconsAtomicImportLoaderOptions>, sourceCode: string): void;
|
package/lib/index.js
CHANGED
|
@@ -9,7 +9,7 @@ function fluentIconsAtomicImportLoader(sourceCode) {
|
|
|
9
9
|
if (!modules_1.SUPPORTED_MODULE_NAMES.some((name) => sourceCode.includes(name))) {
|
|
10
10
|
return this.callback(null, sourceCode);
|
|
11
11
|
}
|
|
12
|
-
const { iconVariant = 'svg', fallbackVariant, headless = false } = this.getOptions();
|
|
12
|
+
const { iconVariant = 'svg', fallbackVariant, headless = false, allowDynamicImports = false } = this.getOptions();
|
|
13
13
|
let code;
|
|
14
14
|
let map;
|
|
15
15
|
let diagnostics;
|
|
@@ -18,6 +18,7 @@ function fluentIconsAtomicImportLoader(sourceCode) {
|
|
|
18
18
|
iconVariant,
|
|
19
19
|
fallbackVariant,
|
|
20
20
|
headless,
|
|
21
|
+
allowDynamicImports,
|
|
21
22
|
path: resourcePath,
|
|
22
23
|
}));
|
|
23
24
|
}
|
package/lib/transform.d.ts
CHANGED
|
@@ -7,6 +7,12 @@ interface TransformOptions {
|
|
|
7
7
|
fallbackVariant?: IconVariant;
|
|
8
8
|
/** Resolve to the headless (Griffel-free) build where the module supports it. */
|
|
9
9
|
headless?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Rewrite a narrow, statically-provable subset of dynamic `import()` barrel
|
|
12
|
+
* calls into atomic dynamic imports (see {@link rewriteDynamicImports}).
|
|
13
|
+
* Defaults to `false`. Un-rewritable dynamic barrel imports still warn.
|
|
14
|
+
*/
|
|
15
|
+
allowDynamicImports?: boolean;
|
|
10
16
|
path: string;
|
|
11
17
|
}
|
|
12
18
|
export interface Diagnostic {
|
package/lib/transform.js
CHANGED
|
@@ -8,14 +8,14 @@ const oxc_parser_1 = require("oxc-parser");
|
|
|
8
8
|
const magic_string_1 = __importDefault(require("magic-string"));
|
|
9
9
|
const modules_1 = require("./modules");
|
|
10
10
|
function transformSource(source, options) {
|
|
11
|
-
const { iconVariant, fallbackVariant, headless = false, path } = options;
|
|
11
|
+
const { iconVariant, fallbackVariant, headless = false, allowDynamicImports = false, path } = options;
|
|
12
12
|
const result = (0, oxc_parser_1.parseSync)(path, source, {
|
|
13
13
|
sourceType: 'module',
|
|
14
14
|
});
|
|
15
15
|
if (result.errors.length > 0) {
|
|
16
16
|
throw new Error(result.errors[0].message);
|
|
17
17
|
}
|
|
18
|
-
const { staticImports, staticExports } = result.module;
|
|
18
|
+
const { staticImports, staticExports, dynamicImports } = result.module;
|
|
19
19
|
const src = new magic_string_1.default(source);
|
|
20
20
|
const diagnostics = [];
|
|
21
21
|
// Dedupe diagnostics by message so a module's variant / color / headless
|
|
@@ -134,6 +134,169 @@ function transformSource(source, options) {
|
|
|
134
134
|
continue;
|
|
135
135
|
src.overwrite(exp.start, exp.end, lines.join('\n'));
|
|
136
136
|
}
|
|
137
|
+
// Source-literal start offsets of dynamic imports that were atomized below.
|
|
138
|
+
// Used to suppress the "cannot be atomized" warning for imports we rewrote.
|
|
139
|
+
const rewrittenImportStarts = new Set();
|
|
140
|
+
if (allowDynamicImports) {
|
|
141
|
+
/**
|
|
142
|
+
* Resolves one destructured binding name to the atomic subpath it should be
|
|
143
|
+
* imported from, honoring the active `iconVariant` / `headless` / color rules
|
|
144
|
+
* (same policy as static imports). Returns `null` when the owning module can't
|
|
145
|
+
* be resolved — `targetFor` has already recorded an error diagnostic — which
|
|
146
|
+
* signals the caller to bail and leave the dynamic import untouched.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* // iconVariant: 'svg'
|
|
150
|
+
* resolveNameSource(reactIcons, 'AddFilled') // → '@fluentui/react-icons/svg/add'
|
|
151
|
+
* resolveNameSource(reactIcons, 'bundleIcon') // → '@fluentui/react-icons/utils'
|
|
152
|
+
* // iconVariant: 'fonts'
|
|
153
|
+
* resolveNameSource(reactIcons, 'AddFilled') // → '@fluentui/react-icons/fonts/add'
|
|
154
|
+
*/
|
|
155
|
+
const resolveNameSource = (descriptor, importedName) => {
|
|
156
|
+
const target = targetFor(descriptor, (0, modules_1.isColorIconName)(importedName));
|
|
157
|
+
if (!target)
|
|
158
|
+
return null;
|
|
159
|
+
return descriptor.resolve(importedName, target.variant, target.headless);
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Groups the properties of a destructuring object pattern by the atomic module
|
|
163
|
+
* each imported name resolves to, preserving first-seen order. Each group's
|
|
164
|
+
* `specs` are the emit-ready specifier strings (`'AddFilled'`, or
|
|
165
|
+
* `'ArrowLeftRegular: arrow'` for a rename).
|
|
166
|
+
*
|
|
167
|
+
* Returns `null` (bail — leave the dynamic import untouched) when any property
|
|
168
|
+
* isn't a plain, statically-known `name → binding` pair: rest elements,
|
|
169
|
+
* computed/string keys, default values, or nested patterns.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* // `{ AddFilled, AddRegular }` — both live in the `add` atom
|
|
173
|
+
* // → [{ source: '@fluentui/react-icons/svg/add', specs: ['AddFilled', 'AddRegular'] }]
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* // `{ AddFilled, ArrowLeftRegular: arrow }` — different atoms, one renamed
|
|
177
|
+
* // → [
|
|
178
|
+
* // { source: '@fluentui/react-icons/svg/add', specs: ['AddFilled'] },
|
|
179
|
+
* // { source: '@fluentui/react-icons/svg/arrow-left', specs: ['ArrowLeftRegular: arrow'] },
|
|
180
|
+
* // ]
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* // `{ AddFilled, ...rest }` → null (rest element → bail)
|
|
184
|
+
*/
|
|
185
|
+
const buildGroups = (objectPattern, descriptor) => {
|
|
186
|
+
const bySource = new Map();
|
|
187
|
+
const order = [];
|
|
188
|
+
for (const prop of objectPattern.properties) {
|
|
189
|
+
if (prop.type !== 'Property' || prop.computed || prop.kind !== 'init')
|
|
190
|
+
return null;
|
|
191
|
+
if (prop.key.type !== 'Identifier' || prop.value.type !== 'Identifier')
|
|
192
|
+
return null;
|
|
193
|
+
const importedName = prop.key.name;
|
|
194
|
+
const localName = prop.value.name;
|
|
195
|
+
const resolvedSource = resolveNameSource(descriptor, importedName);
|
|
196
|
+
if (resolvedSource === null)
|
|
197
|
+
return null;
|
|
198
|
+
const spec = importedName === localName ? importedName : `${importedName}: ${localName}`;
|
|
199
|
+
if (!bySource.has(resolvedSource)) {
|
|
200
|
+
bySource.set(resolvedSource, []);
|
|
201
|
+
order.push(resolvedSource);
|
|
202
|
+
}
|
|
203
|
+
bySource.get(resolvedSource).push(spec);
|
|
204
|
+
}
|
|
205
|
+
if (order.length === 0)
|
|
206
|
+
return null;
|
|
207
|
+
return order.map((groupSource) => ({ source: groupSource, specs: bySource.get(groupSource) }));
|
|
208
|
+
};
|
|
209
|
+
const importCallText = (groups) => groups.length === 1
|
|
210
|
+
? `import('${groups[0].source}')`
|
|
211
|
+
: `Promise.all([${groups.map((g) => `import('${g.source}')`).join(', ')}])`;
|
|
212
|
+
const patternText = (groups) => groups.length === 1
|
|
213
|
+
? `{ ${groups[0].specs.join(', ')} }`
|
|
214
|
+
: `[${groups.map((g) => `{ ${g.specs.join(', ')} }`).join(', ')}]`;
|
|
215
|
+
const visitor = new oxc_parser_1.Visitor({
|
|
216
|
+
// `const { A, B } = await import('barrel')`
|
|
217
|
+
VariableDeclarator(node) {
|
|
218
|
+
if (node.id.type !== 'ObjectPattern' ||
|
|
219
|
+
node.init?.type !== 'AwaitExpression' ||
|
|
220
|
+
node.init.argument.type !== 'ImportExpression') {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const importExpr = node.init.argument;
|
|
224
|
+
if (importExpr.source.type !== 'Literal' || typeof importExpr.source.value !== 'string')
|
|
225
|
+
return;
|
|
226
|
+
const descriptor = (0, modules_1.getModuleDescriptor)(importExpr.source.value);
|
|
227
|
+
if (!descriptor)
|
|
228
|
+
return;
|
|
229
|
+
const groups = buildGroups(node.id, descriptor);
|
|
230
|
+
if (!groups)
|
|
231
|
+
return;
|
|
232
|
+
src.overwrite(node.start, node.end, `${patternText(groups)} = await ${importCallText(groups)}`);
|
|
233
|
+
rewrittenImportStarts.add(importExpr.source.start);
|
|
234
|
+
},
|
|
235
|
+
// `import('barrel').then(({ A, B }) => …)`
|
|
236
|
+
CallExpression(node) {
|
|
237
|
+
if (node.callee.type !== 'MemberExpression' ||
|
|
238
|
+
node.callee.computed ||
|
|
239
|
+
node.callee.property.type !== 'Identifier' ||
|
|
240
|
+
node.callee.property.name !== 'then' ||
|
|
241
|
+
node.callee.object.type !== 'ImportExpression') {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const importExpr = node.callee.object;
|
|
245
|
+
if (importExpr.source.type !== 'Literal' || typeof importExpr.source.value !== 'string')
|
|
246
|
+
return;
|
|
247
|
+
const descriptor = (0, modules_1.getModuleDescriptor)(importExpr.source.value);
|
|
248
|
+
if (!descriptor)
|
|
249
|
+
return;
|
|
250
|
+
const callback = node.arguments[0];
|
|
251
|
+
if (!callback || (callback.type !== 'ArrowFunctionExpression' && callback.type !== 'FunctionExpression')) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const param = callback.params[0];
|
|
255
|
+
if (param?.type !== 'ObjectPattern')
|
|
256
|
+
return;
|
|
257
|
+
const groups = buildGroups(param, descriptor);
|
|
258
|
+
if (!groups)
|
|
259
|
+
return;
|
|
260
|
+
src.overwrite(importExpr.start, importExpr.end, importCallText(groups));
|
|
261
|
+
// Multiple atoms resolve to an array, so the callback must destructure by
|
|
262
|
+
// position instead of by name.
|
|
263
|
+
if (groups.length > 1) {
|
|
264
|
+
src.overwrite(param.start, param.end, patternText(groups));
|
|
265
|
+
}
|
|
266
|
+
rewrittenImportStarts.add(importExpr.source.start);
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
visitor.visit(result.program);
|
|
270
|
+
}
|
|
271
|
+
// Dynamic imports of a barrel (`import('@fluentui/react-icons')`) cannot be
|
|
272
|
+
// atomized: the returned namespace object is a runtime value whose usage is
|
|
273
|
+
// not statically known, so the whole icon set ends up in the async chunk. We
|
|
274
|
+
// can't rewrite it safely, but we can warn and point at the atomic escape
|
|
275
|
+
// hatch (`import('@fluentui/react-icons/svg/add')`). Atomic and subpath
|
|
276
|
+
// requests don't match a barrel descriptor, so they never warn.
|
|
277
|
+
for (const dyn of dynamicImports) {
|
|
278
|
+
const request = dyn.moduleRequest;
|
|
279
|
+
if (!request)
|
|
280
|
+
continue;
|
|
281
|
+
// Skip imports we already atomized above (their usage was statically provable).
|
|
282
|
+
if (rewrittenImportStarts.has(request.start))
|
|
283
|
+
continue;
|
|
284
|
+
// Unlike static imports, oxc doesn't resolve a dynamic import's argument to a
|
|
285
|
+
// specifier value — it's an arbitrary expression. Match the raw span against
|
|
286
|
+
// each supported module's quoted spellings; this naturally ignores variables,
|
|
287
|
+
// interpolated templates, and subpath/atomic requests (module names never
|
|
288
|
+
// contain quotes).
|
|
289
|
+
const raw = source.slice(request.start, request.end);
|
|
290
|
+
const moduleName = modules_1.SUPPORTED_MODULE_NAMES.find((name) => raw === `'${name}'` || raw === `"${name}"` || raw === `\`${name}\``);
|
|
291
|
+
if (!moduleName)
|
|
292
|
+
continue;
|
|
293
|
+
pushDiagnostic({
|
|
294
|
+
level: 'warning',
|
|
295
|
+
message: `dynamic import of the "${moduleName}" barrel cannot be atomized, so the entire icon ` +
|
|
296
|
+
`set will be bundled into the async chunk. Import an atomic path directly instead, ` +
|
|
297
|
+
`e.g. import('${moduleName}/svg/add').`,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
137
300
|
return {
|
|
138
301
|
code: src.toString(),
|
|
139
302
|
map: src.generateMap({ hires: true }),
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluentui/react-icons-atomic-webpack-loader",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "Webpack loader that transforms barrel imports and re-exports from @fluentui/react-icons into atomic deep paths",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"build": "tsc -p .",
|
|
8
|
-
"
|
|
7
|
+
"build": "yarn run -T tsc -p .",
|
|
8
|
+
"lint": "yarn run -T eslint package.json",
|
|
9
|
+
"test": "yarn run -T vitest run && yarn run -T webpack -c test/webpack.config.js"
|
|
9
10
|
},
|
|
10
11
|
"engines": {
|
|
11
12
|
"node": ">=20.0.0"
|
|
@@ -24,11 +25,7 @@
|
|
|
24
25
|
"oxc-parser": "^0.125.0"
|
|
25
26
|
},
|
|
26
27
|
"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"
|
|
28
|
+
"@fluentui/react-icons": "*"
|
|
32
29
|
},
|
|
33
30
|
"peerDependencies": {
|
|
34
31
|
"webpack": ">=5.0.0"
|