|
| 1 | +# Sliced File Structure |
| 2 | + |
| 3 | +**Sliced FS** — методология разработки файловой структуры проектов. Основана на опыте разработки приложений на React, но подходит для любых проектов на JavaScript. |
| 4 | + |
| 5 | +> #### Перевод на другие языки |
| 6 | +> |
| 7 | +> Если у вас есть возможность, пожалуйста, внесите свой вклад и помогите перевести этот документ на другие языки, в первую очередь на английский! |
| 8 | +
|
| 9 | +## Проблема |
| 10 | + |
| 11 | +Чаще всего в проектах используют **простую файловую структуру**, которая выглядит так — в корневой директории (`src`) создают поддиректории, соответствующие типам ресурсов, используемых в проекте. |
| 12 | + |
| 13 | +Для примера, файловая структура приложения на React может выглядеть так: |
| 14 | + |
| 15 | +> ```sh |
| 16 | +> src |
| 17 | +> |-- api |
| 18 | +> |-- assets |
| 19 | +> |-- components |
| 20 | +> |-- contexts |
| 21 | +> |-- helpers |
| 22 | +> |-- hooks |
| 23 | +> |-- values |
| 24 | +> ``` |
| 25 | +
|
| 26 | +Такие ресурсы, как компоненты, помещаются в `components`, хелперы в `helpers`, значения в `values` и т.д. |
| 27 | +
|
| 28 | +В **маленьких проектах** такая файловая структура обычно подходит и не вызывает проблем. |
| 29 | +
|
| 30 | +Но в **средних-больших**, с увеличением количества ресурсов, начинают возникать сложности: |
| 31 | +
|
| 32 | +- **Необходимо бороться с коллизиями имён ресурсов** |
| 33 | +
|
| 34 | + Поскольку в средних-больших проектах много ресурсов, рано или поздно возникает проблема коллизий имён. Обычно её решают двумя способами. |
| 35 | +
|
| 36 | + Первый способ — держать ресурсы на одном уровне вложенности, но усложнить их имена. Например, добавить префиксы. |
| 37 | + |
| 38 | + Недостаток этого подхода заключается в том, что со временем формируются длинные списки ресурсов. Их **долго листать**, в них **сложно найти** нужное. А сложные имена **менее удобно читать** и **использовать**. |
| 39 | + |
| 40 | + > ```sh |
| 41 | + > components |
| 42 | + > |-- basic-feature-content |
| 43 | + > |-- basic-feature-viewer |
| 44 | + > |-- (...ещё 30 директорий, начинающихся с "basic-feature") |
| 45 | + > |-- experimental-feature-info |
| 46 | + > |-- experimental-feature-view-grid |
| 47 | + > |-- experimental-feature-view-list |
| 48 | + > |-- (...ещё 15 директорий, начинающихся с "experimental-feature") |
| 49 | + > |-- pro-feature-editor |
| 50 | + > |-- (...ещё 10 директорий, начинающихся с "pro-feature") |
| 51 | + > ``` |
| 52 | +
|
| 53 | + Второй способ — использовать несколько уровней вложенности. |
| 54 | + |
| 55 | + Недостаток этого подхода заключается в том, что возникает неконтролируемая вложенность. Она **усложняет навигацию** по проекту и **оценку комплексности проекта и отдельного функционала**. |
| 56 | +
|
| 57 | + > ```sh |
| 58 | + > # с виду довольно просто, да? |
| 59 | + > |
| 60 | + > components |
| 61 | + > |-- basic-feature |
| 62 | + > |-- experimental-feature |
| 63 | + > |-- pro-feature |
| 64 | + > ``` |
| 65 | +
|
| 66 | + > ```sh |
| 67 | + > # не тут-то было! |
| 68 | + > |
| 69 | + > components |
| 70 | + > |-- basic-feature |
| 71 | + > | |-- content |
| 72 | + > | |-- viewer |
| 73 | + > |-- experimental-feature |
| 74 | + > | |-- info |
| 75 | + > | |-- view |
| 76 | + > | |-- grid |
| 77 | + > | |-- list |
| 78 | + > |-- pro-feature |
| 79 | + > |-- editor |
| 80 | + > ``` |
| 81 | +
|
| 82 | +- **Сложно отследить связи между ресурсами** |
| 83 | +
|
| 84 | + Если вы работаете над некоторым функционалом в средних-крупных проектах, **понять, какие ресурсы относятся к этому функционалу, и где они располагаются — сложно**. Это происходит из-за слабой группировки ресурсов. Чаще всего, единственный вариант — изучить исходный код и составить представление в голове. |
| 85 | +
|
| 86 | + Эта проблема в разной степени усугубляется, в зависимости от того, какой способ борьбы с коллизиями имён вы выбрали — сложные имена и длинные списки или вложенные директории. |
| 87 | +
|
| 88 | +## Решение |
| 89 | +
|
| 90 | +**Sliced FS** вводит несколько терминов (**супергруппы**, **группы**) и определяет некоторые правила, чтобы решить эту проблему. Подходит для любых проектов на JavaScript. |
| 91 | +
|
| 92 | +Что это даёт: |
| 93 | +
|
| 94 | +* в значительной степени решается проблема коллизий имён ресурсов |
| 95 | +* без сложных имён и длинных списков |
| 96 | +* без неконтролируемой вложенности |
| 97 | +* более сильная группировка ресурсов |
| 98 | +* возможность визуально изучить, какие ресурсы относятся к определённому функционалу, без изучения исходного кода (приблизительно) |
| 99 | +* возможность визуально оценить комплексность проекта в целом и отдельного фунционала (приблизительно) |
| 100 | +
|
| 101 | +Файловая структура проекта на React, разработанная с применением **Sliced FS**, может выглядеть так: |
| 102 | +
|
| 103 | +> ```sh |
| 104 | +> # в src содержатся супергруппы |
| 105 | +> |
| 106 | +> src |
| 107 | +> |-- basic-feature |
| 108 | +> |-- basic-feature.content |
| 109 | +> |-- basic-feature.shared |
| 110 | +> |-- basic-feature.viewer |
| 111 | +> |-- experimental-feature |
| 112 | +> |-- experimental-feature.info |
| 113 | +> |-- experimental-feature.shared |
| 114 | +> |-- experimental-feature.view.grid |
| 115 | +> |-- experimental-feature.view.list |
| 116 | +> |-- pro-feature |
| 117 | +> |-- pro-feature.editor |
| 118 | +> ``` |
| 119 | +
|
| 120 | +> ```sh |
| 121 | +> # каждая супергруппа содержит обычные группы |
| 122 | +> |
| 123 | +> experimental-feature |
| 124 | +> |-- api |
| 125 | +> |-- assets |
| 126 | +> |-- components |
| 127 | +> |-- components.editor |
| 128 | +> |-- components.content.audio |
| 129 | +> |-- components.content.image |
| 130 | +> |-- components.content.video |
| 131 | +> |-- contexts |
| 132 | +> |-- helpers |
| 133 | +> |-- hooks |
| 134 | +> |-- values |
| 135 | +> |-- index.js |
| 136 | +> ``` |
| 137 | +
|
| 138 | +## Концепция |
| 139 | +
|
| 140 | +### Супергруппа |
| 141 | +
|
| 142 | +Директория, которая содержит в себе обычные **группы**, любые ресурсы вне групп и индексный модуль. Создаёт новое пространство имён. |
| 143 | +
|
| 144 | +> ```sh |
| 145 | +> # пример супергруппы |
| 146 | +> |
| 147 | +> src |
| 148 | +> |-- basic-feature |
| 149 | +> |-- api |
| 150 | +> |-- assets |
| 151 | +> |-- components |
| 152 | +> |-- contexts |
| 153 | +> |-- helpers |
| 154 | +> |-- hooks |
| 155 | +> |-- values |
| 156 | +> |-- config.js |
| 157 | +> |-- index.js |
| 158 | +> ``` |
| 159 | +
|
| 160 | +#### Расположение |
| 161 | +
|
| 162 | +Супергруппы располагаются в корневой директории (`src`). |
| 163 | +
|
| 164 | +> ```sh |
| 165 | +> src |
| 166 | +> |-- basic-feature |
| 167 | +> |-- basic-feature.content |
| 168 | +> |-- basic-feature.shared |
| 169 | +> |-- basic-feature.viewer |
| 170 | +> |-- experimental-feature |
| 171 | +> |-- experimental-feature.info |
| 172 | +> |-- experimental-feature.shared |
| 173 | +> |-- experimental-feature.view.grid |
| 174 | +> |-- experimental-feature.view.list |
| 175 | +> |-- pro-feature |
| 176 | +> |-- pro-feature.editor |
| 177 | +> ``` |
| 178 | +
|
| 179 | +#### Вложенность |
| 180 | +
|
| 181 | +Супергруппы могут быть вложенными. Вложенность выражается на уровне **имён директорий**, через перечисление цепочки родительских супергрупп через точку. |
| 182 | +
|
| 183 | +> ```sh |
| 184 | +> src |
| 185 | +> |-- experimental-feature |
| 186 | +> |-- experimental-feature.info |
| 187 | +> |-- experimental-feature.shared |
| 188 | +> |-- experimental-feature.view.grid |
| 189 | +> |-- experimental-feature.view.list |
| 190 | +> ``` |
| 191 | +
|
| 192 | +#### Ограничения |
| 193 | +
|
| 194 | +Супергруппы имеют ограничение на **импорт своих внутренних ресурсов** — любые ресурсы, которые будут использоваться снаружи супергруппы, должны экспортироваться через индексный модуль. **Нельзя обращаться к ресурсам напрямую**, в обход индексного модуля. |
| 195 | +
|
| 196 | +> `basic-feature/components/layout`: |
| 197 | +> |
| 198 | +> ```js |
| 199 | +> // ✅ так можно |
| 200 | +> |
| 201 | +> import ExperimentalFeature from '../../../experimental-feature'; |
| 202 | +> |
| 203 | +> // ❌ так нельзя, обращение к внутренним ресурсам |
| 204 | +> |
| 205 | +> import Layout from '../../../experimental-feature/components/layout'; |
| 206 | +> ``` |
| 207 | +
|
| 208 | +Также, супергруппы имеют ограничение на **импорт из других супергрупп** — нельзя импортировать в дочерней супергруппе ресурсы из родительской супергруппы. |
| 209 | +
|
| 210 | +> `experimental-feature/components/layout`: |
| 211 | +> |
| 212 | +> ```js |
| 213 | +> // ✅ так можно |
| 214 | +> |
| 215 | +> import Content from '../../../experimental-feature.content'; |
| 216 | +> ``` |
| 217 | +
|
| 218 | +> `experimental-feature.content/components/content`: |
| 219 | +> |
| 220 | +> ```js |
| 221 | +> // ❌ так нельзя, обращение к ресурсам родительской супергруппы из дочерней супергруппы |
| 222 | +> |
| 223 | +> import ExperimentalFeature from '../../../experimental-feature'; |
| 224 | +> ``` |
| 225 | +
|
| 226 | +#### Супергруппы `shared` |
| 227 | +
|
| 228 | +Есть исключение — супергруппы `shared`. |
| 229 | +
|
| 230 | +Из такой супергруппы можно импортировать ресурсы напрямую, в обход индексного модуля, но только в её **родительской супергруппе**, а также во всех её **вложенных супергруппах**. |
| 231 | +
|
| 232 | +Из этого следует: |
| 233 | +
|
| 234 | +- нельзя импортировать ресурсы в супергруппах выше родителя |
| 235 | +- ресурсы супергруппы `shared` в корне можно импортировать везде |
| 236 | +
|
| 237 | +Супергруппы `shared` **не могут иметь вложенные супергруппы**. |
| 238 | +
|
| 239 | +> `basic-feature/components/layout`: |
| 240 | +> |
| 241 | +> ```js |
| 242 | +> // ✅ так можно |
| 243 | +> |
| 244 | +> import Input from '../../../basic-feature.shared/components/input'; |
| 245 | +> ``` |
| 246 | +
|
| 247 | +> `basic-feature.content/components/content`: |
| 248 | +> |
| 249 | +> ```js |
| 250 | +> // ✅ так можно |
| 251 | +> |
| 252 | +> import Input from '../../../basic-feature.shared/components/input'; |
| 253 | +> ``` |
| 254 | +
|
| 255 | +> `experimental-feature/components/layout`: |
| 256 | +> |
| 257 | +> ```js |
| 258 | +> // ❌ так нельзя, обращение к ресурсам чужой shared-супергруппы |
| 259 | +> |
| 260 | +> import Input from '../../../basic-feature.shared/components/input'; |
| 261 | +> ``` |
| 262 | +
|
| 263 | +### Группа |
| 264 | +
|
| 265 | +Директория, которая содержит в себе ресурсы. Создаёт новое пространство имён. |
| 266 | +
|
| 267 | +> ```sh |
| 268 | +> # супергруппа "experimental-feature" содержит обычные группы |
| 269 | +> |
| 270 | +> experimental-feature |
| 271 | +> |-- api |
| 272 | +> |-- assets |
| 273 | +> |-- components |
| 274 | +> |-- components.editor |
| 275 | +> |-- components.content.audio |
| 276 | +> |-- components.content.image |
| 277 | +> |-- components.content.video |
| 278 | +> |-- contexts |
| 279 | +> |-- helpers |
| 280 | +> |-- hooks |
| 281 | +> |-- values |
| 282 | +> |-- index.js |
| 283 | +> ``` |
| 284 | +
|
| 285 | +#### Расположение |
| 286 | +
|
| 287 | +Группы располагаются внутри супергрупп. |
| 288 | +
|
| 289 | +#### Вложенность |
| 290 | +
|
| 291 | +Группы могут быть вложенными. Вложенность выражается на уровне **имён директорий**, через перечисление цепочки родительских групп через точку. |
| 292 | +
|
| 293 | +> ```sh |
| 294 | +> experimental-feature |
| 295 | +> |-- components |
| 296 | +> |-- components.editor |
| 297 | +> |-- components.content.audio |
| 298 | +> |-- components.content.image |
| 299 | +> |-- components.content.video |
| 300 | +> ``` |
| 301 | +
|
| 302 | +#### Ограничения |
| 303 | +
|
| 304 | +Группы имеют ограничение на **импорт из других групп** — нельзя импортировать в дочерней группе ресурсы из родительской группы. |
| 305 | +
|
| 306 | +> `experimental-feature/components/content`: |
| 307 | +> |
| 308 | +> ```js |
| 309 | +> // ✅ так можно |
| 310 | +> |
| 311 | +> import Editor from '../../components.editor/editor'; |
| 312 | +> ``` |
| 313 | +
|
| 314 | +> `experimental-feature/components.editor/editor`: |
| 315 | +> |
| 316 | +> ```js |
| 317 | +> // ❌ так нельзя, обращение к ресурсам родительской группы из дочерней группы |
| 318 | +> |
| 319 | +> import Content from '../../components/content'; |
| 320 | +> ``` |
| 321 | +
|
| 322 | +#### Группы `shared` |
| 323 | +
|
| 324 | +Есть исключение — группы `shared`. |
| 325 | +
|
| 326 | +Из такой группы можно импортировать ресурсы как в **родительской группе**, так и во всех её **вложенных группах**. |
| 327 | +
|
| 328 | +Из этого следует: |
| 329 | +
|
| 330 | +- нельзя импортировать ресурсы в группах выше родителя |
| 331 | +
|
| 332 | +Группы `shared` **не могут иметь вложенные группы**. |
| 333 | +
|
| 334 | +> `experimental-feature/components/layout`: |
| 335 | +> |
| 336 | +> ```js |
| 337 | +> // ✅ так можно |
| 338 | +> |
| 339 | +> import Button from '../../components.shared/button'; |
| 340 | +> ``` |
| 341 | +
|
| 342 | +> `experimental-feature/components.editor/editor`: |
| 343 | +> |
| 344 | +> ```js |
| 345 | +> // ✅ так можно |
| 346 | +> |
| 347 | +> import Button from '../../components.shared/button'; |
| 348 | +> ``` |
| 349 | +
|
| 350 | +> `experimental-feature/helpers/render-actions.js`: |
| 351 | +> |
| 352 | +> ```js |
| 353 | +> // ❌ так нельзя, обращение к ресурсам чужой shared-группы |
| 354 | +> |
| 355 | +> import Button from '../../components.shared/button'; |
| 356 | +> ``` |
| 357 | +
|
| 358 | +## FAQ |
| 359 | +
|
| 360 | +#### Можно ли использовать Sliced FS с TypeScript? |
| 361 | +
|
| 362 | +Да! |
| 363 | +
|
| 364 | +#### Можно ли использовать Sliced FS с CommonJS? |
| 365 | +
|
| 366 | +Да! Только синтаксис модулей будет отличаться. |
| 367 | +
|
| 368 | +#### Можно ли использовать Sliced FS с другими языками? |
| 369 | +
|
| 370 | +Полагаю, что да! Только система модулей в вашем языке может значительно отличаться. Если у вас получится, пожалуйста, поделитесть опытом в issues. |
| 371 | +
|
| 372 | +## Обратная связь |
| 373 | +
|
| 374 | +Буду рад любой обратной связи! Пожалуйста, отправляйте её в issues. |
| 375 | +
|
| 376 | +## Поддержать автора |
| 377 | +
|
| 378 | +[тут будут ссылочки на сторонние ресурсы] |
0 commit comments