npm package: @neoflex/fastdata-ui-kit
Description: Проект совмещает библиотеку, сторибук, и CRA приложение consumer. В процессе разработки компонентов, даёт возможность напрямую отображать их в сторибуке или CRA, минуя бандлинг. Предполагается наличие `yarn`, или `npm` установленных глобально.
Author: Neoflex
Latest version: 0.20.6

# Библиотека компонентов DesignSystem UI

Проект совмещает библиотеку, сторибук, и CRA приложение consumer. В процессе разработки компонентов, даёт возможность
напрямую отображать их в сторибуке или CRA, минуя бандлинг. Предполагается наличие `yarn`, или `npm` установленных глобально. 

## Общие сведения

### Библиотека

Бандл библиотеки публикуется в git-репозиторий https://neogit.neoflex.ru/fastdata/fastdata-ui-kit. Каждая опубликованная
версия представляет собой один коммит в ветке `master`. Номер версии бандла (такой, как `0.2.2`) сохраняется в сообщение
коммита, в файл `package.json`, и в легковесный тег, которым помечается коммит. Сохранение версии в теге позволяет
consumer'ам использовать фиксированную версию бандла: 
`npm i @neoflex/fastdata-ui-kit@latest --save`.
Если пакет добавляется без указания версии (`#0.2.2`), будет использоваться последняя опубликованная версия. 

Точка входа бандлера - `src/components/index.js`. Всё что экспортируется в этом файле, попадает в бандл, и будет
доступно consumer'ам через `import { ... } from '@neoflex/fastdata-ui-kit'`.  

Репозиторий с бандлом хранится в каталоге `lib-build` (будет склонирован при первом билде бандла), и используется для
публикации новых версий.

**npm-скрипты билдинга/публикации:**

- `yarn lib-build` - собирает бандл из исходников. Сейчас это один файл - `index.js`. Предварительно клонирует (или
  обновляет, git pull) репозиторий в каталоге `lib-build`. В итоге получаем свежий репозиторий с перезаписанным
  файлом `lib-build/index.js`


- `yarn lib-publish` - коммитит и пушит бандл (если в нём есть изменения). Принимает параметром номер публикуемой
  версии. Если параметр не указан, запросит номер версии в консоли, предлагая в качестве значения по-умолчанию, номер  
  предыдущей версии увеличенный на `0.0.1`


- `yarn lib-bp` - последовательно выполнит скрипты `lib-build` и `lib-publish`. Как и `lib-publish`, может принимать
  параметром номер версии


- `yarn lib-rollup` - только собирает бандл `lib-build/index.js`, без предварительной подготовки репозитория в каталоге
  `lib-build`. Удобно использовать при отладке бандлинга. Также используется командой `lib-build`

### Сторибук

Сторибук визуализирует для каждого компонента *JSX*, возвращаемый одной или более *story* - функциями принимающими
*args* от сторибука, и рендерящими некоторым образом компонент. Поиск *stories* производится в файлах
`src/**/*.stories.js`. Каждый именованный экспорт в таком файле описывает одну *story*, а `export default` задаёт доп.
параметры для *stories* в файле.

**npm-скрипты:**

- `yarn story` - запускает *dev* режим сторибука


- `yarn build-storybook` - собирает бандл сторибука в каталог `storybook-static` 

### CRA приложение consumer

Содержится в каталоге `src/cra`, точка входа - `src/cra/index.js`. 

**npm-скрипты:**

- `yarn start` - запускает *dev* режим приложения


- `yarn build` - собирает бандл приложения в каталог `build`

-----------------

Сторибук и CRA импортируют компоненты библиотеки через файл `src/config/library_proxy.js`, который используется для 
переключения между режимом использования исходников компонентов напрямую из `src/components/index.js`, 
и режимом использования бандла `node_modules/@neoflex/fastdata-ui-kit`. Переключение между этими режимами можно 
производить с помощью **npm-скриптов** `yarn set-dev-conf` и `yarn set-build-conf`.

На текущий момент, режим использования бандла работает только с CRA. Сторибук перестал работать в этом режиме после
добавления одной оптимизации бандлинга, необходимой для работы *tree shaking'a* (см. ниже). Почти наверняка это можно 
починить. 

