How to load environment variables in React - reactjs

I've been trying to load environment variables in React and I can't seem to figure it out. I have tried multiple aproaches:
Load them using the dotenv-webpack package
webpack.config.dev.js
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const template = require('html-webpack-template');
const Dotenv = require('dotenv-webpack');
const baseConfig = require('./webpack.config.base');
module.exports = merge(baseConfig, {
mode: 'production',
plugins: [
new HtmlWebpackPlugin({
template,
inject: false,
appMountId: 'app',
mobile: true,
lang: 'es-ES',
title: 'My App',
meta: [
{
name: 'description',
content: 'My App',
},
],
}),
new Dotenv(),
],
});
.env
API_HOST=http://localhost:8000
REACT_APP_API_HOST=http://localhost:8000
Passing it directly on the package.json script:
"start": "webpack-dev-server --config ./webpack.config.dev.js"
Using .env on the webpack command
webpack --env.API_HOST=http://localhost:8000
Using webpack.environmentPlugin
const webpack = require('webpack');
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const template = require('html-webpack-template');
const baseConfig = require('./webpack.config.base');
module.exports = merge(baseConfig, {
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
publicPath: '/',
contentBase: './dist',
compress: true,
stats: 'minimal',
overlay: true,
historyApiFallback: true,
port: 8081,
hot: true,
},
plugins: [
new HtmlWebpackPlugin({
template,
devServer: 'http://localhost:8081',
inject: false,
appMountId: 'app',
mobile: true,
lang: 'es-ES',
title: 'My App',
meta: [
{
name: 'description',
content: 'React template.',
},
],
}),
new webpack.EnvironmentPlugin({
API_HOST: 'http://localhost:8000',
}),
],
});
None of this approaches work and when I try to access process.env variables in my React code I get undefined
Any ideas of what I could be doing wrong?

I've been fighting with environment variables myself for some time, when I wanted to provide settings to the Firebase project but not load them into the public repository.
As far as I know, you need to name you environment variables should always start with the prefix REACT_APP_. You can define them whereever you like, but if you created your app with create-react-app tool then you can put your variables in .env file, or a few other files - (https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables)
The pain for me started when I wanted to make my own files, because I had two different Firebase projects - one for staging and one for production.
I end up with using react-app-env module which helped with:
- defining my own files - staging.env and production.env
- auto prefix my variables with REACT_APP_
For example:
I have defined Firebase ApiKey in staging.env file:
API_KEY=xxxxxxxxxxxxxxxxxxx
when I use it in my firebase.js file, I use it as:
const config = {
apiKey: process.env.REACT_APP_API_KEY,
}
And to make sure that I develop against staging environment (Firebase project) I've changed my package.json to:
"scripts": {
"start": "react-app-env --env-file=staging.env start",
},
Hope that helps!

You need to specify the webpack config file correct. You will need to create a separate config for dev. (webpack.config.dev.js)
Example here.
scripts: {
"dev": "webpack --env.API_HOST=http://localhost:8000 --config webpack.config.dev.js"
}
Also, you need to use Webpack.DefinePlugin.
plugins: [
...
new webpack.DefinePlugin({ `process.env.API_HOST`: JSON.stringify(${env.API_HOST}) })
]
or you can use reduce to make it more comprehensive.
const envKeys = Object.keys(env).reduce((prev, next) => {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
return prev;
}, {});
return {
plugins: [
...
new webpack.DefinePlugin(envKeys)
]
};

