Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Подход к организации файловой структуры проектов

License

NotificationsYou must be signed in to change notification settings

micropackage-architecture/documentation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Життя переможе смерть, а світ – темряву.

Подход к организациифайловой структуры проектов.

Подходит для JS-проектов любой сложности. Предлагает простую систему терминов и правил, призванных снизить сложность работы над проектами.

Преимущества

  • 🚀 подходит для проектов любой сложности
  • 🍰 легко понять, простые термины и правила
  • 🧩 формирует информативную и предсказуемую файловую структуру проекта, чем облегчает построение его ментальной модели
  • 📦 группирует файлы одновременно по реализуемойфункциональности и потипу, чем облегчает анализ случаев использования отдельных файлов и поиск связанных файлов
  • 📌 обеспечивает фиксированную максимальную вложенность директорий во всём проекте — 3 уровня
  • 🚚 разгружает пространство имён файлов, облегчает именование

Содержание

  1. Мотивация
  2. Быстрый старт
  3. Методология
  4. FAQ
  5. Поддержка IDE
  6. Сообщество
  7. Обратная связь
  8. Благодарности
  9. Поддержать автора

Мотивация

Неудачно-организованная файловая структура проекта — это серьёзная проблема

В сфере разработки ПО принято вести работу над ПО в рамкахпроектов. Под проектом ПО подразумевается набор файлов, содержащих код и другие ресурсы, необходимые для реализации ПО.

По мере развития, проекты становятсясложнее, а вместе с этим становится сложнее работа над ними.

То, как будет расти сложность работы, во многом зависит от подхода к принятиюрешений, которого придерживается разработчик.

Если принимать решенияситуативно, сложность работы над проектом будет расти стремительно. И напротив, если разработать оптимальныйсистемный подход, рост сложности можно замедлить.

Одна из самых сложных задач, которую приходится выполнять разработчику во время работы, — построениементальной модели проекта.

Под ментальной моделью подразумевается представление о том, какую задачу решает ПО, какие особенности имеет и как устроен его проект — какой фрагмент кода за что отвечает и как связан с другими фрагментами.

Если говорить прокод, упростить построение ментальной модели призваны оптимальные архитектура и соглашения, соотносящиеся с лучшими практиками и индивидуальным опытом.

Но код не может существовать сам по себе. Для того, чтобы стать частью проекта, он должен быть помещён вфайл.

Таким образом, построение ментальной модели начинается с изученияфайловой структуры проекта.

В файловой структуреимя файла ипринадлежность к директории позволяют сделать выводы о содержимом файла и связи с другими файлами.

В случае, если файловая структура организовананеудачно (ситуативно, неоптимально), её изучение становится затруднительным. В свою очередь, это затрудняет построение ментальной модели. А поскольку это — одна из самых сложных задач при работе с проектами, этосерьёзная проблема.

Среди разработчиков проблема часто оказываетсянедооценённой, хотя стоит наряду с неудачными архитектурой и соглашениями или их отсутствием.

У текущих подходов есть существенные недостатки

Можно выделить два популярных подхода к организации файловой структуры:

Folders by type

Подход предусматривает группировку файлов потипу содержимого.

Для примера, файловая структура React-приложения может выглядеть так:

src├── api├── assets├── components├── constants├── contexts├── helpers└── hooks

Файлы с содержимым, относящимся к компонентам, размещаются вcomponents, к хелперам — вhelpers и т.д.

Подход достаточен в маленьких проектах, но в средних или больших группы могут содержать десятки или сотни файлов, из-за чего возникаютпроблемы:

components├── (15 директорий, начинающихся с префикса "app")├── (10 директорий, начинающихся с префикса "emoji")├── (20 директорий, начинающихся с префикса "home")├── (10 директорий, начинающихся с префикса "layout")├── (30 директорий, начинающихся с префикса "post")├── (20 директорий, начинающихся с префикса "post_editor")├── (15 директорий, начинающихся с префикса "trends")└── ...
  • Проблемы с пространством имён

    Чем больше файлов содержится в группе, тем выше становится нагрузка на пространство имён. Чтобы избежать коллизий, к именам новых файлов требуется добавлять префиксы, что усложняет имена и сам процесс именования.

  • Проблемы анализа

    Чем больше файлов содержится в группе, тем сложнее её анализировать, поскольку длинный несгруппированный список файлов несёт мало информации.

    Рассматривая отдельный файл, невозможно быстро получить представление о том, в реализации какой функциональности он участвует и с какими другими файлами может быть связан. Чтобы ответить на эти вопросы, потребуется провести мини-исследование, — и так с каждым файлом.

Folders by feature

Подход предусматривает группировку файлов пофункциональности, которую реализует их содержимое.

Для примера, файловая структура React-приложения может выглядеть так:

src├── app├── home├── layout├── posts└── trends

Файлы с содержимым, реализующим домашнюю страницу, размещаются вhome, посты — вposts и т.д.

Подход решает основную проблемуfolders by type — вводит группировку по реализуемойфункциональности. Группировка значительно упрощает анализ того, в реализации какой функциональности участвует файл и с какими другими файлами может быть связан.

Вместе с этим, подход лишён основного преимуществаfolders by type — группировки файлов потипу содержимого.

