generator-bitloops 0.3.26 → 0.3.28
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/package.json +1 -1
- package/setup/index.js +284 -19
- package/setup/templates/platform-next/src/Img.tsx +6 -5
- package/setup/templates/platform-next/src/Link.tsx +6 -0
- package/setup/templates/platform-next/src/index.ts +3 -2
- package/setup/templates/platform-next/src/router/index.ts +1 -0
- package/setup/templates/platform-next/src/router/useNextRouter.ts +14 -0
- package/setup/templates/platform-next/src/setup.ts +7 -0
- package/setup/templates/platform-vite/src/Img.tsx +18 -0
- package/setup/templates/platform-vite/src/Link.tsx +19 -0
- package/setup/templates/platform-vite/src/index.ts +3 -0
- package/setup/templates/platform-vite/src/router/index.ts +1 -0
- package/setup/templates/platform-vite/src/router/useViteRouter.ts +17 -0
- package/setup/templates/platform-vite/src/setup.ts +5 -0
- package/setup/templates/platform-next/src/types.ts +0 -28
package/package.json
CHANGED
package/setup/index.js
CHANGED
|
@@ -12,6 +12,8 @@ const __dirname = path.dirname(__filename);
|
|
|
12
12
|
const DOT = '.';
|
|
13
13
|
const PLATFORM_NEXT_FOLDER = 'platform-next';
|
|
14
14
|
const PLATFORM_NEXT_SRC_FOLDER = `${PLATFORM_NEXT_FOLDER}/src`;
|
|
15
|
+
const PLATFORM_VITE_FOLDER = 'platform-vite';
|
|
16
|
+
const PLATFORM_VITE_SRC_FOLDER = `${PLATFORM_VITE_FOLDER}/src`;
|
|
15
17
|
|
|
16
18
|
function isKebabCase(str) {
|
|
17
19
|
// Check if the string is empty
|
|
@@ -118,6 +120,60 @@ export default class extends Generator {
|
|
|
118
120
|
default: false,
|
|
119
121
|
});
|
|
120
122
|
|
|
123
|
+
this.option('baseUi', {
|
|
124
|
+
type: Boolean,
|
|
125
|
+
description: 'Add Base UI React components (@base-ui/react)',
|
|
126
|
+
default: false,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
this.option('redux', {
|
|
130
|
+
type: Boolean,
|
|
131
|
+
description: 'Add Redux Toolkit and React Redux for state management',
|
|
132
|
+
default: false,
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
this.option('vitest', {
|
|
136
|
+
type: Boolean,
|
|
137
|
+
description: 'Add Vitest testing framework with coverage and UI',
|
|
138
|
+
default: false,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
this.option('webVitals', {
|
|
142
|
+
type: Boolean,
|
|
143
|
+
description: 'Add web-vitals for performance monitoring',
|
|
144
|
+
default: false,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this.option('zod', {
|
|
148
|
+
type: Boolean,
|
|
149
|
+
description: 'Add Zod for schema validation',
|
|
150
|
+
default: false,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this.option('bundleAnalyzer', {
|
|
154
|
+
type: Boolean,
|
|
155
|
+
description: 'Add @next/bundle-analyzer for bundle analysis',
|
|
156
|
+
default: false,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
this.option('reactIcons', {
|
|
160
|
+
type: Boolean,
|
|
161
|
+
description: 'Add react-icons library',
|
|
162
|
+
default: false,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
this.option('msw', {
|
|
166
|
+
type: Boolean,
|
|
167
|
+
description: 'Add Mock Service Worker (MSW) for API mocking',
|
|
168
|
+
default: false,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
this.option('reactCompiler', {
|
|
172
|
+
type: Boolean,
|
|
173
|
+
description: 'Add babel-plugin-react-compiler',
|
|
174
|
+
default: false,
|
|
175
|
+
});
|
|
176
|
+
|
|
121
177
|
this.installNextJS = async function () {
|
|
122
178
|
// Clone Next.js template with Tailwind if specified, using the project name
|
|
123
179
|
const createNextAppCommand = ['-y', 'create-next-app@latest'];
|
|
@@ -253,31 +309,196 @@ export default class extends Generator {
|
|
|
253
309
|
}
|
|
254
310
|
};
|
|
255
311
|
|
|
256
|
-
this.
|
|
257
|
-
// Conditionally add
|
|
258
|
-
if (this.options.
|
|
259
|
-
this.log('Installing
|
|
312
|
+
this.installBaseUi = function () {
|
|
313
|
+
// Conditionally add Base UI
|
|
314
|
+
if (this.options.baseUi) {
|
|
315
|
+
this.log('Installing Base UI...');
|
|
316
|
+
spawnSync(
|
|
317
|
+
'pnpm',
|
|
318
|
+
['add', '@base-ui/react@^1.1.0'],
|
|
319
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
320
|
+
);
|
|
321
|
+
this.log('Base UI installed!');
|
|
322
|
+
}
|
|
323
|
+
};
|
|
260
324
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
325
|
+
this.installRedux = function () {
|
|
326
|
+
// Conditionally add Redux Toolkit and React Redux
|
|
327
|
+
if (this.options.redux) {
|
|
328
|
+
this.log('Installing Redux Toolkit and React Redux...');
|
|
329
|
+
spawnSync(
|
|
330
|
+
'pnpm',
|
|
331
|
+
['add', '@reduxjs/toolkit', 'react-redux'],
|
|
332
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
266
333
|
);
|
|
334
|
+
this.log('Redux Toolkit and React Redux installed!');
|
|
335
|
+
}
|
|
336
|
+
};
|
|
267
337
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
338
|
+
this.installVitest = function () {
|
|
339
|
+
// Conditionally add Vitest and related testing packages
|
|
340
|
+
if (this.options.vitest) {
|
|
341
|
+
this.log('Installing Vitest and testing packages...');
|
|
342
|
+
spawnSync(
|
|
343
|
+
'pnpm',
|
|
344
|
+
[
|
|
345
|
+
'add',
|
|
346
|
+
'-D',
|
|
347
|
+
'vitest',
|
|
348
|
+
'@vitest/ui',
|
|
349
|
+
'@vitest/coverage-v8',
|
|
350
|
+
'@vitest/browser-playwright',
|
|
351
|
+
'@testing-library/react',
|
|
352
|
+
'@testing-library/jest-dom',
|
|
353
|
+
'@testing-library/user-event',
|
|
354
|
+
'@vitejs/plugin-react',
|
|
355
|
+
'vite',
|
|
356
|
+
'jsdom',
|
|
357
|
+
'playwright',
|
|
358
|
+
],
|
|
359
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
273
360
|
);
|
|
361
|
+
this.log('Vitest and testing packages installed!');
|
|
362
|
+
}
|
|
363
|
+
};
|
|
274
364
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
365
|
+
this.installWebVitals = function () {
|
|
366
|
+
// Conditionally add web-vitals
|
|
367
|
+
if (this.options.webVitals) {
|
|
368
|
+
this.log('Installing web-vitals...');
|
|
369
|
+
spawnSync(
|
|
370
|
+
'pnpm',
|
|
371
|
+
['add', 'web-vitals'],
|
|
372
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
280
373
|
);
|
|
374
|
+
this.log('web-vitals installed!');
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
this.installZod = function () {
|
|
379
|
+
// Conditionally add Zod
|
|
380
|
+
if (this.options.zod) {
|
|
381
|
+
this.log('Installing Zod...');
|
|
382
|
+
spawnSync(
|
|
383
|
+
'pnpm',
|
|
384
|
+
['add', 'zod'],
|
|
385
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
386
|
+
);
|
|
387
|
+
this.log('Zod installed!');
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
this.installBundleAnalyzer = function () {
|
|
392
|
+
// Conditionally add @next/bundle-analyzer
|
|
393
|
+
if (this.options.bundleAnalyzer) {
|
|
394
|
+
this.log('Installing @next/bundle-analyzer...');
|
|
395
|
+
spawnSync(
|
|
396
|
+
'pnpm',
|
|
397
|
+
['add', '-D', '@next/bundle-analyzer'],
|
|
398
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
399
|
+
);
|
|
400
|
+
this.log('@next/bundle-analyzer installed!');
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
this.installReactIcons = function () {
|
|
405
|
+
// Conditionally add react-icons
|
|
406
|
+
if (this.options.reactIcons) {
|
|
407
|
+
this.log('Installing react-icons...');
|
|
408
|
+
spawnSync(
|
|
409
|
+
'pnpm',
|
|
410
|
+
['add', 'react-icons'],
|
|
411
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
412
|
+
);
|
|
413
|
+
this.log('react-icons installed!');
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
this.installMsw = function () {
|
|
418
|
+
// Conditionally add Mock Service Worker
|
|
419
|
+
if (this.options.msw) {
|
|
420
|
+
this.log('Installing Mock Service Worker (MSW)...');
|
|
421
|
+
spawnSync(
|
|
422
|
+
'pnpm',
|
|
423
|
+
['add', '-D', 'msw'],
|
|
424
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
425
|
+
);
|
|
426
|
+
// Initialize MSW
|
|
427
|
+
spawnSync(
|
|
428
|
+
'npx',
|
|
429
|
+
['msw', 'init', 'public/', '--save'],
|
|
430
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
431
|
+
);
|
|
432
|
+
this.log('MSW installed!');
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
this.installReactCompiler = function () {
|
|
437
|
+
// Conditionally add babel-plugin-react-compiler
|
|
438
|
+
if (this.options.reactCompiler) {
|
|
439
|
+
this.log('Installing babel-plugin-react-compiler...');
|
|
440
|
+
spawnSync(
|
|
441
|
+
'pnpm',
|
|
442
|
+
['add', '-D', 'babel-plugin-react-compiler'],
|
|
443
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
444
|
+
);
|
|
445
|
+
this.log('babel-plugin-react-compiler installed!');
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
this.installIntlMessageFormat = function () {
|
|
450
|
+
// Add intl-messageformat when i18n is enabled
|
|
451
|
+
if (this.options.i18n) {
|
|
452
|
+
this.log('Installing intl-messageformat...');
|
|
453
|
+
spawnSync(
|
|
454
|
+
'pnpm',
|
|
455
|
+
['add', 'intl-messageformat'],
|
|
456
|
+
{ stdio: 'inherit', cwd: this.destinationRoot() },
|
|
457
|
+
);
|
|
458
|
+
this.log('intl-messageformat installed!');
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
this.installPrimitives = function () {
|
|
463
|
+
// Conditionally add Primitives
|
|
464
|
+
if (this.options.primitives) {
|
|
465
|
+
this.log('Installing Primitives...');
|
|
466
|
+
|
|
467
|
+
// Platform Next files
|
|
468
|
+
const platformNextFiles = [
|
|
469
|
+
`${PLATFORM_NEXT_SRC_FOLDER}/index.ts`,
|
|
470
|
+
`${PLATFORM_NEXT_SRC_FOLDER}/Img.tsx`,
|
|
471
|
+
`${PLATFORM_NEXT_SRC_FOLDER}/Link.tsx`,
|
|
472
|
+
`${PLATFORM_NEXT_SRC_FOLDER}/setup.ts`,
|
|
473
|
+
`${PLATFORM_NEXT_SRC_FOLDER}/router/index.ts`,
|
|
474
|
+
`${PLATFORM_NEXT_SRC_FOLDER}/router/useNextRouter.ts`,
|
|
475
|
+
];
|
|
476
|
+
|
|
477
|
+
platformNextFiles.forEach((filePath) => {
|
|
478
|
+
deleteFileIfExists(this.destinationPath(filePath));
|
|
479
|
+
this.fs.copyTpl(
|
|
480
|
+
this.templatePath(filePath),
|
|
481
|
+
this.destinationPath(filePath),
|
|
482
|
+
);
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// Platform Vite files
|
|
486
|
+
const platformViteFiles = [
|
|
487
|
+
`${PLATFORM_VITE_SRC_FOLDER}/index.ts`,
|
|
488
|
+
`${PLATFORM_VITE_SRC_FOLDER}/Img.tsx`,
|
|
489
|
+
`${PLATFORM_VITE_SRC_FOLDER}/Link.tsx`,
|
|
490
|
+
`${PLATFORM_VITE_SRC_FOLDER}/setup.ts`,
|
|
491
|
+
`${PLATFORM_VITE_SRC_FOLDER}/router/index.ts`,
|
|
492
|
+
`${PLATFORM_VITE_SRC_FOLDER}/router/useViteRouter.ts`,
|
|
493
|
+
];
|
|
494
|
+
|
|
495
|
+
platformViteFiles.forEach((filePath) => {
|
|
496
|
+
deleteFileIfExists(this.destinationPath(filePath));
|
|
497
|
+
this.fs.copyTpl(
|
|
498
|
+
this.templatePath(filePath),
|
|
499
|
+
this.destinationPath(filePath),
|
|
500
|
+
);
|
|
501
|
+
});
|
|
281
502
|
|
|
282
503
|
this.log('Primitives installed!');
|
|
283
504
|
}
|
|
@@ -383,6 +604,39 @@ export default class extends Generator {
|
|
|
383
604
|
}
|
|
384
605
|
};
|
|
385
606
|
|
|
607
|
+
this.patchPackageJsonScripts = async function () {
|
|
608
|
+
this.log('Patching package.json scripts...');
|
|
609
|
+
const packageJsonPath = this.destinationPath('package.json');
|
|
610
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
611
|
+
|
|
612
|
+
// Add vitest scripts if vitest is enabled
|
|
613
|
+
if (this.options.vitest) {
|
|
614
|
+
packageJson.scripts.test = 'vitest';
|
|
615
|
+
packageJson.scripts['test:ui'] = 'vitest --ui';
|
|
616
|
+
packageJson.scripts['test:coverage'] = 'vitest run --coverage';
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Add type-check script if typescript is enabled
|
|
620
|
+
if (this.options.typescript) {
|
|
621
|
+
packageJson.scripts['type-check'] = 'tsc --noEmit';
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Add analyze script if bundleAnalyzer is enabled
|
|
625
|
+
if (this.options.bundleAnalyzer) {
|
|
626
|
+
packageJson.scripts.analyze = 'ANALYZE=true next build';
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Add msw configuration if msw is enabled
|
|
630
|
+
if (this.options.msw) {
|
|
631
|
+
packageJson.msw = {
|
|
632
|
+
workerDirectory: ['public'],
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
|
637
|
+
this.log('package.json scripts patched!');
|
|
638
|
+
};
|
|
639
|
+
|
|
386
640
|
this.commitChanges = async function () {
|
|
387
641
|
this.log('Committing changes to git...');
|
|
388
642
|
await new Promise((resolve) => {
|
|
@@ -429,9 +683,20 @@ export default class extends Generator {
|
|
|
429
683
|
await this.installNextJS();
|
|
430
684
|
this.installCypress();
|
|
431
685
|
this.installI18n();
|
|
686
|
+
this.installIntlMessageFormat();
|
|
687
|
+
this.installBaseUi();
|
|
688
|
+
this.installRedux();
|
|
689
|
+
this.installVitest();
|
|
690
|
+
this.installWebVitals();
|
|
691
|
+
this.installZod();
|
|
692
|
+
this.installBundleAnalyzer();
|
|
693
|
+
this.installReactIcons();
|
|
694
|
+
this.installMsw();
|
|
695
|
+
this.installReactCompiler();
|
|
432
696
|
this.installPrimitives();
|
|
433
697
|
this.installStorybook();
|
|
434
698
|
await this.patchFiles();
|
|
699
|
+
await this.patchPackageJsonScripts();
|
|
435
700
|
if (this.options.git) {
|
|
436
701
|
await this.commitChanges();
|
|
437
702
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import NextImage from
|
|
3
|
-
import type { ImgProps } from
|
|
1
|
+
"use client";
|
|
2
|
+
import NextImage from "next/image";
|
|
3
|
+
import type { ImgProps } from "../../src/lib/types/primitives.types";
|
|
4
|
+
|
|
4
5
|
export function Img(props: ImgProps) {
|
|
5
|
-
const {
|
|
6
|
+
const { ...rest } = props;
|
|
6
7
|
return <NextImage {...rest} />;
|
|
7
|
-
}
|
|
8
|
+
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export * from
|
|
2
|
-
export * from
|
|
1
|
+
export * from "../../src/lib/types/primitives.types";
|
|
2
|
+
export * from "./Img";
|
|
3
|
+
export * from "./Link";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useNextRouter } from './useNextRouter';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useRouter as useNextNavigationRouter } from 'next/navigation';
|
|
4
|
+
import type { Router } from '@/lib/router/types';
|
|
5
|
+
|
|
6
|
+
export function useNextRouter(): Router {
|
|
7
|
+
const nextRouter = useNextNavigationRouter();
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
push: (url: string) => nextRouter.push(url),
|
|
11
|
+
replace: (url: string) => nextRouter.replace(url),
|
|
12
|
+
back: () => nextRouter.back(),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* eslint-disable @next/next/no-img-element */
|
|
2
|
+
import type { ImgProps } from "../../src/lib/types/primitives.types";
|
|
3
|
+
|
|
4
|
+
export function Img(props: ImgProps) {
|
|
5
|
+
const { src, alt, className, style, loading, decoding } =
|
|
6
|
+
props;
|
|
7
|
+
|
|
8
|
+
return (
|
|
9
|
+
<img
|
|
10
|
+
src={src}
|
|
11
|
+
alt={alt}
|
|
12
|
+
className={className}
|
|
13
|
+
style={style}
|
|
14
|
+
loading={loading}
|
|
15
|
+
decoding={decoding}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { LinkProps } from "../../src/lib/types/primitives.types";
|
|
2
|
+
|
|
3
|
+
export function Link(props: LinkProps) {
|
|
4
|
+
const { href, children, target, rel, className, style, onClick } = props;
|
|
5
|
+
|
|
6
|
+
return (
|
|
7
|
+
<a
|
|
8
|
+
href={href}
|
|
9
|
+
target={target}
|
|
10
|
+
rel={rel}
|
|
11
|
+
className={className}
|
|
12
|
+
style={style}
|
|
13
|
+
onClick={onClick}
|
|
14
|
+
aria-label={props["aria-label"]}
|
|
15
|
+
>
|
|
16
|
+
{children}
|
|
17
|
+
</a>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useViteRouter } from "./useViteRouter";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Router } from "@/lib/router/types";
|
|
2
|
+
|
|
3
|
+
export function useViteRouter(): Router {
|
|
4
|
+
return {
|
|
5
|
+
push: (url: string) => {
|
|
6
|
+
console.log("[Storybook Router] Push to:", url);
|
|
7
|
+
// In Storybook, we don't actually navigate
|
|
8
|
+
// This could be enhanced with Storybook actions addon
|
|
9
|
+
},
|
|
10
|
+
replace: (url: string) => {
|
|
11
|
+
console.log("[Storybook Router] Replace with:", url);
|
|
12
|
+
},
|
|
13
|
+
back: () => {
|
|
14
|
+
console.log("[Storybook Router] Go back");
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export type ImgProps = {
|
|
2
|
-
src: string; alt: string;
|
|
3
|
-
width?: number; height?: number; fill?: boolean;
|
|
4
|
-
sizes?: string; priority?: boolean; quality?: number;
|
|
5
|
-
className?: string; style?: React.CSSProperties;
|
|
6
|
-
loading?: 'eager'|'lazy'; decoding?: 'auto'|'sync'|'async';
|
|
7
|
-
responsive?: { sources: Array<{ media: string; srcSet: string }> }; // optional <picture>
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
export type LinkProps = {
|
|
11
|
-
href: string; children: React.ReactNode;
|
|
12
|
-
prefetch?: boolean; replace?: boolean; scroll?: boolean;
|
|
13
|
-
target?: React.HTMLAttributeAnchorTarget; rel?: string;
|
|
14
|
-
className?: string; style?: React.CSSProperties; 'aria-label'?: string;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export type MetaProps = {
|
|
18
|
-
title?: string;
|
|
19
|
-
description?: string;
|
|
20
|
-
lang?: string;
|
|
21
|
-
openGraph?: { title?: string; description?: string; image?: string; url?: string; type?: string };
|
|
22
|
-
twitter?: { card?: string; title?: string; description?: string; image?: string };
|
|
23
|
-
icons?: { icon?: string; apple?: string };
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type FontSpec =
|
|
27
|
-
| { kind: 'next'; fonts: Array<{ variable: string; loader: () => any }> } // Next native loaders
|
|
28
|
-
| { kind: 'css'; hrefs: string[]; className?: string };
|