Agree with #Philip's answer, this is how I structure my dev.config.js
...
plugins: [
new webpack.DefinePlugin({
// process.env
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
WHY_DID_YOU_UPDATE: process.env.WHY_DID_YOU_UPDATE,
},
// my other global flags
__CLIENT__: true,
__SERVER__: false,
__DEVELOPMENT__: true,
__DEVTOOLS__: true
}),
]
I also use better-npm-run to manage my package.json, where you can easily manage the env variables
"betterScripts": {
"dev": {
"command": "concurrently --kill-others \"better-npm-run watch-client\" \"better-npm-run start-dev\" \"gulp watch --gulpfile semantic/gulpfile.js\""
},
"why-did-you-update": {
"command": "better-npm-run dev",
"env": {
"WHY_DID_YOU_UPDATE": true
}
},
"watch-client": {
"command": "node webpack/webpack-dev-server.js",
"env": {
"UV_THREADPOOL_SIZE": 100,
"NODE_ENV": "development",
"NODE_PATH": "./src",
"PORT": 3000
}
},
"build": {
"command": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js"
}
},
Hope this information helps you too!

I find a simple solution for this. You need to install 'dotenv-webpack', then add this configuration to your webpack config:
const Dotenv = require('dotenv-webpack');
...
plugins: [
new Dotenv(),
],
...
.env
DB_HOST=127.0.0.1
DB_PASS=foobar
S3_API=mysecretkey
Finally you can access your env variables in your app
console.log(process.env.DB_PASS);
From the docs: the .env values for DB_HOST and S3_API are NOT present in our bundle, as they were never referenced (as process.env.[VAR_NAME]) in the code.
Hope it helps!

You can specify environment variables in the package.json scripts section:
{
...
"scripts": {
"start": NODE_ENV=development webpack-dev-server
},
...
}

Related

why process.env doesn't work in app.js file but works in webpack.config.js?

why process.env doesn't work in app.js file but works in webpack.config.js? I have my own webpack that I created myself. It was not created with CRA
the version so it is correct to deduce? Or is there any way to get it out? Or is it better not to drive at all? since it seems to me that this is also not correct, since the env file should be in the exception and on the server, when loading, probably nothing will be known about the version ...
I want to output the version to the console so I can see the webpack version. But I can't do it even though I made the necessary settings. if I output data in webpack, then everything works correctly if I output data in a simple js file, erroneous errors. in general, I want to make the version visible on the server after the build, and I want to do it in this way, you can simply output console.log, but this seems like the wrong solution console.log('ver 1.74.2')
import React from "react";
import styles from "./styles.scss";
const onClickEvent = (e) => {
e.preventDefault();
alert("You Clicked Me!");
};
const App = () => {
console.log(process.env.TERM_PROGRAM_VERSION);
return (
<div className={styles.content}>
<div className={styles.label}>OWN WEBPACK</div>
<button className={styles.btn} onClick={onClickEvent}>
Click Me 😎
</button>
</div>
);
};
export default App;
const webpack = require("webpack");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const production = process.env.NODE_ENV === "production";
const Dotenv = require("dotenv-webpack");
console.log(process.env.TERM_PROGRAM_VERSION);
module.exports = {
entry: { myAppName: path.resolve(__dirname, "./src/index.js") },
output: {
path: path.resolve(__dirname, "./dist"),
filename: production ? "[name].[contenthash].js" : "[name].js",
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ["babel-loader"],
},
{
test: /\.s(a|c)ss$/,
exclude: /node_modules/,
use: [
production ? MiniCssExtractPlugin.loader : "style-loader",
{
loader: "css-loader",
options: {
modules: true,
sourceMap: !production,
},
},
{
loader: "sass-loader",
options: {
sourceMap: !production,
},
},
],
},
],
},
resolve: {
extensions: ["*", ".js", ".jsx", ".scss"],
},
plugins: [
new CleanWebpackPlugin(),
new Dotenv(),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title: "Webpack & React",
template: "./src/index.html",
favicon: "./public/favicon.ico",
}),
new MiniCssExtractPlugin({
filename: production ? "[name].[contenthash].css" : "[name].css",
}),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
"process.env.MY_ENV": JSON.stringify(process.env.MY_ENV),
}),
],
devServer: {
port: 3001,
hot: true,
},
mode: production ? "production" : "development",
};
"scripts": {
"start": "npm run build && node server/index.js",
"dev": "NODE_ENV=development test=$TERM_PROGRAM_VERSION webpack serve --config webpack.config.js",
"build": "NODE_ENV=production webpack --config webpack.config.js",
"build:dev": "NODE_ENV=development webpack --config webpack.config.js",
"build:watch": "NODE_ENV=development node ./node_modules/webpack/bin/webpack.js --config webpack.config.js --watch"
},
//server/index.js
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
if (process.env.NODE_ENV === 'development') {
console.log('in development.');
} else {
console.log('in production.');
}
/* App Config */
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, '../dist')));
/* Server Initialization */
app.get('/', (req, res) => res.sendFile('index.html'));
var port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server initialized on: http://localhost:${port} // ${new Date()}`));
Change your env variable to REACT_APP_TERM_PROGRAM_VERSION then reload your app.
React env variables must start with REACT_APP_ prefix. See https://create-react-app.dev/docs/adding-custom-environment-variables/

