Публикация Vue-компонентов в npm

Простой пример

Vue-компоненты по своей сути предназначены для повторного использования. Это легко, когда компонент используется только в одном приложении. Но как мы можем написать компонент один раз и использовать его на нескольких сайтах или приложениях? Пожалуй, самое простое решение — использовать npm.

Подготовив свой компонент для распространения через npm мы сможем его импортировать в процессе сборки для использования в полноценных веб-приложениях:

import MyComponent from 'my-component';

export default {
  components: {
    MyComponent
  },
  // остальной код компонента
}

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

<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/my-component"></script>
...
<my-component></my-component>
...

Данный подход поможет не только избежать копирования компонентов, но также помочь сообществу Vue.

Разве я не могу просто поделиться .vue файлами напрямую?

Компоненты Vue уже могут быть представлены как один файл. Так как однофайловые компоненты уже являются одним файлом, вы можете спросить:

«Почему люди не могут использовать мои .vue файлы напрямую? Не это ли самый простой способ делиться Vue-компонентами?»

Это правда, вы можете делиться непосредственно файлами .vue, и все, кто используют сборку Vue, включающую в себя компилятор, смогут сразу их применять. К тому же, в SSR сборке используется конкатенация строк для оптимизации, в таком случае .vue файл может быть предпочтительнее (Подготовка Vue-компонентов для npm > Использование SSR для более подробного объяснения). Тем не менее, вышеописанный метод исключает использование компонентов непосредственно в браузере с помощью тега <script>, а также для всех, кто использует runtime сборку, или другой тип сборки, который не понимает как обрабатывать .vue файлы.

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

Подготовка компонентов для публикации в npm

Представим следующую файловую структуру:

package.json
build/
   rollup.config.js
src/
   wrapper.js
   my-component.vue
dist/

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

Как npm узнает, какую версию использовать в браузере, а какую в сборке.

Файл package.json, используемый для npm, нуждается только в одной версии (main), но как оказалось, мы этим не ограничены. Мы можем рассмотреть наиболее распространённые варианты использования, указав 2 дополнительные версии (module и unpkg), и предоставить доступ к самому файлу .vue, используя свойство browser. Пример package.json представлен ниже:

{
  "name": "my-component",
  "version": "1.2.3",
  "main": "dist/my-component.umd.js",
  "module": "dist/my-component.esm.js",
  "unpkg": "dist/my-component.min.js",
  "browser": {
    "./sfc": "src/my-component.vue"
  },
  ...
}

Когда используется Webpack 2+, Rollup или другие современные системы сборки, будет выбран файл из сборки module. Более старые приложения будут использовать файл из main, а файл из unpkg может быть использован непосредственно в браузере. На самом деле, cdn unpkg автоматически используется, когда кто-то вбивает URL вашего модуля в свой сервис.

Использование с SSR

Вы могли заметить, что браузеры не будут использовать версию browser вашего модуля. Причина в том, что на самом деле это свойство предназначено для того, чтобы авторы могли предоставить подсказки для сборщиков, которые, в свою очередь, создадут свои собственные версии модуля для использования на клиенте. Проявив немного креативности, мы можем сопоставить псевдоним с самим файлом .vue. Например:

import MyComponent from 'my-component/sfc'; // Обратите внимание на '/sfc'

Системы сборки видят объявление browser в package.json и преобразовывают запрос к my-component/sfc в my-component/src/my-component.vue, в результате используется исходный файл .vue. Теперь процесс SSR сможет использовать оптимизацию конкатенации строк, необходимую для повышения производительности.

Примечание: При непосредственном использовании компонентов .vue обратите внимание на любую предварительную обработку, требуемую тегам script и style. Эти зависимости будут переданы пользователям. Попробуйте сделать простые однофайловые компоненты максимально тривиальными.

Как мне сделать несколько версий моего компонента?

Нет необходимости писать код модуля несколько раз. Можно подготовить все 3 версии вашего компонента за 1 шаг и в считанные секунды. Для примера, ниже используется Rollup из-за своей минимальной конфигурации, но подобное можно сделать и с другими системами сборки - более подробно можно прочитать здесь. Раздел scripts в файле package.json можно обновить, добавив отдельную команду для каждой версии сборки и общую команду build, которая запустит их вместе. После этого, наш package.json будет выглядеть следующим образом:

{
  "name": "my-component",
  "version": "1.2.3",
  "main": "dist/my-component.umd.js",
  "module": "dist/my-component.esm.js",
  "unpkg": "dist/my-component.min.js",
  "browser": {
    "./sfc": "src/my-component.vue"
  },
  "scripts": {
    "build": "npm run build:umd & npm run build:es & npm run build:unpkg",
    "build:umd": "rollup --config build/rollup.config.js --format umd --file dist/my-component.umd.js",
    "build:es": "rollup --config build/rollup.config.js --format es --file dist/my-component.esm.js",
    "build:unpkg": "rollup --config build/rollup.config.js --format iife --file dist/my-component.min.js"
  },
  "devDependencies": {
    "rollup": "^1.17.0",
    "rollup-plugin-buble": "^0.19.8",
    "rollup-plugin-commonjs": "^10.0.1",
    "rollup-plugin-vue": "^5.0.1",
    "vue": "^2.6.10",
    "vue-template-compiler": "^2.6.10"
    ...
  },
  ...
}

