Przejdź do treści PWA

Struktura katalogów w projekcie front-endowym

Każdy ma swój sposób na definicję struktury katalogów w projekcie. Chcę przedstawi Ci mój punkt widzenia. Na wstępie chciałbym abyśmy rozmawiali tym samym językiem więc przedstawię Ci pewną zasadę.

Aplikacja i projekt to dwa różne elementy.

Projekt zawiera w sobie źródła aplikacji, ale również inne elementy konfiguracyjne takie jak: ustawienia systemu kontroli wersji, dokumentację, testy jednostkowe etc.

Aplikacja zawiera kod źródłowy z implementacją logiki, definicją wyglądu etc.

Baner promujący artykuł

Opcja #1 - Jeden katalog z aplikacją (app/)

Główny plik HTML odpowiedzialny jest za dołączenie wszystkich plików *.js oraz *.css. Oczywiście main.css może importować inne definicje styli więc nie trzeba koniecznie dołączać wszystkich plików w index.html.

.
├── app
│   ├── images
│   │   ├── banner.jpg
│   │   ├── favicon.ico
│   │   └── logo.png
│   ├── scripts
│   │   ├── main.js
│   │   └── module-1.js
│   ├── styles
│   │   ├── main.css
│   │   └── module-1.css
│   └── index.html
├── .gitignore
├── README.md
└── package.json

Czasami bywa tak, że z uwagi na prostotę projektu katalog app/ nie jest tworzony. Wtedy taka architektura niweluje tą wyraźną różnicę między aplikacją a projektem.

Kiedy warto zastosować?

Opisaną architekturę warto wykorzystać w sytuacji bardzo prostego projektu, który nie będzie poddawany długiemu procesu utrzymania aplikacji.

Uwaga Jeśli wrzucimy taki katalog na serwer, to dajemy użytkownikowi podgląd pliku .gitignore i package.json, które nie powinny być dla niego dostępne. Jeśli kopiujemy cały katalog na serwer to najgroźniejsze jest to, że możemy skopiować także katalog .git, gdzie zapisana jest historia zmian projektu.

Jak to wygląda w praktyce?

Przykłady zrealizowanych projektów:

Opcja #2 - Wykorzystanie bundlera modułów (dist/)

Kolejnym bardzo częstym rozwiązaniem jest wykorzystanie narzędzia typu module bundler, które tworzy katalog dist/ wraz z jednym plikiem *.js po to, aby nie dołączać wszystkich plików *.js w index.html.

.
├── app
│   ├── dist        # katalog zawierający wynik pracy module bundlera
│   │   └── bundle.js    # wynik pracy module bundlera np. Webpacka
│   ├── images
│   │   ├── banner.jpg
│   │   ├── favicon.ico
│   │   └── logo.png
│   ├── scripts
│   │   ├── main.js
│   │   └── module-1.js
│   ├── styles
│   │   ├── main.css
│   │   └── module-1.css
│   └── index.html
├── .gitignore
├── README.md
└── package.json

Różnica między opcją #1 a #2 jest zasadnicza. Wszystkie pliki *.js zostają potraktowane przez dowolny module bundler (np. Webpack, Browserify) i sklejone w jeden plik wynikowy (lub więcej jeśli pojawi się taka potrzeba) tzw. "bundle" (pakiet, paczka).

Katalog dist/ zawiera źródła projektu, czyli pliki *.js oraz ich mapy (pliki o rozszerzeniu *.js.map).

Oczywiście katalog dist/ nie powinien być dodawany do systemu kontroli wersji (np. Git). Nie ma sensu wersjonować zbudowanego kodu, dlatego też warto go wykluczyć .

Wyjątkiem od tej zasady jest tworzenie bibliotek, które charakteryzują się tym, że dla korzystających nie jest ważne z ilu plików zostały stworzone oraz czy są budowane z użyciem transpilera. W projektach tego typu najważniejszy jest plik wynikowy, czyli plik z biblioteką (plikiem) gotowym do użytku.

Kiedy warto zastosować?

Ta architektura z uwagi na wykorzystanie module bundlera pomocna jest w sytuacji większego projektu. Wykorzystanie Webpacka z definicji pomaga w zarządzaniu modułami.

Jak to wygląda w praktyce?

Przykłady zrealizowanych projektów:

Opcja #3 - Źródło (src/) oraz Dystrybucja (dist/)

Jak już możesz się domyślić trzecia opcja jest ewolucją poprzedniej. Struktura projektu jest taka, że źródła aplikacji silnie oddzielamy od wersji aplikacji, którą uznajemy za "dystrybucyjną".

Wersja dystrybucyjna to taka która zawiera źródła aplikacji, które były poddane różnym procesom, np. transpilacji kodu poprzez Babel.js, sklejane w jeden plik wynikowy.

.
├── dist
│   ├── images
│   │   ├── banner.jpg
│   │   ├── favicon.ico
│   │   └── logo.png
│   ├── bundle.css
│   ├── bundle.js
│   └── index.html
├── src
│   ├── images
│   │   ├── banner.jpg
│   │   ├── favicon.ico
│   │   └── logo.png
│   ├── scripts
│   │   ├── main.js
│   │   └── module-1.js
│   ├── styles
│   │   ├── main.css
│   │   └── module-1.css
│   └── index.html
├── .gitignore
├── README.md
└── package.json

Kiedy warto zastosować?

Takie podejście jest bardzo pomocne kiedy chcemy mieć pewność, że użytkownik nie będzie miał dostępu do źródeł projektu i aplikacji. Świetnym przykładem może być tutaj zbudowanie wersji dystrybucyjnej, którą wysyłamy podmiotom trzecim (poprzez przesłanie paczki z nową wersją aplikacji).

Najlepszym przykładem będzie stworzenie biblioteki. Taka architektura sprawdza się bardzo dobrze.

Jak to wygląda w praktyce?

Przykłady zrealizowanych projektów:

Opcja #4 - Publiczny katalog (public/)

Kolejnym przykładem jest wyodrębnienie katalogów scripts/ oraz styles/ poza katalog dostępny dla użytkownika, czyli public/.

W ustawieniach domeny przekierowujemy użytkownika na katalog na serwerze [...]/example-project/public/, tym samym źródła aplikacji są bezpieczne, tzn. nie ma do nich dostępu z poziomu internauty, czyli przeglądarki internetowej.

.
├── public
│   ├── dist
│   │   ├── bundle.css
│   │   └── bundle.js
│   ├── images
│   │   ├── banner.jpg
│   │   ├── favicon.ico
│   │   └── logo.png
│   └── index.html
├── scripts
│   ├── main.js
│   └── module-1.js
├── styles
│   ├── main.css
│   └── module-1.css
├── .gitignore
├── README.md
└── package.json

Kiedy warto zastosować?

Hybryda poprzednich architektur, łączy ze sobą plusy i minusy poprzednich. Taka koncepcja przydatna jest w sytuacji projektów, które mają podział na pliki dostępne dla użytkownika oraz główne źródła aplikacji. W tej architekturze współdzielone są assety, czyli wszelkie obrazki, fonty czy muzyka.

Jak to wygląda w praktyce?

Przykłady zrealizowanych projektów:

Słowo podsumowania

Nie ma idealnej struktury. Istotne jest dobranie odpowiedniej do celu projektu. Ważne jest aby nie mieszać konwencji.