Разработка плагинов
Основные концепции
Существуют две основные части системы:
@svel/cli
: установленный глобально, предоставляет командуvue create <app>
;@svel/cli-service
: установленный локально, предоставляет командуvue-cli-service
.
Обе части используют архитектуру, основанную на плагинах.
Creator
Creator — это класс, создаваемый при вызове vue create <app>
. Отвечает за запросы пользователю для получения настроек, запускает генераторы и устанавливает зависимости.
Service
Service — это класс, создаваемый при вызове vue-cli-service <command> [...args]
. Отвечает за управление внутренней конфигурацией webpack, и предоставляет команды для запуска и сборки проекта.
Плагин для CLI (CLI Plugin)
Плагин для CLI — это npm-пакет, который может добавлять дополнительные возможности в проект @svel/cli
. Он должен всегда содержать плагин для сервиса в качестве основного экспорта, и может опционально содержать Generator и файл подсказок.
Типичная структура каталогов плагина для CLI выглядит следующим образом:
.
├── README.md
├── generator.js # генератор (опционально)
├── prompts.js # файл подсказок (опционально)
├── index.js # плагин для сервиса
└── package.json
Плагин для сервиса (Service Plugin)
Сервисные плагины загружаются автоматически при создании экземпляра сервиса — т.е. каждый раз когда команда vue-cli-service
вызывается внутри проекта.
Обратите внимание, что концепция «плагина для сервиса», которую мы обсуждаем здесь, несколько уже, чем концепция «плагина для CLI», который публикуется как npm-пакет. Первый относится только к модулю, который будет загружен @svel/cli-service
когда он инициализирован, и обычно является частью последнего.
Кроме того, встроенные команды и конфигурация модулей @svel/cli-service
также реализованы как плагины для сервиса.
Плагин для сервиса должен экспортировать функцию, которая принимает два аргумента:
Экземпляр PluginAPI
Объект, содержащий локальные настройки проекта, указанные в
svelte.config.js
, или в поле"vue"
вpackage.json
.
API позволяет плагинам для сервиса расширять / изменять внутреннюю конфигурацию webpack для разных окружений и внедрять дополнительные команды в vue-cli-service
. Например:
module.exports = (api, projectOptions) => {
api.chainWebpack(webpackConfig => {
// изменение конфигурации webpack с помощью webpack-chain
})
api.configureWebpack(webpackConfig => {
// изменение конфигурации webpack
// или возвращение объекта, который будет объединён с помощью webpack-merge
})
api.registerCommand('test', args => {
// регистрация команды `vue-cli-service test`
})
}
Установка режимов для команд
Примечание: установка режимов плагинами была изменена в beta.10.
Если зарегистрированная в плагине команда должна запускаться в определённом режиме по умолчанию,
плагин должен предоставлять её через module.exports.defaultModes
в формате { [commandName]: mode }
:
module.exports = api => {
api.registerCommand('build', () => {
// ...
})
}
module.exports.defaultModes = {
build: 'production'
}
Это связано с тем, что ожидаемый режим для команды должен быть известен до загрузки переменных окружения, что в свою очередь должно произойти до загрузки пользовательских настроек / применения плагинов.
Получение итоговой конфигурации Webpack в плагинах
Плагин может получить итоговую конфигурацию webpack вызвав api.resolveWebpackConfig()
. Каждый вызов генерирует новую конфигурацию webpack, которая может быть дополнительно изменена при необходимости:
module.exports = api => {
api.registerCommand('my-build', args => {
const configA = api.resolveWebpackConfig()
const configB = api.resolveWebpackConfig()
// изменение configA и configB для разных целей...
})
}
// не забудьте указать режим по умолчанию для правильных переменных окружения
module.exports.defaultModes = {
'my-build': 'production'
}
В качестве альтернативы, плагин также может получить новую конфигурацию в формате chainable вызвав api.resolveChainableWebpackConfig()
:
api.registerCommand('my-build', args => {
const configA = api.resolveChainableWebpackConfig()
const configB = api.resolveChainableWebpackConfig()
// изменяем цепочки configA и configB для разных целей...
const finalConfigA = configA.toConfig()
const finalConfigB = configB.toConfig()
})
Пользовательские настройки для сторонних плагинов
Экспорт из svelte.config.js
валидируется по схеме чтобы избежать опечаток и неправильных значений конфигурации. Тем не менее, можно настраивать поведение сторонних плагинов через поле pluginOptions
. Например, для следующего svelte.config.js
:
module.exports = {
pluginOptions: {
foo: { /* ... */ }
}
}
Сторонний плагин может получить доступ к свойству projectOptions.pluginOptions.foo
для определения собственной конфигурации.
Генератор (Generator)
Плагин для CLI, опубликованный как пакет, может содержать файл generator.js
или generator/index.js
. Генератор внутри плагина вызывается в двух возможных сценариях:
Во время первоначального создания проекта, если плагин для CLI установлен как часть пресета для создания проекта.
Когда плагин устанавливается после создания проекта и вызывается через
vue invoke
.
GeneratorAPI позволяет генератору внедрять дополнительные зависимости или поля в package.json
и добавлять файлы в проект.
Генератор должен экспортировать функцию, которая принимает три аргумента:
Экземпляр
GeneratorAPI
;Настройки генератора для этого плагина. Они будут получены во время интерактивного выбора пользователем на этапе создания проекта, или загружаются из сохранённого пресета в
~/.vuerc
. Например, если сохранённый файл~/.vuerc
выглядит так:{ "presets" : { "foo": { "plugins": { "@svel/cli-plugin-foo": { "option": "bar" } } } } }
И если пользователь создаёт проект с использованием пресета
foo
, тогда генератор@svel/cli-plugin-foo
получит{ option: 'bar' }
в качестве второго аргумента.Для стороннего плагина эти параметры будут получены из интерактивного выбора пользователем или аргументов командной строки, когда выполняется команда
vue invoke
(см. Интерактивные подсказки для сторонних плагинов).Весь пресет (
presets.foo
) будет передан в качестве третьего аргумента.
Например:
module.exports = (api, options, rootOptions) => {
// изменение полей package.json
api.extendPackage({
scripts: {
test: 'vue-cli-service test'
}
})
// копирование и рендеринг всех файлов в ./template с помощью ejs
api.render('./template')
if (options.foo) {
// генерация файлов по условию
}
}
Шаблоны генератора
Когда вы вызываете api.render('./template')
, генератор будет рендерить файлы в ./template
(разрешённые относительно файла генератора) с помощью EJS.
Кроме того, вы можете наследовать и заменять части существующего файла шаблона (даже из другого пакета) с помощью YAML front-matter:
---
extend: '@svel/cli-service/generator/template/src/App.vue'
replace: !!js/regexp /<script>[^]*?<\/script>/
---
<script>
export default {
// Заменяем скрипт по умолчанию
}
</script>
Также возможно выполнять несколько замен в файле, хотя вам потребуется обернуть строки для замены в блоки из <%# REPLACE %>
и <%# END_REPLACE %>
:
---
extend: '@svel/cli-service/generator/template/src/App.vue'
replace:
- !!js/regexp /Welcome to Your Vue\.js App/
- !!js/regexp /<script>[^]*?<\/script>/
---
<%# REPLACE %>
Заменяем приветственное сообщение
<%# END_REPLACE %>
<%# REPLACE %>
<script>
export default {
// Заменяем скрипт по умолчанию
}
</script>
<%# END_REPLACE %>
Ограничения имён файлов
Если вы хотите отрендерить файл шаблона, имя которого начинается с точки (т.е. .env
) вам необходимо следовать определённому соглашению по именованию, поскольку файлы именуемые с точки (dotfiles) игнорируются при публикации вашего плагина в npm:
# dotfile шаблоны должны использовать символ подчёркивания вместо точки:
/generator/template/_env
# При вызове api.render('./template'), это будет отрендерено в каталоге проекта как:
.env
Следовательно, это значит, что необходимо также следовать специальному соглашению по именованию если вы хотите отрендерить файл, чьё имя начинается с подчёркивания:
# такие шаблоны должны иметь два символа подчёркивания вместо точки:
/generator/template/__variables.scss
# При вызове api.render('./template'), это будет отрендерено в каталоге проекта как:
_variables.scss
Интерактивные подсказки
Интерактивные подсказки для встроенных плагинов
Только встроенные плагины имеют возможность настраивать исходные подсказки при создании нового проекта, а модули подсказок расположены внутри пакета @svel/cli
.
Модуль подсказок должен экспортировать функцию, которая получает экземпляр PromptModuleAPI. Подсказки представлены с помощью inquirer под капотом:
module.exports = api => {
// объект возможности должен быть корректным объектом выбора inquirer
api.injectFeature({
name: 'Какая-то суперская возможность',
value: 'my-feature'
})
// injectPrompt ожидает корректный объект подсказки inquirer
api.injectPrompt({
name: 'someFlag',
// убедитесь, что подсказка отображается только если выбрана ваша функция
when: answers => answers.features.include('my-feature'),
message: 'Вы хотите включить флаг foo?',
type: 'confirm'
})
// когда все подсказки завершены, внедряйте ваш плагин в настройки,
// которые будут передаваться генераторам
api.onPromptComplete((answers, options) => {
if (answers.features.includes('my-feature')) {
options.plugins['vue-cli-plugin-my-feature'] = {
someFlag: answers.someFlag
}
}
})
}
Интерактивные подсказки для сторонних плагинов
Плагины сторонних разработчиков обычно устанавливаются вручную после того, как проект уже создан, и пользователь будет инициализировать плагин вызовом команды vue invoke
. Если плагин содержит prompts.js
в своём корневом каталоге, он будет использован во время вызова. Файл должен экспортировать массив вопросов, которые будут обрабатываться Inquirer.js. Объект с ответами будет передаваться генератору плагина в качестве настроек.
В качестве альтернативы, пользователь может пропустить подсказки и напрямую инициализировать плагин, передав параметры через командную строку, например:
vue invoke my-plugin --mode awesome
Распространение плагина
Чтобы CLI-плагин мог использоваться другими разработчиками, он должен быть опубликован на npm придерживаясь соглашения по именованию vue-cli-plugin-<name>
. Следуя соглашению по именованию позволит вашему плагину быть:
- Легко находимым с помощью
@svel/cli-service
; - Легко находимым другими разработчиками через поиск;
- Устанавливаться через
vue add <name>
илиvue invoke <name>
.
Примечание о разработке Core-плагинов
Примечание
Этот раздел применим только в случае, если вы работаете над встроенным плагином непосредственно внутри vuejs/vue-cli
репозитория.
Плагин с генератором, который внедряет дополнительные зависимости, отличные от пакетов в репозитории (например, chai
внедряется @svel/cli-plugin-unit-mocha/generator/index.js
) должен перечислять эти зависимости в собственном поле devDependencies
. Это гарантирует:
что пакет всегда существует в корневом
node_modules
репозитория, поэтому нам не нужно их переустанавливать при каждом тестировании.что
yarn.lock
остаётся постоянным, поэтому CI сможет лучше применять его кэширование.