node programmatically set process environment variables not available to imported modules - javascript

I'm new, hopefully this question is properly formatted and formulated. Can't wait to see your answers on this question. Let's get to it..
Context
Past weekend I was trying to implement es2015 syntax support in my create-react-app configuration files, which was straight forward. All I had to do was use babel-register and babel-preset-env to get it working. So far so good you could say, however it wasn't all good. After some hours of searching I found that process.env variables are not passed through to imported modules. The code below will demonstrate my issue.
The code
package.json
{
...
"scripts": [
"good": "NODE_ENV=development BABEL_ENV=development node -r babel-register scripts/start.js",
"bad": "node -r babel-register scripts/start.js"
],
"devDependencies": {
"babel-core": "^6.26.3",
"babel-preset-env": "^1.7.0",
"babel-register": "^6.26.0"
}
...
}
.babelrc
{
"presets": [ "env" ]
}
scripts/start.js
'use strict'
process.env.NODE_ENV = 'development';
process.env.BABEL_ENV = 'development';
// Always works
const a = require('../src/a');
// Only when environment variables are passed in via the CLI
import b from '../src/b';
console.log('Bye bye..');
src/a.js
'use strict'
console.log('Module A:', process.env.NODE_ENV);
const a = { name: "Module A" };
export default a;
src/b.js
'use strict'
console.log('Module B:', process.env.NODE_ENV);
const b = { name: "Module B" };
export default b;
Running the code
Below you will see the output of both npm scripts:
npm run good
# Outputs:
Module B: development
Module A: development
Bye bye..
npm run bad
# Outputs:
Module B: undefined
Module A: development
Bye bye..
My question(s)
Why aren't programmatically set environment variables passed through to imported modules?
Can this be fixed while keeping es2015 syntax? (e.g. using a babel plugin?)
More info
Just moving my process.env.NODE_PATH over to the CLI won't work, create-react-app programmatically sets environment variables at multiple places in their configuration/script files. I have listed a few links below, pointing to the create-react-app repo and some of the files that are giving me troubles.
create-react-app A link to the create-react-app repo.
scripts/start.js This script sets both process.env.NODE_ENV and process.env.BABEL_ENV.
config/env.js This config file sets process.env.NODE_PATH.
Note(s)
From my current understanding, create-react-app has little to none to do with the problem I'm having. I'm interested in why programmatically set environment variables are not passed through to imported modules.
My setup
OS: Ubuntu 16.04
Node: v8.11.2
Npm: 6.3

