Как правильно верстать 2, или зачем я написал еще одну UI-библиотеку, мой первый npm-модуль?
Это работа является логическим продолжением моего первого подробного текста для сообщества об актуальных подходах к верстке Как верстать веб-интерфейсы быстро, качественно и интересно. Но, если, в первом трактате, внимание уделялось, прежде всего, стилю кода, его качеству и эффектным современным возможностям различных препроцессоров и фреймворков, что демонстрировалось на некоторых конкретных специфических задачах, теперь хочется сфокусироваться на архитектурных или даже организационных аспектах веб-производства. Если вы не читали мой первый текст, но собираетесь при этом прочесть этот — не поленитесь перейти по ссылке и пробежать глазами самые последние разделы каждой из двух частей первого пособия: «Готовые решения» и «Песочницы». Этот текст начинает прямо с этих мест и развивает именно эти идеи: и о пагубности применения раскрученных-популярных «на все готовых» UI-«дизайн-систем»-фреймворков для создания кастомизированных веб-морд любой сложности и, о, по сути, полезности использования хотя бы минимального документирования и явных соглашений при разработке веб-GUI на фронтенде. Но я не стану тратить время, доказывая, что «ни в коем случае нельзя использовать Vuetify или AntDesign» для создания крупных UI-систем с полностью кастомным оформлением. Вам не нужно прикручивать себе огромный геморрой непроницаемый слой плохо кастомизируемого готового GUI для того чтобы написать кнопку или поле ввода! Если вам нужен датапикер — найдите и допилите что-нибудь под себя. Это понимание может только прийти или так и не придти с годами тяжелого опыта, когда вы будете постоянно тратить непростительно много своего времени на то, чтобы написать очевидно отвратительный CSS — «кряки с !important`ами поверх стилей библиотеки», выдумывать чудные костыли на javascript чтобы изменить дефолтное поведение виджетов на кастомное и хитрое-нестандартное затребованное дизайнерами... И, при этом, ваши шаблоны, стили и js-обвязки будут превращаться во все менее читаемые, запутанные нагромождения разнообразно оформленного кода, с различным подходом к наименованию и прочими бедами… Этот текст и написанный для него проект призваны наглядно показать «а как надо?».
Верстка по-прежнему остается достаточно свободной дисциплиной, в которой сосуществуют множество совершенно разных методологий, подходов и связанных с ними технических решений. Часто программисты принимают какой-то один определенный способ, и, в результате, стагнируют в общем понимании и развитии. Привычки формируют удобную зону комфорта и это мешает осознать риски в ситуациях для которых они неадекватны. Знаете, какой аргумент мне уже несколько раз приходилось слышать в технической дискуссии от оппонентов защищавших привычную для них систему, технику разметки, но совершенно непригодную, на мой взгляд, для решения текущих насущных задач, в реальной ситуации данного конкретного проекта и сроков? «Ну это же общепринятая технология?», «Гугл, Фейсбук, … это используют, чем мы хуже»... Если вы слышите от кого-то, что нечто, не относящееся к действительно используемому всеми нами известному перечню базовых спецификаций — «общепринято», это повод сразу сделать вывод о том, что ваш собеседник имеет мало разнообразного опыта в этом и просто защищает свою лень и нежелание учиться новому. Рядом с аргументом про странные пристрастия акул капитализма обычно следует еще и что-нибудь совершенно несостоятельное про «наши программисты не все знают Stylus», при том что, любой препроцессор CSS даже и тем более начинающий программист способен изучить до базового уровня за один полезный приятный вечер. Мне всегда нравилось менять технологии, синтаксисы — это освежает и придает драйву, интереса в работе. И именно совершенно разнообразный опыт реальной коммерческой практики с различными технологиями сформировал мои представления о том «что хорошо, что плохо» в тех или иных ситуациях.
Возможно, я когда-нибудь накоплю достаточно злости и найду время для того чтобы на наглядных вопиющих примерах показать «а почему вот это плохо?». Но эта статья и пример модуля о том «как надо?», что точно будет аккуратно и эффективно. Если вы являетесь упертым сторонником CSS-in-JS-подходов, или, вообще, по-прежнему наносите оформление на разметку в «бородатом» «утилитарном» стиле «много классов с говорящими названиями содержащих одно-два правило» (тут уместно вспомнить уже «продвинутую» — CSS-in-JS — реализацию Tailwind с ее оголтелым слоганом-оксюмороном: «"Best practices” don’t actually work».) — предложенная и в моей первой книжке и в ее практическом продолжении — здесь — методология, использующая только любой классический препроцессор как «абстрактный медиатор стилей» для доставки дизайн-констант и прочей стилевой абстракции, и сам компонентный фреймворк для, простите за сплошную тавтологию — аккуратной декларативной компонентности — вам все это не зайдет, наверняка. Например, попытки посадить Styled Components на дизайн-константы приводят к безобразному коду в таких «стилях»... Или утилиты «ишак вижу — ищак пою» «в стиле Taiwind» вполне способны представлять «атомы», но все равно ограничивают гибкость в случае непрогнозируемых и частых изменений, ну или просто — также выглядят излишне-невменяемо уже на самой разметке. Я встречал проекты где прикручено «вообще сразу все» — препроцессор, CSS-модули и Styled Components, Flow к зачем-то “отключенному” CRA, например — и это уже даже нельзя назвать «оверинжинирингом», так как это просто отвратительная глупость и очевидно плохая архитектура. Возможно, раскаяние и отрезвление наступит когда вы опять превысите сроки и бюджет на очередном проекте в пять, да, Карл, пять раз — и такое бывает сплошь и рядом. И/или — дизайнеры, по просьбе клиента которому никак нельзя отказать в очередной раз «все переделают» в макетах, которые «уже сверстаны»…
Мотивация
Давайте, прежде всего, четко обрисуем проблему которую мы собираемся решать. В реальном мире, в очень многих командах — проектирование практически никак не «дружит» с разработкой, общаясь, по сути — только через конечные «макеты». Даже действительно опытные «фуллы», которых бизнес предпочитает нанимать «и в качестве фронтендеров», на самом то деле, верстают совсем слабо. Кодеры сдают только им самим известно из какого гавна как собранные страницы на тестирование, которое, в свою очередь — тупо сравнивает их с макетами — замыкая порочный круг. Обычно и, тем более, в условиях жестких дедлайнов и/или в разношерстных, наспех собранных командах из специалистов с совершенно разными скилами и опытом — бывает очень сложно ввести соглашения. Общий фокус процесса неминуемо смещается на то чтобы «сделать хоть как-нибудь», «протолкнув» быстрый гавнокод через тестировщиков…
Такая организация работы как раз очень выгодна мракоделам. Мракодел отгораживается «только своей задачей» — «моя хата скраю» — и пишет ее так, как ему удобно, игнорируя best practices и напряжные соглашения, часто вообще — просто изображая работу удаленно — скопировал, чутка перековырял под макеты — ушел гулять, вечером отправил — ждет когда тестировщики обязательно и несколько раз вернут на доработку… «Есть баги — есть работа!». «Переиспользование» превращается в простое копирование и «интуитивную» модификацию «уже готовых кусков» — лавинообразно усугубляя мрачность и запутанность стилей, разметки и js-обвязок для них. И если javascript бывает еще более-менее адекватен, все его «умеют» и понимают — то стили часто превращаются в просто непроходимые дебри и бесконечную излишнюю неоптимизированную колбасу, несмотря на то какие именно технологии используются… При этом руководство, менеджеры которые не ходят читать репо могут пребывать в наивной уверенности что «все готово, только кнопку нажать» или хотя бы — «вот это уже сделано на том проекте и можно переиспользовать»... Мне приходилось получать репо с тонной «индусского» — нереально небрежного и явно копированного целыми крупными кусками и даже файлами кода, игнорирующего вполне имеющийся у фирмы единый UI-кит, его константы и компонентность, с посылом от руководства что «все готово», «надо слегка переписать»… Мне приходилось годами исправлять за «тимлидами-сеньорами» проекты с ужасной архитектурой блокирующей быструю доставку именно того, что нужно заказчику, например — сложного современного дизайна… Причем, когда проект начинали — самые важные требования остались проигнорированы, зато с совершенно ненужными в этом случае «модными трендами» покуражились вовсю...
Я заметил что с молодыми специалистами часто работать и добиваться качественного результата бывает намного проще чем с закостенелыми самоуверенными «фуллстеками», рефлексующими свою недооцененность и прочее… Ребята помоложе еще не потеряли азарт и интерес к тому что делают, не успели несколько раз перегореть, устать, и большинство как раз стремиться стать настоящими профессионалами, открыты новым идеям, подходам, ищут и перенимают хороший стиль, адекватно реагируют на критику и так далее.. Хотя бывают исключения и с теми, и с другими, конечно… Но хватить абстрактной лиричной боли — перейдем уже к «хорошим практикам по Гамбаряну»…
Разрабатывайте визуальный язык и основанные на нем шаблоны своих интерфейсов через простые четкие соглашения и документирование, а не хаотическое копирование!!!
По опыту, особенно, если речь идет о «бренной вьюхе» на фронтенде, очень многие программисты используют годную на все времена стремительно интуитивную методологию «программирование через копирование файлов или больших кусков кода» с девизом «да хз что за функция» — и такой уже распиленный системный хардкор-код практически невозможно полностью отрефакторить, точнее — по опыту — только в том случае, если это позволяют удачно выбранные изначально технологии, плюс, конечно же, организационные ресурсы... Некоторые системы в реальных коммерческих ситуациях можно только «подмести» и «заморозить», продолжая, по сути, доставлять мрак при необходимости какой-то видимости прогресса для клиента. Необходимость непрерывно править плохой код коллег, или работать с неадекватными задачам и целям проектов архитектурами после предшественников резко снижает качество жизни программистов.
Программисты такие же ленивые, хоть и мыслящие, животные как и все другие люди и коты. Да, мне кажется, что мой полосатый много думает и иногда даже пытается поделиться... Вот даже котик пытается рассказать о результатах своего умственного труда? А многие из вас не то что никогда не оставляют комментариев — в принципе — пишут такой код что я бы сразу уволил как это развидеть, к которому очевидно любые комментарии излишни, так скажем. Причем, причины такого состояния кода множества проектов всегда одни и те же: «времени совсем не было», «надо было что-то показать», «руководство попросило сделать очень быстро» и тому подобное, «будет время — перепишем»… Поверьте, в реальной коммерции — оплаченного отдельного времени на оптимизацию и рефакторинг практически никогда не бывает. Клиенты в аутсорсе, например, почти никогда не покупают его как дополнительную опцию. Даже если некий продукт очевидно неаккуратно написан, работает со сбоями и совершенно не годен для масштабирования — бизнес практически всегда будет заинтересован, прежде всего, в доставке нового функционала любой ценой, а не в исправлении «уже проданного» — и все что он действительно захочет, чаще всего, это опять — быстро — «как-нибудь подлатать все баги»… Главная задача команды разработчиков в этом смысле — прямо на старте выработать четкую систему приемлемых и удобных для всех участников соглашений, которая с самого начала позволит писать максимально консистентный, понятный, и, самое главное, в идеале — и гибкий, и, одновременно, железно надежный код.
Среднестатистический разработчик-мракодел, тянущий лямку в капитализме никогда не возвращается к своему поспешному или даже откровенно, простите мой спесишизм, «индусскому» копипасте-коду для того чтобы улучшить его, кроме ситуаций, когда служба контроля качества вернула некий конкретный баг на доработку. И, конечно, тут совсем нельзя говорить о каком-либо структурном улучшении, так как подобные правки всегда носят не системный, а полностью локальный-частный характер «быстрых фиксов», «кряков» и только усугубляют беспорядок. Не дай бог трогать код по «уже закрытым таскам», «так ведь можно что-нибудь поломать!»... Среднестатистический разработчик-мракодел нацелен, прежде всего, на закрытие своих тасков-багов по трекеру и не интересуются общим состоянием проекта, приходящим чужим кодом. Полезные соглашения его напрягают, он игнорирует или даже активно противиться им. Кроме того, я заметил, что профессиональные мракоделы в командах и на проектах стремятся объединяться в устойчивые группы… Мракоделы копируют мракокод из файла в файл, из проекта в проект, покрывая друг-друга, ломая волю ПМов и регулярно рапортуя через них начальству которое не читает репо что «все почти готово, только кнопку нажать»... Мракоделы даже иногда дорастают до сеньоров и лидов. Тогда они начинают занимаются сомнительным оверинжинирингом, накручивая ненужные, но модные технологии, пока не оказывается что все сроки сорваны и клиент не получил того чего хотел, а код такой что заплатки негде ставить без поллитры не прочитать… Как простому верстальщику противостоять вселенскому мраку?
Далее будет изложена простая методика реализации гибкого и масштабируемого GUI веб-интерфейсов, доставки дизайна в код, аккуратное использование которой сводит на минимум возможности добавления низкопробного кода в проекты фронтенда, а также способно помочь более четко, сперва — оценить, и, в дальнейшем, организовать эффективную совместную работу. Вкратце, обобщая можно тезисно описать это как то, что вам нужно добиться от разработчиков участвующих в развитии проекта фронтенда соблюдения следующих основных моментов:
Все оформление вплоть до перекастомизаций необходимых сторонних модулей неукоснительно основано на константах-«атомах» и уже собираемых на их основе более сложных паттернах которые диктует ваш дизайн, руководство по стилю.
При первоначальной оценке интерфейс разбирается на компоненты: «молекулы» и «организмы», «шаблоны» в терминах атомарного дизайна. Элементы, виджеты, раскладки, общие лейауты, все кроме уже окончательных видов роутера — на которых собирается соответствующая макетам конечная реальная вьюха из этих переиспользуемых сущностей — формируют библиотеку компонент и тем самым уже минимально документируются для всей команды. Код автоматически становится намного более оптимальным чем при хаотичном «копипаста»-подходе когда «левая нога не знает что делает правая».
Даже на «отдельном проекте» нужно стараться организовывать компонентную архитектуру таким образом, как будто ваши @/src/components — находятся в удаленном репозитории — «UI-библиотеке», и, поэтому — недоступны для «обвязок» напрямую. Компоненты «реальных вьюх» [соответствующие дизайн-макетам], например на @/src/views во Vue или @/src/pages и @/src/layouts с Nuxt —должны взаимодействовать с UI-компонентами которые они просто «собирают, предоставляя данные» — исключительно через пропсы — через явно обозначенный интерфейс, а не «состоянием». Концептуально, это напоминает нам «инкапсуляцию» из «серьезного программирования», а практически будет приводить к тому что — стили за редким исключением будут сосредоточены практически только «в UI-компонентах», а вся бизнес-логика — совершенно точно исключительно на видах. Это еще и способно организовать работу более эффективно — участники «с хорошей версткой» могут, прежде всего — поставлять качественную разметку.
Часто встречающийся в современной корпоративной культуре случай когда, предположим, два разных отдела ведут разработку разных интерфейсов основанных на одной визуальной дизайн-системе — очевидно основной когда необходимо построить работу через общую библиотеку-модуль.
Антибиблиотека
UI Library Starter это точно не еще одна библиотека «готового на все UI». Это, прежде всего, концепт и пример системного подхода, которые могут позволить вам легко поддерживать свои собственные библиотеки только из необходимых компонент, чистого оптимального кода, сопровождаемого необходимой наглядной документацией и с оформлением точно согласующимся с руководством по стилю. Проект содержит некоторый минимальный набор готовых компонент именно в качестве примера. Это то, что встречается практически в любом большом интерфейсе: простые, базовые примеры — обертка для контента, сетки, и пара важных «дорогих» контролов: датапикер с диапазоном на одном листе и кварталами, обертка над сторонним селектом...
Мысли о том что написание хардкор-кода или, точнее, написание хардкор-кода еще и с помощью чужого, обычно практически полностью изолированного от остальной системы готового кода как-то помогает решать задачи бизнеса — нужно забыть. Возможно, какие-то тактические задачи, может быть, но точно — все равно ненадежно, и всегда — стратегически создает предпосылки для очень серьезных проблем в будущем, возможно, даже самом скором. Кроме того, исключает возможность получать удовольствие от работы для тех кто трудиться с вами параллельно или будет вынужден иметь дело с этим легаси в дальнейшем.
В этом смысле сама идея UI Library Starter противоположна концепции любой обычной популярной библиотеки где «все включено». Она не в том чтобы дать вам возможность лениться, бояться и тратить рабочее время неэффективно мучаясь трудными кастомизациями через !important
с неизвестным результатом, другими адовыми кряками, превращая свои проекты в неведомы зверушки из скрытых под капотом чужих кривых поделок, нечитаемые лоскутные одеяла повторяющегося кода или запутанные описания одного и того же совсем по-разному, с позорно огромным опасным пулом зависимостей. Идея этого проекта в том, чтобы аккуратно помочь вам начать писать действительно оптимальный, понятный и задокументированный, гибкий и легко масштабируемый код, который вы сможете постоянно переиспользовать и улучшать.
Пример дочернего проекта использующего модуль-библиотеку.
Далее — простите мне странные вкрапления моего никакого английского в документации…)))
Getting Started
Installation
Скачайте код ui-library-starter и оформите его в отдельный репозиторий. При выборе имени для нового репозитория необходимо сразу убедиться в том, что оно не занято на npmjs.com. Пусть это будет ui-library-starter-test.
Или, в случае, если вы не планируете менять стиль проекта под свои собственные гайды, но, собираетесь поиграться или даже внести вклад в его развитие, например, предложив еще какие-то важные компоненты — сделайте форк, конечно же. Дальнейшие инструкции относятся к первому случаю — пилим свежую либу с кастомным стилем под конкретные задачи — в этом случае многие могут захотеть удалить эту документацию и почти все компоненты, чтобы не выполнять лишнюю кастомизацию.
$ npm install
Customization
README.md
Поправьте первую строчку в @/README.md
:
# Ui-library-starter test project
package.json
Далее в @/package.json
вам необходимо крайне аккуратно переписать актуальной информацией следующие поля, ничего не пропустив:
{
"name": "ui-library-starter-test",
"description": "UI Library Starter Demonstration",
"version": "0.1.0",
"main": "dist/ui-library-starter-test.umd.min.js",
"unpkg": "dist/ui-library-starter-test.umd.min.js",
"jsdelivr": "dist/ui-library-starter-test.umd.min.js",
"scripts": {
"build": "rimraf ./src/static && cp -r ./docs/.vuepress/public ./src/static && vue-cli-service build --target lib --name ui-library-starter-test src/main.js"
},
"author": "Levon Gambaryan",
"license": "MIT",
"homepage": "",
"repository": {
"type": "git",
"url": "https://github.com/ushliypakostnik/ui-library-starter-test"
},
"bugs": {
"url": "https://github.com/ushliypakostnik/ui-library-starter-test/issues"
},
"keywords": []
}
Обратите внимание на имя проекта в конце длинной команды деплоя build
!
Documentation config
Перейдите к документации на VuePress и сконфигурируйте ее под себя @/docs/.vuepress/config.js
:
module.exports = {
locales: {
'/': {
lang: 'en-US',
title: 'UI Library',
description: 'Vue Component UI Library',
},
},
themeConfig: {
repoLabel: 'GitHub repo',
repo: 'https://github.com/ushliypakostnik/ui-library-starter-test.git',
docsDir: 'docs',
search: false,
locales: {
'/': {
nav: [{ text: '', link: '' }],
sidebar: [
{
title: `Components`,
children: [
// ... готовые компоненты библиотеки без Sandbox и папки /Tests с тестовыми
],
},
{
title: `Sandbox`,
path: '/sandbox/sandbox',
},
],
},
},
},
};
Connecting fonts
Перепишите имя шрифта и переменные начертаний если требуется в файле ~/src/stylus/utils/_typography.styl
:
$font-family = "Open Sans"
$font-weight = {
regular: 400,
bold: 700,
}
Поместите папку с правильным шрифтом рядом с папкой /Ubuntu
в @/docs/.vuepress/public/fonts/
.
Пропишите правильные импорты и пути в файле кастомизации документации на VuePress @/docs/.vuepress/styles/palette.styl/
:
@import "../../../src/stylus/_stylebase.styl"
// Import fonts
//////////////////////////////////////////////////////
@font-face {
font-family: $font-family;
src: url('/fonts/OpenSans/OpenSans-Regular.eot');
src: local('Open Sans Regular'), local('OpenSans-Regular'),
url('/fonts/OpenSans/OpenSans-Regular.eot?#iefix') format('embedded-opentype'),
url('/fonts/OpenSans/OpenSans-Regular.woff') format('woff'),
url('/fonts/OpenSans/OpenSans-Regular.ttf') format('truetype');
font-weight: $font-weight.regular;
font-style: normal;
}
@font-face {
font-family: $font-family;
src: url('/fonts/OpenSans/OpenSans-Bold.eot');
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('/fonts/OpenSans/OpenSans-Bold.eot?#iefix') format('embedded-opentype'),
url('/fonts/OpenSans/OpenSans-Bold.woff') format('woff'),
url('/fonts/OpenSans/OpenSans-Bold.ttf') format('truetype');
font-weight: $font-weight.bold;
font-style: normal;
}
// Customization
//////////////////////////////////////////////////////
...
Удалите директорию со старым шрифтом @/docs/.vuepress/public/fonts/Ubuntu
.
Сleaning project
Если вы хотите получить полностью чистую документацию — произведите следующую очистку папок и файлов.
Удалите все папки и файлы в @/docs/
кроме директорий @/docs/.vuepress
, @/docs/components
и @/docs/sandbox
— если желаете оставить Песочницу. И файла @/docs/README.md
, который нужно оставить, но очистить:
# UI Library
...
Удалите директорию @/src/components/tests
.
Вычистите импорты тестовых компонент в индексном файле @/src/components/index.js
:
// Tests - следующие три строчки удалить!!!
export { default as TestColors } from './tests/TestColors';
export { default as TestBreakpoints } from './tests/TestBreakpoints';
export { default as TestTypography } from './tests/TestTypography';
// Elements
...
Вы можете выбрать какие компоненты оставить или даже удалить их все, если уверенны в себе и не нуждаетесь в наглядных примерах под рукой. Вернитесь к конфигурации документации и отразите изменения в @/docs/.vuepress/config.js
.
Style setting
Запустите разработку документации командой:
$ npm run docs:dev
Прочитайте раздел Constants этой документации.
Вам необходимо настроить препроцессор вашей библиотеки в точном соответствии с вашим руководством по фирменному стилю.
Adding a component
После того как стили библиотеки настроены вы можете добавлять свои специфические компоненты.
Выберете имя для компонента в PascalCase стиле написания, предположим это ComponentName
.
Некоторые имена могут оказаться зарезервированы VuePress. Layout
, например. Самая достойная замена Layout
видится как View
.
Добавьте директорию @/src/components/ComponentName
.
Добавьте в нее индексный файл c импортом-экспортом @/src/components/ComponentName/index.js:
import ComponentName from './ComponentName.vue';
export default ComponentName;
И сам компонент @/src/components/ComponentName/ComponentName.vue:
<template>
<div
class="component-name"
:class="{
'.component-name__element--modifier1': prop1,
'.component-name__element--modifier2': prop2,
}"
>
This is test component!!!
</div>
</template>
<script>
export default {
name: 'ComponentName',
props: {
prop1: {
type: Boolean,
required: true,
},
prop2: {
type: Boolean,
required: false,
default: false,
},
},
};
</script>
<style lang="stylus" scoped>
@import "~/src/stylus/_stylebase.styl";
.component-name
background $colors.primary // test styles
// add adaptive
+$mobile()
display block
&__element
$text("natasha") // add typography
&--modifier1
color $colors.primary // add good color
&--modifier2
color $colors.secondary
</style>
Добавьте экспорт в индексный файл библиотеки @/src/components/index.js
:
export { default as ComponentName } from './ComponentName';
Добавьте документацию компонента в файл @/docs/components/component-name.md
:
# ComponentName
## Description
This is new custom component.
## Connection
```vue
<template>
<ComponentName prop1 />
</template>
```
## Render
<ComponentName prop1 />
## API
### Props
| **Name** | **Type** | **Description** | **Default** |
| :-------- | :------- | :-------------- | ----------: |
| **prop1** | Boolean | - | (required) |
| **prop2** | Boolean | - | `false` |
## Source code
<code>@/src/components/ComponentName/ComponentName.vue</code>
<<< @/src/components/ComponentName/ComponentName.vue
И далее — рендер-тест и исходный код по аналогии с другими файлами.
Добавьте компонент в конфигурацию VuePress @/docs/.vuepress/config.js
:
module.exports = {
themeConfig: {
locales: {
'/': {
sidebar: [
{
title: `Components`,
children: [
{
title: `ComponentName`,
path: '/components/component-name',
},
],
},
],
},
},
},
};
Using third party modules
Используйте только относительные пути для импорта чего-либо в javascript ваших компонентов. Не используйте «абсолютные» алиасы:
<script>
import { dateFilter } from '../../../node_modules/vue-date-fns/src/index';
import Icon from '../Icon/Icon';
export default {
components: {
Icon,
},
};
</script>
В реальных проектах вам потребуется очень часто закрывать «самые дорогие требования» с помощью аккуратно подобранных подходящих готовых решений. В таких случаях логично будет создавать обертку над чужим модулем, предоставляющую всю необходимую кастомизацию. Пример этого: Select.
Установите и импортируйте модуль как обычно в главном файле @/src/main.js
:
import vSelect from 'vue-select';
import 'vue-select/dist/vue-select.css';
Vue.component('v-select', vSelect);
Так как мы используем глобальные стили собственной кастомизации модуля — невозможно будет защитить стили перекастомизации в SFC-обертке с помощью scoped
:
<style lang="stylus">
@import "~/src/stylus/_stylebase.styl";
.vs
&__dropdown-toggle
// ...
// ...
// ...
</style>
Use the sandbox
Используйте специальный компонент @/src/components/Sandbox/Sandbox.vue
и роут документации Sandbox как экспериментальную площадку и холст для создания новых компонент на простых мокках или тестирования взаимодействия между ними. Хотя, очевидно, некоторые компоненты, такие как, например, лейаут — удобнее создавать непосредственно в проекте и уже после этого переносить в библиотеку.
Library publishing
Зарегистрируйтесь на npmjs.com и подтвердите регистрацию (дождитесь письма на почту).
$ npm run build
$ npm version patch
$ npm publish
Connecting to projects
Вы можете либо использовать стартовый шаблон для новых проектов ui-library-start, тогда вам придется заменить библиотеку:
$ npm uninstall ui-library-starter --save-dev
$ npm install ui-library-starter-test --save-dev
Либо установить библиотеку как любой другой модуль в любой другой проект:
$ npm install ui-library-starter-test --save-dev
Организация стилей дочерних проектов может или иметь подобную библиотеке структуру или любую другую (например, если вы внедряете бибилиотеку в старый проект). Единственное требование: первый импорт в основном файле — основного файла библиотеки. Второй — подключение шрифтов и стилизация :root
и body
.
@/src/stylus/_stylebase.styl
проекта использующего библиотеку:
// Import UI Library stylebase
@import '~ui-library-starter-test/src/stylus/_stylebase.styl';
// core
@import "core/_base"; // normalize
@/src/stylus/core/_base.styl
проекта использующего библиотеку:
// Import UI Library fonts
@font-face {
font-family: $font-family;
src: url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Regular.eot');
src: local('Open Sans Regular'), local('OpenSans-Regular'),
url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Regular.eot?#iefix') format('embedded-opentype'),
url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Regular.woff') format('woff'),
url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Regular.ttf') format('truetype');
font-weight: $font-weight.regular;
font-style: normal;
}
@font-face {
font-family: $font-family;
src: url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Bold.eot');
src: local('Open Sans Bold'), local('OpenSans-Bold'),
url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Bold.eot?#iefix') format('embedded-opentype'),
url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Bold.woff') format('woff'),
url('~ui-library-starter-test/src/static/fonts/OpenSans/OpenSans-Bold.ttf') format('truetype');
font-weight: $font-weight.bold;
font-style: bold;
}
// Base normalize
:root
scroll-behavior smooth
body
font-family $font-family, sans-serif
-moz-osx-font-smoothing grayscale
-webkit-font-smoothing antialiased
text-rendering: optimizeSpeed
color $colors.text
overflow-x hidden
Практически единственный повод что-то поменять в этом файле — крайне маловероятная ситуация — замена или добавление шрифта в гайдлайн. Предполагается что отредактировать пути шрифтов придется только один раз — при подключении библиотеки под определенный стиль.
Подключите все это к главному шаблону @/src/App.vue
:
<template>
<div id="app"></div>
</template>
<script>
export default {
name: 'App',
};
</script>
<style lang="stylus">
@import "~/src/stylus/_stylebase.styl";
</style>
Исправьте имя библиотеки в импортах в точку входа @/src/main.js
если вы брали готовый репо или подключите:
import ComponentLibrary from 'ui-library-starter-test';
import 'ui-library-starter-test/dist/ui-library-starter-test.css';
Vue.use(ComponentLibrary);
Исправьте имя или добавьте команду update
в @/package.json
:
{
"name": "ui-library-start-test",
"scripts": {
"update": "npm install ui-library-starter-test@latest"
},
}
Updating in projects
Обновляйте библиотеку до последней версии в проектах:
$ npm run update
Constants
_stylebase.styl
Глобальный медиатор стилей собирает не компилируемые /utils
, компилируемые /core
сущности и необходимые глобально кастомизации используемых сторонних модулей /libs
(но, те которые позволяют это сделать без scoped
— стоит разместить в SFC-обертках).
@/src/stylus/_stylebase.styl
библиотеки:
// utils
@import "utils/_variables";
@import "utils/_placeholders";
@import "utils/_mixins";
@import "utils/_typography";
// core
@import "core/_base"; // normalize
@import "core/_animations";
// libs
...
Теперь можно использовать всю эту кухню на компонентах:
<style lang="stylus" scoped>
@import "~/src/stylus/_stylebase.styl";
...
</style>
Это можно назвать «глобальными стилями-невидимками», что-то такое — они, с одной стороны — участвуют в правильном оформлении везде, но, при этом, «их не видно». Мы предоставляем глобальные константы гайдлайна и всю прочую мощь препроцессора всем компонентным системам — библиотеке и всем ее «читателям».
Mixins and placeholders
UI Library Starter дает надежду на то, что в вашей разметке код будет полностью оптимальный и консистентный. Это способно сделать даже крупную систему из нескольких проектов базирующихся на одном визуальном языке — и полностью управляемой, и, в тоже время — гибкой.
Не копируйте код кусками по компонентам — оптимизируйте очевидно одинаковые наборы в миксинах и плейсхолдерах!
Или вы можете делать примеси без параметров для того, чтобы — забегая вперед (см. раздел Breakpoints) — такой же набор стал доступен внутри медиа-миксинов @/src/stylus/utils/_mixins.styl
:
// Utilities
//////////////////////////////////////////////////////
// Mixins without arguments duplicate placeholders,
// so it can be passed to media mixins
$flex--center()
display flex
align-items center
justify-content center
Теперь:
.selector
+$tablet()
$flex--center()
Colors
Абстрагируйте все цвета из гайдлайна в короткие имена-маркеры.
~/src/stylus/utils/_variables.styl
:
// Palette
//////////////////////////////////////////////////////
$colors = {
cat: #515bd4,
dog: #ffffff,
bird: #fd5f00,
wood: #fed564,
stone: #8bc24c,
sea: #13334c,
}
// Dependencies colors
$colors["text"] = $colors.sea
$colors["placeholder"] = rgba($colors.sea, $opacites.rock)
$colors["primary"] = $colors.cat
$colors["secondary"] = $colors.dog
В любом месте кода препроцессора или секции стилей SFC (при условии импорта стилевой базы) библиотеки или дочерних проектов вы можете передавать правильные цвета:
.selector
color $colors.primary
Легко поддерживать тестовый компонент наглядно демонстрирующий палитру в @/src/components/tests/TestColors/TestColors.vue
.
Breakpoints
Переменные-брекпоинты лучше называть более интуитивно-понятно.
~/src/stylus/utils/_variables.styl
:
// Breakpoints
//////////////////////////////////////////////////////
$breakpoints = {
tablet: 768px,
desktop: 1025px,
}
$breakpoints["mobile--max"] = $breakpoints.tablet - 1
$breakpoints["tablet--max"] = $breakpoints.desktop - 1
Основные точки перехода: в стилевой базе препроцессора в px
и в константах скриптов библиотеки в Number
— должны соответствовать друг-другу.@/src/utils/сonstants.js
:
// Design constants
//////////////////////////////////////////////////////
export const DESIGN = {
BREAKPOINTS: {
tablet: 768,
desktop: 1025,
},
};
В препроцессоре — мощнейшее, очень удобное средство — построенные на брекпоинтах примеси принимающие контент:
// Media
//////////////////////////////////////////////////////
$no-gadgets()
@media only screen and (max-width $breakpoints.desktop--max)
{block}
$desktop()
@media only screen and (min-width $breakpoints.desktop)
{block}
$gadgets()
@media only screen and (max-width $breakpoints.tablet--max)
{block}
$tablet()
@media only screen and (min-width $breakpoints.tablet) and (max-width $breakpoints.tablet--max)
{block}
$not-mobile()
@media only screen and (min-width $breakpoints.tablet)
{block}
$mobile()
@media only screen and (max-width $breakpoints.mobile--max)
{block}
Использование в любом блоке стилей SFC:
.selector
+$desktop()
// styles only for desktops
+$tablet()
// styles only for tablet
+$mobile()
// styles only for mobile
В строгой традиции запрещается использование любых глобальных классов со стилями, за исключением анимаций для Vue и вынужденных кастомизаций действительно необходимых сторонних модулей где «классический ад с !important
»))). Мы стараемся минимизировать количество зависимостей и «точечно» закрываем самые «дорогие», неподъемные по ресурсам проблемные места.
Точки перехода скриптов обрабатываются специальным модулем-помощником для экрана через matchMedia:
// Viewport utils module
//////////////////////////////////////////////////////
import { DESIGN } from './constants.js';
const ScreenHelper = (() => {
const TABLET = DESIGN.BREAKPOINTS.tablet;
const DESKTOP = DESIGN.BREAKPOINTS.desktop;
const isMobile = () => {
return window.matchMedia(`(max-width: ${TABLET - 1}px)`).matches;
};
const isTablet = () => {
return window.matchMedia(
`(min-width: ${TABLET}px) and (max-width: ${DESKTOP - 1}px)`,
).matches;
};
const isDesktop = () => {
return window.matchMedia(`(min-width: ${DESKTOP}px)`).matches;
};
const getOrientation = () => {
if (window.matchMedia('(orientation: portrait)').matches) {
return 'portrait';
}
return 'landscape';
};
const getPixelRatio = () => {
// eslint-disable-next-line prettier/prettier
return (
window.devicePixelRatio ||
window.screen.deviceXDPI / window.screen.logicalXDPI
);
};
return {
isMobile,
isTablet,
isDesktop,
getOrientation,
getPixelRatio,
};
})();
export default ScreenHelper;
Для того чтобы компоненты могли всегда верно определять типоразмер устройства предоставлена общая функциональность обновляющая переменные в событии ресайза. Этот миксин может быть невероятно полезен и на этапе конечной сборки адаптивных видов — в дочерних проектах.
@/src/mixins/resize.js
:
import ScreenHelper from '../utils/screen-helper.js';
export default {
data() {
return {
isMobile: null,
isTablet: null,
isDesktop: null,
};
},
mounted() {
window.addEventListener('resize', this.onWindowResizeCommon, false);
this.onWindowResizeCommon();
},
beforeDestroy() {
window.removeEventListener('resize', this.onWindowResizeCommon, false);
},
methods: {
onWindowResizeCommon() {
// console.log('onWindowResizeCommon!!!!');
this.isMobile = ScreenHelper.isMobile();
this.isTablet = ScreenHelper.isTablet();
this.isDesktop = ScreenHelper.isDesktop();
},
},
};
На любых компонентах или видах в библиотеке:
<template>
<Component v-show="isDesktop" />
<div v-if="isDesktop" />
</template>
<script>
import resize from '../../../src/mixins/resize.js';
export default {
name: 'ComponentName',
mixins: [resize],
};
</script>
В проектах:
<script>
import resize from 'ui-library-starter/src/mixins/resize.js';
export default {
name: 'Test',
mixins: [resize],
};
</script>
Тестовый компонент в @/src/components/Tests/TestBreakpoints/TestBreakpoints.vue
.
Typography
Абстрагируйте все гарнитуры из гайдлайна в короткие имена-маркеры.
~/src/stylus/utils/_typography.styl
:
// Typography
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// Typographic Variables
//////////////////////////////////////////////////////
// Guide
$font-family = "Ubuntu"
$font-weight = {
regular: 400,
bold: 700,
}
$letter-spacing = {
normal: normal
}
// Universal Typographic Mixin
//////////////////////////////////////////////////////
// We use one, only one, Karl, a universal mixin for all cases!
$text($name)
font-family $font-family
letter-spacing $letter-spacing.normal
if $name == "elena"
font-size 72px
line-height 72px
font-weight $font-weight.bold
if $name == "olga"
font-size 56px
line-height 56px
font-weight $font-weight.bold
if $name == "anna"
font-size 40px
line-height 44px
font-weight $font-weight.bold
if $name == "maria"
font-size 32px
line-height 36px
font-weight $font-weight.bold
if $name == "katya"
font-size 24px
line-height 28px
font-weight $font-weight.regular
if $name == "alena"
font-size 20px
line-height 28px
font-weight $font-weight.regular
if $name == "natasha"
font-size 16px
line-height 28px
font-weight $font-weight.regular
if $name == "nina"
font-size 13px
line-height 16px
font-weight $font-weight.regular
Others
~/src/stylus/utils/_variables.styl
:
// Others from guide
//////////////////////////////////////////////////////
$border-radiuses = {
soccer: 0,
dancing: 2px,
swimming: 3px,
shooting: 10px,
}
$opacites = {
waltz: 1,
funky: 0.75,
rock: 0.66,
psy: 0.45,
pop: 0.2,
reggae: 0,
}
$effects = {
duration: 0.1s,
}
Можно получить из этого миксины для более лаконичного синтаксиса ~/src/stylus/utils/_mixins.styl
:
// Rounding
//////////////////////////////////////////////////////
$border-radius($name)
if $name == "soccer"
border-radius $border-radiuses.soccer // 0
if $name == "dancing"
border-radius $border-radiuses.dancing // 2px
if $name == "swimming"
border-radius $border-radiuses.swimming // 3px
if $name == "shooting"
border-radius $border-radiuses.shooting // 10px
// Opacity
//////////////////////////////////////////////////////
$opacity($name)
if $name == "waltz"
opacity $opacites.waltz // 1
if $name == "funky"
opacity $opacites.funky // 0.75
if $name == "rock"
opacity $opacites.rock // 0.66
if $name == "psy"
opacity $opacites.psy // 0.45
if $name == "pop"
opacity $opacites.pop // 0.2
if $name == "reggae"
opacity $opacites.reggae // 0
Теперь можно:
.selector
$opacity("dancing")
$border-radius("funky")
Переменные все равно могут пригодиться если дизайнеры захотят сделать новый цвет с разрешенной прозрачностью:
.selector
color rgba($colors.dog, $opacites.psy)
Анимации
Единственные глобально компилируемые стилевые классы которые в строгой традиции разрешено использовать — для анимаций Vue. Вы можете добавлять их после соответсвующих @keyframes
в специальном файле стилевой базы ~/src/stylus/core/_animation.styl
:
// Project animations
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
//////////////////////////////////////////////////////
// Keyframes
//////////////////////////////////////////////////////
@keyframes fade
0%
opacity 0
100%
opacity 1
// Vue classes for animation
//////////////////////////////////////////////////////
.fade
&-enter-active
animation fade ($effects.duration * 2) forwards
&-leave-active
animation fade ($effects.duration * 2) reverse
Теперь в разметке:
<template>
<transition name="fade">
<div v-if="Boolean" />
</transition>
</template>
Structure
.
├─ docs/ // documentation folder
│ ├── .vuepress/ // VuePress
│ │ ├─ stylus/ // import of fonts and customization of documentation
│ │ │ └─ palette.styl
│ │ ├─ config.js // configuration
│ │ └─ enhanceApp.js // improvements - installation of library components
│ ├─ components/ // components documentation folder
│ │ ├─ link.md
│ │ └─ ...
│ ├─ constants/ // library documentation folder
│ │ ├─ breakpoints.md
│ │ ├─ colors.md
│ │ ├─ others.md
│ │ ├─ stylebase.md
│ │ └─ typography.md
│ ├─ sandbox/ // sandbox view
│ │ └─ sandbox.md
│ ├─ links.md // useful reading links
│ ├─ README.md // homepage
│ ├─ start.md // getting started
│ └─ structure.md // structure
├─ src/ // source folder
│ ├─ components/
│ │ ├─ Icon
│ │ │ ├─ index.js
│ │ │ └─ Icon.vue
│ │ ├─ Sandbox
│ │ │ ├─ index.js
│ │ │ └─ Sandbox.vue
│ │ ├─ Tests
│ │ │ └─ ...
│ │ └─ ...
│ ├─ mixins/
│ │ ├─ resize.js // adaptive to components
│ │ └─ ...
│ ├─ static/ // after build fonts will be copied here
│ │ └─ fonts/
│ │ └─ ...
│ ├─ stylus/
│ │ ├─ core
│ │ │ ├─ _animations.styl // keyframes and Vue animationss classes
│ │ │ └─ _base.styl // normalize
│ │ ├─ utils
│ │ │ ├─ _mixins.styl
│ │ │ ├─ _placeholders.styl
│ │ │ ├─ _typography.styl // Use one, only one, Karl, a universal mixin for all cases!
│ │ │ └─ _variables.styl
│ │ └─ _stylebase.styl // main file of stylus
│ ├─ utils/ // scripts
│ │ ├─ constants.js // javascript constants
│ │ ├─ screen-helper.js // adaptive viewport
│ │ └─ ...
│ └─ main.js // library connection
├─ .browserslistrc // configuration of supported browsers
├─ .eslintrc.js // linter configuration
├─ .gitignore // git ignore
├─ .prettierrc // prettier configuration
├─ babel.config.js // babel configuration
├─ colors.jpg // image for README
├─ package.json // project configuration
└─ README.md
```
Мы сегодня многое поняли
Чистый код начинается с четких концепций и годной архитектуры, эффективная работа команды — с аккуратной коммуникации и внятных соглашений. Я потратил всего один вечер для того чтобы написать сам пример модуля-библиотеки на Vue со Stylus (без компонент). Точно тоже самое можно быстро легко осуществить для связки любого современного компонентного фреймворка и препроцессора. Даже если вы не хотите, или по каким-то внешним причинам — не можете — выделить атомарный и подробно-компонентный UI своего интерфейса в модуль-библиотеку, изложенные выше подходы все равно способны помочь вам и вашей команде писать действительно красивые и удобные проекты фронтенда с по-настоящему качественной версткой.
Удачи в написании собственных библиотек!
@@