de Krunoslav Banovac

Cum se implementează variabile de mediu de rulare cu create-react-app, Docker și Nginx

Există multe modalități de a vă configura aplicația React. Să folosim o abordare care să respecte Metodologia aplicației cu 12 factori. Aceasta înseamnă că aplică reconfigurarea în timpul rulării. Prin urmare, nu ar fi necesară nicio construcție pe mediu.

Cum se implementeaza variabile de mediu de executie cu create react app

? Ce vrem să realizăm?

Vrem să putem rula aplicația noastră React ca un container Docker care este construit o singură dată. Funcționează peste tot, fiind configurabil în timpul rulării. Rezultatul ar trebui să fie un container ușor și performant care servește aplicației noastre React ca conținut static, pe care îl realizăm utilizând Ngnix Alpine. Aplicația noastră ar trebui să permită configurarea în fișierul docker-compose, cum ar fi acesta:

version: "3.2"
services:
  my-react-app:
    image: my-react-app
    ports:
      - "3000:80"
    environment:
      - "API_URL=https://production.example.com"

Ar trebui să putem configura aplicația noastră React folosind -e semnalizator (variabile de mediu) atunci când se utilizează Docker run comanda.

La prima vedere, această abordare poate părea să aducă un beneficiu prea mic pentru munca suplimentară necesară pentru configurarea inițială. Dar odată ce configurarea este realizată, configurațiile și implementarea specifice mediului vor fi mult mai ușor de gestionat. Deci, pentru oricine vizează medii dinamice sau utilizează sisteme de orchestrație, această abordare este cu siguranță ceva de luat în considerare.

? Problema

În primul rând, trebuie să fie clar că nu există variabile de mediu în mediul browserului. Orice soluție pe care o folosim în zilele noastre nu este altceva decât o abstractizare falsă.

Dar, atunci s-ar putea să întrebați, ce zici de .env fișiere și REACT_APP variabile de mediu prefixate care vin direct din documentație? Chiar și în interiorul codului sursă, acestea sunt utilizate ca process.env la fel cum folosim variabile de mediu în interiorul Node.js.

În realitate, obiectul process nu există în mediul browserului, este specific nodului. În mod implicit, CRA nu face redarea de pe server. Nu poate injecta variabile de mediu în timpul difuzării conținutului (cum ar fi Next.js face). În timpul transpilării, Procesul Webpack înlocuiește toate aparițiile process.env cu o valoare șir care a fost dată. Asta înseamnă poate fi configurat numai în timpul construirii.

? Soluţie

Momentul specific în care este încă posibil să injectăm variabile de mediu se întâmplă atunci când pornim containerul nostru. Apoi putem citi variabilele de mediu din interiorul containerului. Le putem scrie într-un fișier care poate fi difuzat prin Nginx (care servește și aplicația noastră React). Acestea sunt importate în aplicația noastră folosind <script> etichetați în secțiunea cap a index.html. Deci, în acel moment, rulăm un script bash care creează un fișier JavaScript cu variabile de mediu atribuite ca proprietăți ale globalului window obiect. Injectat pentru a fi disponibil la nivel global în aplicația noastră în modul browser.

1612076708 562 Cum se implementeaza variabile de mediu de executie cu create react app
Veți găsi un link către depozitul GitHub la sfârșitul articolului.

? Ghid pas cu pas

Să începem cu un simplu create-react-app proiectează și creează .env fișier cu prima noastră variabilă de mediu pe care dorim să o expunem.

# Generate React App
create-react-app cra-runtime-environment-variables
cd cra-runtime-environment-variables

# Create default environment variables that we want to use
touch .env
echo "API_URL=https//default.dev.api.com" >> .env
(1) Utilizarea CRA CLI pentru a genera aplicația React (2) Crearea fișierului .env în directorul rădăcină al proiectului proaspăt generat

Apoi, să scriem un mic script bash care va citi.env fișier și extrageți variabilele de mediu care vor fi scrise în fișier. Dacă setați o variabilă de mediu în interiorul containerului, valoarea acesteia va fi utilizată, în caz contrar, va reveni la valoarea implicită din fișierul .env. Acesta va crea un fișier JavaScript care pune valorile variabilei de mediu ca obiect care este atribuit ca proprietate a window obiect.

#!/bin/bash

# Recreate config file
rm -rf ./env-config.js
touch ./env-config.js

# Add assignment 
echo "window._env_ = {" >> ./env-config.js

