Dockeryzacja frontendu – zrób to dobrze React.js + Vite

Autor Autor:
Zespół Innokrea
Data publikacji: 2024-07-04
Kategorie: Programowanie

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

 

Inicjalizacja projektu w oparciu o Vite

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

 

Uruchomione środowisko deweloperskie - terminal

Rysunek 2 – Uruchomione środowisko deweloperskie – terminal

 

Uruchomione środowisko deweloperskie - przeglądarka

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.

 

Utworzony kontener ze zbudowanego obrazu

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.

 

Wyświetlenie zdefiniowanej w docker-compose zmiennej środowiskowej

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.

 

ostateczna struktura plików całego projektu

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.

 

mała modyfikacja pliku App.jsx

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.

 

Widok przeglądarki i terminala po zbudowaniu projektu w wersji produkcyjnej.

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!

 

Zobacz więcej na naszym blogu:

Zarządzanie tożsamością i dostępem użytkownika, czyli o co chodzi z IDP?

Zarządzanie tożsamością i dostępem użytkownika, czyli o co chodzi z IDP?

Czym jest tożsamość użytkownika? Z czego wynika potrzeba zarządzania dostępem w firmie? Jak działa tzw. IDP? Odpowiedź na te pytania znajdziesz w artykule.

Bezpieczeństwo

Wzorce projektowe – część 2

Wzorce projektowe – część 2

Hej, hej... Programisto, to kolejny artykuł dla Ciebie! Druga część artykułu na temat wzorców projektowych. Poznaj Adapter oraz Memento.

Programowanie

Wzorce projektowe – część 1

Wzorce projektowe – część 1

Programisto, to artykuł dla Ciebie! Łap garść przydatnych informacji na temat wzorców projektowych.

Programowanie