Как создать и развернуть библиотеку компонентов Vue в NPM

от автора

Эта статья — перевод оригинальной статьи Adesoji Temitope «How to Create and Deploy a Vue Component Library to NPM«

Также я веду телеграм канал “Frontend по-флотски”, где рассказываю про интересные вещи из мира разработки интерфейсов.

Вступление

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

  • Создание библиотеки компонентов Vue.

  • Регистрация компонентов библиотеки.

  • Настройка процесса сборки.

  • Локальное тестирование, а затем публикация в npm.

Создание библиотеки компонентов Vue

Настройка проекта

Начнем с создания нашего проекта Vue. Мы будем использовать yarn для управления пакетами.

Для начала запустим:

npm init

Для этого урока мы собираемся создать только один компонент в нашей библиотеке компонентов. Одна из вещей, которую следует учитывать при создании библиотеки в масштабе, — это разрешить импорт только отдельных компонентов, чтобы активировать tree shaking.

Мы будем использовать такую структуру папок:

- src /   - components /     - button /       - button.vue       - index.ts      - index.ts    - styles /     - components /         - _button.scss      - index.scss  - Package.json - rollup.config.js

Создание компонента кнопки

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

Добавьте это в файл button.vue.

<!-- src/components/button/button.vue --> <template>     <button         v-bind="$attrs"         :class="rootClasses"         :type="type"         :disabled="computedDisabled"     >       <slot></slot>     </button> </template>  <script lang="ts"> import { defineComponent } from 'vue'  export default defineComponent({     name: 'DSButton',     inheritAttrs: false,     props: {         /**          * disabled status          * @values true, false          */         disabled: {             type: Boolean,         },         /**         * Color of button         * @values primary, secondary         */         variant: {             type: String,             validator: (value: string) => {                 return [                     'primary',                     'secondary'                 ].indexOf(value) >= 0             }         },         /**          * type of button          * @values button, submit          */         type: {             type: String,             default: 'button',             validator: (value: string) => {                 return [                     'button',                     'submit',                     'reset'                 ].indexOf(value) >= 0             }         },         /**          * Size of button          * @values sm, md, lg          */         size: {             type: String,             validator: (value: string) => {                 return [                     'sm',                     'md',                     'lg'                 ].indexOf(value) >= 0             }         }     },     computed: {         rootClasses() {             return [                 'ds-button',                 'ds-button--' + this.size,                 'ds-button--' + this.variant             ]         },         computedDisabled() {             if (this.disabled) return true             return null         }     } }) </script>

Давайте стилизуем компонент, добавим классы вариантов и размеров на основе того, что указано в файле.

Добавьте это в файл _button.scss.

// src/styles/components/_button.scss $primary: '#0e34cd'; $secondary: '#b9b9b9'; $white: '#ffffff'; $black: '#000000'; $small: '.75rem'; $medium: '1.25rem'; $large: '1.5rem';  .ds-button {     position: relative;     display: inline-flex;     cursor: pointer;     text-align: center;     white-space: nowrap;     align-items: center;     justify-content: center;     vertical-align: top;     text-decoration: none;     outline: none;      // variant     &--primary {         background-color: $primary;         color: $white;     }     &--secondary {         background-color: $secondary;         color: $black;     }      // size     &--sm {         min-width: $small;     }     &--md {         min-width: $medium;     }     &--lg {         min-width: $large;     } }

Регистрация компонентов библиотеки

Далее нам нужно зарегистрировать наши компоненты. Для этого мы импортируем все наши компоненты в один файл и создаем наш метод установки.

src/components/button/index.ts Этот файл экспортирует методы установки по умолчанию. В тех случаях, когда нам нужно импортировать только этот компонент кнопки в другие наши проекты, он также экспортирует кнопку, которую мы будем использовать чуть позже.

// src/components/button/index.ts import { App, Plugin } from 'vue'  import Button from './button.vue'  export default {     install(Vue: App) {         Vue.component(Button.name, Button)     } } as Plugin  export {     Button as DSButton }