# Read each line in .env file
# Each line represents key=value pairs
while read -r line || [[ -n "$line" ]];
do
  # Split env variables by character `=`
  if printf '%sn' "$line" | grep -q -e '='; then
    varname=$(printf '%sn' "$line" | sed -e 's/=.*//')
    varvalue=$(printf '%sn' "$line" | sed -e 's/^[^=]*=//')
  fi

  # Read value of current variable if exists as Environment variable
  value=$(printf '%sn' "${!varname}")
  # Otherwise use value from .env file
  [[ -z $value ]] && value=${varvalue}
  
  # Append configuration property to JS file
  echo "  $varname: "$value"," >> ./env-config.js
done < .env

echo "}" >> ./env-config.js
env.sh – (1) Elimină fișierul vechi și creează unul nou. (2) scrie cod JS care deschide obiectul literal și îl atribuie obiectului ferestrei globale. (3) Citește fiecare linie a fișierului .env și se împarte în pereche cheie / valoare. (4) Căutați variabila de mediu, dacă este setată, utilizați valoarea acesteia, în caz contrar, utilizați valoarea implicită din fișierul .env. (5) Adăugați-l la obiectul pe care l-am atribuit obiectului ferestrei globale (6) Închideți obiectul literal

Trebuie să adăugăm următoarea linie la <head> element din interior index.htmlcare apoi importă fișierul creat de scriptul nostru bash.

<script src="https://www.freecodecamp.org/news/how-to-implement-runtime-environment-variables-with-create-react-app-docker-and-nginx-7f9d42a91d70/%PUBLIC_URL%/env-config.js"></script>
index.html

Să afișăm variabila de mediu în cadrul aplicației:

<p>API_URL: {window._env_.API_URL}</p>
src / App.js

? Dezvoltare

În timpul dezvoltării, dacă nu dorim să folosim Docker, putem rula script bash prin npm script alergător prin modificarea package.json:

  "scripts": {
    "dev": "chmod +x ./env.sh && ./env.sh && cp env-config.js ./public/ && react-scripts start",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "build": "react-scripts build'"
  },
Rularea scriptului shell, apoi react-script start

Și dacă alergăm yarn dev ar trebui să vedem ieșiri astfel:

1612076708 412 Cum se implementeaza variabile de mediu de executie cu create react app
Folosind valoarea implicită API_URL din fișierul .env

Există două moduri de a reconfigura variabilele de mediu în cadrul dev. Fie modificați valoarea implicită din interior .env fișier sau suprascrieți valorile implicite rulând yarn devcomanda cu variabile de mediu preconizate:

API_URL=https://my.new.dev.api.com yarn dev
1612076708 950 Cum se implementeaza variabile de mediu de executie cu create react app
Folosind valoarea API_URL care a fost transmisă prin CLI

Și, în cele din urmă, editați .gitignore astfel încât să excludem configurațiile de mediu din codul sursă:

# Temporary env files
/public/env-config.js
env-config.js

În ceea ce privește mediul de dezvoltare, atât! Suntem la jumătatea drumului. Nu am făcut o diferență uriașă în acest moment în comparație cu ceea ce CRA a oferit în mod implicit pentru mediul de dezvoltare. Adevăratul potențial al acestei abordări strălucește în producție.

? Producție

Acum vom crea o configurație minimă Nginx, astfel încât să putem construi o imagine optimizată care să servească aplicația pregătită pentru producție.

# Create directory for Ngnix configuration
mkdir -p conf/conf.d
touch conf/conf.d/default.conf conf/conf.d/gzip.conf

Fișierul principal de configurare ar trebui să arate oarecum astfel:

server {
  listen 80;
  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
    expires -1; # Set it to different value depending on your standard requirements
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}
conf / conf.d / default.conf

De asemenea, este util să activați compresia gzip, astfel încât activele noastre să fie mai ușoare în timpul tranziției în rețea:

gzip on;
gzip_http_version  1.0;
gzip_comp_level    5; # 1-9
gzip_min_length    256;
gzip_proxied       any;
gzip_vary          on;

# MIME-types
gzip_types
  application/atom+xml
  application/javascript
  application/json
  application/rss+xml
  application/vnd.ms-fontobject
  application/x-font-ttf
  application/x-web-app-manifest+json
  application/xhtml+xml
  application/xml
  font/opentype
  image/svg+xml
  image/x-icon
  text/css
  text/plain
  text/x-component;