В больших проектах группы могут содержать десятки или сотни файлов, и нехватка дополнительной группировки становитсяпроблемой:

src├── post│   ├── actions_like.jsx│   ├── actions_like.styles.js│   ├── actions_reply.jsx│   ├── actions_reply.styles.js│   ├── actions_repost.jsx│   ├── actions_repost.styles.js│   ├── actions_share.jsx│   ├── actions_share.styles.js│   ├── content_image.jsx│   ├── content_image.styles.jsx│   ├── content_text.jsx│   ├── content_text.styles.js│   ├── content_video.jsx│   ├── content_video.styles.js│   ├── content.jsx│   ├── content.styles.js│   ├── image_viewer_backdrop.jsx│   ├── image_viewer_backdrop.styles.js│   ├── image_viewer_navigation.jsx│   ├── image_viewer_navigation.styles.js│   ├── image_viewer.jsx│   ├── image_viewer.styles.js│   ├── post.jsx│   ├── post.styles.js│   ├── views.jsx│   └── views.styles.js│├── post_editor│   ├── audience.jsx│   ├── audience.styles.js│   ├── editor.jsx│   ├── editor.styles.js│   ├── emoji.jsx│   ├── emoji.styles.js│   ├── media.jsx│   ├── media.styles.js│   ├── poll.jsx│   ├── poll.styles.js│   ├── post_limits.constants.js│   ├── settings.jsx│   ├── settings.styles.js│   ├── text_area.jsx│   └── text_area.styles.js│└── ...
  • Проблемы анализа

    Чем больше файлов содержится в группе, тем сложнее её анализировать, поскольку длинный несгруппированный список файлов несёт мало информации.

    Рассматривая группу, невозможно быстро получить представление о том, из чего она состоит: есть ли файлы определённого типа, сколько их и т.д. Чтобы ответить на эти вопросы, потребуется провести мини-исследование, — и так с каждой группой.

Вложенность решает проблемы, но создаёт новые

Использование вложенности решает некоторые проблемы обоих подходов, но значительно затрудняет анализ проекта, поскольку часть файлов оказываетсяскрытой — до тех пор, пока не раскрыть все вложенные директории.

Изучим фичуpost

src└── post

Выглядит просто!

src└── post    ├── actions    ├── content    ├── image_viewer    ├── post.jsx    ├── post.styles.js    ├── views.jsx    └── views.styles.js

Заглянем во вложенные директории

src└── post    ├── actions    │   ├── like.jsx    │   ├── like.styles.js    │   ├── reply.jsx    │   ├── reply.styles.js    │   ├── repost.jsx    │   ├── repost.styles.js    │   ├── share.jsx    │   └── share.styles.js    ├── content    │   ├── content.jsx    │   ├── content.styles.js    │   ├── image.jsx    │   ├── image.styles.jsx    │   ├── text.jsx    │   ├── text.styles.js    │   ├── video.jsx    │   └── video.styles.js    ├── image_viewer    │   ├── backdrop.jsx    │   ├── backdrop.styles.js    │   ├── image_viewer.jsx    │   ├── image_viewer.styles.js    │   ├── navigation.jsx    │   └── navigation.styles.js    ├── post.jsx    ├── post.styles.js    ├── views.jsx    └── views.styles.js

Упс! Это займёт какое-то время 😳

⬆️К содержанию

Быстрый старт

Краткий обзор

Файловая структура проекта, разработанная с применением подходаvirtual.nesting, выглядит так:

  • В директории с исходным кодом (по умолчаниюsrc) размещаютсякатегории. Они группируют файлы пофункциональности, которую реализует их содержимое.

    Файлы, сгруппированные категорией, образуютфункциональную единицу проекта.

    В примере ниже
    app,home,layout и т.д. — категории.

    src├── app│   └── ...├── home│   └── ...├── layout│   └── ...├── post│   └── ...├── post.image_viewer│   └── ...├── post@shared│   └── ...├── post_editor│   └── ...├── trends│   └── ...└── ui_kit    └── ...

    В файловой структуре проекта категории образуют одноуровневый список, что достигается за счёт применениявиртуальной вложенности — выражения вложенности директорий на уровне их имён.

    Это позволяет иметь быстрый доступ к полному списку функциональных единиц проекта, без необходимости раскрытия неопределённого количества вложенных директорий.

    Примечание

    Категорияpost.image_viewer — пример применениявиртуальной вложенности.

  • Вкатегориях размещаютсягруппы, они группируют файлы потипу содержимого.

    В примере ниже
    components иhelpers — группы.

    src└── layout    ├── components    │   └── ...    ├── helpers    │   └── ...    └── index.js
  • Вгруппах размещаютсяэлементы. Элемент может бытьфайлом илидиректорией с файлами.

    В примере ниже
    scroll.js — элемент.

    src└── layout    └── helpers        └── scroll.js

    В примере ниже
    navigation — элемент.

    src└── layout    └── components        └── navigation            ├── component.jsx            ├── index.js            └── styles.js

Больше примеров можно найтиздесь.

⬆️К содержанию

Применяем шаг за шагом

Пошаговое руководство 📘

Давайте представим, что мы веб-разработчик и некая компания предложила нам работу над проектом новой социальной сети.