Webpack and Lighthouse performance test

Hy,
I'm new to webpack and after hard learning I know that I know nothing :-).
I'm using React and I have 2 questions:
How can I run my build version in the browser, for the lighthouse test. At the moment I'm using npm run dist (below package.json). Which is working but I have the feeling it is not the correct way and my dist folder gets deleted. If I use npx create-react-app I can use serve -s build therefor.
If I make my Lighthouse performance test I get "Enable text compression". So I installed the compression-webpack-plugin and brotli-webpack-plugin. I have now in the dist folder the br and gz files but in the HTTP response header I don't get Content-Encoding: br or gzip and lighthouse still blames me for this.
package.jsong
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js --open --progress --colors --hot",
"dist": "webpack-dev-server --config webpack.prod.js --open --mode production",
"build": "webpack --config webpack.prod.js"
}
webpack.common.js
const HtmlWebPackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
module.exports = {
entry: {
main: "./src/index.js"
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
use: [
{
loader: "html-loader",
options: {
attributes: true
}
}
]
},
{
test: /\.(svg|png|jpg|gif|webp|jpeg)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath: "imgs"
}
}
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "./index.html"
})
]
};
webpack.dev.js
var path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
module.exports = merge(common, {
mode: "development",
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].bundle.js",
publicPath: "/"
},
devServer: {
historyApiFallback: true,
contentBase: "./dist"
}
});
webpack.prod.js
var path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const CompressionPlugin = require("compression-webpack-plugin");
const BrotliPlugin = require("brotli-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = merge(common, {
mode: "production",
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[hash].bundle.js"
},
devtool: "source-map",
performance: {
hints: false
},
optimization: {
splitChunks: {
chunks: "all"
},
minimizer: [
new TerserPlugin({
parallel: true,
cache: true,
sourceMap: true
})
]
},
plugins: [
new CompressionPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: /\.(js|html|svg)$/,
threshold: 8192,
minRatio: 0.8
}),
new BrotliPlugin({
asset: "[path].br[query]",
test: /\.(js|html|svg)$/,
threshold: 10240,
minRatio: 0.8
})
]
});
.babelrc
{
"presets": ["#babel/preset-env", "#babel/preset-react"],
"plugins": [["#babel/transform-runtime"]]
}
THX for any help.
If you run the build script you will get the optimized code that you can deploy on a webserver. Running npx serve build would be an easy way to simulate a webserver with the bulid files. I guess the dist script does the same using the webpack dev server.
Text compression is something you would normally configure on the webserver or proxy that hosts your website. I guess these webpack plugins do the same thing but it's normally a one liner in a webserver or proxy and you don't have to interfere with the create-react-app standard config.
Text compression examples:
Express.js
Nginx
Here the solution:
I added server.js
const express = require("express");
const path = require("path");
const port = 8080;
const app = express();
app.get("*.js", (req, res, next) => {
req.url = req.url + ".br";
res.set("Content-Encoding", "br");
res.set("Content-Type", "application/javascript; charset=UTF-8");
next();
});
app.use(express.static("./dist"));
app.get("*", (req, res) => {
res.sendFile(path.resolve("./dist", "index.html"));
});
app.listen(port);
console.log("Server started");
Also changed my package.json
"scripts": {
"stard": "webpack-dev-server --config webpack.dev.js --open --progress --colors --hot",
"build": "webpack --config webpack.prod.js",
"prod": "node server.js"
},
had to add to my webpack.prod.js publicPath: "/"
...
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].[hash].bundle.js",
publicPath: "/"
},
...

