Skip to content

PluginReexport ​

Генерирует реэкспортные barrel-файлы и локальные package.json для выбранных папок, а также может использоваться для создания RootStore, reducers map, объекта с изображениями или иконками и т.п.

Для каждой найденной папки плагин по умолчанию создает два файла:

  • _reexport.ts с результатом template;
  • package.json с "main" и "types", указывающими на файл реэкспорта.

Глубина генерации реэкспортов зависит от entries: utils/* выбирает только непосредственные подпапки utils, а utils/** — саму utils и все вложенные папки. При utils/** структура сгенерированных файлов выглядит так:

text
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
entries: Array<string>

Массив с glob-паттернами поиска папок внутри rootDir

ts
template: (params: { 
  folderName: string, 
  files: Array<{
    ext: string;
    name: string;
    fileName: string; 
  }> 
}) => string

Функция-шаблон, возвращающая содержимое файла реэкспорта. files — отсортированный список дочерних файлов и папок

ts
exclude?: RegExp

Регулярное выражение для исключения файлов/директорий из обработки

ts
noPackage?: boolean

Если true, package.json не будет сгенерирован

ts
onlyPackage?: boolean

Если true, будет сгенерирован только package.json без файла реэкспорта

ts
validateContent?: (params: {
  ext: string;
  name: string;
  content: string;
  fileName: string;
  folderName: string;
}) => boolean

Функция валидации содержимого файла. Если возвращает false, файл не добавляется в реэкспорт. Проверяет только файлы; дочерние папки добавляются без проверки содержимого

ts
fileNameTemplate?: (params: { 
  folderName: string 
}) => string

Функция-шаблон для имени генерируемого файла реэкспорта. По умолчанию — _reexport.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
USER: actions/getOrders.ts создан с содержимым:
ts
export async function getOrders() {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] actions/package.json
[ARFA-GEN] [write] actions/_reexport.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export * from './getOrders';

Пример: index.ts вместо _reexport.ts и package.json

При этой настройке генерируется только index.ts; включена дополнительная фильтрация

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
USER: utils/getTime.ts создан с содержимым:
ts
export function getTime() {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] utils/index.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export * from './getTime';

Пример: package.json указывает на определенный файл

В папках компонентов часто удобнее держать один package.json вместо index.ts. Валидация через validateContent для главного файла при этом недоступна

ts
new PluginReexport([
  {
    entries: ['components/*'],
    onlyPackage: true,
    fileNameTemplate({ folderName }) {
      return `${folderName}.tsx`;
    },
    template() {
      return '';
    },
  },
]),
Запустить Sandbox
USER: components/Button/Button.tsx создан с содержимым:
tsx
export function Button() {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] components/Button/package.json
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
json
{ "main": "Button.tsx", "types": "Button.tsx" }

Пример: глубокая генерация реэкспортов

В utils и всех вложенных папках создаются _reexport.ts и package.json. Это позволяет получать вложенные экспорты через единый импорт из utils

ts
new PluginReexport([
  {
    entries: ['utils/**'],
    template({ files }) {
      return files.map(({ name }) => `export * from './${name}';`).join('\n');
    },
  },
]),
Запустить Sandbox
USER: utils/scroll.ts создан с содержимым:
ts
export function scroll() {}
USER: utils/date/getTime.ts создан с содержимым:
ts
export function getTime() {}
USER: utils/date/format/formatDate.ts создан с содержимым:
ts
export function formatDate() {}
ARFA Generator выводит логи:
txt
[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
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export * from './date';
export * from './scroll';

Пример: раздельный реэкспорт типов и значений

Файлы с суффиксом .types.ts реэкспортируются через export type *, а остальные остаются runtime-экспортами

ts
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
USER: models/order.types.ts создан с содержимым:
ts
export type TypeOrder = { id: string };
USER: models/formatOrder.ts создан с содержимым:
ts
export function formatOrder() {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] models/package.json
[ARFA-GEN] [write] models/_reexport.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export * from './formatOrder';
export type * from './order.types';

Пример: публичный API feature-модулей

Для каждого feature-модуля создается публичный entrypoint, в который не попадают тесты, stories, стили и приватные файлы

ts
new PluginReexport([
  {
    entries: ['features/*'],
    exclude: /(^_|\.test\.|\.stories\.|\.css$)/,
    template({ files }) {
      return files.map(({ name }) => `export * from './${name}';`).join('\n');
    },
  },
]),
Запустить Sandbox
USER: features/orders/api.ts создан с содержимым:
ts
export async function getOrders() {}
USER: features/orders/ui.tsx создан с содержимым:
tsx
export function Orders() {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] features/orders/package.json
[ARFA-GEN] [write] features/orders/_reexport.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export * from './api';
export * from './ui';

Пример: lazy-загрузка React-страниц

Entry-файл создает React.lazy-компонент для каждой страницы и обновляется при добавлении или удалении файлов

ts
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
USER: pages/HomePage.tsx создан с содержимым:
tsx
export default function HomePage() {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] pages/package.json
[ARFA-GEN] [write] pages/_reexport.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

import { lazy } from 'react';

export const HomePage = lazy(() => import('./HomePage'));

Пример: реэкспорт ассетов с tree-shaking

Как правило svg, png и т.п. файлы считаются бандлерами как экспортирующие default. В данном случае реэкспортный файл назван по имени папки

ts
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
USER: assets/icons/logo.svg создан с содержимым:
txt
<svg />
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] assets/icons/package.json
[ARFA-GEN] [write] assets/icons/icons.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export { default as logo } from './logo.svg';

Пример: реэкспорт одним объектом или классом

В ряде случаев не обязательно поддерживать строгое именование переменных внутри файлов (в данном случае везде Store), так как семантичные имена выводятся из имен файлов

ts
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
USER: stores/user.ts создан с содержимым:
ts
export default class Store {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] stores/package.json
[ARFA-GEN] [write] stores/RootStore.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

import { default as User } from './user.ts';

export class RootStore {
  user = new User();
}

Пример: сборка reducer map

Тот же паттерн агрегации можно использовать для сборки Redux reducers: имя каждого поля выводится из имени файла

ts
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
USER: store/reducers/orders.ts создан с содержимым:
ts
export default function orders() {}
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] store/reducers/package.json
[ARFA-GEN] [write] store/reducers/reducers.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

import orders from './orders';

export const reducers = {
  orders,
};

MIT License