@t1ep1l0t/create-fsd 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +107 -9
- package/bin/cli.js +145 -1
- package/package.json +1 -1
- package/templates/react/.editorconfig +12 -0
- package/templates/react/.prettierignore +7 -0
- package/templates/react/.prettierrc +10 -0
- package/templates/react/.vscode/extensions.json +7 -0
- package/templates/react/eslint.config.js +102 -3
- package/templates/react/package.json +16 -1
- package/templates/react/public/locales/en/basic.json +9 -0
- package/templates/react/public/locales/ru/basic.json +9 -0
- package/templates/react/src/app/providers/i18n/index.js +23 -26
- package/templates/react/src/app/styles/global.css +15 -0
- package/templates/react/src/app/styles/index.css +0 -19
- package/templates/react/src/main.jsx +14 -11
- package/templates/react/vite.config.js +9 -5
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
# 🚀 create-fsd
|
|
3
|
+
# 🚀 @t1ep1l0t/create-fsd
|
|
4
4
|
|
|
5
|
-
**Scaffold modern
|
|
5
|
+
**Scaffold modern frontend applications with Feature-Sliced Design architecture and generate FSD structures on-the-fly**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@t1ep1l0t/create-fsd)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
|
|
15
15
|
## 📖 About
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
**@t1ep1l0t/create-fsd** is a powerful CLI tool that helps you quickly bootstrap production-ready React applications following the [Feature-Sliced Design (FSD)](https://feature-sliced.design/) architectural methodology. Not only can you create fully configured projects in seconds, but you can also generate FSD-compliant structures for features, entities, widgets, and pages on-demand!
|
|
18
18
|
|
|
19
|
-
> 🎯 Currently supports **React** template. More frameworks (Vue, Next
|
|
19
|
+
> 🎯 Currently supports **React** template. More frameworks (Vue, Next, Nuxt, Svelte) coming soon!
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -24,8 +24,9 @@
|
|
|
24
24
|
|
|
25
25
|
### 🏗️ **Architecture**
|
|
26
26
|
- **Feature-Sliced Design (FSD)** - Scalable and maintainable project structure
|
|
27
|
+
- **FSD Structure Generator** - Quickly generate features, entities, widgets, and pages
|
|
27
28
|
- Clear separation of concerns across layers
|
|
28
|
-
- Ready-to-use folder structure
|
|
29
|
+
- Ready-to-use folder structure with automated scaffolding
|
|
29
30
|
|
|
30
31
|
### ⚡ **Modern Stack**
|
|
31
32
|
- **React 18** - Latest React with concurrent features
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
- **Axios** - HTTP client with interceptors configured
|
|
39
40
|
- **React Query** - Powerful server state management
|
|
40
41
|
- **i18next** - Internationalization (i18n) support
|
|
41
|
-
- **ESLint** - Code quality and consistency
|
|
42
|
+
- **ESLint & Prettier** - Code quality and consistency
|
|
42
43
|
|
|
43
44
|
### 🎨 **Developer Experience**
|
|
44
45
|
- Path aliases configured (`@app`, `@pages`, `@widgets`, `@features`, `@entities`, `@shared`)
|
|
@@ -73,6 +74,68 @@ npx @t1ep1l0t/create-fsd my-app --template react
|
|
|
73
74
|
|
|
74
75
|
---
|
|
75
76
|
|
|
77
|
+
## 🏗️ FSD Structure Generator
|
|
78
|
+
|
|
79
|
+
After creating your project, you can use the `fsd` command to quickly generate FSD-compliant directory structures for features, entities, widgets, and pages.
|
|
80
|
+
|
|
81
|
+
### Usage
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
create-fsd fsd [options]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Options
|
|
88
|
+
|
|
89
|
+
| Option | Alias | Description |
|
|
90
|
+
|--------|-------|-------------|
|
|
91
|
+
| `--feature <name>` | `-f` | Create feature in `features/` |
|
|
92
|
+
| `--entity <name>` | `-e` | Create entity in `entities/` |
|
|
93
|
+
| `--widget <name>` | `-w` | Create widget in `widgets/` |
|
|
94
|
+
| `--page <name>` | `-p` | Create page in `pages/` |
|
|
95
|
+
| `--segments <segments...>` | `-s` | Segments to create (e.g., model ui api lib) |
|
|
96
|
+
| `--index` | `-i` | Create index files in root and all segments |
|
|
97
|
+
|
|
98
|
+
### Examples
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Create a feature with UI and model segments
|
|
102
|
+
create-fsd fsd -f user -s ui model -i
|
|
103
|
+
|
|
104
|
+
# Result:
|
|
105
|
+
# src/features/user/
|
|
106
|
+
# ├── ui/
|
|
107
|
+
# │ └── index.js
|
|
108
|
+
# ├── model/
|
|
109
|
+
# │ └── index.js
|
|
110
|
+
# └── index.js (exports all segments)
|
|
111
|
+
|
|
112
|
+
# Create an entity with API and lib segments
|
|
113
|
+
create-fsd fsd -e product -s api lib -i
|
|
114
|
+
|
|
115
|
+
# Create a widget without index files (uses .gitkeep)
|
|
116
|
+
create-fsd fsd -w header -s ui model
|
|
117
|
+
|
|
118
|
+
# Create a page
|
|
119
|
+
create-fsd fsd -p home -s ui api -i
|
|
120
|
+
|
|
121
|
+
# Add more segments to existing structure
|
|
122
|
+
create-fsd fsd -f user -s api config -i
|
|
123
|
+
# This will add api/ and config/ folders and update the root index.js
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Common Segment Names
|
|
127
|
+
|
|
128
|
+
| Segment | Purpose |
|
|
129
|
+
|---------|---------|
|
|
130
|
+
| `ui` | UI components |
|
|
131
|
+
| `model` | Business logic, state, hooks |
|
|
132
|
+
| `api` | API requests and endpoints |
|
|
133
|
+
| `lib` | Helper functions and utilities |
|
|
134
|
+
| `config` | Configuration and constants |
|
|
135
|
+
| `types` | TypeScript types/interfaces |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
76
139
|
## 📂 Project Structure
|
|
77
140
|
|
|
78
141
|
The generated project follows the FSD architecture with clear layer separation:
|
|
@@ -145,19 +208,21 @@ The generated project includes working examples for all included libraries:
|
|
|
145
208
|
|
|
146
209
|
---
|
|
147
210
|
|
|
148
|
-
## 📋 CLI
|
|
211
|
+
## 📋 CLI Commands
|
|
212
|
+
|
|
213
|
+
### Create Project
|
|
149
214
|
|
|
150
215
|
```bash
|
|
151
216
|
create-fsd <project-name> [options]
|
|
152
217
|
```
|
|
153
218
|
|
|
154
|
-
|
|
219
|
+
#### Options
|
|
155
220
|
|
|
156
221
|
| Option | Alias | Description | Default |
|
|
157
222
|
|--------|-------|-------------|---------|
|
|
158
223
|
| `--template <name>` | `-t` | Template to use | `react` |
|
|
159
224
|
|
|
160
|
-
|
|
225
|
+
#### Examples
|
|
161
226
|
|
|
162
227
|
```bash
|
|
163
228
|
# Create project with React template (default)
|
|
@@ -170,6 +235,39 @@ npx @t1ep1l0t/create-fsd my-app --template react
|
|
|
170
235
|
npx @t1ep1l0t/create-fsd my-app -t react
|
|
171
236
|
```
|
|
172
237
|
|
|
238
|
+
### Generate FSD Structure
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
create-fsd fsd [options]
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Options
|
|
245
|
+
|
|
246
|
+
| Option | Alias | Description | Required |
|
|
247
|
+
|--------|-------|-------------|----------|
|
|
248
|
+
| `--feature <name>` | `-f` | Create feature in `features/` | One layer required |
|
|
249
|
+
| `--entity <name>` | `-e` | Create entity in `entities/` | One layer required |
|
|
250
|
+
| `--widget <name>` | `-w` | Create widget in `widgets/` | One layer required |
|
|
251
|
+
| `--page <name>` | `-p` | Create page in `pages/` | One layer required |
|
|
252
|
+
| `--segments <segments...>` | `-s` | Segments to create | Yes |
|
|
253
|
+
| `--index` | `-i` | Create index files | No |
|
|
254
|
+
|
|
255
|
+
#### Examples
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Create feature with index files
|
|
259
|
+
create-fsd fsd -f authentication -s ui model api -i
|
|
260
|
+
|
|
261
|
+
# Create entity without index files
|
|
262
|
+
create-fsd fsd -e user -s model api
|
|
263
|
+
|
|
264
|
+
# Create widget
|
|
265
|
+
create-fsd fsd -w sidebar -s ui lib -i
|
|
266
|
+
|
|
267
|
+
# Create page
|
|
268
|
+
create-fsd fsd -p dashboard -s ui model -i
|
|
269
|
+
```
|
|
270
|
+
|
|
173
271
|
---
|
|
174
272
|
|
|
175
273
|
## 🎓 Learn More
|
package/bin/cli.js
CHANGED
|
@@ -16,7 +16,7 @@ const program = new Command();
|
|
|
16
16
|
program
|
|
17
17
|
.name('create-fsd')
|
|
18
18
|
.description('CLI to create React projects with FSD architecture')
|
|
19
|
-
.version('
|
|
19
|
+
.version('2.0.0')
|
|
20
20
|
.argument('<project-name>', 'name of the project')
|
|
21
21
|
.option('-t, --template <template>', 'template to use', 'react')
|
|
22
22
|
.action(async (projectName, options) => {
|
|
@@ -81,4 +81,148 @@ program
|
|
|
81
81
|
console.log();
|
|
82
82
|
});
|
|
83
83
|
|
|
84
|
+
// FSD structure generation command
|
|
85
|
+
program
|
|
86
|
+
.command('fsd')
|
|
87
|
+
.description('Generate FSD (Feature-Sliced Design) structure')
|
|
88
|
+
.option('-f, --feature <name>', 'create feature in features/')
|
|
89
|
+
.option('-e, --entity <name>', 'create entity in entities/')
|
|
90
|
+
.option('-w, --widget <name>', 'create widget in widgets/')
|
|
91
|
+
.option('-p, --page <name>', 'create page in pages/')
|
|
92
|
+
.option('-s, --segments <segments...>', 'segments to create (e.g., model ui api lib)')
|
|
93
|
+
.option('-i, --index', 'create index files in root and all segments', false)
|
|
94
|
+
.action(async (options) => {
|
|
95
|
+
const { feature, entity, widget, page, segments, index } = options;
|
|
96
|
+
|
|
97
|
+
// Determine layer and name based on which flag was used
|
|
98
|
+
let layer, name, layerType;
|
|
99
|
+
|
|
100
|
+
if (feature) {
|
|
101
|
+
layer = 'features';
|
|
102
|
+
name = feature;
|
|
103
|
+
layerType = 'Feature';
|
|
104
|
+
} else if (entity) {
|
|
105
|
+
layer = 'entities';
|
|
106
|
+
name = entity;
|
|
107
|
+
layerType = 'Entity';
|
|
108
|
+
} else if (widget) {
|
|
109
|
+
layer = 'widgets';
|
|
110
|
+
name = widget;
|
|
111
|
+
layerType = 'Widget';
|
|
112
|
+
} else if (page) {
|
|
113
|
+
layer = 'pages';
|
|
114
|
+
name = page;
|
|
115
|
+
layerType = 'Page';
|
|
116
|
+
} else {
|
|
117
|
+
console.log(chalk.red('❌ Layer flag is required! Use -f (feature), -e (entity), -w (widget), or -p (page)'));
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Validation
|
|
122
|
+
if (!segments || segments.length === 0) {
|
|
123
|
+
console.log(chalk.red('❌ At least one segment is required! Use -s or --segments'));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(chalk.blue.bold(`\n🏗️ Generating FSD structure...\n`));
|
|
128
|
+
console.log(chalk.cyan(`Layer: ${layer}`));
|
|
129
|
+
console.log(chalk.cyan(`${layerType}: ${name}`));
|
|
130
|
+
console.log(chalk.cyan(`Segments: ${segments.join(', ')}`));
|
|
131
|
+
console.log(chalk.cyan(`Index files: ${index ? 'Yes' : 'No'}\n`));
|
|
132
|
+
|
|
133
|
+
const baseDir = path.join(process.cwd(), 'src', layer, name);
|
|
134
|
+
|
|
135
|
+
// Check if directory already exists
|
|
136
|
+
if (fs.existsSync(baseDir)) {
|
|
137
|
+
console.log(chalk.yellow(`⚠️ Directory ${layer}/${name} already exists!`));
|
|
138
|
+
console.log(chalk.yellow('Adding segments to existing structure...\n'));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Create directory
|
|
142
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
143
|
+
|
|
144
|
+
// Create segments
|
|
145
|
+
const spinner = ora('Creating segments...').start();
|
|
146
|
+
try {
|
|
147
|
+
for (const segment of segments) {
|
|
148
|
+
const segmentDir = path.join(baseDir, segment);
|
|
149
|
+
fs.mkdirSync(segmentDir, { recursive: true });
|
|
150
|
+
|
|
151
|
+
// Create index file in segment if requested
|
|
152
|
+
if (index) {
|
|
153
|
+
const segmentIndexPath = path.join(segmentDir, 'index.js');
|
|
154
|
+
fs.writeFileSync(segmentIndexPath, '');
|
|
155
|
+
} else {
|
|
156
|
+
// Create a .gitkeep file to ensure empty directories are tracked
|
|
157
|
+
const gitkeepPath = path.join(segmentDir, '.gitkeep');
|
|
158
|
+
fs.writeFileSync(gitkeepPath, '');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
spinner.succeed('Segments created!');
|
|
162
|
+
} catch (error) {
|
|
163
|
+
spinner.fail('Failed to create segments');
|
|
164
|
+
console.error(error);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Create index file if requested
|
|
169
|
+
if (index) {
|
|
170
|
+
const indexSpinner = ora('Creating root index file...').start();
|
|
171
|
+
try {
|
|
172
|
+
const indexPath = path.join(baseDir, 'index.js');
|
|
173
|
+
|
|
174
|
+
// If index file exists, read existing exports and merge with new ones
|
|
175
|
+
let existingExports = new Set();
|
|
176
|
+
if (fs.existsSync(indexPath)) {
|
|
177
|
+
const existingContent = fs.readFileSync(indexPath, 'utf-8');
|
|
178
|
+
const exportMatches = existingContent.matchAll(/export \* from ['"]\.\/(.+?)['"]/g);
|
|
179
|
+
for (const match of exportMatches) {
|
|
180
|
+
existingExports.add(match[1]);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Add new segments to existing exports
|
|
185
|
+
segments.forEach(segment => existingExports.add(segment));
|
|
186
|
+
|
|
187
|
+
// Generate exports for all segments
|
|
188
|
+
let indexContent = `// ${name} exports\n\n`;
|
|
189
|
+
|
|
190
|
+
// Sort segments for consistency
|
|
191
|
+
const allSegments = Array.from(existingExports).sort();
|
|
192
|
+
for (const segment of allSegments) {
|
|
193
|
+
indexContent += `export * from './${segment}';\n`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
197
|
+
|
|
198
|
+
if (existingExports.size > segments.length) {
|
|
199
|
+
indexSpinner.succeed('Root index file updated with new exports!');
|
|
200
|
+
} else {
|
|
201
|
+
indexSpinner.succeed('Root index file created!');
|
|
202
|
+
}
|
|
203
|
+
} catch (error) {
|
|
204
|
+
indexSpinner.fail('Failed to create root index file');
|
|
205
|
+
console.error(error);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Success message
|
|
211
|
+
console.log(chalk.green.bold('\n✅ FSD structure generated successfully!\n'));
|
|
212
|
+
console.log(chalk.cyan('Generated structure:'));
|
|
213
|
+
console.log(chalk.white(` src/${layer}/${name}/`));
|
|
214
|
+
segments.forEach((segment, idx) => {
|
|
215
|
+
const isLast = idx === segments.length - 1;
|
|
216
|
+
const prefix = index ? '├──' : (isLast ? '└──' : '├──');
|
|
217
|
+
console.log(chalk.white(` ${prefix} ${segment}/`));
|
|
218
|
+
if (index) {
|
|
219
|
+
console.log(chalk.white(` │ └── index.js`));
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
if (index) {
|
|
223
|
+
console.log(chalk.white(` └── index.js`));
|
|
224
|
+
}
|
|
225
|
+
console.log();
|
|
226
|
+
});
|
|
227
|
+
|
|
84
228
|
program.parse();
|
package/package.json
CHANGED
|
@@ -3,9 +3,14 @@ import globals from 'globals'
|
|
|
3
3
|
import react from 'eslint-plugin-react'
|
|
4
4
|
import reactHooks from 'eslint-plugin-react-hooks'
|
|
5
5
|
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
6
|
+
import reactQuery from '@tanstack/eslint-plugin-query'
|
|
7
|
+
import importPlugin from 'eslint-plugin-import'
|
|
8
|
+
import jsxA11y from 'eslint-plugin-jsx-a11y'
|
|
9
|
+
import prettier from 'eslint-plugin-prettier'
|
|
10
|
+
import prettierConfig from 'eslint-config-prettier'
|
|
6
11
|
|
|
7
12
|
export default [
|
|
8
|
-
{ ignores: ['dist'] },
|
|
13
|
+
{ ignores: ['dist', 'node_modules', '.vite'] },
|
|
9
14
|
{
|
|
10
15
|
files: ['**/*.{js,jsx}'],
|
|
11
16
|
languageOptions: {
|
|
@@ -17,23 +22,117 @@ export default [
|
|
|
17
22
|
sourceType: 'module',
|
|
18
23
|
},
|
|
19
24
|
},
|
|
20
|
-
settings: {
|
|
25
|
+
settings: {
|
|
26
|
+
react: { version: '18.3' },
|
|
27
|
+
'import/resolver': {
|
|
28
|
+
node: {
|
|
29
|
+
extensions: ['.js', '.jsx'],
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
21
33
|
plugins: {
|
|
22
34
|
react,
|
|
23
35
|
'react-hooks': reactHooks,
|
|
24
36
|
'react-refresh': reactRefresh,
|
|
37
|
+
'@tanstack/query': reactQuery,
|
|
38
|
+
import: importPlugin,
|
|
39
|
+
'jsx-a11y': jsxA11y,
|
|
40
|
+
prettier,
|
|
25
41
|
},
|
|
26
42
|
rules: {
|
|
27
43
|
...js.configs.recommended.rules,
|
|
28
44
|
...react.configs.recommended.rules,
|
|
29
45
|
...react.configs['jsx-runtime'].rules,
|
|
30
46
|
...reactHooks.configs.recommended.rules,
|
|
47
|
+
...jsxA11y.configs.recommended.rules,
|
|
48
|
+
...prettierConfig.rules,
|
|
49
|
+
|
|
50
|
+
// React rules
|
|
31
51
|
'react/jsx-no-target-blank': 'off',
|
|
52
|
+
'react/prop-types': 'off',
|
|
32
53
|
'react-refresh/only-export-components': [
|
|
33
54
|
'warn',
|
|
34
55
|
{ allowConstantExport: true },
|
|
35
56
|
],
|
|
36
|
-
|
|
57
|
+
|
|
58
|
+
// React Query rules
|
|
59
|
+
'@tanstack/query/exhaustive-deps': 'error',
|
|
60
|
+
'@tanstack/query/no-rest-destructuring': 'warn',
|
|
61
|
+
'@tanstack/query/stable-query-client': 'error',
|
|
62
|
+
|
|
63
|
+
// Import rules
|
|
64
|
+
'import/order': [
|
|
65
|
+
'error',
|
|
66
|
+
{
|
|
67
|
+
groups: [
|
|
68
|
+
'builtin',
|
|
69
|
+
'external',
|
|
70
|
+
'internal',
|
|
71
|
+
['parent', 'sibling'],
|
|
72
|
+
'index',
|
|
73
|
+
'object',
|
|
74
|
+
'type',
|
|
75
|
+
],
|
|
76
|
+
'newlines-between': 'always',
|
|
77
|
+
alphabetize: {
|
|
78
|
+
order: 'asc',
|
|
79
|
+
caseInsensitive: true,
|
|
80
|
+
},
|
|
81
|
+
pathGroups: [
|
|
82
|
+
{
|
|
83
|
+
pattern: 'react',
|
|
84
|
+
group: 'external',
|
|
85
|
+
position: 'before',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
pattern: '@app/**',
|
|
89
|
+
group: 'internal',
|
|
90
|
+
position: 'before',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
pattern: '@pages/**',
|
|
94
|
+
group: 'internal',
|
|
95
|
+
position: 'before',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
pattern: '@widgets/**',
|
|
99
|
+
group: 'internal',
|
|
100
|
+
position: 'before',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
pattern: '@features/**',
|
|
104
|
+
group: 'internal',
|
|
105
|
+
position: 'before',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
pattern: '@entities/**',
|
|
109
|
+
group: 'internal',
|
|
110
|
+
position: 'before',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
pattern: '@shared/**',
|
|
114
|
+
group: 'internal',
|
|
115
|
+
position: 'before',
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
pathGroupsExcludedImportTypes: ['react'],
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
'import/no-unresolved': 'off',
|
|
122
|
+
'import/no-duplicates': 'error',
|
|
123
|
+
'import/newline-after-import': 'error',
|
|
124
|
+
|
|
125
|
+
// Accessibility rules
|
|
126
|
+
'jsx-a11y/anchor-is-valid': [
|
|
127
|
+
'error',
|
|
128
|
+
{
|
|
129
|
+
components: ['Link'],
|
|
130
|
+
specialLink: ['to'],
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
|
|
134
|
+
// Prettier integration
|
|
135
|
+
'prettier/prettier': 'error',
|
|
37
136
|
},
|
|
38
137
|
},
|
|
39
138
|
]
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
"dev": "vite",
|
|
8
8
|
"build": "vite build",
|
|
9
9
|
"lint": "eslint .",
|
|
10
|
+
"lint:fix": "eslint . --fix",
|
|
11
|
+
"format": "prettier --write \"src/**/*.{js,jsx,json,css}\"",
|
|
12
|
+
"format:check": "prettier --check \"src/**/*.{js,jsx,json,css}\"",
|
|
10
13
|
"preview": "vite preview"
|
|
11
14
|
},
|
|
12
15
|
"dependencies": {
|
|
@@ -16,8 +19,13 @@
|
|
|
16
19
|
"zustand": "^4.5.0",
|
|
17
20
|
"axios": "^1.6.7",
|
|
18
21
|
"i18next": "^23.8.2",
|
|
22
|
+
"i18next-browser-languagedetector": "^8.2.0",
|
|
23
|
+
"i18next-http-backend": "^3.0.2",
|
|
19
24
|
"react-i18next": "^14.0.5",
|
|
20
|
-
"@tanstack/react-query": "^5.22.2"
|
|
25
|
+
"@tanstack/react-query": "^5.22.2",
|
|
26
|
+
"classnames": "^2.5.1",
|
|
27
|
+
"react-hook-form": "^7.65.0",
|
|
28
|
+
"@hookform/resolvers": "^5.2.2"
|
|
21
29
|
},
|
|
22
30
|
"devDependencies": {
|
|
23
31
|
"@types/react": "^18.3.12",
|
|
@@ -30,6 +38,13 @@
|
|
|
30
38
|
"eslint-plugin-react": "^7.37.2",
|
|
31
39
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
32
40
|
"eslint-plugin-react-refresh": "^0.4.16",
|
|
41
|
+
"@tanstack/eslint-plugin-query": "^5.62.3",
|
|
42
|
+
"eslint-plugin-import": "^2.31.0",
|
|
43
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
44
|
+
"eslint-config-prettier": "^9.1.0",
|
|
45
|
+
"eslint-plugin-prettier": "^5.2.1",
|
|
46
|
+
"prettier": "^3.4.2",
|
|
47
|
+
"prettier-plugin-tailwindcss": "^0.6.9",
|
|
33
48
|
"@tailwindcss/vite": "^4.0.0",
|
|
34
49
|
"tailwindcss": "^4.0.0"
|
|
35
50
|
}
|
|
@@ -1,40 +1,37 @@
|
|
|
1
1
|
import i18n from 'i18next';
|
|
2
2
|
import { initReactI18next } from 'react-i18next';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
en: {
|
|
6
|
-
translation: {
|
|
7
|
-
welcome: 'Welcome to FSD React App',
|
|
8
|
-
home: 'Home',
|
|
9
|
-
about: 'About',
|
|
10
|
-
counter: 'Counter',
|
|
11
|
-
increment: 'Increment',
|
|
12
|
-
decrement: 'Decrement',
|
|
13
|
-
reset: 'Reset',
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
ru: {
|
|
17
|
-
translation: {
|
|
18
|
-
welcome: 'Добро пожаловать в FSD React приложение',
|
|
19
|
-
home: 'Главная',
|
|
20
|
-
about: 'О нас',
|
|
21
|
-
counter: 'Счетчик',
|
|
22
|
-
increment: 'Увеличить',
|
|
23
|
-
decrement: 'Уменьшить',
|
|
24
|
-
reset: 'Сбросить',
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
};
|
|
3
|
+
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
4
|
+
import Backend from 'i18next-http-backend';
|
|
28
5
|
|
|
29
6
|
i18n
|
|
7
|
+
.use(Backend)
|
|
8
|
+
.use(LanguageDetector)
|
|
30
9
|
.use(initReactI18next)
|
|
31
10
|
.init({
|
|
32
|
-
resources,
|
|
33
11
|
lng: 'en',
|
|
34
12
|
fallbackLng: 'en',
|
|
35
13
|
interpolation: {
|
|
36
14
|
escapeValue: false,
|
|
37
15
|
},
|
|
16
|
+
detection: {
|
|
17
|
+
order: ['path', 'cookie', 'localStorage'],
|
|
18
|
+
caches: ['cookie', 'localStorage'],
|
|
19
|
+
},
|
|
20
|
+
fallbackNS: 'basic',
|
|
21
|
+
ns: ['basic'],
|
|
22
|
+
defaultNS: 'basic',
|
|
23
|
+
backend: {
|
|
24
|
+
loadPath: '/locales/{{lng}}/{{ns}}.json',
|
|
25
|
+
},
|
|
26
|
+
missingKeyHandler: (lng, ns, key) => {
|
|
27
|
+
console.warn(`Missing translation: ${lng}:${ns}:${key}`);
|
|
28
|
+
},
|
|
38
29
|
});
|
|
39
30
|
|
|
31
|
+
document.documentElement.lang = i18n.language;
|
|
32
|
+
|
|
33
|
+
i18n.on('languageChanged', (lng) => {
|
|
34
|
+
document.documentElement.lang = lng;
|
|
35
|
+
});
|
|
36
|
+
|
|
40
37
|
export default i18n;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
* {
|
|
2
|
+
box-sizing: border-box;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
html {
|
|
6
|
+
font-size: 14px;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
body {
|
|
10
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
11
|
+
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
12
|
+
sans-serif;
|
|
13
|
+
-webkit-font-smoothing: antialiased;
|
|
14
|
+
-moz-osx-font-smoothing: grayscale;
|
|
15
|
+
}
|
|
@@ -7,22 +7,3 @@
|
|
|
7
7
|
--color-danger: #ef4444;
|
|
8
8
|
--color-warning: #f59e0b;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
* {
|
|
12
|
-
margin: 0;
|
|
13
|
-
padding: 0;
|
|
14
|
-
box-sizing: border-box;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
body {
|
|
18
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
19
|
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
20
|
-
sans-serif;
|
|
21
|
-
-webkit-font-smoothing: antialiased;
|
|
22
|
-
-moz-osx-font-smoothing: grayscale;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
code {
|
|
26
|
-
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
|
27
|
-
monospace;
|
|
28
|
-
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
import '@app/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
import React from 'react'
|
|
2
|
+
|
|
3
|
+
import ReactDOM from 'react-dom/client'
|
|
4
|
+
|
|
5
|
+
import { App } from '@app/App'
|
|
6
|
+
import '@app/styles/index.css'
|
|
7
|
+
import '@app/styles/global.css'
|
|
8
|
+
import '@app/providers/i18n'
|
|
9
|
+
|
|
10
|
+
ReactDOM.createRoot(document.getElementById('root')).render(
|
|
11
|
+
<React.StrictMode>
|
|
12
|
+
<App />
|
|
13
|
+
</React.StrictMode>
|
|
14
|
+
)
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { fileURLToPath } from 'url'
|
|
3
|
+
|
|
4
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
5
|
+
import react from '@vitejs/plugin-react'
|
|
6
|
+
import { defineConfig } from 'vite'
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
5
9
|
|
|
6
10
|
export default defineConfig({
|
|
7
11
|
plugins: [react(), tailwindcss()],
|
|
@@ -16,4 +20,4 @@ export default defineConfig({
|
|
|
16
20
|
'@shared': path.resolve(__dirname, './src/shared'),
|
|
17
21
|
},
|
|
18
22
|
},
|
|
19
|
-
})
|
|
23
|
+
})
|