Проект на начальном этапе разработки и нам нужно выступить в роли ведущего разработчика фронтенда.

У нас есть требования, которые подготовил аналитик, и план работ, который составил менеджер.

Содержание

  1. Начинаем работу над домашней страницей
  2. Инициализируем проект
  3. Реализуем компонент, отвечающий за раскладку элементов страницы
  4. Реализуем UI-кит
  5. Начинаем работу над постом
  6. Реализуем компонент, отвечающий за визуализацию поста
  7. Реализуем компонент, отвечающий за просмотр изображений
  8. Организуем доступ к общим ресурсам в разных категориях
  9. Реорганизуем категорию
  10. Реализуем редактор постов
  11. Завершаем работу над домашней страницей
  12. Интегрируем домашнюю страницу в приложение

Начинаем работу над домашней страницей

Погружаемся в контекст. Первые примеры уже в следующей главе!

Наша первая (и единственная в рамках этого руководства) задача — реализовать домашнюю страницу.

Задача — комплексная и трудозатратная. Чтобы сократить возможные риски, необходимо еёдекомпозировать.

Сперва отметим, что перед тем, как к ней приступить, нам необходимоинициализировать проект.

Затем проанализируем требования и макеты:

  1. Лейаут страницы совпадает с лейаутом других страниц. Под лейаутом подразумевается раскладка элементов, а именно: хедера, навигации, контента, футера и т.д.

    Исходя из этого, необходимо реализовать переиспользуемый компонент, отвечающий зараскладку элементов страницы. Его можно будет использовать для реализации текущей страницы и следующих.

  2. На странице используются элементы, входящие в UI-кит. Необходимо спроектировать и подготовить минимальную реализациюUI-кита, которой будет достаточно для реализации страницы.

  3. На странице используются элементы, которые также будут использоваться на других страницах:посты иредактор постов. Необходимо реализовать соответствующие переиспользуемые компоненты.

Учитывая вышеперечисленные замечания, составим следующий список задач:

  1. инициализировать проект;

  2. реализовать переиспользуемый компонент, отвечающий зараскладку элементов странице;

  3. спроектировать и подготовить минимальную реализациюUI-кита;

  4. реализовать переиспользуемые компоненты для визуализациипостов иредактора постов;

  5. реализовать страницу с использованием компонентов, разработанных ранее.

Приступим! 👩‍🔧

Инициализируем проект

Здесь мы создаёмкатегорию,группу иэлемент, знакомимся с их свойствами и ограничениями.

Первая задача в нашем списке — инициализировать проект. Задача включает в себя создание скелета и настройку сборки приложения.


Начнём с созданияскелета. Он будет состоять из двух элементов:

  • root – в нём будет содержаться корневой компонент приложения,
  • init.js — в нём будет происходить инициализация приложения.

Сперва реализуем элементroot. Для этого:

  1. создадим категориюapp,

    Категория — директория, применяемая для группировки элементов по реализуемойфункциональности.

    — методология, разделкатегории

  2. в ней создадим группуcomponents,

    Группа — директория, применяемая для группировки элементов потипу.

    — методология, разделгруппы

  3. в ней создадим элементroot.

    Элемент — файл или директория с файлами.

    — методология, разделэлементы

Обусловимся, что элементы группыcomponents будут директориями, поскольку чаще всего элементы этого типа являются комплексными и требуют декомпозиции на несколько модулей.

В директории элементаroot создадим два модуля:

  • component.jsx — в нём разместим код компонента,

  • index.js — в нём реэкспортируем компонент из модуляcomponent.jsx

    Почему?

    Если элемент является директорией, внешний доступ к его внутренним ресурсам разрешёнтолько через индексный модуль (index.js).

    Это ограничение обеспечивает сокрытие внутренних ресурсов элемента и формирует явные точки входа.

    — методология, разделограничения элементов

В результате, получим следующую структуру:

src└── app    └── components        └── root            ├── component.jsx            └── index.js

Элементroot готов!

Вслед за ним займёмся реализацией элементаinit.js. Он тоже относится к категорииapp, но к группеinit.

src└── app    ├── components    │   └── root    │       ├── component.jsx    │       └── index.js    └── init        └── init.js

Элементinit.js готов.

А с ним и скелет приложения!


Перейдём ксборке приложения.

Для этого необходимо указать путь к модулю с кодом инициализацииinit.js в конфигурации сборщика. Нужный модуль расположен по путиsrc/app/init/init.js.

Cоздадим в корне категорииapp модульinit.js, в котором импортируем модульinit/init.js.

Почему?

Внешний доступ к внутренним ресурсам категорий разрешёнтолько через модули, размещённые в корне категории.

Это ограничение обеспечивает сокрытие внутренних ресурсов категории и формирует явные точки входа.

— методология, разделограничения категорий

src└── app    ├── components    │   └── root    │       ├── component.jsx    │       └── index.js    ├── init    │   └── init.js    └── init.js

Затем укажем путь к модулюsrc/app/init.js в конфигурации сборщика.

Дело сделано! 🎉

Реализуем компонент, отвечающий за раскладку элементов страницы

Здесь мы создаёмкатегорию и точку входа в неё.

