Kako implementirati varijable runtime okruženja s create-response-app, Docker i Nginx

Postoji mnogo načina za konfiguriranje vaše React aplikacije. Upotrijebimo pristup koji poštuje metodologiju aplikacije dvanaest faktora. To znači da provodi rekonfiguraciju tijekom izvođenja. Stoga neće biti potrebna gradnja po okruženju.

? Što želimo postići?

Želimo biti u mogućnosti da našu aplikaciju React pokrenemo kao Docker spremnik koji je napravljen jednom. Izvodi se svugdje tako što ga je moguće konfigurirati tijekom izvođenja. Rezultat bi trebao biti lagan i učinkovit spremnik koji našoj aplikaciji React služi kao statički sadržaj, što postižemo uporabom Ngnix Alpine. Naša bi aplikacija trebala omogućiti konfiguraciju unutar datoteke za sastavljanje dockera, poput ove:

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

Morali bismo biti u mogućnosti konfigurirati našu React aplikaciju pomoću -ezastavice (varijable okruženja) kada koristimo Docker runnaredbu.

Na prvi pogled može se činiti da ovaj pristup donosi premale koristi za dodatni rad potreban za početno postavljanje. No, nakon što se završi postavljanje, konfiguracijama i implementacijom specifičnim za okruženje bit će puno lakše rukovati. Dakle, za svakoga tko cilja dinamično okruženje ili koristi sustave za orkestraciju, ovaj je pristup svakako nešto za razmotriti.

? Problem

Prije svega, mora biti jasno da u okruženju preglednika ne postoje varijable okruženja. Bilo koje rješenje koje danas koristimo nije ništa drugo do lažna apstrakcija.

Ali, onda biste mogli pitati, što je s .envdatotekama i REACT_APPprefiksnim varijablama okruženja koje dolaze izravno iz dokumentacije? Čak i unutar izvornog koda, koriste se kao process.envšto i mi koristimo varijable okoline unutar Node.js.

U stvarnosti objekt processne postoji unutar okruženja preglednika, on je specifičan za Node. CRA prema zadanim postavkama ne vrši prikazivanje na strani poslužitelja. Ne može ubrizgati varijable okruženja tijekom posluživanja sadržaja (kao što to čini Next.js). Tijekom prevođenja , Webpack postupak zamjenjuje sve pojave process.envs zadanom vrijednošću niza. To znači da se može konfigurirati samo za vrijeme gradnje .

? Riješenje

Određeni trenutak kada je još uvijek moguće ubrizgati varijable okoline događa se kad pokrenemo naš spremnik. Tada možemo čitati varijable okruženja iz unutrašnjosti spremnika. Možemo ih zapisati u datoteku koja se može poslužiti putem Nginxa (koji također služi našoj aplikaciji React). U našu aplikaciju uvoze se pomoću oznake unutar odjeljka glave index.html. U tom trenutku pokrećemo bash skriptu koja stvara JavaScript datoteku s varijablama okruženja dodijeljenim kao svojstva globalnog windowobjekta. Ubrizgano da bude globalno dostupno u našoj aplikaciji putem preglednika.

? Vodič korak po korak

Počnimo s jednostavnim create-react-appprojektom i stvorimo .envdatoteku s našom prvom varijablom okruženja koju želimo izložiti.

# 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

Onda napišimo malu bash skriptu koja će čitati .envdatoteku i izdvajati varijable okruženja koje će biti zapisane u datoteku. Ako unutar spremnika postavite varijablu okruženja, koristit će se njezina vrijednost, inače će se vratiti na zadanu vrijednost iz .env datoteke. Stvorit će JavaScript datoteku koja vrijednosti varijabli okruženja stavlja kao objekt koji je dodijeljen kao svojstvo windowobjekta.

#!/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 '%s\n' "$line" | grep -q -e '='; then varname=$(printf '%s\n' "$line" | sed -e 's/=.*//') varvalue=$(printf '%s\n' "$line" | sed -e 's/^[^=]*=//') fi # Read value of current variable if exists as Environment variable value=$(printf '%s\n' "${!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-config.js

