Skip to content

Commit

Permalink
add toggle features + auto remove script
Browse files Browse the repository at this point in the history
  • Loading branch information
Bender101 committed Dec 18, 2023
1 parent 1276370 commit 8ec31d5
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 32 deletions.
88 changes: 56 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ npm install - устанавливаем зависимости
npm run start:dev или npm run start:dev:vite - запуск сервера + frontend проекта в dev режиме
```

----
---

## Скрипты

Expand All @@ -32,15 +32,15 @@ npm run start:dev или npm run start:dev:vite - запуск сервера +
- `npm run prepare` - прекоммит хуки
- `npm run generate:slice` - Скрипт для генерации FSD слайсов

----
---

## Архитектура проекта

Проект написан в соответствии с методологией Feature sliced design

Ссылка на документацию - [feature sliced design](https://feature-sliced.design/docs/get-started/tutorial)

----
---

## Работа с переводами

Expand All @@ -51,19 +51,20 @@ npm run start:dev или npm run start:dev:vite - запуск сервера +

Документация i18next - [https://react.i18next.com/](https://react.i18next.com/)

----
---

## Тесты

В проекте используются 4 вида тестов:
1) Обычные unit тесты на jest - `npm run test:unit`
2) Тесты на компоненты с React testing library -`npm run test:unit`
3) Скриншотное тестирование с loki `npm run test:ui`
4) e2e тестирование с Cypress `npm run test:e2e`

1. Обычные unit тесты на jest - `npm run test:unit`
2. Тесты на компоненты с React testing library -`npm run test:unit`
3. Скриншотное тестирование с loki `npm run test:ui`
4. e2e тестирование с Cypress `npm run test:e2e`

Подробнее о тестах - [документация тестирование](/docs/tests.md)

----
---

## Линтинг

Expand All @@ -73,18 +74,21 @@ npm run start:dev или npm run start:dev:vite - запуск сервера +
используется собственный eslint plugin - [eslint-plugin-fsd-rules-checker-latest](https://www.npmjs.com/package/eslint-plugin-fsd-rules-checker-latest)
,
который содержит 3 правила
1) path-checker - запрещает использовать абсолютные импорты в рамках одного модуля
2) layer-imports - проверяет корректность использования слоев с точки зрения FSD

1. path-checker - запрещает использовать абсолютные импорты в рамках одного модуля
2. layer-imports - проверяет корректность использования слоев с точки зрения FSD
(например widgets нельзя использовать в features и entitites)
3) public-api-imports - разрешает импорт из других модулей только из public api. Имеет auto fix
3. public-api-imports - разрешает импорт из других модулей только из public api. Имеет auto fix

##### Запуск линтеров

- `npm run lint:ts` - Проверка ts файлов линтером
- `npm run lint:ts:fix` - Исправление ts файлов линтером
- `npm run lint:scss` - Проверка scss файлов style линтером
- `npm run lint:scss:fix` - Исправление scss файлов style линтером

----
---

## Storybook

В проекте для каждого компонента описываются стори-кейсы.
Expand All @@ -93,62 +97,64 @@ npm run start:dev или npm run start:dev:vite - запуск сервера +
Файл со сторикейсами создает рядом с компонентом с расширением .stories.tsx

Запустить сторибук можно командой:

- `npm run storybook`

Подробнее о [Storybook](/docs/storybook.md)

Пример:

```typescript jsx
import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import React from "react";
import { ComponentStory, ComponentMeta } from "@storybook/react";

import { ThemeDecorator } from '@/shared/config/storybook/ThemeDecorator/ThemeDecorator';
import { Button, ButtonSize, ButtonTheme } from './Button';
import { Theme } from '@/shared/const/theme';
import { ThemeDecorator } from "@/shared/config/storybook/ThemeDecorator/ThemeDecorator";
import { Button, ButtonSize, ButtonTheme } from "./Button";
import { Theme } from "@/shared/const/theme";

export default {
title: 'shared/Button',
component: Button,
argTypes: {
backgroundColor: { control: 'color' },
},
title: "shared/Button",
component: Button,
argTypes: {
backgroundColor: { control: "color" },
},
} as ComponentMeta<typeof Button>;

const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
children: 'Text',
children: "Text",
};