Наша следующая задача — реализовать переиспользуемый компонент, отвечающий за раскладку элементов страницы.

Реализация компонента включает в себя реализацию нескольких дочерних компонентов и хелперов.

Создадим новую категорию —layout. Разместим в ней соответствующие группы и элементы.

В результате, получим следующую структуру:

src└── layout    └── components        ├── about        │   ├── component.jsx        │   ├── index.js        │   └── styles.js        ├── layout        │   ├── component.jsx        │   ├── index.js        │   └── styles.js        ├── logo        │   ├── component.jsx        │   ├── index.js        │   └── styles.js        ├── navigation        │   ├── component.jsx        │   ├── index.js        │   └── styles.js        └── profile            ├── component.jsx            ├── index.js            └── styles.js

Перейдём к созданию точки входа в категорию.

Создадим в корне категории индексный модуль (index.js) и реэкспортируем в нём компонентLayout из./components/layout.

Почему?

Имя категории (layout) и её экспортируемого элемента (layout) совпадают. В этом случае предпочтительнее реэкспортировать элемент в индексном модуле (index.js).

Это позволит избежать дублирования имени при обращении к ресурсу.

// 🟡 имя `layout` дублируетсяimportLayoutfrom'../../../layout/layout';// 🟢 okimportLayoutfrom'../../../layout';
src└── layout    ├── components    │   ├── about    │   │   ├── component.jsx    │   │   ├── index.js    │   │   └── styles.js    │   ├── layout    │   │   ├── component.jsx    │   │   ├── index.js    │   │   └── styles.js    │   ├── logo    │   │   ├── component.jsx    │   │   ├── index.js    │   │   └── styles.js    │   ├── navigation    │   │   ├── component.jsx    │   │   ├── index.js    │   │   └── styles.js    │   └── profile    │       ├── component.jsx    │       ├── index.js    │       └── styles.js    └── index.js

Теперь к компонентуLayout можно обратиться в других категориях:

importLayoutfrom'../../../layout';

Дело сделано! 🎉

Реализуем UI-кит

Здесь мы создаёмкатегорию инесколько точек входа в неё.

Наша следующая задача — спроектировать и подготовить минимальную реализацию UI-кита.

Создадим новую категорию —ui_kit. Разместим в ней соответствующие группы и элементы.

В результате, получим следующую структуру:

src└── ui_kit    └── components        ├── button        │   ├── component.jsx        │   ├── index.js        │   └── styles.js        ├── link        │   ├── component.jsx        │   ├── index.js        │   └── styles.js        ├── popover        │   ├── component.jsx        │   ├── index.js        │   └── styles.js        └── profile_pic            ├── component.jsx            ├── index.js            └── styles.js

Перейдём к созданию точек входа в категорию.

Создадим в корне категории модулиbutton.js,link.js,popover.js,profile_pic.js и реэкспортируем в них соответствующие компоненты.

src└── ui_kit    ├── components    │   ├── button    │   │   ├── component.jsx    │   │   ├── index.js    │   │   └── styles.js    │   ├── link    │   │   ├── component.jsx    │   │   ├── index.js    │   │   └── styles.js    │   ├── popover    │   │   ├── component.jsx    │   │   ├── index.js    │   │   └── styles.js    │   └── profile_pic    │       ├── component.jsx    │       ├── index.js    │       └── styles.js    ├── button.js    ├── link.js    ├── popover.js    └── profile_pic.js

Теперь к компонентам можно обратиться в других категориях:

importProfilePicfrom'../../../ui_kit/profile_pic';

Дело сделано! 🎉

Начинаем работу над постом

Наша следующая задача — реализовать переиспользуемый компонент, отвечающий за визуализацию поста.

Исходя из требований и макетов следует, что:

  1. пост может содержатьтекст, а также прикреплённыекартинки иливидео — и для их просмотра требуются соответствующие просмотрщики;

  2. у поста естьсчётчик просмотров, также посту можно поставитьлайк, сделатьрепост, написатькомментарий;

  3. у поста есть меню действий, в котором можноподписаться на автора илизаблокировать его, а так жепожаловаться на контент.

Как мы видим, полноценная реализация поста — задача комплексная и трудозатратная.

Поэтому будет разумно разбить его реализацию на итерации, сейчас же сосредоточиться только на следующих задачах:

  1. реализовать основную визуализацию поста,
  2. реализовать просмотрщик изображений.

Реализуем компонент, отвечающий за визуализацию поста

Начнём с компонента, отвечающего за визуализацию поста.

Создадим новую категорию —post. Разместим в ней соответствующие группы и элементы, а также создадим точку входа.

Примечание

Здесь и далее примеры файловой структуры будут упрощены

src└── post    ├── components    │   ├── actions    │   ├── block    │   ├── content    │   ├── follow    │   ├── image    │   ├── like    │   ├── menu    │   ├── post    │   ├── reply    │   ├── report    │   ├── repost    │   ├── share    │   ├── text    │   └── views    ├── contexts    │   └── post.js    └── index.js

Реализуем компонент, отвечающий за просмотр изображений

Здесь мы создаёмподкатегорию, знакомимся с её свойствами и ограничениями.

Перейдём к компоненту, отвечающему за просмотр прикреплённых к посту изображений.