Moramo dodati sljedeći redak elementu unutar index.htmlkojeg se zatim uvozi datoteka koju je stvorila naša bash skripta.

Prikažimo našu varijablu okruženja unutar aplikacije:

API_URL: {window._env_.API_URL}

? Razvoj

Tijekom razvoja, ako ne želimo koristiti Docker, možemo pokrenuti bash skriptu putem npm scriptpokretača izmjenom 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'" },

A ako pokrenemo yarn devtrebali bismo vidjeti izlaz poput ovog:

Postoje dva načina za rekonfiguriranje varijabli okoline unutar programa dev. Ili promijenite zadanu vrijednost u .envdatoteci ili poništite zadane postavke izvođenjem yarn devnaredbe s unaprijed pripremljenim varijablama okoline:

API_URL=//my.new.dev.api.com yarn dev

I na kraju, uredite .gitignoretako da iz izvornog koda izuzmemo konfiguracije okoline:

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

Što se tiče razvojnog okruženja, to je to! Na pola smo puta. U ovom trenutku nismo napravili veliku razliku u odnosu na ono što je CRA standardno ponudio za razvojno okruženje. Pravi potencijal ovog pristupa blista u proizvodnji.

? Proizvodnja

Sada ćemo stvoriti minimalnu Nginx konfiguraciju kako bismo mogli stvoriti optimiziranu sliku koja služi proizvodno spremnoj aplikaciji.

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

Glavna konfiguracijska datoteka trebala bi izgledati otprilike ovako:

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; } }

Također je korisno omogućiti gzip kompresiju kako bi naša imovina bila lakša tijekom mrežne tranzicije:

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;

Sada kada je naša Nginx konfiguracija spremna, konačno možemo stvoriti Dockerfile i docker-compose datoteke:

touch Dockerfile docker-compose.yml

U početku koristimo node:alpinesliku za stvaranje optimizirane proizvodne verzije naše aplikacije. Zatim na vrhu gradimo sliku vremena izvođenja 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;\""]

Sad je naš spremnik spreman. S njim možemo raditi sve standardne stvari. Možemo izgraditi spremnik, pokrenuti ga s ugrađenim konfiguracijama i gurnuti u spremište koje pružaju usluge poput Dockerhub-a.

docker build . -t kunokdev/cra-runtime-environment-variables docker run -p 3000:80 -e API_URL=//staging.api.com -t kunokdev/cra-runtime-environment-variables docker push -t kunokdev/cra-runtime-environment-variables

Gornja docker runnaredba trebala bi izlaziti tako:

Na kraju, kreirajmo našu datoteku za sastavljanje dockera. Obično ćete imati različite datoteke za sastavljanje dockera, ovisno o okruženju, a -fzastavicom ćete odabrati koju ćete datoteku koristiti.

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

A ako to učinimo docker-compose up, trebali bismo vidjeti izlaz tako:

Sjajno! Sada smo postigli svoj cilj. Svoju aplikaciju možemo vrlo lako konfigurirati u razvojnom i proizvodnom okruženju na vrlo prikladan način. Sada konačno možemo graditi samo jednom i trčati svugdje!

Ako ste zapeli ili imate dodatnih ideja, pristupite izvornom kodu na GitHubu.

? Sljedeći koraci

Trenutna implementacija skripte ljuske ispisat će sve varijable uključene u .env datoteku. Većinu vremena ne želimo sve razotkriti. Možete primijeniti filtre za varijable koje ne želite izlagati pomoću prefiksa ili slične tehnike.

? Alternativna rješenja

As noted above, the build time configuration will satisfy most use cases. You can rely on the default approach using .env file per environment and build a container for each environment and inject values via CRA Webpack provided environment variables.

You could also have a look at this CRA GitHub repository issue which covers this problem. By now, there should be more posts and issues which cover this topic. Each offers a similar solution as above. It’s up to you to decide how are you going to implement specific details. You might use Node.js to serve your application which means that you can also replace shells script with Node.js script. Note that Nginx is more convenient to serve static content.

If you have any questions or want to offer feedback; feel free to open issue on GitHub. Optionally follow me for further posts related to web technologies.