## Темизация

Библиотека поддерживает темизацию на основе *SCSS* переменных. Данные тем описываются файлами `src/scss/themes/_*.scss`.
Эти файлы, помимо темизируемых значений *CSS*-свойств, могут содержать значения любых типов данных *SCSS*, используемые
в *SCSS*-модулях компонентов. Всегда должен присутствовать файл дефолтной темы `_default.scss`. Остальные темы могут 
описывать только отличия от дефолтной (или любой другой) темы, ссылаясь на имя родительской темы в поле *parent*. 
В объектах (мапах) тем предусмотрен особый объект *globals*, предназначенный для описания глобальных переменных темы.
На значения *globals*-переменных можно ссылаться из других переменных (определяемых в данной теме, или в её потомках):

```scss
$default: (
    globals: (
        color-text-primary: $gray-9,
        ...
    ),
    typography: (
        colors: (
            primary: _color-text-primary, // ссылка должна начинаться с символа _ 
            ...
        ),
        ...
    ),
    ...
);
```
\
Стили компонентов должны писаться так, как если бы описывали какую-то одну тему, например дефолтную. Для доступа к 
значениям, определяемым в файлах тем, *SCSS*-модули компонентов могут использовать функцию `get` определённую в файле 
`src/scss/_themes_builder.scss` (вручную импортировать этот файл не нужно). Для генерации стилей всех тем, *SCSS*-модули
всех компонентов будут использоваться несколько раз подряд, столько, сколько генерируется тем, при этом функция `get`
всякий раз будет возвращать значения текущей генерируемой темы. Все селекторы находящиеся на самом верхнем уровне 
*SCSS*-модуля (не вложенные в другие конструкции) будут автоматически конкатенироваться с классом текущей темы, вида
`.theme-magenta` (не считая дефолтной темы; можно считать что доп. класс дефолтной темы является пустой строкой).
Остальные селекторы (не находящиеся на самом верхнем уровне), останутся без изменений. При необходимости темизировать
такие селекторы, можно воспользоваться переменной `$curThemeClass` из файла `_themes_builder.scss`, подобным образом:

```scss
@mixin icons($opacities) {
    @for $i from 1 through 3 {
        #{$curThemeClass}.icon#{nth($states, $i)} {
            opacity: nth(get(component opacities), $i);
        }
    }
}
```
\
Следует учитывать что значением `$curThemeClass` при генерации дефолтной темы, будет пустая строка. Т.е. код
использующий данную переменную должен быть к этому готов. Автоматическую темизацию селекторов верхнего уровня можно
отключать с помощью комментария `// custom`, размещённого внутри блока, следующего за селектором:

```scss
.selector { // custom
    ...
}
```
\
*JavaScript* компонента должен сам позаботиться о назначении класса темы *DOM*-элементам (название темы он может
получить с помощью хука `useTheme` экспортируемого файлом `src/components/Theme.js`). В случае если вложенность
селекторов в файле стилей повторяет вложенность *DOM*-элементов компонента, и используется автоматическая темизация
селекторов, достаточно будет назначить класс темы только одному внешнему *DOM*-элементу, подобным образом (см. также
компоненты `Radio` и `Button`):

```jsx
const theme = useTheme();
return (
    <button 
        className={classNames(
            theme && css[`theme-${theme}`], 
            otherClass, 
            ...
        )}
    > 
        ... 
    </button>
);
```
\
Для задания темы компонентам, consumer'ам предлагается использовать компонент `ThemeProvider` из файла
`src/components/Theme.js`, передающий вложенным в него компонентам библиотеки название темы через контекст:

```jsx
<ThemeProvider theme="magenta">
    ...
    <div>
        ...
        <Button {...args} />
        ...
    </div>
    ...
</ThemeProvider>
```
\
Для использования одной лишь дефолтной темы, наличие выше по дереву компонента ThemeProvider необязательно. 

Список включаемых в бандл тем задаётся массивом `themes` в корневом `package.json`.  

-----------------

Механизм темизации использует кастомный процессинг *SCSS*-файлов: генерируются темизированные версии *SCSS* для всех тем
компонента как описано выше, далее они по отдельности рендерятся в *CSS* с помощью стандартного *SASS*-препроцессора,
затем из полученных фрагментов *CSS* каждой темы удаляются декларации, дублирующие декларации соответствующих селекторов
дефолтной темы, и наконец оставшийся *CSS* объединяется. Удаление избыточных деклараций позволяет заметно сократить
размер бандла.