Перед нами возникает вопрос — где разместить его код? Мы можем предположить, что:

  1. реализация просмотрщика потребует созданиябольшого объёма файлов;
  2. просмотрщик не является самостоятельной функциональностью и всегда будет использоваться в паре с постом, будет полезно указатьсвязь между ними;
  3. код просмотрщика не требует тесной связи с кодом поста, следовательно, код необходиморазграничить.

Исходя из этого, рассмотрим доступные варианты:

  1. Разместить код просмотрщика в той же категории —post.

    Однако, поскольку его реализация потребует создания большого объёма файлов, смешение файлов поста и просмотрщика:

    • затруднит анализ категории,
    • создаст дополнительнуюнагрузку на пространство имён.
  2. Разместить код просмотрщика в новой категории —image_viewer.

    Это облегчит анализ категории и создаст новое пространство имён.

    Однако, в таком случае не хватает указаниясвязи между постом и просмотрщиком.

  3. Разместить код просмотрщика вподкатегорииpost.image_viewer.

    Это облегчит анализ категории и создаст новое пространство имён, а также укажет на связь между категориямиpost иimage_viewer.

    Отличный вариант! 💯

Создадим подкатегорию —post.image_viewer.

Подкатегория — подвид категории, применяемый для декомпозиции комплексных категорий. Связана с другими категориями отношениями родитель-потомок.

Связь с родителями выражается черезвиртуальную вложенность — на уровне имени директории, перечислением списка родителей через точку.

— методология, разделподкатегории

Разместим в ней необходимые группы и элементы, а также создадим точку входа.

src├── post│   ├── components│   │   ├── actions│   │   ├── block│   │   ├── content│   │   ├── follow│   │   ├── image│   │   ├── like│   │   ├── menu│   │   ├── post│   │   ├── reply│   │   ├── report│   │   ├── repost│   │   ├── share│   │   ├── text│   │   └── views│   ├── contexts│   │   └── post.js│   └── index.js│└── post.image_viewer    ├── components    │   ├── actions    │   ├── navigation    │   └── image_viewer    └── index.js

В процессе реализации мы обращаем внимание на то, что в просмотрщике необходимо использовать ресурсы, уже реализованные в категорииpost:

  • components/like,
  • components/repost,
  • components/share,
  • contexts/post.js.

Но просто взять и обратиться к нужным компонентам из категорииpost мы не можем.

Почему?

В подкатегориях запрещён доступ к ресурсам своих родителей.

Это ограничение структурируетсвязи между родителями и потомками и позволяет избежатьциклических зависимостей.

— методология, разделограничения категорий

Что же делать? 😳

Организуем доступ к общим ресурсам в разных категориях

Здесь мы создаёммета-категорию, знакомимся с её свойствами и ограничениями.

В таком случае, необходимо разместить файлы вмета-категории@shared.

Мета-категория — подвид категории. Отличается от обычной категории тем, что:

  • доступ к ресурсам мета-категории разрешён вбазовой категории (без мета-тега) и всех еёподкатегориях — и только в них,

  • обратный доступ, в мета-категории к ресурсам базовой категории и её подкатегорий, — запрещён,

  • мета-категория можетпереопределять ограничения обычных категорий.

Имя мета-категории имеет структуру:<базовая категория?>@<мета-тег>.

Мета-категория@shared применяется для размещения элементов, к которымнеобходимо иметь доступ одновременно в категории и всех её подкатегориях.

В ней не действует требование о создании точек входа для доступа к внутренним ресурсам (можно обращатьсянапрямую).

— методология, разделмета-категории

Создадим мета-категорию —post@shared и перенесём в неё необходимые элементы из категорииpost:

src├── post│   ├── components│   │   ├── actions│   │   ├── block│   │   ├── content│   │   ├── follow│   │   ├── image│   │   ├── menu│   │   ├── post│   │   ├── report│   │   ├── text│   │   └── views│   └── index.js│├── post.image_viewer│   ├── components│   │   ├── actions│   │   ├── image_viewer│   │   └── navigation│   └── index.js│└── post@shared    ├── components    │   ├── like    │   ├── reply    │   ├── repost    │   └── share    └── contexts        └── post.js

Теперь к нужным компонентам можно обратиться как в категорииpost, так и вpost.image_viewer:

importLikefrom'../../../post@shared/components/like'

Примечание

Обратите внимание, в мета-категории@shared не действует требование о создании точек входа для доступа к внутренним ресурсам. К ним можно обращатьсянапрямую!

Это позволяет завершить реализацию просмотрщика.

Дело сделано! 🎉

Реорганизуем категорию

Здесь мы создаёмименованную группу, знакомимся с её свойствами и ограничениями.

Вернёмся к категорииpost.

src└── post    ├── components    │   ├── actions    │   ├── block    │   ├── content    │   ├── follow    │   ├── image    │   ├── menu    │   ├── post    │   ├── report    │   ├── text    │   └── views    └── index.js

Давайте проанализируем группуcomponents.

В ней размещено довольно много файлов. Если провести мини-исследование, среди них можно выделить две подгруппы:

  • файлы, связанные с реализацией поста,
  • файлы, связанные с реализацией меню действий.

