Skip to content

PluginModular ​

Собирает информацию о наборе файлов или папок и генерирует один общий файл. Он подходит для случаев, где нужен централизованный модуль на основе структуры проекта: реестр страниц, карта виджетов, список публичных entrypoint, конфигурация роутов или импортов.

В отличие от PluginReexport, этот плагин не создает файл в каждой найденной папке, а пишет один targetFile для каждой конфигурации.

В watch-режиме плагин реагирует только на добавление или удаление файлов, игнорируя изменение контента файлов.

ts
entries: Array<string>

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

ts
entryKind: 'file' | 'dir'

Определяет, как обрабатываются совпадения с entries

ts
targetFile: string

Абсолютный путь к файлу, который будет сгенерирован

ts
template: (params: {
  modules: Array<{
    entryName: string;
    entryPath: string;
    moduleName: string;
    relativePath: string;
    entryFileName: string;
    rootRelativePath: string;
    files: Array<{
      ext: string;
      name: string;
      fileName: string;
      entryPath: string;
      relativePath: string;
      rootRelativePath: string;
    }>;
  }>;
  rootDir: string;
  targetFile: string;
  targetFolder: string;
}) => string;

Функция-шаблон, возвращающая содержимое целевого файла

Поля modules ​

ts
entryPath

Абсолютный путь найденного файла или папки

ts
entryFileName

Полное имя найденного файла или папки, включая расширение

ts
entryName

Имя из path.parse(entryPath).name, без расширения

ts
moduleName

Имя родительской папки найденного элемента

ts
relativePath

Путь от targetFolder к найденному элементу без расширения

ts
rootRelativePath

Путь от rootDir к найденному элементу, с расширением

ts
files

При entryKind: 'dir' — непосредственные дочерние файлы и папки; при 'file' — пустой массив

Пример: центральные entrypoint feature-модулей

Плагин собирает index.ts из разных feature-модулей в один файл с namespace-экспортами. Имя экспорта берется из родительской папки

ts
new PluginModular([
  {
    entries: ['features/*/index.ts'],
    entryKind: 'file',
    targetFile: path.resolve(rootDir, 'features.generated.ts'),
    template({ modules }) {
      return modules
        .map(({ moduleName, relativePath }) => `export * as ${moduleName} from './${relativePath}';`)
        .join('\n');
    },
  },
]),
Запустить Sandbox
USER: features/orders/index.ts создан с содержимым:
ts
export const featureName = 'orders';
USER: features/profile/index.ts создан с содержимым:
ts
export const featureName = 'profile';
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] features.generated.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export * as orders from './features/orders/index';
export * as profile from './features/profile/index';

Пример: типы страниц

Плагин создает типы либо реалтаймовые объекты, описывающие store и actions каждой страницы при их наличии

ts
new PluginModular([
  {
    entries: ['pages/*'],
    entryKind: 'dir',
    targetFile: path.resolve(rootDir, 'modularPages.ts'),
    template({ modules }) {
      return modules
        .map(({ entryName: pageName, files }) => ({
          pageName,
          storePath: files.find(({ fileName }) => fileName === 'store.ts')?.relativePath,
          actionsPath: files.find(({ fileName }) => fileName === 'actions')?.relativePath,
        }))
        .map(
          ({ pageName, actionsPath, storePath }) => `export declare const ${pageName}: {
  store: ${storePath ? `typeof import('./${storePath}').PageStore` : 'never'};
  actions: ${actionsPath ? `typeof import('./${actionsPath}')` : 'never'};
};`
        )
        .join('\n\n');
    },
  },
]),
Запустить Sandbox
USER: pages/orders/index.ts создан с содержимым:
ts
export const pageName = 'orders';
USER: pages/orders/store.ts создан с содержимым:
ts
export class PageStore {}
USER: pages/orders/actions/index.ts создан с содержимым:
ts
export async function createOrder() {}
USER: pages/profile/index.ts создан с содержимым:
ts
export const pageName = 'profile';
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] modularPages.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export declare const orders: {
  store: typeof import('./pages/orders/store').PageStore;
  actions: typeof import('./pages/orders/actions');
};

export declare const profile: {
  store: never;
  actions: never;
};

Пример: маршруты feature-модулей

Плагин находит feature-модули с файлом routes.ts и объединяет их маршруты. Добавление routes.ts подключает модуль автоматически

ts
new PluginModular([
  {
    entries: ['features/*'],
    entryKind: 'dir',
    targetFile: path.resolve(rootDir, 'routes.generated.ts'),
    template({ modules }) {
      const routeModules = modules.flatMap(({ entryName, files }) =>
        files
          .filter(({ fileName }) => fileName === 'routes.ts')
          .map(({ relativePath }) => ({ name: `${entryName}Routes`, relativePath }))
      );
      const imports = routeModules
        .map(({ name, relativePath }) => `import { routes as ${name} } from './${relativePath}';`)
        .join('\n');
      const routes = routeModules.map(({ name }) => `  ...${name},`).join('\n');

      return `${imports}\n\nexport const routes = [\n${routes}\n];`;
    },
  },
]),
Запустить Sandbox
USER: features/orders/model.ts создан с содержимым:
ts
export const orders = [];
USER: features/orders/routes.ts создан с содержимым:
ts
export const routes = [{ path: '/orders' }];
USER: features/profile/model.ts создан с содержимым:
ts
export const profile = {};
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] routes.generated.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

import { routes as ordersRoutes } from './features/orders/routes';

export const routes = [
  ...ordersRoutes,
];

Пример: манифест feature-модулей

По набору файлов в каждой директории генерируется типизированное описание возможностей feature-модулей

ts
new PluginModular([
  {
    entries: ['features/*'],
    entryKind: 'dir',
    targetFile: path.resolve(rootDir, 'featureManifest.generated.ts'),
    template({ modules }) {
      const manifest: Record<string, { hasApi: boolean; hasRoutes: boolean; hasMessages: boolean }> = {};

      for (const { entryName, files } of modules) {
        const fileNames = new Set(files.map(({ fileName }) => fileName));

        manifest[entryName] = {
          hasApi: fileNames.has('api.ts'),
          hasRoutes: fileNames.has('routes.ts'),
          hasMessages: fileNames.has('messages.ts'),
        };
      }

      return `export const featureManifest = ${JSON.stringify(manifest, null, 2)} as const;`;
    },
  },
]),
Запустить Sandbox
USER: features/orders/api.ts создан с содержимым:
ts
export async function getOrders() {}
USER: features/orders/routes.ts создан с содержимым:
ts
export const routes = [{ path: '/orders' }];
USER: features/orders/messages.ts создан с содержимым:
ts
export const messages = {};
USER: features/profile/routes.ts создан с содержимым:
ts
export const routes = [{ path: '/profile' }];
ARFA Generator выводит логи:
txt
[ARFA-GEN] [write] featureManifest.generated.ts
[ARFA-GEN] finished in 0ms
ARFA Generator сгенерировал файлы:
ts
// this file is generated

export const featureManifest = {
  "orders": {
    "hasApi": true,
    "hasRoutes": true,
    "hasMessages": true
  },
  "profile": {
    "hasApi": false,
    "hasRoutes": true,
    "hasMessages": false
  }
} as const;

MIT License