ES6 imports are hoisted. This means they will run before the rest of the code regardless of where they are in the source. The result is that b.js will run before you have set process.env.NODE_ENV = 'development'.
Babel's output will be consistent with this and will simulate hoisted imports by moving b's require statement to the top of the file. Babel will create a start file that looks like:
'use strict';
var _b = require('../src/b');
var _b2 = _interopRequireDefault(_b);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
process.env.NODE_ENV = 'development';
process.env.BABEL_ENV = 'development';
// Always works
var a = require('../src/a');
// Only when environment variables are passed in via the CLI
It should be clear looking at that why this isn't working.
[ As a side note, many people strongly recommend that you don't set NODE_ENV at runtime ]

Answer to my second question
Thanks to the insights provided by #Mark Meyer I was able to get it to work.
scripts/start.js
'use strict'
import '../config/devEnv';
config/devEnv.js
'use strict'
process.env.NODE_ENV = 'development';
process.env.BABEL_ENV = 'development';
Now the environment variable setters are hoisted as well, making them available to all imported modules.

Related

environment variables in babel.config.js

We need to modify certain configuration/variables in our React Native app (built using Expo) depending on environment (local/dev/staging/production). I've looked at a number of libraries meant for this purpose but all seem to have a flaw that breaks for our use case:
dotenv (breaks because it tries to access 'fs' at runtime, when it's no longer available since it's not pure JS package and can't be bundled by Expo)
react-native-config (can't use it with Expo because it needs native code as part of the plugin)
react-native-dotenv (kinda works but caches config internally and ignores any .env changes until the file importing the variable is modified)
As a cleaner alternative that does not require third party plugins, I'm considering using babel's env option and just listing all of the environments as separate json objects within babel.config.js. I'm not seeing much documentation or examples on this, however. Do I just add env field at the same level as presets and plugins that contains production, development, etc. fields as in example below:
module.exports = (api) => {
api.cache(true);
return {
presets: [...],
env: {
development: {
CONFIG_VAR: 'foo'
},
production: {
CONFIG_VAR: 'bar'
}
}
}
}
Would that work? And how would I access this CONFIG_VAR later in the code?
I just ran into the same issues when trying to setup environment variables in my Expo project. I have used babel-plugin-inline-dotenv for this.
Install the plugin
npm install babel-plugin-inline-dotenv
Include the plugin and the path to the .env file in your babel.config.js file
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
env: {
production: {
plugins: [["inline-dotenv",{
path: '.env.production'
}]]
},
development: {
plugins: [["inline-dotenv",{
path: '.env.development'
}]]
}
}
};
};
In your .env.production or .env.development files add environment variables like this:
API_KEY='<YOUR_API_KEY>'
Later in your code you can access the above variable like this:
process.env.API_KEY
To access your env variables within the babel.config.js file, use the dotenv package like this:
require('dotenv').config({ path: './.env.development' })
console.log('API_KEY: ' + process.env.API_KEY)
module.exports = function() {
// ...
}

What is the difference between webpack --env.production and --mode="production"

Correct me if I'm wrong but as far as I understand from the documentation,
--env option is used ONLY for being able to get access to it within webpack.config.js if it exports a function e.g.
module.exports = function(env, options) {
console.log(env); // "production"
}
and let's say that I have my entry point index.js with the following code:
console.log(process.env.NODE_ENV); // undefined ???
I wonder if
process.env.NODE_ENV won't be assigned to any value regardless I pass --env.production or --env.development
I wonder if
webpack will enable any sort of optimizations automatically depending on a value for --env
--mode option is used to enable some bunch of optimizations right away and it will set process.env.NODE_ENV to be accessible inside my source files, e.g.
console.log(process.env.NODE_ENV); // "production" OR "development", etc ???
I wonder if
process.env.NODE_ENV will be assigned to any value accessing it from within webpack.config.js
I wonder if
Let's say that I run webpack with the following command $ webpack --mode="development"
and I have the following contents of webpack.config.js:
module.exports = {
devtool: 'source-map'
};
so, will the devtool option eventually be set to eval(if I weren't redefining devtool in my webpack.config.js or the value will be source-map, so it will be rewritten with those from my webpack.config.js file?
I asked a similar question here: Webpack environment variables confusion
First of all: both options have nothing to do with process.env.NODE_ENV. Yeah, it's confusing especially because the docs mention NODE_ENV many times.
webpack's environment variables are different from the environment variables of operating system shells like bash and CMD.exe
--env command-line option basically allows you to change the value of env.{some property} so if you just pass --env.production env.NODE_ENV will be undefined and env.production will be set to true. You would need to set it separately with --env.NODE_ENV=yourvalue. Note this has nothing to do with process.env. env is just the object passed as parameter to your function exported from webpack.config.js.
--mode command-line option was introduced in webpack v4 and you can use it to set process.env.NODE_ENV on DefinePlugin only to one of 3 values - development, production or none. It looks like it was made exclusively for DefinePlugin. If you try to console.log(process.env.NODE_ENV); inside your webpack config it will be undefined. https://github.com/webpack/webpack/issues/7074
If you want to read those options you need to export a function instead of an object from your webpack.config.js.
// webpack --mode=production --env.foo=bar --env.NODE_ENV=production
var config = {
entry: './app.js'
//...
};
console.log(process.env.NODE_ENV); // undefined!! unless you really set it in OS or with cross-env
module.exports = (env, argv) => {
console.log(argv.mode); // will print "production"
console.log(env.foo); // will print "bar"
return config;
};
cross-env
People also use cross-env to set process.env.NODE_ENV and they don't use webpack's --env or --mode at all.
The only reason for using cross-env would be if you had multiple configs in your project like postcss.config.js and webpack.config.js and you wanted to set your environment just once, use process.env.NODE_ENV everywhere and be done with it. It just won't work with DefinePlugin out of the box.
You could also do this if you don't want to use cross-env:
module.exports = (env, argv) => {
process.env.NODE_ENV = argv.mode;
return config;
};
or set mode based on process.env.NODE_ENV:
var config = {
entry: './app.js',
mode: process.env.NODE_ENV
//...
};
update 2021
webpack now added a new option --node-env so you don't need to rely on cross-env unless you use it in other places https://github.com/webpack/webpack-cli/issues/2362#issuecomment-771776945

How to load config json as environment variable nodejs

I am a newbie to node js. I have different configs for different environments viz dev, prod etc. Currently while creating my app package I copy .json to config.json and then export config.json as config variable (global) and use it throughout the app.
config = require(__dirname + '/config/config');
(config.httpProxy && config.httpProxy.enabled);
I want to load specific env.json as part of environment variable (for dev dev.json's all keys are exported as global variable in app) instead of copying it into app's config.json, so that same app can be used in different env. How to do that.
PS: for application packaging support and dependency management I use gulp and npm.
Please help.
you can name your files like this:
config.development.json
config.production.json
config.test.json
Then load files as:
config = require(__dirname + '/config/config.' + process.env.NODE_ENV);
where process.env.NODE_ENV value can be development/production/test
you have to start your application as
NODE_ENV=development node app.js
for this to work.
I suggest you use this module called config it handles all your env config files.
https://www.npmjs.com/package/config
Just make a folder named config and makes files in it as :
1. development.json
2. qa.json
3. production.json
While starting server provide relevant environment as others mentioned.
Then you can use any property mentioned in your config files.
If you are running your project from your script then set NODE_ENV into your package.json file.
{
...
"scripts": {
"nodemon:server": "NODE_ENV=dev NODE_PATH=. nodemon --exec \"babel-node --stage 1\" server.js",
"prod:server": "NODE_ENV=prod NODE_PATH=. nodemon --exec \"babel-node --stage 1\" server.js"
},
"author": "'Shubham Batra'",
"license": "MIT",
....
}
"prod:server": "**NODE_ENV=prod** NODE_PATH=. nodemon --exec \"babel-node --stage 1\" server.js"
than use in config,js file e.g.
if (process.env.NODE_ENV === 'test' ) {
url = 'http://abc:3000';
dbUrl= 'xyz';
password = '***';
} else if (process.env.NODE_ENV === 'prod') {
url = 'http://def:3000';
...
}
export default {
conf: {
url,
dbUrl,
...
}
}
after that, you can import this config file anywhere in your project and use conf

pass variable to webpack to build different bundle

I am trying to build a different bundle based on an argument passed to webpack.
I have a create-react-app that I have ejected from and currently currently if I do npm run build it builds a bundle using webpack. As I have both an english and spanish version of the site I was hoping that I could pass an argument here. i.e. to build a Spanish version something like npm run build:es.
My webpack file currently just builds the English bundle. There is a separate process during the application to pull in translations, but during the building of the bundle it would be great if I could stipulate which language to build the bundle for.
Anyone have any ideas.
The relevant webpack code is below:
//default messages for translations
var defaultMessages = require('/translations/en.json');
//more webpack stuff......
{
test: /\.(js|jsx)$/,
loader: require.resolve('string-replace-loader'),
query: {
multiple: Object.keys(defaultMessages).map(key => ({
search: `__${key}__`,
replace: defaultMessages[key]
}))
}
},
Webpack can receive a --env argument, which is then passed to the webpack.config file. The key is to export a function returning the configuration from your webpack.config.js, not the raw configuration.
$ webpack --env=lang_es
And in webpack.config.js:
module.exports = function(env) {
if (env === 'lang_es') {
// ...
}
return {
module: {
// ...
},
entry: {
// ...
}
}
}
And in package.json:
"scripts": {
"build_es": "webpack --env=lang_es",
}
This was originally really meant to distinguish between build types, e.g. development or production, but it's just a string passed into the config file - you can give it any meaning you want.
As hinted in the comments, using environment variables is the second, webpack-independent, approach. You can set the environment variable directly in package.json's scripts section:
"scripts": {
"build_es": "BUILD_LANG=es webpack",
}
(Use cross-env to set the environment when developing on Windows).
And in webpack.config.js:
if (process.env.BUILD_LANG === 'es') {
// ...
}
This environment-based approach has been used in a few places already (for example Babel's BABEL_ENV variable), so I'd say that it has gathered enough mileage to consider it proven and tested.
Edit: fixed the cross-env part to mention that it's only necessary on Windows.

How to initialise a global variable in unit test runs?

I understand that global variables are bad but I want to use one.
excerpt from package.json:
"scripts": {
"start": "nodemon jobsServer.js",
"test": "cross-env NODE_ENV=test ./node_modules/.bin/istanbul cover -x \"**/*.spec.js\" ./node_modules/mocha/bin/_mocha -- jobs js --recursive -R spec"
},
jobsServer.js:
global.appPath = require('app-root-path');
// more code here
Now I want to be able to access appPath anywhere in the app.
When I run npm start it picks up the global variable and I am happy.
But when I run npm test it does not load the global (since the global is defined in the server file) and therefore all references to appPath break.
I DO NOT want to do:
const appPath = require('app-root-path');
In every single .spec.js test file.
How can I load the global variable for every spec file?
You just need to add a setup file in test/mocha.opts that will be loaded before to start any test, then you will be available to access those global variables, for example:
test/mocha.opts
--require should
--require ./test/setup
--ui bdd
--globals global
--timeout 200
test/setup.js
global.app = require('some-library')
global.window = {}
global.window.document = {}
docs:
http://unitjs.com/guide/mocha.html#mocha-opts
You could probably write a module to hold your globals and import it in your test:
import getGlobals from './whatever.globals.spec.mjs';
...
describe('Whatever', () => {
it('test global', () => {
const globals = getGlobals();
...
});
Where whatever.globals.spec.mjs is just :
export default function getGlobals() {
return ...; // your global info
}

Categories