Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork1
Подход к организации файловой структуры проектов
License
micropackage-architecture/documentation
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
Життя переможе смерть, а світ – темряву.
Подход к организациифайловой структуры проектов.
Подходит для JS-проектов любой сложности. Предлагает простую систему терминов и правил, призванных снизить сложность работы над проектами.
- 🚀 подходит для проектов любой сложности
- 🍰 легко понять, простые термины и правила
- 🧩 формирует информативную и предсказуемую файловую структуру проекта, чем облегчает построение его ментальной модели
- 📦 группирует файлы одновременно по реализуемойфункциональности и потипу, чем облегчает анализ случаев использования отдельных файлов и поиск связанных файлов
- 📌 обеспечивает фиксированную максимальную вложенность директорий во всём проекте — 3 уровня
- 🚚 разгружает пространство имён файлов, облегчает именование
- Мотивация
- Быстрый старт
- Методология
- FAQ
- Поддержка IDE
- Сообщество
- Обратная связь
- Благодарности
- Поддержать автора
В сфере разработки ПО принято вести работу над ПО в рамкахпроектов. Под проектом ПО подразумевается набор файлов, содержащих код и другие ресурсы, необходимые для реализации ПО.
По мере развития, проекты становятсясложнее, а вместе с этим становится сложнее работа над ними.
То, как будет расти сложность работы, во многом зависит от подхода к принятиюрешений, которого придерживается разработчик.
Если принимать решенияситуативно, сложность работы над проектом будет расти стремительно. И напротив, если разработать оптимальныйсистемный подход, рост сложности можно замедлить.
Одна из самых сложных задач, которую приходится выполнять разработчику во время работы, — построениементальной модели проекта.
Под ментальной моделью подразумевается представление о том, какую задачу решает ПО, какие особенности имеет и как устроен его проект — какой фрагмент кода за что отвечает и как связан с другими фрагментами.
Если говорить прокод, упростить построение ментальной модели призваны оптимальные архитектура и соглашения, соотносящиеся с лучшими практиками и индивидуальным опытом.
Но код не может существовать сам по себе. Для того, чтобы стать частью проекта, он должен быть помещён вфайл.
Таким образом, построение ментальной модели начинается с изученияфайловой структуры проекта.
В файловой структуреимя файла ипринадлежность к директории позволяют сделать выводы о содержимом файла и связи с другими файлами.
В случае, если файловая структура организовананеудачно (ситуативно, неоптимально), её изучение становится затруднительным. В свою очередь, это затрудняет построение ментальной модели. А поскольку это — одна из самых сложных задач при работе с проектами, этосерьёзная проблема.
Среди разработчиков проблема часто оказываетсянедооценённой, хотя стоит наряду с неудачными архитектурой и соглашениями или их отсутствием.
Можно выделить два популярных подхода к организации файловой структуры:
Подход предусматривает группировку файлов потипу содержимого.
Для примера, файловая структура 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")└── ...
Проблемы с пространством имён
Чем больше файлов содержится в группе, тем выше становится нагрузка на пространство имён. Чтобы избежать коллизий, к именам новых файлов требуется добавлять префиксы, что усложняет имена и сам процесс именования.
Проблемы анализа
Чем больше файлов содержится в группе, тем сложнее её анализировать, поскольку длинный несгруппированный список файлов несёт мало информации.
Рассматривая отдельный файл, невозможно быстро получить представление о том, в реализации какой функциональности он участвует и с какими другими файлами может быть связан. Чтобы ответить на эти вопросы, потребуется провести мини-исследование, — и так с каждым файлом.
Подход предусматривает группировку файлов пофункциональности, которую реализует их содержимое.
Для примера, файловая структура 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│└── ...
Проблемы анализа
Чем больше файлов содержится в группе, тем сложнее её анализировать, поскольку длинный несгруппированный список файлов несёт мало информации.
Рассматривая группу, невозможно быстро получить представление о том, из чего она состоит: есть ли файлы определённого типа, сколько их и т.д. Чтобы ответить на эти вопросы, потребуется провести мини-исследование, — и так с каждой группой.
Использование вложенности решает некоторые проблемы обоих подходов, но значительно затрудняет анализ проекта, поскольку часть файлов оказываетсяскрытой — до тех пор, пока не раскрыть все вложенные директории.
Изучим фичу
postsrc└── 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
Больше примеров можно найтиздесь.
Пошаговое руководство 📘
Давайте представим, что мы веб-разработчик и некая компания предложила нам работу над проектом новой социальной сети.
Проект на начальном этапе разработки и нам нужно выступить в роли ведущего разработчика фронтенда.
У нас есть требования, которые подготовил аналитик, и план работ, который составил менеджер.
- Начинаем работу над домашней страницей
- Инициализируем проект
- Реализуем компонент, отвечающий за раскладку элементов страницы
- Реализуем UI-кит
- Начинаем работу над постом
- Реализуем компонент, отвечающий за визуализацию поста
- Реализуем компонент, отвечающий за просмотр изображений
- Организуем доступ к общим ресурсам в разных категориях
- Реорганизуем категорию
- Реализуем редактор постов
- Завершаем работу над домашней страницей
- Интегрируем домашнюю страницу в приложение
Погружаемся в контекст. Первые примеры уже в следующей главе!
Наша первая (и единственная в рамках этого руководства) задача — реализовать домашнюю страницу.
Задача — комплексная и трудозатратная. Чтобы сократить возможные риски, необходимо еёдекомпозировать.
Сперва отметим, что перед тем, как к ней приступить, нам необходимоинициализировать проект.
Затем проанализируем требования и макеты:
Лейаут страницы совпадает с лейаутом других страниц. Под лейаутом подразумевается раскладка элементов, а именно: хедера, навигации, контента, футера и т.д.
Исходя из этого, необходимо реализовать переиспользуемый компонент, отвечающий зараскладку элементов страницы. Его можно будет использовать для реализации текущей страницы и следующих.
На странице используются элементы, входящие в UI-кит. Необходимо спроектировать и подготовить минимальную реализациюUI-кита, которой будет достаточно для реализации страницы.
На странице используются элементы, которые также будут использоваться на других страницах:посты иредактор постов. Необходимо реализовать соответствующие переиспользуемые компоненты.
Учитывая вышеперечисленные замечания, составим следующий список задач:
инициализировать проект;
реализовать переиспользуемый компонент, отвечающий зараскладку элементов странице;
спроектировать и подготовить минимальную реализациюUI-кита;
реализовать переиспользуемые компоненты для визуализациипостов иредактора постов;
реализовать страницу с использованием компонентов, разработанных ранее.
Приступим! 👩🔧
Здесь мы создаёмкатегорию,группу иэлемент, знакомимся с их свойствами и ограничениями.
Первая задача в нашем списке — инициализировать проект. Задача включает в себя создание скелета и настройку сборки приложения.
Начнём с созданияскелета. Он будет состоять из двух элементов:
root– в нём будет содержаться корневой компонент приложения,init.js— в нём будет происходить инициализация приложения.
Сперва реализуем элементroot. Для этого:
создадим категорию
app,Категория — директория, применяемая для группировки элементов по реализуемойфункциональности.
— методология, разделкатегории
в ней создадим группу
components,Группа — директория, применяемая для группировки элементов потипу.
— методология, разделгруппы
в ней создадим элемент
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_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';
Дело сделано! 🎉
Наша следующая задача — реализовать переиспользуемый компонент, отвечающий за визуализацию поста.
Исходя из требований и макетов следует, что:
пост может содержатьтекст, а также прикреплённыекартинки иливидео — и для их просмотра требуются соответствующие просмотрщики;
у поста естьсчётчик просмотров, также посту можно поставитьлайк, сделатьрепост, написатькомментарий;
у поста есть меню действий, в котором можноподписаться на автора илизаблокировать его, а так жепожаловаться на контент.
Как мы видим, полноценная реализация поста — задача комплексная и трудозатратная.
Поэтому будет разумно разбить его реализацию на итерации, сейчас же сосредоточиться только на следующих задачах:
- реализовать основную визуализацию поста,
- реализовать просмотрщик изображений.
Начнём с компонента, отвечающего за визуализацию поста.
Создадим новую категорию —post. Разместим в ней соответствующие группы и элементы, а также создадим точку входа.
Примечание
Здесь и далее примеры файловой структуры будут упрощены
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.Это облегчит анализ категории и создаст новое пространство имён.
Однако, в таком случае не хватает указаниясвязи между постом и просмотрщиком.
Разместить код просмотрщика вподкатегории —
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.
В ней размещено довольно много файлов. Если провести мини-исследование, среди них можно выделить две подгруппы:
- файлы, связанные с реализацией поста,
- файлы, связанные с реализацией меню действий.
Отдельное мини-исследование — относительно лёгкая задача. Однако, чем больше их требуется, темсложнее становится анализ проекта. В какой-то момент в проекте невозможно будет разобраться без блокнота и ручки.
Таким образом, лучше всего свести необходимость мини-исследований в проекте к минимуму.
В случае с файлами, которые мы рассматривали выше, будет полезнозакрепить результаты мини-исследования и применить к файлам дополнительную группировку.
Перед нами возникает вопрос — как будет лучше это сделать? Рассмотрим доступные варианты:
Разместить файлы в новых подкатегориях.
Однако, подкатегории уместнее применять для группировки набора файлов с содержимым разного типа. В нашем же случае, файлы одного типа —
components.Также это потребует соблюдения всех накладываемых на категории ограничений.
Разместить файлы вименованных группах.
Это позволит применить дополнительную группировку без создания новых категорий.
Отличный вариант! 💯
Создадим именованные группы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. Перейдём к реализации самой страницы.
Для этого:
используя внешние компоненты
PostиPostEditor, подготовленные ранее, реализуем комплексные компонентыPostFeedиPostEditor, отвечающие за полноценную реализацию и интеграциюленты иредактора постов;реализуем компонент
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.jssrc└── post_editor └── components └── editor ├── component.jsx ├── index.js ├── styles.js └── styles.actions.jsВ примере ниже
организована виртуальная директорияassetssrc└── 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 в полной мере, вы также можете рассмотреть возможности применить отдельные идеи подхода.
Да!
Да!
Полагаю, что да! Если у вас получится интерпретировать подход в проекте на другом языке, пожалуйста, поделитесь опытом.
По умолчанию для сортировки списка файлов используется алгоритм, приводящий в некоторых случаях к перемешиванию групп директорий:
В примере ниже категории
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
Нет известных проблем.
@virtual_nesting_community — сообщество в Telegram, где можно пообщаться и получить помощь.
Буду рад любой обратной связи! Пожалуйста, пишите в issues или discussions.
Хочу поблагодарить мою жену@daryabratova, а также моих друзей@kindaro,@ruzovska и@a1ex-kaufmann.
Я потратил сотни часов, работая над этим проектом. Надеюсь, он сэкономит тысячи часов времени другим разработчикам.
About
Подход к организации файловой структуры проектов
Topics
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.