Создание собственного плагина по умолчанию для рабочего пространства NX

Мне нравится работать с Nrwl Nx. Он повысил мою производительность и «заставил» меня писать лучший код и лучшую архитектуру для моих проектов. NX также замечателен тем, что для Nx доступно множество плагинов для создания библиотек и приложений для различных фреймворков. Эти плагины доступны от Nrwl, а также от сообщества.

Иногда есть небольшие различия в том, как вы хотите генерировать свои библиотеки. Возможно, у вас есть некоторые специфические опции, которые вы всегда выбираете, или есть некоторые дополнительные файлы, которые вы хотите включить. Для этого существуют пользовательские генераторы. Вы можете создавать свои собственные генераторы, которые могут включать и расширять другие существующие генераторы. Это позволяет генерировать библиотеки по своему усмотрению, и вам не придется каждый раз изменять их вручную.

Собственный плагин

Вы можете создать собственный плагин в рабочем пространстве Nx.

  • Сначала добавьте пакет nx-plugin: npm install @nrwl/nx-plugin -D.
  • Затем создайте новый плагин: npx nx generate @nrwl/nx-plugin:plugin workspace-extensions.

Теперь у вас будет новая workspace-extensions — библиотека внутри вашей библиотеки.

Установите ваш плагин в качестве коллекции по умолчанию

Чтобы иметь возможность запускать генераторы из вашего собственного плагина по умолчанию, вы можете установить его как коллекцию по умолчанию в вашем nx.json -configuration:

...
"cli": {
    "defaultCollection": "@santicon/workspace-extensions"
},
...
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь вы можете создать плагин с npx nx g my-lib вместо того, чтобы указывать плагин как npx nx g @santicon/workspace-extensions:lib my-lib.

Создание нового генератора в вашем плагине

Вы можете добавить новые генераторы в ваш плагин с помощью Nx. Я собираюсь создать генератор для генерации библиотек react с моими пользовательскими настройками по умолчанию. Сначала я создам генератор для своего плагина:

npx nx generate @nrwl/nx-plugin:generator react-library --project=workspace-extensions
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем я изменю генератор для запуска генератора библиотек react по умолчанию из @nrwl/react.

Сначала я просто скопирую свойства из оригинального schema.json генератора библиотек react и определения typescript из schema.d.ts. Я могу оставить все параметры и изменить только значения по умолчанию или добавить свои собственные параметры.

В этот раз я изменил css на none (потому что я буду использовать tailwindcss) и builder на swc. Я использую правила линтинга для обеспечения соблюдения границ модуля с помощью тегов. Поэтому я добавил свои собственные необходимые входы для scope, type и domain.

Схема

// schema.json
{
  "$schema": "http://json-schema.org/schema",
  "cli": "nx",
  "$id": "ReactLibrary",
  "title": "",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Library name",
      "$default": {
        "$source": "argv",
        "index": 0
      },
      "x-prompt": "What name would you like to use for the library?",
      "pattern": "^[a-zA-Z].*$"
    },
    "directory": {
      "type": "string",
      "description": "A directory where the lib is placed.",
      "alias": "dir"
    },
    "domain": {
      "description": "Domain where this library belongs.",
      "type": "string",
      "default": "none",
      "alias": "dom",
      "x-prompt": {
        "message": "Which domain this library belongs?",
        "type": "list",
        "items": [
          {
            "value": "web",
            "label": "Web"
          },
          {
            "value": "common",
            "label": "Common"
          }
        ]
      }
    },
    "scope": {
      "type": "string",
      "description": "A scope for the lib.",
      "alias": "sc"
    },
    "type": {
      "description": "Library type",
      "type": "string",
      "alias": "t",
      "x-prompt": {
        "message": "Select library type?",
        "type": "list",
        "items": [
          {
            "value": "data",
            "label": "Data"
          },
          {
            "value": "model",
            "label": "Model"
          },
          {
            "value": "util",
            "label": "Util"
          },
          {
            "value": "feature",
            "label": "Feature"
          },
          {
            "value": "ui",
            "label": "Ui"
          }
        ]
      }
    },
    "style": {
      "description": "The file extension to be used for style files.",
      "type": "string",
      "default": "none",
      "alias": "s",
      "x-prompt": {
        "message": "Which stylesheet format would you like to use?",
        "type": "list",
        "items": [
          { "value": "css", "label": "CSS" },
          {
            "value": "scss",
            "label": "SASS(.scss)       [ http://sass-lang.com          ]"
          },
          {
            "value": "styl",
            "label": "Stylus(.styl)     [ http://stylus-lang.com        ]"
          },
          {
            "value": "less",
            "label": "LESS              [ http://lesscss.org            ]"
          },
          {
            "value": "styled-components",
            "label": "styled-components [ https://styled-components.com ]"
          },
          {
            "value": "@emotion/styled",
            "label": "emotion           [ https://emotion.sh            ]"
          },
          {
            "value": "styled-jsx",
            "label": "styled-jsx        [ https://www.npmjs.com/package/styled-jsx ]"
          },
          {
            "value": "none",
            "label": "None"
          }
        ]
      }
    },
    "linter": {
      "description": "The tool to use for running lint checks.",
      "type": "string",
      "enum": ["eslint", "tslint"],
      "default": "eslint"
    },
    "unitTestRunner": {
      "type": "string",
      "enum": ["jest", "none"],
      "description": "Test runner to use for unit tests.",
      "default": "jest"
    },
    "skipFormat": {
      "description": "Skip formatting files.",
      "type": "boolean",
      "default": false
    },
    "skipTsConfig": {
      "type": "boolean",
      "default": false,
      "description": "Do not update `tsconfig.json` for development experience."
    },
    "pascalCaseFiles": {
      "type": "boolean",
      "description": "Use pascal case component file name (e.g. `App.tsx`).",
      "alias": "P",
      "default": false
    },
    "routing": {
      "type": "boolean",
      "description": "Generate library with routes."
    },
    "appProject": {
      "type": "string",
      "description": "The application project to add the library route to.",
      "alias": "a"
    },
    "publishable": {
      "type": "boolean",
      "description": "Create a publishable library."
    },
    "buildable": {
      "type": "boolean",
      "default": false,
      "description": "Generate a buildable library."
    },
    "importPath": {
      "type": "string",
      "description": "The library name used to import it, like `@myorg/my-awesome-lib`."
    },
    "component": {
      "type": "boolean",
      "description": "Generate a default component.",
      "default": true
    },
    "js": {
      "type": "boolean",
      "description": "Generate JavaScript files rather than TypeScript files.",
      "default": false
    },
    "globalCss": {
      "type": "boolean",
      "description": "When `true`, the stylesheet is generated using global CSS instead of CSS modules (e.g. file is `*.css` rather than `*.module.css`).",
      "default": false
    },
    "strict": {
      "type": "boolean",
      "description": "Whether to enable tsconfig strict mode or not.",
      "default": true
    },
    "setParserOptionsProject": {
      "type": "boolean",
      "description": "Whether or not to configure the ESLint `parserOptions.project` option. We do not do this by default for lint performance reasons.",
      "default": false
    },
    "standaloneConfig": {
      "description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",
      "type": "boolean"
    },
    "compiler": {
      "type": "string",
      "enum": ["babel", "swc"],
      "default": "swc",
      "description": "Which compiler to use."
    }
  },
  "required": ["name", "type", "scope", "domain"]
}
Вход в полноэкранный режим Выход из полноэкранного режима

