Closed seanblonien closed 3 years ago
Hey!
First of all, thank you for considering the use of this package.
The idea behind this package was that I can only have one build of my app and then change the environment variables accordingly to my environments. My specific use case was that I always deploy my frontend with Docker or inside some VM where I have full control over how my app is started, I'm not using any static file serving solutions (e.g: S3 buckets, GCP's Storage, or the same in Azure) because they are not allowing me to set how I want to start my app. So in short, you can not use this package when you are trying to serve your frontend from a hosted solution (e.g: S3 bucket).
The reason for the start
script modification and to insert there my runtime-env-cra
command is to have a runtime-env.js
file inside the public/build
directory. That is needed to have your parsed environment variables on windows.__RUNTIME_CONFIG__
. If you check out the content of the runtime-env.js
file that's all you have there:
window.__RUNTIME_CONFIG__ = {"NODE_ENV":"development","API_URL":"https://my-awesome-api.com"};
How is this will appear in your code?
That's why you need to include the following in your index.html
file
<!-- Runtime environment variables -->
<script src="%PUBLIC_URL%/runtime-env.js"></script>
This way you will have access to your environment variables on window.__RUNTIME_CONFIG__
.
The "magic" is really simple behind the runtime-env-cra
command, if you supply NODE_ENV=development
to the script, it will use your .env
file to parse the environment variables to window.__RUNTIME_CONFIG__
, if you pass nothing or any other word then DEVELOPMENT
it will parse the variables from your OS based on the keys inside the .env
file in the directory.
So for example, you are running your app in production with the following command:
runtime-env-cra && nginx -g "daemon off;"
and your .env
looks like this (which could be your .env.example, will explain this later)
NODE_ENV=
API_URL=
The script will go through the .env
file and only use the keys (NODE_ENV
and API_URL
).
When it finds the API_URL
key, it will parse API_URL
from the OS ( process.env.API_URL
), and applies the key and value to the runtime-env.js
file (the key is from your .env
file, but the value will come from the OS).
To have the ability to change the variables during a restart or to have them on your window
object, you must start your app with the runtime-env-cra
command. Hot reloads won't trigger the regeneration of the runtime-env.js
file. You need to restart the container or rerun the command to reflect the environment variable changes you applied before. It will just recreate the runtime-env.js
file with the updated vars.
Like I mentioned in the first part, you must have full control over how your app is starting and you will need node.js and npm in that environment (that's why in the Docker example I installed node.js and npm)
You can also specify a custom config and env file name.
So if you have your final build inside a build
folder, just use runtime-env-cra --config-name=./build/runtime-env.js
and if you have a different env file name you can also give that runtime-env-cra --config-name=./build/runtime-env.js --env-file=./.env.production
via the supported flags.
You can also use a different name than runtime-env.js
, just don't forget to change inside your index.html
the reference.
The default values are:
config-name=./runtime-env.js
env-file=./.env
Hope I answered everything, and I would be glad to answer more If you have any. :)
All in all, if you are using Docker to deploy your application it's really easy to make use of runtime-env-cra
(that's how I use it in production). The same goes if your using it inside some VM where you have full control, but it's not usable if you are using S3 buckets.
Here is a small article I wrote about this package.
Yes, you really answered everything - thank you for such a thorough response!
How you are running the app in Docker/VM makes total sense to me, and this solution seems like a good fit for it. I am curious though, if you are using react-scripts start
in production, is it any less performant or any different from serving the built version of the app? I always thought react-scripts start
was a developmental command (with hot reloading/no minification/no optimization) and it was never suitable for running production. Curious to see your experience/findings running that in production.
Back to the original problem - your specific example of how to pass in those different args into runtime-env-cra
really cleared up my specific use-case for me a ton! I think it's as simple as doing what you suggested. I'll outline the step-by-step process I'm thinking about implementing this (mainly for anyone else who may run into this same issue of "wanting to statically-serve the app, but also use run-time-like environment variables that you can re-generate without rebuilding"):
(assumes your app has window.__RUNTIME_CONFIG__
usage in the app already and index.html has link to /runtime-env.js
)
react-scripts build
build
as the 'base' artifact for the app, duplicate that artifact for each environment.
build-dev
, build-stage
, build-prod
where the contents of the folder are all the same/copies of build
runtime-env-cra --config-name=./build-[env]/runtime-env.js --env-file=./.env.[env]
runtime-env-cra --config-name=./build-dev/runtime-env.js --env-file=./.env.dev
I recognize this solution is still "static" because it uses config values from static files in your repo, but I believe that's a decent place to store the environment variables so it is clear where the values come from (Docker is the same thing anyways I believe). And if you did want to change the variables during run time, you can re-run runtime-env-cra
in the environment itself as long as you bundle its respective .env file) to regenerate runtime-env.js
. Or, you could just manually change runtime-env.js
on the server if you wanted to instantly change it.
Let me know if this solution makes sense/sounds feasible @kHRISl33t
I think it solves our use case, so I will be proving this out in the next couple of weeks. If you don't hear back from me it means it's working haha. I feel like this package just automates or sets a pattern for generating global Javascript variables from .env files, which doesn't sound like an inherently difficult problem, but it does completely fix the environment variables being coupled to a CRA build, so I love the solution. The fact that I understand how straightforward this package is a good thing π
Hey, sorry for the late reply :) I was away for a few days.
I tried to be as simple as it can get with this package, so I'm glad you got the key points. :)
Well, it's never recommended to use react-scripts start
in production, although I used it a few times with serve
for testing purposes in the office to show something to my peers or PMs quickly.
The points you mentioned seem a good starting point, but the 2. point why would you need build-dev
, stage
& prod
?
I think it would be better to have one build folder, which represents the app and you can customize it via environment variables. I like to keep my master as the source of truth, and when I'm merging or pushing something there, it will be always deployed to dev/staging and the production release should be manual. But it totally depends on your needs and the workflow of your team, but I find it a bit hard to manage in the long run.
The rest seems fine, but --env-file
can be always used with the .env.sample
for example because it is just a reference point for the keys and not for the values, all keys should be the same in all environments.
Well, yeah, this solution is static, because you need a reference file for the environment variables, which must live in the repo, but that could also be left out, if we use some sort of a prefix like REACT_APP_<ENV_NAME>
and we only search and parse environment variables which are prefixed with REACT_APP
, that way .env.sample
can be left out. This would mean a breaking change, what do you think @seanblonien ? :)
I'm glad you like the idea of this package, and that was my main purpose to solve these problems for not just me, but for everyone else who struggles with environment variables on the client-side. :)
Also thanks for the submitted PR's, you are awesome!
Yes, one, singular build folder like you described there is what I intended to say, I just made it seem more complicated than that. One source of truth definitely best way to go.
And hmm interesting suggestion. I do like the current functionality where it gives an error when it looks for an environment variable but doesn't find it, and I believe that can only happen if it knows what environment variables to look in the first place, obviously from the .env file for now. So if we left out the .env.sample file and only used REACTAPP* variables and have no "reference list" to use, it sounds like we may lose that validation. If losing that functionality is the case, I currently like the existing functionality then
Okay then, anyway it was just a brainstorm, I like the current design too. :)
@seanblonien
Merged your PR, update the README with more information (and also with you as a contributor π :) ), released version 0.2.4.
Sorry that it took a bit long, hope now you and your team can use it easily! :)
Thank you! And yea no worries about it being long, the fact that it's in now makes me happy and is all I care about π
Really love the idea of this package - it has the potential of saving my team from multiple rebuilds for each environment.
But I'm not quite clear on how it's supposed to be used in production, and I didn't quite understand how the docker examples are relevant to the problem either.
So, it's clear we need the .env file to create the
runtime-env.js
file in development, and it's clear from the examples you just runruntime-env-cra --config-name=./build/runtime-env.js && react-scripts start
to do so. That makes sense. What I don't get is how these environment variables work after you runreact-scripts build
. None of the examples showed how to actually serve the app after it's built with different/varying runtime environment variables.As I understand it, CRA will create a build directory of all of the static files to serve for the site. In production, you serve this directory and everything works by serving /index.html. So some questions arise:
runtime-env-cra
in each environment (to pull values from process.env) to re-generate theruntime-env.js
file?runtime-env-cra
while the app is already being served?.env.production
and generate a differentruntime-env.js
file accordingly when deployed to that environment?I feel like the answer is just "yes, you need to run
runtime-env-cra
in every environment and you have to use a node environment", but I just wanted to confirm that this makes purely static file serving not possible.