Для просмотра итогового *CSS* компонента, можно прописать его имя в массиве `saveCssFilePatterns` в файле 
`src/build-utils/webpack-scss-loader.js`. При работе сторибука или CRA, *CSS*-файл такого компонента будет сохраняться
по соседству с *SCSS* файлом. Для временного отключения кастомного процессинга *SCSS*-модуля (например в отладочных  
целях), можно в первой строке модуля разместить коммент `// skipTheming`.

Если библиотека будет собираться только с единственной дефолтной темой, кастомный процессинг *SCSS* не должен создавать
бо́льшую вычислительную нагрузку чем стандартный. Если всё же потребуется его отключить, сделать это можно в файлах
`src/build-utils/webpackFinal.js`, для сторибука/CRA, и `rollup.config.js` для бандла (в `rollup.config.js` нужно будет
заменить `use: ['scss-custom-loader']` на `use: ['sass']`). Компоненты импортирующие файл стилей `src/scss/_core.scss`
не будут ломать сборку библиотеки после отключения кастомного процессинга, т.к. в этом файле определены переменная
`$curThemeClass` и облегчённая версия функции `get`, обеспечивающие правильную генерацию дефолтной темы.  

## Бандлинг

### Tree Shaking

*Tree Shaking* - механизм используемый вебпаком для удаления из бандла проекта неиспользуемого кода. Библиотека
совместима с данным механизмом. Для обеспечения его работы в приложениях-consumer'ах, используются следующие средства:
- бандл создаётся в формате ESM (ECMAScript modules), этим обеспечивается *tree shaking* JS кода;
- стили инжектятся в JS бандла с помощью `rollup-plugin-styles`, умеющего делать их *tree shaking* совместимыми;
- используется `babel-plugin-pure-static-props` для поддержки *tree shaking'а* функц. компонентов имеющих 
`propTypes` и `defaultProps`.
  
Проблема с *tree shaking'ом* функц. компонентов, имеющимх `propTypes` и `defaultProps` описана [здесь]( 
https://medium.com/sainsburys-engineering/tree-shaking-problems-with-component-libraries-e1afe683fd29)
и [здесь](https://dev.to/layershifter/how-to-kill-tree-shaking-in-webpack-with-static-properties-p72).

Вообще говоря, если использовать только `propTypes` (без `defaultProps`), то проблемы не будет. Механизм перестаёт
корректно работать при наличии двух и более присваиваний свойств объекту-функции компонента. 
`babel-plugin-pure-static-props` решает эту проблему оборачивая компонент внутри бандла в IIFE:

```javascript
const Typography = /*#__PURE__*/function () {
    const Typography = function Typography(props) { ... };
    Typography.propTypes = { ... };
    Typography.defaultProps = { ... };
    return Typography;
}();
```
\
Именно эта манипуляция приводит к отказу сторибука работать с компонентами из бандла, однако поддержка библиотекой
*tree shaking'а* видится более весомой.

Подобная проблема существует и у классовых компонентов, и решить её можно с помощью 
`babel-plugin-no-side-effect-class-properties`. `defaultProps` для функц. компонентов в будущем, вероятно станет
*deprecated* (https://twitter.com/dan_abramov/status/1133878326358171650). В нашей библиотеке `propTypes` и
`defaultProps` используются из-за требований сторибука. Если поддержка сторибука не нужна, но нужен *tree shaking*,
вероятно, лучше не использовать `defaultProps`. Тогда можно будет отключить `babel-plugin-pure-static-props`.

### Scoped CSS Classnames

Для уменьшения размера бандла, имена *CSS* классов заменяются в нём на короткие уникальные хэши. Для формированиия
такого хэша используется имя SCSS файла компонента объединённое с исходным именем *CSS* класса. Функция формирующая хэш
определена в файле `rollup.config.js` (`generateScopedName`). Может быть модифицирована: например, если компонентов в
библиотеке станет много и потребуется увеличить уникальность хэшей. 

### Возможные ошибки

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