Определения типов для схемы

// schema.d.ts
import { SupportedStyles } from '@nrwl/react';
export interface ReactLibraryGeneratorSchema {
  name: string;
  directory?: string;
  style: SupportedStyles;
  skipTsConfig: boolean;
  skipFormat: boolean;
  pascalCaseFiles?: boolean;
  routing?: boolean;
  appProject?: string;
  unitTestRunner: 'jest' | 'none';
  linter: Linter;
  component?: boolean;
  publishable?: boolean;
  buildable?: boolean;
  importPath?: string;
  js?: boolean;
  globalCss?: boolean;
  strict?: boolean;
  setParserOptionsProject?: boolean;
  standaloneConfig?: boolean;
  compiler?: 'babel' | 'swc';
  domain: 'web' | 'common';
  type: 'data' | 'model' | 'util' | 'feature' | 'ui';
  scope: string;
}
Войти в полноэкранный режим Выход из полноэкранного режима

Генератор

Фактически генератор будет очень простым. Он будет запускать оригинальный генератор, импортированный из @nrwl/react. Он установит теги для типа, области видимости и домена из опций. Установит тег фреймворка как react. Затем он добавит файлы из шаблонов плагина.

// generator.ts
import {
  generateFiles,
  getWorkspaceLayout,
  joinPathFragments,
  names,
  Tree,
} from '@nrwl/devkit';
import { libraryGenerator } from '@nrwl/react';
import { ReactLibraryGeneratorSchema } from './schema';

export default async function (
  tree: Tree,
  options: ReactLibraryGeneratorSchema,
) {
  const { libsDir } = getWorkspaceLayout(tree);
  const name = names(options.name).fileName;
  const projectDirectory = options.directory
    ? `${names(options.directory).fileName}/${name}`
    : name;
  const projectRoot = joinPathFragments(libsDir, projectDirectory);
  const tags = `project:${options.domain},scope:${options.scope},type:${options.type},framework:react`;

  // run the original generator
  await libraryGenerator(tree, { ...options, tags });

  // Add own custom files
  generateFiles(
    tree, // the virtual file system
    joinPathFragments(__dirname, './files'), // path to the file templates)
    projectRoot, // destination path of the files
    { ...options, name: names(options.name).className, tmpl: '' }, // config object to replace variable in file templates
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

Шаблон

README.md_tmpl_:

# <%= name %>

This is React library for <%= domain %>

type: <%= type %>
scope: <%= scope %>

Войти в полноэкранный режим Выход из полноэкранного режима

Примечание! При использовании кода VS и NX Console необходимо перезагрузить окно, прежде чем изменения в вашем генераторе будут видны в NX Console.

Тестирование

Что замечательно в NX Plugin, так это то, что он поставляется с модульными и e2e тестами. Я считаю e2e тесты особенно полезными при написании моих генераторов, потому что они создают новое рабочее пространство tmp под path_to_your_workspace/tmp/nx-e2e. Легко проверить правильность сгенерированных файлов, не загрязняя собственный репозиторий тестовыми библиотеками. Также легко добавить модульные тесты для генераторов. Вот небольшой пример, как проверить, что генератор добавляет файлы правильно.

// generator.spec.ts
// ...
  it('should create readme', async () => {
    await generator(appTree, options);
    const readme = appTree.read('libs/test/README.md');
    expect(readme.toString()).toMatchInlineSnapshot(`
      "# Test

      This is React library for web

      type: data
      scope: shared
      "
    `);
  });
Вход в полноэкранный режим Выйти из полноэкранного режима

Следующие шаги

Теперь у меня есть простой плагин, который я могу расширить для создания более сложных генераторов. Помните, что мы всегда можем запустить генераторы из других плагинов и добавить только то, чего не хватает. Лучшее место для получения вдохновения о том, что возможно, это посмотреть на существующие плагины из репозитория NX. Также есть хорошая документация в NX Docs.

Оцените статью
Procodings.ru
Добавить комментарий