Не забудьте, если у вас уже есть файл package.json, вероятнее всего он будет содержать больше кода. Пример выше только демонстрирует, как можно начинать. Также, пакеты, перечисленные в devDependencies (не их версии), единственное требование для rollup, чтобы создать три отдельных вида сборки (umd, es, и unpkg). С появлением новых версий, модули следует обновить по мере необходимости.

Мы закончили с изменениями в package.json. Далее, нам понадобится небольшая обёртка для экспорта или автоматической установки актуального однофайлового компонента. Плюс - минимальная конфигурация системы сборки Rollup - и настройка завершена!

Как выглядит мой подготовленный для npm компонент?

В зависимости от того, как используется ваш компонент, он должен быть JavaScript-модулем, совместимым с CommonJS/UMD, ES6 javascript модулем, или, в случае использования тега <script>, он будет автоматически загружаться во Vue с помощью Vue.use(...), поэтому он будет сразу доступен на странице. Это достигается простым файлом wrapper.js, который обрабатывает экспорт и автоматическую установку модуля. В целом, обёртка выглядит следующим образом:

// Импорт vue компонента
import component from './my-component.vue';

// Объявление функции установки, выполняемой Vue.use()
export function install(Vue) {
  if (install.installed) return;
  install.installed = true;
  Vue.component('MyComponent', component);
}

// Создание значения модуля для Vue.use()
const plugin = {
  install
};

// Автоматическая установка, когда vue найден (например в браузере с помощью тега <script>)
let GlobalVue = null;
if (typeof window !== 'undefined') {
  GlobalVue = window.Vue;
} else if (typeof global !== 'undefined') {
  GlobalVue = global.Vue;
}
if (GlobalVue) {
  GlobalVue.use(plugin);
}

// Экспорт компонента, для использования в качестве модуля (npm/webpack/etc.)
export default component;

Обратите внимание, что в первой строке импортируется непосредственно ваш однофайловый компонент, а последняя строка экспортирует его без изменений. Как указано в комментариях в остальной части кода, обёртка предоставляет функцию install для Vue, затем пытается обнаружить Vue и автоматически установить компонент. Выполнив 90% работы, настало время ускориться к финишу!

Как настроить сборку с Rollup?

С готовой секцией scripts и обёрткой для однофайловых компонентов, всё что нам осталось - убедиться, что Rollup правильно настроен. К счастью, это можно сделать с помощью маленького файла rollup.config.js, состоящего из 16 строк кода:

import commonjs from 'rollup-plugin-commonjs'; // Конвертирование CommonJS модулей в ES6
import vue from 'rollup-plugin-vue'; // Обработка однофайловых компонентов .vue
import buble from 'rollup-plugin-buble'; // Транспиляция/добавление полифилов для умеренной поддержки браузеров
export default {
  input: 'src/wrapper.js', // Путь до относительного package.json
  output: {
    name: 'MyComponent',
    exports: 'named'
  },
  plugins: [
    commonjs(),
    vue({
        css: true, // Динамически внедряем CSS в тег <style>
        compileTemplate: true, // Явное преобразование шаблона в рендер-функцию
    }),
    buble() // Транспиляция в ES5
  ],
};

Этот пример конфигурации содержит минимальные настройки чтобы подготовить ваш однофайловый компонент для npm. Есть множество способов изменения, например экспорт CSS в отдельный файл, использование CSS-препроцессоров, минификации JS и т.д.

Кроме того, стоит обратить внимание на name, данное компоненту. Компоненту будет присвоено имя в формате PascalCase, и оно должно соответствовать имени компонента в формате kebab-case, которое используется в данной статье.

Это заменит мой текущий процесс разработки?

Представленная конфигурация не предназначена для замены процесса разработки, который вы используете в настоящее время. Если у вас настроен webpack с горячей перезагрузкой модулей (HMR), продолжайте его использовать! Если вы начинаете с нуля - не стесняйтесь установить Vue CLI 3, который предоставит вам готовую конфигурацию с HMR:

vue serve --open src/my-component.vue

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

Когда избегать этого способа

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

Кроме того, обратите внимание на любые зависимости, которые может иметь ваш компонент. Например, если вы подключаете стороннюю библиотеку для сортировки или для работы с API, Rollup может включить их в итоговый код сборки, если будет неправильно настроен. Чтобы иметь возможность использовать данный способ, вам нужно будет настроить Rollup таким образом, чтобы он исключал эти файлы из финального результата. Затем вам нужно будет обновить документацию, чтобы сообщить своим пользователям о зависимостях.

Альтернативные варианты

В то время, когда эта статья была написана, Vue CLI 3 был в бета-версии. Эта версия CLI содержит в себе встроенный режим сборки library, который создаёт версии CommonJS и UMD компонента. Этого должно быть достаточно, тем не менее вам нужно будет убедиться, что в вашем файле package.json правильно указаны значения main и unpkg. К тому же, отсутствует поддержка ES6 module, если только она не будет добавлена до релиза CLI или через плагин.

Благодарности

Этот рецепт является результатом выступления Mike Dodge на VueConf.us в марте 2018. Он опубликовал программу в npm, которая быстро сгенерирует образец однофайлового компонента. Вы можете скачать программу vue-sfc-rollup из npm. Также, вы можете склонировать код и отредактировать его.