You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Building once for multiple environments seems to be incredibly hacky. Currently it seems like the least painful solution is to inspect the hostname to determine which environment we are in, and then select the correct variables from there (ALL of which must be included in the build, preventing optimal minification, etc.) E.g.:
I would like a new react-scripts command (let's call it initenv) to add previously absent REACT_APP_* variables after the build has completed using the current server's environment variables as input. This would allow me to save the original build as a single docker image, and then later insert those environment-specific variables right before starting up the server.
This would effectively mean the ability to re-run the webpack DefinePlugin followed by another terser minification pass as a single command over the js bundle files. Which could then be run in a docker image like this:
FROM node:16-alpine AS builder
... blah blah blah ...
RUN npm run build
FROM node:16-alpine
COPY --from=builder /app/build build
# 1. insert REACT_APP_* variables from server environment into build folder js files# 2. serve applicationCMD react-scripts initenv build && serve -s build
Implementing this functionality might be tricky, since the first Define/minification pass obliterates process.env by replacing it with an object literal everywhere, which is then further minified. However, Terser has a bug/feature that would actually make this functionality possible with some clever surgery: variables accessed from process.env which are not present in that object are not replaced with undefined. Instead, the whole object literal is kept intact (e.g. {NODE_ENV: "production"}.REACT_APP_ABSENT_VARIABLE). They need to avoid minifying further here in case REACT_APP_ABSENT_VARIABLE is defined on the Object prototype! Which means, that if you were to add a random GUID to the process.env object, you could walk the AST looking for this guid in the initenv function. Any object containing this guid must have been originally process.env, so you could insert the server environment variables into that object literal at that point (above example now becomes {NODE_ENV: "production", REACT_APP_ABSENT_VARIABLE: "now present"}.REACT_APP_ABSENT_VARIABLE), and then re-run the minification step.
Alternatively, if that's too complicated, a second implementation option would be to simply add a flag to the build step that disables the DefinePlugin on REACT_APP_* variables for the first pass (and then simply run the DefinePlugin as normal later, within the specific server environment).
Describe alternatives you've considered
I've considered re-building the entire app for each environment, however I reaaaally don't like that option, philosophically speaking. npm ci helps with build reproducibility, but it can only guarantee so much... ultimately it is still trusting the package server. Also it is really annoying to have to commit package-lock.json when the slightest change (or no change--someone simply running a different npm version) adds and removes thousands of lines of code in their commits for no reason. This is also a much SLOWER solution. I simply don't want to have to wait for the app to build 4 times.
I've considered writing my own script that implements the first solution I described above, however, I would be duplicating/copy-pasting/making assumptions about a lot of existing CRA functionality, and would thus have to keep it up to date with the CRA implementation... so it seems like it would be preferable if this were baked into CRA.
I've considered ditching the process.env.REACT_APP_* pattern altogether in favor of a custom environment variable file that is swapped out right before server startup on a per-environment basis. But I would prefer a built-in solution that integrates well with the existing react development patterns. This seems like a fairly common use-case. There are a ton of articles describing various super-hacky solutions to hack around this limitation. But it would be nice if this just came out of the box.
The text was updated successfully, but these errors were encountered:
Is your proposal related to a problem?
Building once for multiple environments seems to be incredibly hacky. Currently it seems like the least painful solution is to inspect the hostname to determine which environment we are in, and then select the correct variables from there (ALL of which must be included in the build, preventing optimal minification, etc.) E.g.:
Describe the solution you'd like
I would like a new react-scripts command (let's call it
initenv
) to add previously absentREACT_APP_*
variables after the build has completed using the current server's environment variables as input. This would allow me to save the original build as a single docker image, and then later insert those environment-specific variables right before starting up the server.This would effectively mean the ability to re-run the webpack
DefinePlugin
followed by another terser minification pass as a single command over the js bundle files. Which could then be run in a docker image like this:Implementing this functionality might be tricky, since the first Define/minification pass obliterates
process.env
by replacing it with an object literal everywhere, which is then further minified. However, Terser has a bug/feature that would actually make this functionality possible with some clever surgery: variables accessed fromprocess.env
which are not present in that object are not replaced with undefined. Instead, the whole object literal is kept intact (e.g.{NODE_ENV: "production"}.REACT_APP_ABSENT_VARIABLE
). They need to avoid minifying further here in caseREACT_APP_ABSENT_VARIABLE
is defined on the Object prototype! Which means, that if you were to add a random GUID to theprocess.env
object, you could walk the AST looking for this guid in theinitenv
function. Any object containing this guid must have been originally process.env, so you could insert the server environment variables into that object literal at that point (above example now becomes{NODE_ENV: "production", REACT_APP_ABSENT_VARIABLE: "now present"}.REACT_APP_ABSENT_VARIABLE
), and then re-run the minification step.Alternatively, if that's too complicated, a second implementation option would be to simply add a flag to the build step that disables the DefinePlugin on
REACT_APP_*
variables for the first pass (and then simply run the DefinePlugin as normal later, within the specific server environment).Describe alternatives you've considered
I've considered re-building the entire app for each environment, however I reaaaally don't like that option, philosophically speaking.
npm ci
helps with build reproducibility, but it can only guarantee so much... ultimately it is still trusting the package server. Also it is really annoying to have to commit package-lock.json when the slightest change (or no change--someone simply running a different npm version) adds and removes thousands of lines of code in their commits for no reason. This is also a much SLOWER solution. I simply don't want to have to wait for the app to build 4 times.I've considered writing my own script that implements the first solution I described above, however, I would be duplicating/copy-pasting/making assumptions about a lot of existing CRA functionality, and would thus have to keep it up to date with the CRA implementation... so it seems like it would be preferable if this were baked into CRA.
I've considered ditching the
process.env.REACT_APP_*
pattern altogether in favor of a custom environment variable file that is swapped out right before server startup on a per-environment basis. But I would prefer a built-in solution that integrates well with the existing react development patterns. This seems like a fairly common use-case. There are a ton of articles describing various super-hacky solutions to hack around this limitation. But it would be nice if this just came out of the box.The text was updated successfully, but these errors were encountered: