Hej, hej... Programisto, to kolejny artykuł dla Ciebie! Druga część artykułu na temat wzorców projektowych. Poznaj Adapter oraz Memento.
Hej dzisiaj jako Innokrea chcemy przedstawić Wam gotowy poradnik do dockeryzacji React.js z Vite. Jeśli chcecie mieć środowisko deweloperskie z hot-reload, działające zmienne środowiskowe oraz optymalnego 2-stage build z nginx na produkcję, to zapraszamy do lektury.
Czym jest Vite?
Vite jest narzędziem, które umożliwia serwowanie naszego kodu podczas fazy rozwijania naszej aplikacji lokalnie oraz budowanie naszego projektu do formy produkcyjnej. Taki kod jest zoptymalizowany poprzez odpowiednie paczkowanie przed dostarczeniem go do przeglądarki użytkownika. Vite wspiera takie mechanizmy jak HMR (ang. Hot Module Replacement), czyli możliwość przeładowania aplikacji po zmianie kodu podczas jej rozwijania oraz pozwala na zastosowanie SSR (ang. Server Side Rendering). Dodatkowo, po zmianie kodu podczas developmentu nie jest przeładowywany cały stan aplikacji, a jedynie zmiana, której dokonaliśmy w kodzie. Używając Vite instalowanie nowych modułów nie zwiększa znacząco czasu przeładowania czy budowania projektu. W react.js coraz częściej zamiast create-react-app poleca się właśnie stosowanie Vite.
Utworzenie projektu
Tworzenie projektu zaczniemy od wykonania następujących komend:
mkdir innokrea
cd innokrea
npm init vite@5.2.3 frontend
cd frontend
npm install
Rysunek 1 – Inicjalizacja projektu w oparciu o Vite
Konfiguracja vite znajduje się w pliku vite.config.js. Spróbujmy edytować ten plik i zdefiniować inny port, a także inne parametry, takie jak usePolling pomagające np. WSL (jeśli ktoś używa Windowsa i Docker Desktop + WSL) w dynamicznym wykrywaniu zmian w plikach.
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: {
port: 3000,
host: true,
watch: {
usePolling: true,
},
},
});
Następnie spróbujmy uruchomić aplikację:
npm run dev
Rysunek 2 – Uruchomione środowisko deweloperskie – terminal
Rysunek 3 – Uruchomione środowisko deweloperskie – przeglądarka
Dockeryzacja projektu
Wyłączmy teraz aplikację i spróbujmy dodać plik Dockerfile do folderu innokrea/frontend.
FROM node:20-alpine
WORKDIR /app
COPY package*.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "run","dev"]
Teraz, w folderze innokrea utworzymy plik docker-compose-dev.yml:
version: "3.9"
services:
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: frontend
restart: always
ports:
- 3000:3000
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- VITE_APP_BACKEND_ADDRESS=http://localhost/api
networks:
- network
networks:
network:
Definiujemy kontekst, gdzie znajduje się nasz plik Dockerfile (powinien to być folder frontend wewnątrz folderu innokrea), następnie bind mount, który sprawi, że nasz kod będzie zamontowany wewnątrz kontenera oraz wolumen /app/node_modules, w celu uniknięcia błędów związanych z instalacją node_modules wewnątrz kontenera. Następnie zdefiniujemy zmienną środowiskową VITE_APP_BACKEND_ADDRESS. Wszystkie zmienne, które chcemy przekazać, powinny zaczynać się od prefixu VITE_. Upewnijmy się, że nasz serwer lokalny jest wygaszony i spróbujmy wykonać komendę:
docker-compose -f docker-compose-dev.yml up --build
W terminalu powinniśmy zobaczyć poprawne zbudowanie się obrazu oraz stworzenie kontenera na porcie 3000.
Rysunek 4 – Utworzony kontener ze zbudowanego obrazu
Spróbujmy teraz sprawdzić czy edytując kod zostanie on dynamicznie przeładowany. W tym celu możemy skorzystać z wyświetlania na ekranie zmiennej środowiskowej, którą zdefiniowaliśmy w ramach pliku docker-compose.yml.
Rysunek 5 – Wyświetlenie zdefiniowanej w docker-compose zmiennej środowiskowej
Wersja produkcyjna – 2-stage build i nginx
Aby stworzyć wersję produkcyjną naszego projektu zaczniemy od zmodyfikowania pliku vite.config.js ponownie w następujący sposób:
import { defineConfig, loadEnv } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig(({ command, mode }) => {
const env = loadEnv(mode, process.cwd());
return {
plugins: [react()],
server: {
port: 3000,
host: true,
watch: {
usePolling: true,
},
esbuild: {
target: "esnext",
platform: "linux",
},
},
define: {
VITE_APP_BACKEND_ADDRESS: JSON.stringify(env.VITE_APP_BACKEND_ADDRESS),
},
};
});
Następnie, w folderze frontend tworzymy nowy plik Dockerfile o nazwie Dockerfile.prod.
FROM node:20-alpine as builder
WORKDIR /app
COPY . .
ARG VITE_APP_BACKEND_ADDRESS
ENV VITE_APP_BACKEND_ADDRESS $VITE_APP_BACKEND_ADDRESS
RUN npm install
RUN npm run build
FROM nginx:1.25.4-alpine-slim as prod
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
Dockerfile składa się z dwóch etapów. Pierwszym jest faza budowania, w której przekazujemy nasze zmienne środowiskowe jako build args. Najprostszym sposobem, aby używać takich zmiennych w kodzie w przypadku środowiska produkcyjnego jest przekazanie ich na etapie budowania projektu, aby kompilator mógł je rozwiązać, a następnie rozwiązane wartości umieścić w działającym kodzie. Komenda npm run build kompiluje nasz kod z użyciem Vite (package.json definiuje build jako vite build). W drugim etapie pobieramy obraz nginx z użyciem komendy FROM i kopiujemy konfigurację nginx.conf do środka kontenera. Plik nginx.conf może wyglądać następująco:
server {
listen 3000;
root /usr/share/nginx/html;
index index.html;
etag on;
location / {
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
Zarówno plik nginx.conf oraz Dockerfile.prod powinny znajdować się w ścieżce innokrea/frotend. W ścieżce innokrea/ teraz utworzymy nowy plik docker-compose o nazwie docker-compose-prod.yml, który będzie korzystał z Dockerfile.prod.
version: "3.9"
services:
frontend-prod:
build:
context: ./frontend
dockerfile: Dockerfile.prod
args:
- VITE_APP_BACKEND_ADDRESS=https://localhost/api/prod
container_name: frontend-prod
restart: always
ports:
- 3000:3000
networks:
- network
networks:
network:
Należy zwrócić szczególną uwagę na to, że zmienna środowiskowa jest teraz dostarczana podczas budowania obrazu wraz z argumentami – a nie z sekcją environment, jak to było w wersji deweloperskiej. Zniknęły także wolumeny, które nie są w tym przypadku potrzebne ze względu na kopiowanie całego kodu do wewnątrz z użyciem Dockerfile.prod, a hot reload nie jest wymagany w środowisku produkcyjnym.
Rysunek 6 – ostateczna struktura plików całego projektu
Przed uruchomieniem spróbujmy zmodyfikować jakoś nasz projekt np. dopisując do pliku App.jsx słówko production w nagłówku h1.
Rysunek 7 – mała modyfikacja pliku App.jsx
Aby uruchomić projekt w wersji produkcyjnej wykonamy następujące komendy:
docker-compose -f docker-compose-dev.yml down
docker-compose -f docker-compose-prod.yml up --build
Naszym oczom powinien ukazać się poniższy widok.
Rysunek 8 – Widok przeglądarki i terminala po zbudowaniu projektu w wersji produkcyjnej.
Podsumowanie
Udało nam się dzisiaj stworzyć projekt w oparciu o technologie Vite, React, Docker and Nginx. Dzięki temu możecie stworzyć własne środowisko deweloperskie i efektywnie programować z funkcjonalnością hot-reload używając Docker’a oraz wdrożyć optymalną wersję produkcyjną w oparciu o 2-stage-build oraz nginx. Jeśli jesteście zainteresowani Dockerem to polecamy nasze artykuły w tym temacie:
https://www.innokrea.pl/docker-zrob-to-dobrze-i-bezpiecznie-cz-1/
https://www.innokrea.pl/docker-konteryzacja-wirtualizacja/
Do usłyszenia za tydzień!
Kod do pobrania na naszym gitlabie!