Отдельное мини-исследование — относительно лёгкая задача. Однако, чем больше их требуется, темсложнее становится анализ проекта. В какой-то момент в проекте невозможно будет разобраться без блокнота и ручки.

Таким образом, лучше всего свести необходимость мини-исследований в проекте к минимуму.

В случае с файлами, которые мы рассматривали выше, будет полезнозакрепить результаты мини-исследования и применить к файлам дополнительную группировку.

Перед нами возникает вопрос — как будет лучше это сделать? Рассмотрим доступные варианты:

  1. Разместить файлы в новых подкатегориях.

    Однако, подкатегории уместнее применять для группировки набора файлов с содержимым разного типа. В нашем же случае, файлы одного типа —components.

    Также это потребует соблюдения всех накладываемых на категории ограничений.

  2. Разместить файлы вименованных группах.

    Это позволит применить дополнительную группировку без создания новых категорий.

    Отличный вариант! 💯

Создадим именованные группыcomponents#content иcomponents#menu.

Именованная группа — подвид группы, применяемый для дополнительной группировки элементов.

Имя именованной группы имеет структуру:<базовая группа>#<имя>.

Перенесём в них соответствующие элементы.

src└── post    ├── components    │   ├── actions    │   └── post    ├── components#content    │   ├── content    │   ├── image    │   ├── text    │   └── views    ├── components#menu    │   ├── block    │   ├── follow    │   ├── menu    │   └── report    └── index.js

Таким образом, анализ категорииpost и группыcomponents больше не требует проведения мини-исследования.

Стоит отметить, что в именованных группах действуют некоторые ограничения:

В именованных группах запрещён доступ к ресурсам своей базовой группы.

Это ограничение структурируетсвязи между группами и позволяет избежатьциклических зависимостей.

Если к ресурсунеобходимо иметь доступ одновременно в базовой группе и всех именованных группах на её основе, необходимо разместить его в мета-группе@shared.

— методология, разделограничения групп

Дело сделано! 🎉

Реализуем редактор постов

Наша следующая задача — реализовать редактор постов.

Для этого создадим новую категорию —post_editor. Разместим в ней соответствующие группы и элементы, а также создадим точку входа.

В редакторе постов есть пикер emoji, который будет использоваться и в других интерфейсах. Выделим его в отдельную категорию —emoji.

src├── emoji│   ├── components│   │   └── picker│   ├── constants│   │   └── emoji_list.js│   └── picker.js|└── post_editor    ├── components    │   ├── audience    │   ├── editor    │   ├── emoji    │   ├── media    │   ├── poll    │   ├── settings    │   └── text_area    ├── contexts    │   └── post_editor.js    └── index.js

Дело сделано! 🎉

Завершаем работу над домашней страницей

Наша следующая задача — реализовать домашнюю страницу.

Мы проделали большую работу и подготовили её основные компоненты:Layout,Post иPostEditor. Перейдём к реализации самой страницы.

Для этого:

  1. используя внешние компонентыPost иPostEditor, подготовленные ранее, реализуем комплексные компонентыPostFeed иPostEditor, отвечающие за полноценную реализацию и интеграциюленты иредактора постов;

  2. реализуем компонентHome, отвечающий за визуализацию страницы.

Создадим новую категорию —home. Разместим в ней соответствующие группы и элементы, а также создадим точку входа.

src└── home    ├── components    │   ├── home    │   ├── post_editor    │   └── post_feed    └── index.js

Приближаемся к финишу! 🏁

Интегрируем домашнюю страницу в приложение

Наша последняя задача — интегрировать страницу в приложение.

Для этого понадобится обновить код компонентаRoot из категорииapp: обратиться в нём к компонентуHome из категорииhome и вызвать его.

importHomefrom'../../../home'constRoot=()=>{return(<Home/>)}exportdefaultRoot

С этого момента, в нашем приложении доступна домашняя страница.

Задача выполнена! 🎉

Гордимся собой 🤗

Поздравляю, вы изучили подходvirtual.nesting на 95% и можете начать применять его в своих проектах!

Также вы можете:

Примеры проектов

Примеры проектов, в которыхvirtual.nesting используется для организации файловой структуры, можно найти наздесь.

⬆️К содержанию

Методология

Категории

Категория — директория, применяемая для группировкиэлементов по реализуемойфункциональности.

Файлы, сгруппированные категорией, образуютфункциональную единицу проекта.

Создаёт новое пространство имён.

В примере ниже
layout — категория

src└── layout    ├── components    │   └── ...    ├── helpers    │   └── ...    └── index.js

Размещение категорий

Категории размещаются в директории с исходным кодом (по умолчаниюsrc), и только в ней.

Подкатегории

Подкатегория — подвид категории, применяемый для декомпозиции комплексных категорий. Связана с другими категориями отношениями родитель-потомок.

Связь с родителями выражается черезвиртуальную вложенность — на уровне имени директории, перечислением списка родителей через точку.

В подкатегориях действует дополнительноеограничение на доступ к ресурсам.

В примере ниже
post.image_viewer — подкатегория

src├── post│   ├── components│   │   └── ...│   ├── components#content│   │   └── ...│   └── index.js│└── post.image_viewer    ├── components    │   └── ...    └── index.js