React App ignores NODE_ENV of the production mode

I'm fairly new to React and I've built a client side react app using webpack as module bundler and npm. It works smoothly in development with Webpack devServer. While in production, I've used express as a server. When running at localhost:8080, it displays fine but I get these warnings. I set up the NODE_ENV='production', but still same warnings.
Here is my production configuration file
production.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const webpack = require('webpack')
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const config ={
devtool: 'cheap-module-source-map',
entry: [
'react-hot-loader/patch',
'./client/main.js'
],
output: {
path: path.resolve(__dirname, 'build'),
filename: 'app.bundle.js',
publicPath:'/'
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
//query: { sourceMap: false },
options: {
importLoaders: 1,
}
},
{
loader: 'postcss-loader'
}
]
})
},
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.(png|jpe?g|gif|svg|ico|\.woff$|\.ttf$|\.wav$|\.mp3$)$/i,
use: ['file-loader?name=img/[name].[ext]',
'image-webpack-loader']
},
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
loader: 'file-loader?name=fonts/[name].[ext]'
}
]
},
plugins: [
//index.html custom template
new HtmlWebpackPlugin({
title: 'Index',
template: './index.html'
}),
new webpack.EnvironmentPlugin(
{
'process.env':
{
NODE_ENV: JSON.stringify('production') }
}
),
//extract css files
new ExtractTextPlugin({filename:"styles.css",disable:false,allChunks:true}),
new UglifyJsPlugin({
sourceMap: false,
mangle: true,
beautify:false,
compress: {
warnings: false, // Suppress uglification warnings
pure_getters: true,
unsafe: true,
unsafe_comps: true,
screw_ie8: true
},
output: {
comments: false
}
})
]
};
module.exports=config
package.json
{
"name": "react-app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"webpack": "webpack",
"dev": "webpack-dev-server --colors",
"prod": "npm run build && node deploy.js",
"build": "webpack --config production.config.js --progress --colors"
}
//dependencies are omitted
}
Try using this package: https://www.npmjs.com/package/cross-env
I believe there is a problem with setting NODE_ENV=production in windows command prompts which it solves.
Usage example:
Build script:
cross-env NODE_ENV=production webpack
webpack.config.js:
const webpack = require('webpack');
const env = process.env.NODE_ENV || 'production';
//...
plugins = [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(env)
}
})
]
//...
Hope that helps.

webpack DefinePlugin use external config.js