export const Clear = Template.bind({});
Clear.args = {
children: 'Text',
theme: ButtonTheme.CLEAR,
children: "Text",
theme: ButtonTheme.CLEAR,
};
```


----
---

## Конфигурация проекта

Для разработки проект содержит 2 конфига:

1. Webpack - ./config/build
2. vite - vite.config.ts

Оба сборщика адаптированы под основные фичи приложения.

Вся конфигурация хранится в /config

- /config/babel - babel
- /config/build - конфигурация webpack
- /config/jest - конфигурация тестовой среды
- /config/storybook - конфигурация сторибука

В папке `scripts` находятся различные скрипты для рефакторинга\упрощения написания кода\генерации отчетов и тд.

----
---

## CI pipeline и pre commit хуки

Expand All @@ -157,7 +163,7 @@ Clear.args = {

В прекоммит хуках проверяем проект линтерами, конфиг в /.husky

----
---

### Работа с данными

Expand All @@ -169,8 +175,27 @@ Clear.args = {
Для асинхронного подключения редюсеров (чтобы не тянуть их в общий бандл) используется
[DynamicModuleLoader](/src/shared/lib/components/DynamicModuleLoader/DynamicModuleLoader.tsx)

----
---

### Работа с feature-flags

Разрешено использование feature flags только с помощью хелпера toggleFeatures

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

{
name: название фича-флага,
on: функция, которая отработает после Включения фичи
off: функция, которая отработает после ВЫключения фичи
}

Для автоматического удаления фичи использовать скрипт remove-feature.ts,
который принимает 2 аргумента

1. Название удаляемого фича-флага
2. Состояние (on\off)

---

## Сущности (entities)

Expand All @@ -195,6 +220,5 @@ Clear.args = {
- [editableProfileCard](/src/features/editableProfileCard)
- [LangSwitcher](/src/features/LangSwitcher)
- [notificationButton](/src/features/notificationButton)
- [profileRating](/src/features/profileRating)
- [ThemeSwitcher](/src/features/ThemeSwitcher)
- [UI](/src/features/UI)
78 changes: 78 additions & 0 deletions scripts/remove-feature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Node, Project, SyntaxKind } from 'ts-morph';

const removedFeatureName = process.argv[2]; // example isArticleEnabled
const featureState = process.argv[3]; // example off\on

if (!removedFeatureName) {
throw new Error('Укажите название фича-флага');
}

if (!featureState) {
throw new Error('Укажите состояние фичи (on или off)');
}

if (featureState !== 'on' && featureState !== 'off') {
throw new Error('Некорректное значение состояния фичи (on или off)');
}

const project = new Project({});

project.addSourceFilesAtPaths('src/**/*.ts');
project.addSourceFilesAtPaths('src/**/*.tsx');

const files = project.getSourceFiles();

function isToggleFunction(node: Node) {
let isToggleFeatures = false;

node.forEachChild((child) => {
if (
child.isKind(SyntaxKind.Identifier) &&
child.getText() === 'toggleFeatures'
) {
isToggleFeatures = true;
}
});

return isToggleFeatures;
}

files.forEach((sourceFile) => {
sourceFile.forEachDescendant((node) => {
if (node.isKind(SyntaxKind.CallExpression) && isToggleFunction(node)) {
const objectOptions = node.getFirstDescendantByKind(
SyntaxKind.ObjectLiteralExpression,
);

if (!objectOptions) return;

const offFunctionProperty = objectOptions.getProperty('off');
const onFunctionProperty = objectOptions.getProperty('on');

const featureNameProperty = objectOptions.getProperty('name');

const onFunction = onFunctionProperty?.getFirstDescendantByKind(
SyntaxKind.ArrowFunction,
);
const offFunction = offFunctionProperty?.getFirstDescendantByKind(
SyntaxKind.ArrowFunction,
);
const featureName = featureNameProperty
?.getFirstDescendantByKind(SyntaxKind.StringLiteral)
?.getText()
.slice(1, -1);

if (featureName !== removedFeatureName) return;

if (featureState === 'on') {
node.replaceWithText(onFunction?.getBody().getText() ?? '');
}

if (featureState === 'off') {
node.replaceWithText(offFunction?.getBody().getText() ?? '');
}
}
});
});

project.save();
1 change: 1 addition & 0 deletions src/shared/lib/features/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { getFeatureFlag, setFeatureFlags } from "./setGetFeatures";
export { toggleFeatures } from "./toggleFeatures";
16 changes: 16 additions & 0 deletions src/shared/lib/features/toggleFeatures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { FeatureFlags } from "@/shared/types/featureFlags";
import { getFeatureFlag } from './setGetFeatures';

export interface ToggleFeaturesOptions<T> {
name: keyof FeatureFlags;
on: () => T;
off: () => T;
}

export function toggleFeatures<T>({ name, on, off }: ToggleFeaturesOptions<T>) {
if (getFeatureFlag(name)) {
return on();
}

return off();
}

0 comments on commit 8ec31d5

Please sign in to comment.