Эта статья — перевод оригинальной статьи 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/
Добавить комментарий