conf / conf.d / gzip.conf

Acum, când configurația noastră Nginx este gata, putem crea în cele din urmă fișiere Dockerfile și docker-compose:

touch Dockerfile docker-compose.yml
Crearea fișierelor Docker

Inițial, folosim node:alpine imagine pentru a crea o construcție de producție optimizată a aplicației noastre. Apoi, construim o imagine de execuție deasupra nginx:alpine .

# => Build container
FROM node:alpine as builder
WORKDIR /app
COPY package.json .
COPY yarn.lock .
RUN yarn
COPY . .
RUN yarn build

# => Run container
FROM nginx:1.15.2-alpine

# Nginx config
RUN rm -rf /etc/nginx/conf.d
COPY conf /etc/nginx

# Static build
COPY --from=builder /app/build /usr/share/nginx/html/

# Default port exposure
EXPOSE 80

# Copy .env file and shell script to container
WORKDIR /usr/share/nginx/html
COPY ./env.sh .
COPY .env .

# Add bash
RUN apk add --no-cache bash

# Make our shell script executable
RUN chmod +x env.sh

# Start Nginx server
CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g "daemon off;""]

Acum containerul nostru este gata. Putem face toate lucrurile standard cu el. Putem construi un container, îl putem rula cu configurații inline și îl putem împinge către un depozit furnizat de servicii precum Dockerhub.

docker build . -t kunokdev/cra-runtime-environment-variables
docker run -p 3000:80 -e API_URL=https://staging.api.com -t kunokdev/cra-runtime-environment-variables
docker push -t kunokdev/cra-runtime-environment-variables
Exemplu de comenzi build, run și push

De mai sus docker run comanda ar trebui să producă aplicația astfel:

1612076709 992 Cum se implementeaza variabile de mediu de executie cu create react app
Folosind API_URL care a fost furnizat prin semnalul variabilelor de mediu pentru comanda de rulare a andocatorului

În sfârșit, haideți să creăm fișierul nostru de compunere docker. De obicei, veți avea diferite fișiere docker-compose în funcție de mediu și le veți utiliza -f pavilion pentru a selecta ce fișier să utilizați.

version: "3.2"
services:
  cra-runtime-environment-variables:
    image: kunokdev/cra-runtime-environment-variables
    ports:
      - "5000:80"
    environment:
      - "API_URL=production.example.com"

Și dacă o facem docker-compose up ar trebui să vedem rezultate astfel:

1612076709 791 Cum se implementeaza variabile de mediu de executie cu create react app
Folosind API_URL care a fost furnizat prin intermediul proprietății de mediu docker-compose

Grozav! Acum ne-am atins obiectivul. Ne putem reconfigura aplicația cu ușurință atât în ​​mediile de dezvoltare, cât și în cele de producție într-un mod foarte convenabil. Acum putem construi în sfârșit o singură dată și alergăm peste tot!

Dacă v-ați blocat sau aveți idei suplimentare, accesați cod sursă pe GitHub.

? Pasii urmatori

Implementarea curentă a scriptului shell va imprima toate variabilele incluse în fișierul .env. De cele mai multe ori nu vrem să le expunem pe toate. Puteți implementa filtre pentru variabilele pe care nu doriți să le expuneți folosind prefixe sau o tehnică similară.

? Soluții alternative

După cum sa menționat mai sus, configurația timpului de construcție va satisface majoritatea cazurilor de utilizare. Vă puteți baza pe abordarea implicită folosind fișierul .env per mediu și să creați un container pentru fiecare mediu și să injectați valori prin variabilele de mediu furnizate de CRA Webpack.

Ai putea, de asemenea, să arunci o privire la asta Problema depozitului CRA GitHub care acoperă această problemă. Până acum, ar trebui să existe mai multe postări și probleme care să acopere acest subiect. Fiecare oferă o soluție similară cu cea de mai sus. Depinde de dvs. să decideți cum veți implementa detalii specifice. S-ar putea să utilizați Node.js pentru a vă difuza aplicația, ceea ce înseamnă că puteți înlocui, de asemenea, scriptul shell cu scriptul Node.js. Rețineți că Nginx este mai convenabil pentru a difuza conținut static.

Dacă aveți întrebări sau doriți să oferiți feedback; nu ezitați să deschideți numărul pe GitHub. Opțional, urmați-mă pentru alte postări legate de tehnologiile web.