Ограничения категорий

  • Внешний доступ к внутренним ресурсам категорий разрешёнтолько через модули, размещённые в корне категории.

    Почему?

    Это ограничение обеспечивает сокрытие внутренних ресурсов категории и формирует явные точки входа.

    Пример

    src/**post_editor**/components/emoji/component.jsx:

    // ✅ okimport{Picker}from'../../../emoji';// ✅ okimportPickerfrom'../../../emoji/picker';// ❌ ошибка, прямой доступ к внутренним ресурсам категории запрещёнimportPickerfrom'../../../emoji/components/picker';
  • В подкатегориях запрещён доступ к ресурсам своих родителей.

    Почему?

    Это ограничение структурируетсвязи между родителями и потомками и позволяет избежатьциклических зависимостей.

    Примечание

    Если к ресурсунеобходимо иметь доступ одновременно в категории и всех её подкатегориях, необходимо разместить его вмета-категории @shared.

    Пример

    src/**post**/components/post/component.jsx:

    // ✅ okimportImageViewerfrom'../../../post.image_viewer';

    src/**post.image_viewer**/components/viewer/component.jsx:

    // ❌ ошибка, доступ к ресурсам родительской категории запрещёнimportLikefrom'../../../post/like';// ✅ okimportLikefrom'../../../post@shared/components#actions/like';

Мета-категории

Мета-категория — подвид категории. Отличается от обычной категории тем, что:

  • доступ к ресурсам мета-категории разрешён вбазовой категории (без мета-тега) и всех еёподкатегориях — и только в них,

  • обратный доступ, в мета-категории к ресурсам базовой категории и её подкатегорий, — запрещён,

  • мета-категория можетпереопределять ограничения обычных категорий.

Имя мета-категории имеет структуру:<базовая категория?>@<мета-тег>.

Примечание

Если базовая категория не указана, доступ к ресурсам мета-категории разрешёнво всём проекте.

Доступные мета-категории:

  • @shared

    Применяется для размещения элементов, к которымнеобходимо иметь доступ одновременно в категории и всех её подкатегориях.

    Переопределяет ограничения:

    • не действует требование о создании точек входа для доступа к внутренним ресурсам (можно обращатьсянапрямую).

    Примечание

    Доступ к ресурсам мета-категории@shared (без указания базовой категории) разрешёнво всём проекте.

    Пример

    src/**post**/components/post/component.jsx:

    // ✅ okimportSharefrom'../../../post@shared/components/share';

    src/**post@shared**/components/share/component.jsx:

    // ❌ ошибка, доступ к ресурсам базовой категории запрещёнimportshareOptionsfrom'../../../post/constants/share_options';

    src/**home**/components/post_feed/component.jsx:

    // ❌ ошибка, доступ к ресурсам мета-категории `post@shared` разрешён только в её базовой категории и подкатегорияхimportSharefrom'../../../post@shared/components/share';

⬆️К содержанию

Группы

Группа — директория, применяемая для группировкиэлементов потипу.

Создаёт новое пространство имён.

В примере ниже
components — группа

src└── layout    └── components        ├── about        │   └── ...        ├── layout        │   └── ...        ├── logo        │   └── ...        ├── navigation        │   └── ...        └── profile            └── ...

Размещение групп

Группы размещаются внутрикатегорий, и только в них.

Именованные группы

Именованная группа — подвид группы, применяемый для дополнительной группировки элементов.

Имя именованной группы имеет структуру:<базовая группа>#<имя>.

В именованных группах действует дополнительноеограничение на доступ к ресурсам.

В примере ниже
components#content — именованная группа

src└── post    ├── components    │   └── ...    └── components#content        └── ...

Ограничения групп

  • В именованных группах запрещён доступ к ресурсам своей базовой группы.

    Почему?

    Это ограничение структурируетсвязи между группами и позволяет избежатьциклических зависимостей.

    Примечание

    Если к ресурсунеобходимо иметь доступ одновременно в базовой группе и всех именованных группах на её основе, необходимо разместить его вмета-группе @shared.

    Пример

    src/post/**components**/post/component.jsx:

    // ✅ okimportContentfrom'../components#content/content';

    src/post/**components#content**/text/component.jsx:

    // ❌ ошибка, доступ к ресурсам базовой группы запрещёнimportProfilePopoverfrom'../components/profile_popover';// ✅ okimportProfilePopoverfrom'../components@shared/profile_popover';

Мета-группы

Мета-группа — подвид группы. Отличается от обычной группы тем, что:

  • доступ к ресурсам мета-группы разрешён вбазовой группе (без мета-тега) и всехименованных группах на её основе — и только в них,

  • обратный доступ, в мета-группе к ресурсам базовой группы и именованных групп на её основе, — запрещён,

  • мета-группа можетпереопределять ограничения обычных групп.

Имя мета-группы имеет структуру:<базовая группа>@<мета-тег>.