I am new in webpack
I have this code in webpack.config.js
var path = require('path');
var webpack = require('webpack');
var pkg = require('./package.json');
var myconfig = require('./webpack.myconfig.js');
// bundle dependencies in separate vendor bundle
var dependencies = Object.keys(pkg.dependencies).filter(function (el) {
//exclude font packages from vendor bundle & css-toggle-switch
if (el.indexOf('font') !== -1 || el.indexOf('css-toggle-switch') !== -1) {
return false;
}
else return true;
});
module.exports = {
entry: {
libs: dependencies,
main: './src/index'
},
output: {
path: path.join(__dirname, myconfig.buildPath),
publicPath: myconfig.uiURL,
filename: 'dist/js/[name].js',
chunkFilename: '[id].[name].js'
},
plugins: [
new webpack.DefinePlugin({
DEBUG_MODE: JSON.stringify(false),
TIMEOUT: JSON.stringify(30000),
API_URL: JSON.stringify(myconfig.apiUrl),
"process.env": {
NODE_ENV: JSON.stringify("production")
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'libs',
filename: 'dist/js/libs.js',
minChunks: Infinity
})
],
};
and this is myconfig.js
myconfig = {
uiURL: 'http://example.com/',
apiUrl: 'http://api.example.com/api/'
};
module.exports = myconfig;
and if I run this syntax
webpack -p --config webpack.config.js --progress --colors
I will get the result like
/dist/js/libs.js
/dist/js/main.js
but I want the result not like this, I want the result to be:
/dist/js/myconfig.js
/dist/js/libs.js
/dist/js/main.js
What really I want is,
I want in main.js file, it will use myconfig.apiUrl as depedencies,
so when I deployed to production or qserver, I just change myconfig.js.
Thank you
webpack.myconfig.js is used in webpack config file so you have to rebuild every time when webpack.myconfig.js is changed.
You could use myconfig.json and send http request to get it dynamically.
If you just want to get myconfig.js in /dist/js/, you can use CopyWebpackPlugin. It can copy static files from one path to another.
Add a new new line plugins section like :
plugins: [
new CopyWebpackPlugin([{ from: APP_DIR + '\\images', to: APP_DIR + '\\build\\images' }]),
new webpack.DefinePlugin({
DEBUG_MODE: JSON.stringify(false),
TIMEOUT: JSON.stringify(30000),
API_URL: JSON.stringify(myconfig.apiUrl),
"process.env": {
NODE_ENV: JSON.stringify("production")
}
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'libs',
filename: 'dist/js/libs.js',
minChunks: Infinity
})
],
Here CopyWebpackPlugin copies file from one path to another.
Make sure you var CopyWebpackPlugin = require('copy-webpack-plugin');at top of webpack.config.js & added same in package.json & installed via npm.

hot-reload angularjs with webpack

I use webpack with this configuration:
var webpack = require('webpack');
var CopyWebpackPlugin = require('copy-webpack-plugin');
module.exports = {
context: __dirname + '/app',
entry: {
app: './app.js',
vendor: ['angular', 'angular-route', 'angular-resource', 'angular-bootstrap']
},
output: {
path: __dirname + '/app',
filename: 'js/app.bundle.min.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"js/vendor.bundle.min.js"),
new CopyWebpackPlugin([
{ from: "../node_modules/bootstrap/dist/fonts/", to:"fonts/"},
{ from: "../node_modules/bootstrap/dist/css/bootstrap.min.css", to:"css/"}
]),
new webpack.optimize.UglifyJsPlugin({
minimize: true,
sourceMap: false,
mangle: false
})
]
};
In script of my package.json i have:
scripts": {
"clean": "rimraf ../assets",
"prestart": "npm install",
"build": "webpack",
"start": "webpack-dev-server -d --hot --inline --content-base ./app"
},
It is possible reload application when i change everything in js file without stop and start server?
Check it out angular-hot-reloader. It is based on a few simple webpack loaders which are just adding a little piece of code to each reloadable file. The piece of code declares the file as hot-reloadable to webpack, and then does some hacky magic to make angular aware of the changes, recompiling the templates against the scope, and patching the controller’s prototype methods.
Usage:
const componentHotLoader = require.resolve('../loaders/component-loader');
const serviceHotLoader = require.resolve('../loaders/service-loader');
const jadeHotLoader = require.resolve('../loaders/jade-loader');
// add componentHotLoader and serviceLoader
(webpackConf.module.preLoaders = webpackConf.module.preLoaders || []).push(
{ test: /\.component\.js$/, loader: componentHotLoader, exclude: [/client\/lib/, /node_modules/, /\.spec\.js/] }
);
(webpackConf.module.preLoaders = webpackConf.module.preLoaders || []).push(
{ test: /\.service\.js$/, loader: serviceHotLoader, exclude: [/client\/lib/, /node_modules/, /\.spec\.js/] }
);
(webpackConf.module.postLoaders = webpackConf.module.postLoaders || []).push(
{ test: /\.html/, loader: jadeHotLoader }
);
You can use this example as a reference

Resources