Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow building once for multiple environments #12904

Open
HansBrende opened this issue Dec 11, 2022 · 1 comment
Open

Allow building once for multiple environments #12904

HansBrende opened this issue Dec 11, 2022 · 1 comment

Comments

@HansBrende
Copy link

HansBrende commented Dec 11, 2022

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.:

const correctValue = window.location.hostname.startsWith("dev.") ? process.env.REACT_APP_DEV_VALUE : 
    window.location.hostname.startsWith("staging.") ? process.env.REACT_APP_STAGING_VALUE :
        process.env.REACT_APP_PROD_VALUE;

Describe the solution you'd like

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 application
CMD 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.

@melloware
Copy link

We have run into this too and are currently using a hacky solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants