A futásidejű környezeti változók megvalósítása a create-react-app, a Docker és az Nginx segítségével

A React alkalmazás konfigurálásának számos módja van. Használjunk olyan megközelítést, amely tiszteletben tartja a tizenkét tényező alkalmazás módszertanát. Ez azt jelenti, hogy végrehajtja az újrakonfigurációt futás közben. Ezért nincs szükség környezetre építésre.

? Mit akarunk elérni?

Szeretnénk tudni, hogy a React alkalmazásunk Docker konténerként fusson, amely egyszer felépül. Úgy fut mindenütt, hogy futás közben konfigurálható . A kimenetnek egy könnyű és előadó tartálynak kell lennie, amely statikus tartalomként szolgálja a React alkalmazásunkat, amelyet az Ngnix Alpine használatával érünk el. Alkalmazásunknak lehetővé kell tennie a konfigurációt a következő dokkoló-írási fájlban:

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

A parancs -ehasználata során képesnek kell lenniünk a React alkalmazás konfigurálására a flag (környezeti változók) használatával Docker run.

Első pillantásra úgy tűnhet, hogy ez a megközelítés túl kicsi előnyt jelent az első beállításhoz szükséges többletmunkához. De miután a beállítás megtörtént, a környezeti specifikus konfigurációk és a telepítés sokkal könnyebben kezelhető. Tehát bárki számára, aki dinamikus környezeteket céloz meg, vagy hangszerelési rendszereket használ, mindenképpen figyelembe kell venni ezt a megközelítést.

? A probléma

Először is egyértelműnek kell lennie, hogy a böngésző környezetében nincsenek környezeti változók. Bármelyik megoldást használjuk manapság, az nem más, mint egy hamis absztrakció.

De akkor megkérdezheti, mi a helyzet a .envfájlokkal és az REACT_APPelőtagolt környezeti változókkal, amelyek közvetlenül a dokumentációból származnak? Még a forráskódban is ezeket használják, process.envmint ahogy a környezeti változókat is a Node.js-en belül használjuk.

A valóságban az objektum processnem létezik a böngésző környezetében, hanem csomópont-specifikus. A CRA alapértelmezés szerint nem szerveroldali megjelenítést végez. Nem tud környezeti változókat injektálni a tartalom megjelenítése során (mint például a Next.js). A transzlálás során a Webpack folyamat minden előfordulását process.envegy megadott karakterlánc-értékkel helyettesíti . Ez azt jelenti , hogy csak a gyártási idő alatt konfigurálható .

? Megoldás

Az a konkrét pillanat, amikor még mindig lehet környezeti változókat beadni, akkor történik, amikor elindítjuk a tárolót. Ezután kiolvashatjuk a környezeti változókat a tartály belsejéből. Beírhatjuk őket egy fájlba, amelyet az Nginx-en keresztül lehet kiszolgálni (amely a React alkalmazásunkat is szolgálja). Ezeket az alkalmazás fejlécébe beillesztett címke segítségével importáljuk az alkalmazásunkba index.html. Tehát abban a pillanatban futtatunk egy bash szkriptet, amely JavaScript-fájlt hoz létre a globális windowobjektum tulajdonságaként kijelölt környezeti változókkal . Injektálva, hogy az alkalmazásunkban böngésző módon globálisan elérhető legyen.

? Lépésről lépésre útmutató

Kezdjük egy egyszerű create-react-appprojekttel, és hozzunk létre egy .envfájlt az első környezeti változónkkal, amelyet ki akarunk tenni.

# 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

Ezután írjunk egy kis bash szkriptet, amely beolvassa a .envfájlt, és kibontja a fájlba írandó környezeti változókat. Ha a konténeren belül beállít egy környezeti változót, annak értéke lesz felhasználva, különben vissza fog térni az .env fájl alapértelmezett értékére. Létrehoz egy JavaScript fájlt, amely a környezeti változó értékeit objektumként hozza meg, amelyet az objektum tulajdonságaként rendelnek hozzá window.

#!/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

A következő sort hozzá kell adnunk ahhoz az elemhez, index.htmlamelyen belül importálja a bash szkript által létrehozott fájlt.

Jelenítsük meg környezeti változónkat az alkalmazáson belül:

API_URL: {window._env_.API_URL}

? Fejlődés

A fejlesztés során, ha nem akarjuk használni a Dockert, a bash parancsfájlt futtathatjuk a futón keresztül npm scripta következő módosításokkal 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'" },

És ha futunk, yarn devakkor az ilyen kimenetet kell látnunk:

A környezeti változók újrakonfigurálásának két módja van a dev-en belül. Vagy módosítsa az alapértelmezett értéket a .envfájlban, vagy felülírja az alapértelmezéseket a yarn devparancs futtatásával előre elkészített környezeti változókkal:

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

Végül szerkessze .gitignoreúgy, hogy kizárja a környezeti konfigurációkat a forráskódból:

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

Ami a fejlesztői környezetet illeti, ennyi! Félúton vagyunk. Ezen a ponton nem értünk el óriási különbséget ahhoz képest, amit a CRA alapértelmezés szerint felajánlott a fejlesztői környezet számára. Ennek a megközelítésnek a valódi lehetőségei ragyognak a termelésben.

? Termelés

Most minimális Nginx konfigurációt fogunk létrehozni, hogy optimalizált képet készítsünk, amely a gyártásra kész alkalmazást szolgálja.

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

A fő konfigurációs fájlnak kissé így kell kinéznie:

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

Hasznos a gzip tömörítés engedélyezése is, hogy eszközeink könnyebbek legyenek a hálózati átállás során:

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;

Most, hogy készen áll a Nginx konfigurációnk, végre létrehozhatunk Dockerfile és docker-compose fájlokat:

touch Dockerfile docker-compose.yml

Kezdetben a node:alpinekép segítségével optimalizált gyártási összeállítást hozunk létre alkalmazásunk számára. Ezután egy futásidejű képet építünk a tetejére 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;\""]

Most készen áll a konténerünk. Megtehetjük vele az összes szokásos dolgot. Felépíthetünk egy konténert, futtathatjuk inline konfigurációkkal, és egy olyan szolgáltatások tárházába tolhatjuk, mint például a Dockerhub.

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

A fenti docker runparancsnak az alkalmazást így kell kiadnia:

Végül hozzuk létre a docker-compose fájlt. A környezettől függően általában különböző dokkoló-író fájlokkal rendelkezik, és a -fflag használatával kiválaszthatja a használni kívánt fájlt.

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

And if we do docker-compose up we should see output like so:

Great! We have now achieved our goal. We can reconfigure our application easily in both development and production environments in a very convenient way. We can now finally build only once and run everywhere!

If you got stuck or have additional ideas, access the source code on GitHub.

? Next steps

The current implementation of the shell script will print all variables included within the .env file. Most of the time we don’t want to expose all of them. You could implement filters for variables you don’t want to expose using prefixes or a similar technique.

? Alternative solutions

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.