src/components/index.ts Давайте импортируем сюда все компоненты из нашей папки компонентов. Поскольку у нас есть только наш компонент кнопки, мы импортируем его.

// src/components/index.ts import Button from './button'  export {     Button }

src/styles/index.scss Позволяет импортировать все стили компонентов в index.scss в нашей папке стилей. Это помогает нам иметь один источник экспорта для всех наших стилей.

// src/styles/index.scss @import "components/_button";

src/index.ts Давайте импортируем все компоненты в index.ts в нашей папке src. Здесь мы создаем наш метод установки для всех компонентов. Мы экспортируем DSLibrary по умолчанию, а также экспортируем все наши компоненты.

// src/index.ts import { App } from 'vue'  import * as components from './components'  const DSLibrary = {     install(app: App) {         // Auto import all components         for (const componentKey in components) {             app.use((components as any)[componentKey])         }     } }  export default DSLibrary  // export all components as vue plugin export * from './components'

Давайте создадим файл с именем shim-vue.d.ts, чтобы помочь нам импортировать файлы Vue в наши файлы TypeScript и удалить все вызванные им ошибки линтинга.

// src/shim-vue.d.ts declare module '*.vue' {   import type { DefineComponent } from 'vue';   const component: DefineComponent<{}, {}, any>;   export default component; }

Настройка процесса сборки

При сборке библиотеки нам нужно создать пакет и минификацию библиотеки, которая будет использоваться совместно с npm, для этого мы будем использовать Rollup. Rollup — это сборщик модулей для JavaScript, который компилирует небольшие фрагменты кода в нечто большее.

Установите rollup и все необходимые модули.

npm i -D rollup rollup-plugin-vue rollup-plugin-terser rollup-plugin-typescript2 @rollup/plugin-babel @rollup/plugin-commonjs @rollup/plugin-node-resolve

Сборка компонента (Код Vue)

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

  • ES module

  • CommonJS

Давайте создадим файл rollup.config.js в корневой папке и вставим это туда.

import { text } from './build/banner.json' import packageInfo from './package.json'  import vue from 'rollup-plugin-vue' import node from '@rollup/plugin-node-resolve' import cjs from '@rollup/plugin-commonjs' import babel from '@rollup/plugin-babel' import { terser } from 'rollup-plugin-terser' import typescript from 'rollup-plugin-typescript2';  import fs from 'fs' import path from 'path'  const baseFolderPath = './src/components/' const banner = text.replace('${version}', packageInfo.version)  const components = fs     .readdirSync(baseFolderPath)     .filter((f) =>         fs.statSync(path.join(baseFolderPath, f)).isDirectory()     )  const entries = {     'index': './src/index.ts',     ...components.reduce((obj, name) => {         obj[name] = (baseFolderPath + name)         return obj     }, {}) }  const babelOptions = {     babelHelpers: 'bundled' }  const vuePluginConfig = {     template: {         isProduction: true,         compilerOptions: {             whitespace: 'condense'         }     } }  const capitalize = (s) => {     if (typeof s !== 'string') return ''     return s.charAt(0).toUpperCase() + s.slice(1) }  export default () => {     let config = []      if (process.env.MINIFY === 'true') {         config = config.filter((c) => !!c.output.file)         config.forEach((c) => {             c.output.file = c.output.file.replace(/.m?js/g, r => `.min${r}`)             c.plugins.push(terser({                 output: {                     comments: '/^!/'                 }             }))         })     }     return config }

Далее мы создаем папку сборки в корне нашего проекта и добавляем в нее файл с именем banner.json. Мы хотим, чтобы наши сборки содержали текущую версию приложения каждый раз, когда мы делаем сборку. Этот файл уже импортирован в файл rollup.config.js, и мы используем версию пакета из нашего package.json для обновления версии.

{     "text": "/*! DS Library v${version} */ " }

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

entries: путь к файлам, которые мы хотим объединить в пакет.

external: необходим сторонний пакет Vue.

output.format: формат файла на выходе

output.dir: директория для бандла

output.banner: текст, добавленный в начало файла