Доступные мета-группы:

  • @shared

    Применяется для размещения ресурсов, к которымнеобходимо иметь доступ одновременно в группе и всех именованных группах на её основе.

    Пример

    src/post/**helpers**/playback.js:

    // ✅ okimporteasingsfrom'../../helpers@shared/easings';

    src/post/**helpers@shared**/easings.js:

    // ❌ ошибка, доступ к ресурсам базовой категории запрещёнimportmathfrom'../../helpers/math';

    src/post/**components**/audio/component.jsx:

    // ❌ ошибка, доступ к ресурсам мета-группы `helpers@shared` разрешён только в её базовой группе и именованных группах на её основеimporteasingsfrom'../../helpers@shared/easings';

⬆️К содержанию

Элементы

Элемент — файл или директория с файлами.

В примере ниже
scroll.js — элемент

src└── layout    └── helpers        └── scroll.js

В примере ниже
navigation — элемент

src└── layout    └── components        └── navigation            ├── component.jsx            ├── index.js            └── styles.js

Размещение элементов

Элементы размещаются внутригрупп, и только в них.

Ограничения элементов

  • Если элемент является директорией, внешний доступ к его внутренним ресурсам разрешёнтолько через индексный модуль (index.js).

    Почему?

    Это ограничение обеспечивает сокрытие внутренних ресурсов элемента и формирует явные точки входа.

    Пример

    src/post_editor/components/**editor**/component.jsx:

    // ✅ okimportMediafrom'../media';// ❌ ошибка, прямой доступ к внутренним ресурсам элемента запрещёнimportmediaStylesfrom'../media/styles';

Варианты использования виртуальной вложенности в элементах

Если элемент является директорией, в нём можно использовать виртуальную вложенность, чтобы выразить связь файлов или организовать виртуальную директорию.

В примере ниже
файлstyles.actions.js связан с файломstyles.js

src└── post_editor    └── components        └── editor            ├── component.jsx            ├── index.js            ├── styles.js            └── styles.actions.js

В примере ниже
организована виртуальная директорияassets

src└── post_editor    └── components        └── editor            ├── assets.emoji_icon.svg            ├── assets.media_icon.svg            ├── assets.poll_icon.svg            ├── component.jsx            ├── index.js            └── styles.js

⬆️К содержанию

Ресурсы

Ресурс — любое значение, которое участвует в экспорте/импорте.

В примере ниже
Page,circleArea иmeaningOfLife — ресурсы

// .../components/page/component.jsxexportconstPage=()=>{return<h1>Hello, world!</h1>;};// .../helpers/circle_area.jsexportconstcircleArea=(r)=>{returnMath.PI*r**2;};// .../questions/meaning_of_life.jsexportconstmeaningOfLife=()=>{returnwait('7.5m years').then(()=>42);};

⬆️К содержанию

Рекомендации

  • Используйтеsnake_case для именования файлов и директорий.
  • Используйте относительные пути для обращения к файлам в пределах проекта, не используйте сокращения путей.
  • Избегайте циклических зависимостей.

⬆️К содержанию

Совместимость с архитектурными шаблонами

Подходы к организации файловой структуры и архитектурные шаблоны имеют общую цель — упростить построение метальной модели проекта.

Еслиподход к организации файловой структуры и архитектурныйшаблон совместимы, их совместное применение может быть более эффективным в достижении этой цели.

Из этого следует, что вы можете применить в проектеvirtual.nesting и получить дополнительные преимущества, если выбранный вами архитектурный шаблон не имеет требований к организации файловой структуры проекта или эти требования совместимы с принципамиvirtual.nesting.

Если требования архитектуры не позволяют вам применить в проектеvirtual.nesting в полной мере, вы также можете рассмотреть возможности применить отдельные идеи подхода.

FAQ

Можно ли использоватьv.n с TypeScript?

Да!

Можно ли использоватьv.n с CommonJS и другими системами модулей?

Да!

Можно ли использоватьv.n с другими языками?

Полагаю, что да! Если у вас получится интерпретировать подход в проекте на другом языке, пожалуйста, поделитесь опытом.

Поддержка IDE

Visual Studio Code

Проблема с сортировкой файлов

По умолчанию для сортировки списка файлов используется алгоритм, приводящий в некоторых случаях к перемешиванию групп директорий:

В примере ниже категорииpost_editor перемешались с категориямиpost.

src├── post├── post_editor├── post_editor.preview├── post.image-viewer├── post.video-viewer└── post@shared

Это поведение можно изменить, указав другой алгоритм сортировки в настройках:

settings.json

{"explorer.sortOrderLexicographicOptions":"unicode"}

После этого список файлов будет сортироваться корректно.

src├── post├── post.image-viewer├── post.video-viewer├── post@shared├── post_editor└── post_editor.preview

JetBrains

Нет известных проблем.

Сообщество

@virtual_nesting_community — сообщество в Telegram, где можно пообщаться и получить помощь.

Обратная связь

Буду рад любой обратной связи! Пожалуйста, пишите в issues или discussions.

Благодарности

Хочу поблагодарить мою жену@daryabratova, а также моих друзей@kindaro,@ruzovska и@a1ex-kaufmann.

Поддержать автора

Я потратил сотни часов, работая над этим проектом. Надеюсь, он сэкономит тысячи часов времени другим разработчикам.

Вы можете поддержать меня наPatreon илиBoosty.

About

Подход к организации файловой структуры проектов

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project


    [8]ページ先頭

    ©2009-2025 Movatter.jp