Как создать простой плагин Vue CLI

Если вы используете фреймворк Vue, вы, вероятно, уже знаете, что такое Vue CLI. Это полноценная система для быстрой разработки на Vue.js, обеспечивающая создание строительных конструкций и прототипов.

Важной частью CLI являются плагины cli. Они могут изменять внутреннюю конфигурацию веб-пакета и вводить команды в vue-cli-service. Отличным примером является @vue/cli-plugin-typescript: когда вы вызываете его, он добавляет tsconfig.json в ваш проект и изменяет App.vue, чтобы иметь типы, поэтому вам не нужно делать это вручную.

Плагины очень полезны, и их сегодня много для разных случаев - поддержка GraphQL + Apollo, Electron builder, добавление UI-библиотек, таких как Vuetify или ElementUI… Но что делать, если вы хотите иметь плагин для какой-то конкретной библиотеки, а ее нет? Что ж, это был мой случай 😅… и я решил построить его сам.

В этой статье мы будем создавать vue-cli-plugin-rx. Это позволяет нам добавить vue-rx библиотеку в наш проект и получить поддержку RxJS в нашем приложении Vue.

🎛️ Структура плагина Vue-cli

Прежде всего, что такое плагин CLI? Это просто пакет npm с определенной структурой. Что касается документов, он должен иметь служебный плагин в качестве основного экспорта и может иметь дополнительные функции, такие как генератор и файл подсказок.

Пока совершенно неясно, что такое сервисный плагин или генератор, но не беспокойтесь - это будет объяснено позже!

Конечно, как и любой пакет npm, плагин CLI должен иметь package.json в своей корневой папке, и было бы неплохо иметь README.md с некоторым описанием.

Итак, начнем со следующей структуры:

.
├── README.md
├── index.js      # service plugin
└── package.json

Теперь давайте посмотрим на необязательную часть. генератор может вставлять дополнительные зависимости или поля в package.json и добавлять файлы в проект. Оно нам нужно? Определенно, мы хотим добавить rxjs и vue-rx в качестве наших зависимостей! Более того, мы хотим создать некоторый пример компонента, если пользователь хочет добавить его во время установки плагина. Итак, нам нужно добавить либо generator.js, либо generator/index.js. Я предпочитаю второй способ. Теперь структура выглядит так:

.
├── README.md
├── index.js      # service plugin
├── generator
│   └── index.js  # generator
└── package.json

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

├── README.md
├── index.js      # service plugin
├── generator
│   └── index.js  # generator
├── prompts.js    # prompts file
└── package.json

⚙️ Сервисный плагин

Плагин службы должен экспортировать функцию, которая получает два аргумента: экземпляр PluginAPI и объект, содержащий локальные параметры проекта. Он может расширять / изменять внутреннюю конфигурацию веб-пакета для различных сред и вводить дополнительные команды в vue-cli-service. Давайте подумаем на минутку: хотим ли мы как-то изменить конфигурацию webpack или создать дополнительную задачу npm? Ответ - НЕТ, мы просто хотим добавить некоторые зависимости и пример компонента, если это необходимо. Итак, все, что нам нужно изменить в index.js, это:

module.exports = (api, opts) => {}

Если ваш плагин требует изменения конфигурации веб-пакета, прочтите этот раздел в официальных документах Vue CLI.

🛠️ Добавление зависимостей через генератор

Как упоминалось выше, генератор плагинов CLI помогает нам добавлять зависимости и изменять файлы проекта. Итак, первый шаг, который нам нужен, - это добавить в наш плагин две зависимости: rxjs и vue-rx:

module.exports = (api, options, rootOptions) => {
  api.extendPackage({
    dependencies: {
      'rxjs': '^6.3.3',
      'vue-rx': '^6.0.1',
    },
  });
}

Генератор должен экспортировать функцию, которая получает три аргумента: экземпляр GeneratorAPI, параметры генератора и - если пользователь создает проект с использованием определенного пресета - весь пресет будет передан в качестве третьего аргумента.

api.extendPackage расширяет package.json проект. Вложенные поля объединяются глубоко, если вы не передаете { merge: false } в качестве параметра. В нашем случае мы добавляем две зависимости к dependenciessection.

Теперь нам нужно изменить main.js файл. Чтобы заставить RxJS работать внутри компонентов Vue, нам нужно импортировать VueRx и вызвать Vue.use(VueRx)

Во-первых, давайте создадим строку, которую мы хотим добавить в основной файл:

let rxLines = `\nimport VueRx from 'vue-rx';\n\nVue.use(VueRx);`;

Теперь мы будем использовать api.onCreateComplete хук. Он вызывается, когда файлы записаны на диск.

api.onCreateComplete(() => {
    // inject to main.js
    const fs = require('fs');
    const ext = api.hasPlugin('typescript') ? 'ts' : 'js';
    const mainPath = api.resolve(`./src/main.${ext}`);
};

Здесь мы ищем главный файл: если это проект TypeScript, это будет main.ts, в противном случае это будет файл main.js. fs вот файловая система.

Теперь давайте изменим содержимое файла

api.onCreateComplete(() => {
    // inject to main.js
    const fs = require('fs');
    const ext = api.hasPlugin('typescript') ? 'ts' : 'js';
    const mainPath = api.resolve(`./src/main.${ext}`);
    // get content
    let contentMain = fs.readFileSync(mainPath, { encoding: 'utf-8' });
    const lines = contentMain.split(/\r?\n/g).reverse();
    // inject import
    const lastImportIndex = lines.findIndex(line => line.match(/^import/));
    lines[lastImportIndex] += rxLines;
    // modify app
    contentMain = lines.reverse().join('\n');
    fs.writeFileSync(mainPath, contentMain, { encoding: 'utf-8' });
  });
};