plugins: указать методы, используемые для настройки rollup

Сначала мы создаем сборку esm для каждого компонента в нашей библиотеке:

config = [{   input: entries,   external: ['vue'],   output: {      format: 'esm',      dir: `dist/esm`,      entryFileNames: '[name].mjs',      chunkFileNames: '[name]-[hash].mjs',   },   plugins: [       node({           extensions: ['.vue', '.ts']       }),       typescript({           typescript: require('typescript')       }),       vue(vuePluginConfig),       babel(babelOptions),       cjs()    ], }],

Далее мы создаем единую сборку esm для всех компонентов:

config = [     ...,     {        input: 'src/index.ts',        external: ['vue'],        output: {            format: 'esm',            file: 'dist/ds-library.mjs',            banner: banner        },        plugins: [            node({                extensions: ['.vue', '.ts']            }),            typescript({                typescript: require('typescript')            }),            vue(vuePluginConfig),            babel(babelOptions),            cjs()        ]    } ],

Затем мы создаем сборку cjs для каждого компонента в нашей библиотеке:

config = [   ...,   ...,  {      input: entries,      external: ['vue'],      output: {          format: 'cjs',          dir: 'dist/cjs',          exports: 'named'      },      plugins: [          node({              extensions: ['.vue', '.ts']          }),          typescript({              typescript: require('typescript')          }),          vue(vuePluginConfig),          babel(babelOptions),          cjs()     ]  } ],

После этого мы создаем единую сборку cjs для каждого компонента в нашей библиотеке.

config = [   ...,   ...,   ...,  {      input: 'src/index.ts',      external: ['vue'],      output: {          format: 'umd',          name: capitalize('ds-library'),          file: 'dist/ds-library.js',          exports: 'named',          banner: banner,          globals: {              vue: 'Vue'          }      },      plugins: [          node({              extensions: ['.vue', '.ts']          }),          typescript({              typescript: require('typescript')          }),          vue(vuePluginConfig),          babel(babelOptions),          cjs()      ]  } ],

Наконец, мы обновляем наш package.json с помощью нашей команды скрипта. Нам нужны как rimraf (чтобы удалить нашу старую папку dist перед созданием нового пакета), так и clean-css (чтобы минимизировать наш файл css), поэтому давайте установим:

npm i -D rimraf clean-css-cli

теперь давайте обновим наш скрипт package.json

build:vue = rollup и минификацияbuild:style = объединил наши стили из scss в css, добавил текст нашего баннера (номер версии, как мы сделали выше) и сохранил в dist/ds-library.css, а затем создал уменьшенную версию.

Для текста баннера в нашем файле css нам нужно создать файл print-banner.js внутри папки сборки. Он берет текст нашего баннера и записывает его в файл.

// build/print-banner.js const packageInfo = require('../package.json') const { text } = require('./banner.json')  process.stdout.write(text.replace('${version}', packageInfo.version)) process.stdin.pipe(process.stdout)

build:lib = удалить папку dist, собрать код vue и стили, publish:lib = запустить нашу команду build lib, а затем опубликовать в npm

"scripts": {     "build:vue": "rollup -c && rollup -c --environment MINIFY",     "build:vue:watch": "rollup -c --watch",     "build:style": "sass --no-charset ./src/styles/index.scss | node ./build/print-banner.js > dist/ds-library.css && cleancss -o dist/ds-library.min.css dist/ds-library.css",     "build:lib": "rimraf dist && npm run build:vue && npm run build:style",     "publish:lib": "npm run build:lib && npm publish" }, "peerDependencies": {    "vue": "^3.0.0" },

Локальное тестирование, а затем публикация в npm

Теперь, когда мы закончили, мы можем протестировать локально, запустив npm link в корневом каталоге этого репозитория, а также запустив npm link «имя пакета» в корневом каталоге нашего тестового проекта. После этого пакет будет доступен для использования в нашем тестовом пакете.

После тестирования вы можете запустить npm publish:lib для сборки и развертывания в npm.


ссылка на оригинал статьи https://habr.com/ru/post/678274/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *