How to have Nx environment variables in React app per environment? - reactjs

Context
I have a three projects inside my Nx workspace, two applications which are react apps (both of them have shared logic, however they are intended for different platforms web and microsoft teams and must be separated) and a library which contains logic for an api client, which both of the applications use.
The api client project requires the variable base URL which is environment specific.
I can define environment variables and introduce file replacements in the build process for .ts files (e.g. environment.ts is replaced with environment.production.ts, when configuration is production), however I do not want to reference this file (environment.ts) in the api client project so as not to introduce two way dependencies.
What have I tried
From the api project I was not able to extract the logic which depends on the URL variable as this is tied to some code generation which is changeable.
I succeeded in providing this variable by using .env file in the root of the application project, the variable is in the format NX_MY_URL, and could be accessed with process.env.NX_MY_URL.
However I was not able to change this variable when changing the build configuration (e.g. development, test, production). I have tried adding a fileReplacements task such as
"configurations": {
"development": {
"fileReplacements": [
{
"replace": "apps/ra-web/src/environments/environment.ts",
"with": "apps/ra-web/src/environments/environment.development.ts" //<----- This works fine
},
{
"replace": "apps/ra-web/.env",
"with": "apps/ra-web/.development.env" //<----- This does not work, .env values remain
}
],
Question
How can .env files be replaced based on Nx target's configuration?

Why the mentioned approach does not work?
The reason of why the fileReplacements approach is not working is because those replacements are meant for the building process, which is the bundler the one in charge (webpack or vite, etc). That file content replacement is actually working, yet it doesn't happen at file system level but at memory level (for the bundling process). As your application code does not "import" that .env file (and you should not directly depend on it), then those file replacements make no difference in the build output.
On the other side, Nx is in charge of reading the .env file (at file-system level) and loading the data as environment variables so that when the bundling process starts, those are available. This a completely separate process than the fileReplacements done by the bundler.
How to achieve what you are looking for?
If you think about projects in libs as shareable/re-usable code, then you can imagine those are external dependencies added to your apps (or other libs). As those dependencies are external, they should rely on their implementor to get the data needed for them to work.
Having the above in mind, your lib public API (the main index.ts file) should be parametrized to receive that base URL that will depend on each application. With that you can keep working with the environment.ts file replacements, get the value in the app and pass it down to the lib.
Example:
// app's main.tsx
import { environment } from './environments/environment';
import { apiClient } from '#myorg/api-client';
apiClient.init({ baseUrl: environment.baseUrl });
// api-client's index.ts
export { apiClient } from './lib/api-client';
// api-client's api-client.ts
export const apiClient = {
init: ({ baseUrl }) => { ... }
};
If you still need to work with the .env file (not trying to replace it), just the the env vars in the environment.ts as work with it as mentioned above.

Related

Gatsby, Environment variables not accessible in browser

I want to use environment variables. I created .env.development file and I put some variables. Then I include the dotenv plugin to read the variables in gatsby-config.js:
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`
});
The content of my .env.development:
GATSBY_APP=MYAPP
It's working in gatbsy-node.js but in browser (REACT) it's empty. I display console.log(process.env) and it return empty object.
Even if I install and configure gatsby-plugin-env-variables.
It looks like you're combining two approaches, and that might be where you're running into trouble.
Gatsby comes out-of-the-box with support for defining environment variables in environment-specific .env.[environment] files (e.g. .env.development), provided these files are in the root of your project (i.e. your-project/.env.development). Documentation for this feature. You do not need to install or configure dotenv for this to work.
Another approach is to use dotenv, which will allows you to use a general .env file. You need to then import and configure the tool, which is generally done at the very top line of gatsby-config.js and looks like this:
require("dotenv").config()
Note that you do not specify the environment name (e.g. development) in this scenario, and you would not commit the .env file to your repository.
The other issue you might run into is that part of your code runs server-side, using Node, and part runs client-side (in the browser). Since process.env is only available in Node, Gatsby does some additional work to make it available in the browser. We don't want all of these variables, which frequently hold secrets, to be provided to the browser, though, so Gatsby only copies over those that whose names begin with GATSBY_. Finally, as a side effect of the way that these variables get copied over, you must explicitly reference them for your build to work:
// this is okay everywhere
const GATSBY_APP = process.env.GATSBY_APP
// this won't work in code that runs client-side, but will work
// in `gatsby-node.js` and other files that only run in Node
const { GATSBY_APP } = process.env
If you wanted to whitelist your own environment variables, either as a prefix (shown here) or have a list of them, you can add something like this in the gatsby-node.js file:
exports.onCreateWebpackConfig = ({ actions, getConfig }) => {
const config = getConfig();
// Allow process.env.MY_WHITELIST_PREFIX_* environment variables
const definePlugin = config.plugins.find(p => p.definitions);
for (const [k, v] of Object.entries(process.env)) {
if (k.startsWith("MY_WHITELIST_PREFIX_")) {
definePlugin.definitions[`process.env.${k}`] = JSON.stringify(v);
}
}
actions.replaceWebpackConfig(config);
};
I was running into the same issue and just found out what I was doing wrong. It looks like you're doing the same thing.
The explanation both the documentation and #coreyward give are correct, however, note that while corey implies they're two different approaches and the issue might be that they're conflicting, they're not, they do different things - so that code of yours is fine.
What's wrong, though, is that you're console logging process.env. And as it turns out, this will always output an empty object even though the variables might be there.
You have to console.log(process.env.GATSBY_APP) directly to see any value.
(Reference)
This is essentially an extension of what Corey pointed out in his "finally as a side-effect" section, however, the way he's written it implies it's an issue with destructuring - but it's not limited to that.
In my case, I was doing the above error AND not exposing the variables with GATSBY_ properly, then while testing I started logging the whole env object like you while I searched for the cause. This meant that even after I added the correct GATSBY_ prefix I still couldn't see anything.
So in short, it's probably only your console.log line that's an issue - access the variable directly, not the env object as a whole.
The Gatsby site as a well documented section on working with environment variables. The gist is that the environment variables are only available during build time when the internal nodejs server is server rendering your site. In order, get those environment variables in the browser you need to programmatically embed them using the special gatsby-*.js files. The example they provide seems to be close to what you want to achieve.

Webpack/React json file not loading externally on build

Im trying to use the azure environment variables with my react build. So far I have an appsettings.json file that I load into react with:
import settings from './appsettings.json';
I then have webpack copy the json into build folder which goes to azure. However I think after build the app isnt actually loading the file as I can see some of my variables embedded in the "chunk.js" so its not actually reaching out the the json file in root anymore? Am I importing the file in the wrong way?
C
Two possible solutions:
var json = require('./data.json'); //with path
change your settings.json to settings.js and use module.exports = {} in it.
I believe azure would accept different forms of setting files, not limited to json.

Where in my code should I put my Meteor user methods and allow() permissions?

I am using alanning:roles and have put my users in admin groups successfully in my server - main file. However now I'm trying to add Meteor.users.allow() to my admin in order to allow them to delete other users. I'm having trouble finding an example of the right location within my code to put this. Do I put it in the server main under startup? or in a separate users collection (I'm using React)?
I think this is illustrating a blindspot in my understanding of meteor or react so if you're feeling instructive, please help me out :) Thanks!
If you are working with Meteor more recent than 1.3.0, you can place your hooks/allows/methods files anywhere under a server subdirectory, as long as you import (as in the ES6 module import) them. It's helpful to have them separated logically.
Here is a sample directory structure we're using in our project:
public/ (static files, assets)
settings/ (to be loaded as command-line args for different environments)
test/
imports/
client/
startup/
components/
views/
server/
startup/
allows/
hooks/
methods/
publications/
both/
utils/
collections/ (collections are here, because they're shared)
Now, to be honest, this is an older project, so React was not considered here, but this could still be helpful for you when you're organizing your imports. Obviously, you're going to need a couple of entry files for your client and server that import all of the necessary dependencies.
From then on, for example, in your imports/server/allows/<collection_name>.js file, you place your allows like:
import { SomeCollection } from '/imports/both/collections/someCollection.js';
SomeCollection.allow({
insert: function () {
return true;
},
update: function () {
return true;
},
remove: function () {
return true;
}
});
I prefer using absolute file path imports in Meteor projects, since there the root path resolves to the root of your project. Makes them easier to copy-paste.
Hope that helps.

React with Webpack - package a module for use in Dynamic loading in another site

I'm using Webpack as our build/bundler for an application using a standard React/Redux/etc.
We have a requirement to build out custom components that can be loaded dynamically into the main application. This would require that the component is created OUTSIDE the main development so would not be involved in the main app build. The ideal solution would be to build out the components in their own side projects, bundle up (since they will have imports/require, etc) and spit out a bundle.js file that is only that component (could be multiple components merged together). Then we'd like to be able to take that file and dynamically load it in the main application dynamically.
I understand how code splitting works with webpack to a certain degree which we use in our main project. I've also been able to successfully import SIMPLE components externally I built out externally. The problem is that these external components can get pretty hefty so using a build/bundler to put it all together in one package would be ideal. I have no idea how to go about building components externally from the main project, bundle up using webpack to merge in all of the goodies into one package and inject that new bundled component which is typically wrapped in webpackjsonp and all the other runtime stuff.
Has anybody else been able to do something crazy like this?
Thanks!
EDIT
I've been able to successfully build a silo component in it's own project using webpack and dynamically loading that into a different running application bundled with webpack by using the Output.Library options as described here
Below is the sample config I used for testing a custom react component called TestMe located inside the index.js file of the test folder.
module.exports = {
entry: {
developer: "./test"
},
output: {
path: path.join(__dirname, "dist", "static", "developer"),
filename: "MyLibrary.[name].js",
library: ["MyLibrary", "[name]"],
libraryTarget: "var"
},
externals: {
'react': 'React'
, 'react-dom': 'ReactDOM'
, 'react-dom/server': 'ReactDOMServer'
, 'react/lisb/ReactTransitionGroup': 'React.addons.TransitionGroup'
, 'react/lib/ReactCSSTransitionGroup': 'React.addons.CSSTransitionGroup'
}, ...
When imported in you will have access to the TestMe component as a global variable MyLibrary.developer.TestMe (or MyLibrary.developer.default depending on how you exported the component). The externals are there to keep Webpack from including those in the final bundle which was already included in the main application. Otherwise you're going to get a really big nasty bundle file. Check out LibraryTarget if you rather have UMD, etc.
Moral of the story here is "when all else fails, read the docs again".

gruntjs / angularjs - optional development config?

Like most js web apps we have a config.js file that contains global config information about the app, base api urls and such. These values are often different in local development than in production.
I've looked at answers like: Development mode for AngularJS using GruntJS, and also things like grunt-replace for creating an on-the-fly config file.
My issue is that the "development" part varies from developer to developer, we all need a version of the API setup so the base api urls will be different. I'd like to allow each developer to override specific variables in the config in a way that doesn't require them to commit that info to the git repo (I agree that this isn't best practice, everything should be in the repo, but as this is only 1/2 variables for this project I can overlook it)
Any ideas on how to achieve this setup?
You can use grunt-preprocess. I would have production (and dev-server, etc) values in a file, say env.json. You could use grunt to look for an optional file, say overrides.json or developer.json, which would extend/overwrite the values from env.json.
var envFile = require('./env.json');
You can create command line options to grunt with grunt.option, e.g. var env = grunt.option('env') || undefined;, which could be used to turn off overriding.
You can get data from the optional file using fs.existsSync:
var fs = require('fs');
var developerFile;
if (fs.existsSync('./developer.json')) {
developerFile = require('./developer.json');
}
The simplest way to define the grunt-preprocess context would be to use the developer.json file if present, or the env.json file if not:
context: developerFile ? developerFile : envFile;
This requires the developer file to be complete. An alternative is to extend the envFile with options from developerFile if it's present.
In my project, we use different config files (which are basically files with JS object). So every developer has his app/configs/developer/config.js file, which is not comited in the source control, so every developer has his own setup. Project uses link to app/scripts/config.js by default and this file is just a soft link to developers config file. However, there are also app/configs/staging/config.js and app/configs/production/config.js files, which are replaced when using gruntjj to build project. Those configs are just copied to build solution instead of soft linked file.
I hope that makes sense..

Resources