PluginReexport
Генерирует реэкспортные barrel-файлы и локальные package.json для выбранных папок, а также может использоваться для создания RootStore, reducers map, объекта с изображениями или иконками и т.п.
Для каждой найденной папки плагин по умолчанию создает два файла:
_reexport.tsс результатомtemplate;package.jsonс"main"и"types", указывающими на файл реэкспорта.
Глубина генерации реэкспортов зависит от entries: utils/* выбирает только непосредственные подпапки utils, а utils/** — саму utils и все вложенные папки. При utils/** структура сгенерированных файлов выглядит так:
utils/
├── _reexport.ts
├── package.json
├── scroll.ts
└── date/
├── _reexport.ts
├── package.json
├── getTime.ts
└── format/
├── _reexport.ts
├── package.json
└── formatDate.tsПри utils/* реэкспортные файлы создавались бы только в utils/date.
ts | Массив с glob-паттернами поиска папок внутри |
ts | Функция-шаблон, возвращающая содержимое файла реэкспорта. |
ts | Регулярное выражение для исключения файлов/директорий из обработки |
ts | Если |
ts | Если |
ts | Функция валидации содержимого файла. Если возвращает |
ts | Функция-шаблон для имени генерируемого файла реэкспорта. По умолчанию — |
Пример: валидация содержимого файлов
В файл реэкспорта попадают только файлы, экспортирующие функцию с именем файла, что позволяет проверять нейминг и валидировать содержимое
new PluginReexport([
{
entries: ['actions'],
template({ files }) {
return files.map(({ name }) => `export * from './${name}';`).join('\n');
},
validateContent({ content, name }) {
return content.includes(`export async function ${name}(`);
},
},
]),
Запустить Sandbox
export async function getOrders() {}
[ARFA-GEN] [write] actions/package.json
[ARFA-GEN] [write] actions/_reexport.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export * from './getOrders';
Пример: index.ts вместо _reexport.ts и package.json
При этой настройке генерируется только index.ts; включена дополнительная фильтрация
new PluginReexport([
{
entries: ['utils'],
exclude: /\.css$/,
noPackage: true,
fileNameTemplate() {
return `index.ts`;
},
template({ files }) {
return files.map(({ name }) => `export * from './${name}';`).join('\n');
},
},
]),
Запустить Sandbox
export function getTime() {}
[ARFA-GEN] [write] utils/index.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export * from './getTime';
Пример: package.json указывает на определенный файл
В папках компонентов часто удобнее держать один package.json вместо index.ts. Валидация через validateContent для главного файла при этом недоступна
new PluginReexport([
{
entries: ['components/*'],
onlyPackage: true,
fileNameTemplate({ folderName }) {
return `${folderName}.tsx`;
},
template() {
return '';
},
},
]),
Запустить Sandbox
export function Button() {}
[ARFA-GEN] [write] components/Button/package.json
[ARFA-GEN] finished in 0ms
{ "main": "Button.tsx", "types": "Button.tsx" }
Пример: глубокая генерация реэкспортов
В utils и всех вложенных папках создаются _reexport.ts и package.json. Это позволяет получать вложенные экспорты через единый импорт из utils
new PluginReexport([
{
entries: ['utils/**'],
template({ files }) {
return files.map(({ name }) => `export * from './${name}';`).join('\n');
},
},
]),
Запустить Sandbox
export function scroll() {}
export function getTime() {}
export function formatDate() {}
[ARFA-GEN] [write] utils/package.json
[ARFA-GEN] [write] utils/_reexport.ts
[ARFA-GEN] [write] utils/date/package.json
[ARFA-GEN] [write] utils/date/_reexport.ts
[ARFA-GEN] [write] utils/date/format/package.json
[ARFA-GEN] [write] utils/date/format/_reexport.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export * from './date';
export * from './scroll';
Пример: раздельный реэкспорт типов и значений
Файлы с суффиксом .types.ts реэкспортируются через export type *, а остальные остаются runtime-экспортами
new PluginReexport([
{
entries: ['models'],
template({ files }) {
return files
.map(({ name, fileName }) => {
const exportType = fileName.endsWith('.types.ts') ? ' type' : '';
return `export${exportType} * from './${name}';`;
})
.join('\n');
},
},
]),
Запустить Sandbox
export type TypeOrder = { id: string };
export function formatOrder() {}
[ARFA-GEN] [write] models/package.json
[ARFA-GEN] [write] models/_reexport.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export * from './formatOrder';
export type * from './order.types';
Пример: публичный API feature-модулей
Для каждого feature-модуля создается публичный entrypoint, в который не попадают тесты, stories, стили и приватные файлы
new PluginReexport([
{
entries: ['features/*'],
exclude: /(^_|\.test\.|\.stories\.|\.css$)/,
template({ files }) {
return files.map(({ name }) => `export * from './${name}';`).join('\n');
},
},
]),
Запустить Sandbox
export async function getOrders() {}
export function Orders() {}
[ARFA-GEN] [write] features/orders/package.json
[ARFA-GEN] [write] features/orders/_reexport.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export * from './api';
export * from './ui';
Пример: lazy-загрузка React-страниц
Entry-файл создает React.lazy-компонент для каждой страницы и обновляется при добавлении или удалении файлов
new PluginReexport([
{
entries: ['pages'],
exclude: /(\.test\.tsx$|\.stories\.tsx$|\.css$)/,
template({ files }) {
const pages = files.filter(({ ext }) => ext === '.tsx');
const exports = pages
.map(({ name }) => `export const ${name} = lazy(() => import('./${name}'));`)
.join('\n');
return `import { lazy } from 'react';\n\n${exports}`;
},
},
]),
Запустить Sandbox
export default function HomePage() {}
[ARFA-GEN] [write] pages/package.json
[ARFA-GEN] [write] pages/_reexport.ts
[ARFA-GEN] finished in 0ms
// this file is generated
import { lazy } from 'react';
export const HomePage = lazy(() => import('./HomePage'));
Пример: реэкспорт ассетов с tree-shaking
Как правило svg, png и т.п. файлы считаются бандлерами как экспортирующие default. В данном случае реэкспортный файл назван по имени папки
new PluginReexport([
{
entries: ['assets/icons'],
fileNameTemplate({ folderName }) {
return `${folderName}.ts`;
},
template({ files }) {
return files.map(({ name, fileName }) => `export { default as ${name} } from './${fileName}';`).join('\n');
},
},
]),
Запустить Sandbox
<svg />
[ARFA-GEN] [write] assets/icons/package.json
[ARFA-GEN] [write] assets/icons/icons.ts
[ARFA-GEN] finished in 0ms
// this file is generated
export { default as logo } from './logo.svg';
Пример: реэкспорт одним объектом или классом
В ряде случаев не обязательно поддерживать строгое именование переменных внутри файлов (в данном случае везде Store), так как семантичные имена выводятся из имен файлов
new PluginReexport([
{
entries: ['stores'],
fileNameTemplate() {
return `RootStore.ts`;
},
template({ files }) {
function upperFirst(value: string) {
return `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
}
const imports = files
.map(({ name, fileName }) => `import { default as ${upperFirst(name)} } from './${fileName}';`)
.join('\n');
const fields = files.map(({ name }) => ` ${name} = new ${upperFirst(name)}();`).join('\n');
return `${imports}\n\nexport class RootStore {\n${fields}\n}`;
},
},
]),
Запустить Sandbox
export default class Store {}
[ARFA-GEN] [write] stores/package.json
[ARFA-GEN] [write] stores/RootStore.ts
[ARFA-GEN] finished in 0ms
// this file is generated
import { default as User } from './user.ts';
export class RootStore {
user = new User();
}
Пример: сборка reducer map
Тот же паттерн агрегации можно использовать для сборки Redux reducers: имя каждого поля выводится из имени файла
new PluginReexport([
{
entries: ['store/reducers'],
fileNameTemplate() {
return `reducers.ts`;
},
template({ files }) {
const imports = files.map(({ name }) => `import ${name} from './${name}';`).join('\n');
const fields = files.map(({ name }) => ` ${name},`).join('\n');
return `${imports}\n\nexport const reducers = {\n${fields}\n};`;
},
},
]),
Запустить Sandbox
export default function orders() {}
[ARFA-GEN] [write] store/reducers/package.json
[ARFA-GEN] [write] store/reducers/reducers.ts
[ARFA-GEN] finished in 0ms
// this file is generated
import orders from './orders';
export const reducers = {
orders,
};