Что здесь происходит? Мы читаем содержимое основного файла, разбиваем его на строки и меняем их порядок. Затем мы ищем первую строку с помощью оператора import (он будет последним после второго возврата) и добавляем туда наш rxLines. После этого мы переворачиваем массив строк и сохраняем файл.

💻 Тестирование cli-plugin локально

Давайте добавим информацию о нашем подключаемом модуле в package.json и попробуем установить его локально для тестирования. Самая важная информация обычно - это имя и версия плагина (эти поля потребуются при публикации плагина в npm), но не стесняйтесь добавлять дополнительную информацию! Полный список package.jsonполей можно найти здесь. Ниже мой файл:

{
  "name": "vue-cli-plugin-rx",
  "version": "0.1.5",
  "description": "Vue-cli 3 plugin for adding RxJS support to project using vue-rx",
  "main": "index.js",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/NataliaTepluhina/vue-cli-plugin-rx.git"
  },
  "keywords": [
    "vue",
    "vue-cli",
    "rxjs",
    "vue-rx"
  ],
  "author": "Natalia Tepluhina <[email protected]>",
  "license": "MIT",
  "homepage": "https://github.com/NataliaTepluhina/vue-cli-plugin-rx#readme"
}

Пришло время проверить, как работает наш плагин! Для этого давайте создадим простой проект на базе vue-cli:

vue create test-app

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

cd test-app
npm install --save-dev file:/full/path/to/your/plugin

После установки плагина его нужно вызвать:

vue invoke vue-cli-plugin-rx

Теперь, если вы попытаетесь проверить файл main.js, вы увидите, что он изменился:

import Vue from 'vue'
import App from './App.vue'
import VueRx from 'vue-rx';
Vue.use(VueRx);

Кроме того, вы можете найти свой плагин в разделе devDependencies тестового приложения package.json.

📂 Создание нового компонента с генератором

Отлично, плагин работает! Пришло время немного расширить его функциональность и дать возможность создать пример компонента. Generator API использует для этой цели метод render.

Во-первых, давайте создадим этот пример компонента. Это должен быть файл .vue, расположенный в папке проекта src/components. Создайте папку template внутри папки generator, а затем имитируйте всю эту структуру внутри нее:

Ваш примерный компонент должен быть… ну, просто однофайловым компонентом Vue! Я не буду углубляться в объяснения RxJS в этой статье, но я создал простой счетчик кликов на основе RxJS с двумя кнопками:

Его исходный код можно найти здесь.

Теперь нам нужно указать нашему плагину, чтобы он отображал этот компонент при вызове. Для этого добавим этот код в generator/index.js:

api.render('./template', {
  ...options,
});

Это отобразит всю папку template. Теперь при вызове плагина в папку src/components будет добавлен новый RxExample.vue.

Я решил не перезаписывать App.vue и разрешил пользователям добавлять пример компонента самостоятельно. Однако вы можете заменить части существующих файлов, см. Примеры в документации

⌨️ Обработка выбора пользователя с помощью подсказок

Что делать, если пользователь не хочет иметь компонент-пример? Разве не разумно позволить пользователям решать это во время установки плагина? Вот почему существуют подсказки!

Ранее мы создали prompts.js файл в корневой папке плагина. Этот файл должен содержать массив вопросов: каждый вопрос - это объект с определенным набором полей, таких как name, message, choices, type и т. Д.

Имя важно: мы будем использовать его позже в генераторе, чтобы создать условие для рендеринга примера компонента!

Подсказки могут иметь разные типы, но в CLI наиболее широко используются checkbox и confirm. Мы будем использовать последний, чтобы создать вопрос с ответом да / нет.

Итак, давайте добавим наш код приглашения в prompts.js!

module.exports = [
  {
    name: `addExample`,
    type: 'confirm',
    message: 'Add example component to components folder?',
    default: false,
  },
];

У нас есть приглашение addExample, которое спросит пользователя, хочет ли он добавить компонент в папку компонентов. Ответ по умолчанию - No.

Вернемся к файлу генератора и внесем некоторые исправления. Замените api.render вызов на

if (options.addExample) {
    api.render('./template', {
      ...options,
    });
}

Мы проверяем, есть ли у addExample положительный ответ, и если да, компонент будет отрисован.

Не забывайте переустанавливать и тестировать плагин после каждого изменения!

📦 Сделайте это общедоступным!

Важное примечание: перед публикацией плагина проверьте, соответствует ли его имя шаблону vue-cli-plugin-<YOUR-PLUGIN-NAME>. Это позволяет @vue/cli-service обнаруживать ваш плагин и устанавливать его через vue add.

Я также хотел, чтобы мой плагин красиво отображался в пользовательском интерфейсе Vue CLI, поэтому я добавил описание, теги и имя репозитория в package.json и создал логотип. Изображение логотипа должно быть названо logo.png и помещено в корневую папку плагина. В результате мой плагин в списке UI-плагинов выглядит так:

Теперь мы готовы к публикации. Вы должны быть зарегистрированы как npmjs.com и, очевидно, у вас должен быть установлен npm.

Чтобы опубликовать плагин, перейдите в корневую папку плагина и введите npm publish в терминале. Вуаля, вы только что опубликовали пакет npm!

В этот момент вы сможете установить плагин с помощью vue addcommand. Попытайся!

Конечно, плагин, описанный в этой статье, очень прост, но я надеюсь, что мои инструкции помогут кому-то начать разработку cli-плагинов.

Какой плагин CLI вам не хватает? Поделитесь своими идеями в комментариях 🤗