Webpack: process.env undefined using DefinePlugin and DotEnv - reactjs

I would like to get my variable from my .env file but I always get undefined
This is my js code :
require('dotenv').config();
class Header extends React.Component{
constructor(props){...}
render(){
console.log("NODE ENV", process.env.NODE_ENV);
console.log("REACT_APP_MYAPP", process.env.REACT_APP_MYAPP);
...
}
}
This prints :
NODE_ENV development
REACT_APP_MYAPP undefined
In my package.json there is :
"scripts":{
"start" : "webpack-dev-server --config webpack.dev.js",
"build" : "webpack --config webpack.prod.js"
}
And in my webpack.dev.js:
const webpack = require("webpack");
const merge = require("webpack-merge");
const path = require("path");
const common = require("./webpack.common.js");
module.exports = merge.smart(common, {
devServer: {
contentBase: path.resolve(__dirname, "dist"),
hot: true,
overlay: {
warnings: true,
errors: true
},
inline :true,
historyApiFallback: true,
port: 8085
},
devtool: "inline-sourcemap",
optimization: {
namedModules: true
},
plugins: [
new webpack.HotModulReplacementPlugin(),
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
"process.env.REACT_APP_MYAPP": JSON.stringify(process.env.REACT_APP_MYAPP)
})
],
mode: "development"
});
And I placed my .env file at the root of my project, next to webpack.dev.js and package.json:
REACT_APP_MYAPP=http://localhost:8080/
So I think, it doesn't success to get the variable in the file.
How can I get the REACT_APP_MYAPP value in the code please ?

Journeying through the deep, dark rabbit hole will lead you to the following facts:
Webpack 5 no longer provides process or other Node.js variables.
The role of DefinePlugin requires redefining.
Values passed to DefinePlugin must be stringified, even if they're strings.
EnvironmentPlugin makes things more confusing, given Webpack 5.
process.env isn't really something you should be using in the frontend.
You can use polyfills to re-add process and others back in, but there is a reason it's no longer available.
dotenv-wepack is neither necessary nor the way to go.
The reason for using process.env is to access variables globally. But it's a passenger on the wrong flight. We can drop it and instead just access the intended variable directly:
plugins: [
new DefinePlugin({
// With dotenv (values must be stringified)
...Object.entries(dotenv.config().parsed).reduce((acc, curr) => ({...acc, [`${curr[0]}`]: JSON.stringify(curr[1]) }), {}),
// Without dotenv
'myString': JSON.stringify('IAmAString')
})
]
In the frontend:
declare var myString: string;
console.log(myString); // 'IAmAString'
If you have several objects with variables it makes sense to abstract the stringification:
// Create a function to stringify values
function stringifyValues(object: {[key: string]: any;}){
return Object.entries(object).reduce((acc, curr) => ({...acc, [`${curr[0]}`]: JSON.stringify(curr[1]) }), {} as { [key: string]: string; });
}
// use with DefinePlugin
plugins: [
new DefinePlugin({
...stringifyValues(dotenv.config().parsed),
...stringifyValues(dotenv.config({ path: '/.env.special' }).parsed),
'myObject': stringifyValues({
name: 'Object',
description: 'to be an object'
})
})
]
If you really want access to process.env:
plugins: [
new DefinePlugin({
// this might expose confidential data about your environment
'process.env': JSON.stringify(process.env),
// the correct way
'process.env.USERNAME': JSON.stringify('Donald Hump')
})
]
In the frontend:
declare var process: any;
console.log(process) // will NOT work, because process hasn't been injected
console.log(process.env); // will work but risky
console.log(process.env.USERNAME); // correct: 'Donald Hump'

First solution by adding REACT_APP_MYAPP in start didn't worked.
But second solution worked.
SOLUTION:
Adding require('dotenv').config() file inside my webpack.dev.js and replacing :
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("development"),
"process.env.REACT_APP_MYAPP": JSON.stringify(process.env.REACT_APP_MYAPP)
})
with
new webpack.EnvironmentPlugin(['NODE_ENV', 'REACT_APP_MYAPP']);
Thank you!

There's a few ways you could make this work.
The easiest to test is to change your "start" : "webpack-dev-server --config webpack.dev.js", to "start" : "REACT_APP_MYAPP=http://localhost:8080/ node webpack-dev-server --config webpack.dev.js",
This will inject the environment variable and it will be available during the webpack build process. You can use this technique whenever using npm or node to run a command. For instance, NODE_ENV='development REACT_MY_APP=http://localhost:8080/ node myapp.js and both will be available on process.env.
You could also call your require('dotenv').config() file inside your webpack.dev.js. Then it would be set during your usage of the DefinePlugin.
Generally, you don't use the npm start command to run your development server.
As the application grows you should look at the Webpack Environment Plugin. Then you can use the .env for the "production" build settings, requiring it in the production webpack.prod.js, while setting your default/fallback environment variables using the plugin.

It works for me in my custom React app model, only if I do not include 'REACT_APP_' in variable names:
in webpack.config.js:
// Node import
const path = require('path');
const webpack = require('webpack');
require('dotenv').config();
module.exports = [
plugins: [
new webpack.EnvironmentPlugin(['NODE_ENV', 'API'])
],
]
in .env:
NODE_ENV=development
API=http://localhost:5000
When I deploy the app though (on Netlify for example), I exclude NODE_ENV from the production environment variables.

Related

How to configure the start script in webpack to use the api I want?

How to configure the start script in webpack to use the api I want?
For example when I run "npm start" my webpack source will use the productuion api like 'https://abc_loginapi.com, and when I run "npm run dev" the webpack source Mine will use local apis like http://localhost:9500/login.
My current way of doing it is quite manual, when I want to run one, I will comment the other one
export const API_HOST_LIST =
{
HostBaseURL: 'https://abc_loginapi.com'
// HostBaseURL: 'http://localhost:9500/login'
}
Is there any way to handle this problem, my source is webpack4 + reactJS
You can use environment variables to handle it, add env to script with --env argument, like this:
webpack --env prod // result { prod: true }
And call it in webpack config file
const path = require('path');
module.exports = env => {
// Use env.<YOUR VARIABLE> here:
console.log('Production: ', env.prod); // true
return {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
};
For more detail, check Webpack docs here
Like Phr0gggg mentions, using environment variables is one option.
Another option is to use the "NODE_ENV" variable.
In dev mode, process.env.NODE_ENV is equal to 'development' and in production, it's equal to 'productoin'
And you can use something like this:
const isProduction = process.env.NODE_ENV === "production"
const hostBaseUrl = () => {
if (isProduction) {
return 'http://localhost:9500';
}
return 'https://abc_loginapi.com';
}
export const API_HOST_LIST =
{
HostBaseURL: hostBaseUrl()
}
You can use process.env api to judge in webpack,
The code in webpack file like.
export const API_HOST_LIST =
{
HostBaseURL: process.env.NODE_ENV ==='dev'? 'http://localhost:9500/login':'https://abc_loginapi.com'
}
The code in package.json file like.
"scripts": {
"dev": "NODE_ENV=dev npm run start",
"start": "npm start"
}

Invalid hook call. When using external folders with webpack

I have divided my app into folders to be able to reuse code. My project consists of two folder common and common-web that I need to use in my customer-web-app project.
This is the directory structure
The customer-web-app is using razzle and its webpack config is
module.exports = {
modifyWebpackConfig({
env: {
target, // the target 'node' or 'web'
dev, // is this a development build? true or false
},
webpackConfig, // the created webpack config
webpackObject, // the imported webpack node module
options: {
razzleOptions, // the modified options passed to Razzle in the `options` key in `razzle.config.js` (options: { key: 'value'})
webpackOptions, // the modified options that will be used to configure webpack/ webpack loaders and plugins
},
paths, // the modified paths that will be used by Razzle.
}) {
console.log("TT:", webpackConfig, paths);
const copy = Object.assign({}, webpackConfig);
copy.resolve.modules = (copy.resolve.modules || []).concat([
path.resolve(__dirname, "../common-web/src"),
path.resolve(__dirname, "../common/src"),
path.resolve(__dirname, "../admin-common/src"),
]);
copy.resolve.alias = {
...(copy.resolve.alias || {}),
"#material-ui/core": path.resolve(
__dirname,
"./node_modules/#material-ui/core"
),
react: path.resolve("./node_modules/react"),
"react-dom": path.resolve("./node_modules/react-dom"),
"react-router-dom": path.resolve(
__dirname,
"./node_modules/react-router-dom"
),
"#common": path.resolve(__dirname, "../common/src"),
"#customer": path.resolve(__dirname, "../customer-common/src"),
"#web": path.resolve(__dirname, "../common-web/src"),
};
// Do some stuff...
return copy;
},
modifyWebpackOptions({
env: {
target, // the target 'node' or 'web'
dev, // is this a development build? true or false
},
options: {
webpackOptions, // the default options that will be used to configure webpack/ webpack loaders and plugins
},
}) {
webpackOptions.notNodeExternalResMatch = (request, context) => {
return /\#web|\#common|\#customer/.test(request);
};
webpackOptions.babelRule.include = webpackOptions.babelRule.include.concat([
path.resolve("../common-web/src"),
path.resolve("../common/src"),
path.resolve("../customer-common/src"),
]);
return webpackOptions;
},
};
But when I run the app its giving me
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app

Ejected React app stuck on “Starting the development server” after changing entry config

I need to support multiple entry points in the React App. One of the option to do that is via eject: npm run eject.
Here is an original source I use How to Add Multiple Entry Points to Your React App
Create a new application and eject it:
$ npx create-react-app react-hello
$ cd react-hello
$ npm run eject
Then add into App.js:
import React from "react";
And start application to make sure it works with default configuration:
$ npm start
To be able to have multiple entry points, I have to adopt the configuration of entry in the config/webpack.config.js. Original configuration looks like (without comments):
entry: [
isEnvDevelopment &&
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appIndexJs,
].filter(Boolean),
To support multiple entries, I have to convert it into the associative array/hash table, in this case I use index as a key for the default index page:
entry: {
index: [
isEnvDevelopment && require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appIndexJs
].filter(Boolean)
},
And as soon as I change the format, the npm start stucks and Starting the development server... lasts forever. I try to build it and get an error:
$ npm run build
...
> node scripts/build.js
Creating an optimized production build...
Failed to compile.
Cannot read property 'filter' of undefined
Note: I haven't added a new entry yet, I only modify the current entry to be able to add another one later.
Webpack Entry Points says my configuration is correct.
What am I doing wrong and miss, could you please help?
I ran into this same issue. For me, the problem came from the "ManifestPlugin" further down. It looks like the initial configuration refers to the default "main" entry point key.
new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
// This next line is the problem!!!!
const entrypointFiles = entrypoints.main.filter(
fileName => !fileName.endsWith('.map')
);
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
Because you made the entry point into "index" instead of "main", entrypoints.main is now undefined. If you were to change the above code to entrypoints.index, then the code should work as expected. However, this solution is not very flexible and only includes one entry point. The final solution I came up with was this.
new ManifestPlugin({
fileName: 'asset-manifest.json',
publicPath: paths.publicUrlOrPath,
generate: (seed, files, entrypoints) => {
const manifestFiles = files.reduce((manifest, file) => {
manifest[file.name] = file.path;
return manifest;
}, seed);
const entrypointFiles = Object.values(entrypoints).map(values => {
return values.filter(
fileName => !fileName.endsWith('.map')
)
});
return {
files: manifestFiles,
entrypoints: entrypointFiles,
};
},
}),
Encountered this issue using an ejected create-react-app 5.0.1 as-well.
In addition to the above answer, had to change the webpack output filename from static/js/bundle.js static/js/[name].js
output: {
filename: isEnvProduction
? "static/js/[name].[contenthash:8].js"
: isEnvDevelopment && "static/js/bundle.js
to
output: {
filename: isEnvProduction
? "static/js/[name].[contenthash:8].js"
: isEnvDevelopment && "static/js/[name].js"

How to set buildId to config (publicRuntimeConfig) or environment variable?

We're using nextjs (9.5.3) and next-i18next (6.0.3) for translations. To implement a special caching, I need to access the build ID outside of next.config.js in i18n.js in order to set it to the locale path:
localePath: path.resolve(`./public/static/cache/${config.buildId}/locales`)
In next.config.js I can access the build ID pretty easily:
withPWA({
webpack(config, { buildId }) {
[...]
config.plugins.push(
new CopyWebpackPlugin({
patterns: [
{
context: path.join(__dirname, 'public/static/locales'),
from: '**/*',
to: path.join(__dirname, `public/static/cache/${buildId}/locales`)
}
]
})
);
// not working:-(
process.env.CONFIG_BUILD_ID = buildId;
return config;
},
publicRuntimeConfig: {
buildId: process.env.CONFIG_BUILD_ID
}
});
However setting it to the environment variable process.env.CONFIG_BUILD_ID is not working, so publicRuntimeConfig.buildId will still be undefined.
Is there any way to access the build ID outside the next config?
Instead of
// not working:-(
process.env.CONFIG_BUILD_ID = buildId;
You should use Webpack DefinePlugin (https://webpack.js.org/plugins/define-plugin/). No extra imports or installs are needed: this plugin is already a part of the next.js package. Just replace those lines with:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.CONFIG_BUILD_ID': JSON.stringify(buildId)
})
);
Full next.js.config may look like this:
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
config.plugins.push(
new webpack.DefinePlugin({
'process.env.CONFIG_BUILD_ID': JSON.stringify(buildId)
})
);
return config;
}
}
module.exports = nextConfig;

Evaluate conditionals at compile time in babel / webpack

I have a config file where I have a variable that can have the value X or Y depending if it's prod or dev.
I want to evaluate it at compile time.
I was able to get the NODE_ENV value from babel-plugin-transform-node-env-inline but can't figure out how to evaluate the expression. I tried babel-plugin-transform-conditionals but it throws strange code errors. Errors that don't exist.
I can evaluate at runtime since in that case both prod and dev values will be available in app.js, which I can't have.
No idea where to go from here!!
Any totally different solution would work too (like somehow compile a different file), but not sure how to go about it.
Not sure about your question, you can just write the if statement like this
if (process.env.NODE_ENV === 'development') {
// ...
}
It will be transform to
// or true
if (false) {
// ...
}
if it is false, then that's a kind of unreachable code, which will be stripped by UglifyJs plugin automatically (use new webpack.optimize.UglifyJsPlugin() in your webpack config)
also don't forget to use DefinePlugin (and cross-env if you are using windows) to declare the process.env.NODE_ENV variable or it may not work
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV)
})
I have never use the plugin you mention. But default webpack setup should work like what I have done in the following:
webpack.config.js
var config = {
entry: {
...
},
output: {
...
},
module: {
...
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
}
}),
// some other plugins
]
};
So the NODE_ENV is coming from your environment when you run webpack.
package.json
{
"scripts": {
"start": "NODE_ENV=development webpack-dev-server --progress --hot --inline --colors --host 0.0.0.0 --port 9999",
"production": "NODE_ENV=production webpack",
}
}
In your package.json run the webpack in environment you specify.
Your config.js
export default {
isDebug: process.env.NODE_ENV === "development",
logLevel: process.env.NODE_ENV === "development" ? "error" : "info"
};
Another example I use in my application for redux. I only wanted to have redux-logger during development.
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import logger from "redux-logger";
import promise from "redux-promise-middleware";
import reducer from "./reducers";
const middleware = process.env.NODE_ENV === "production" ? [promise(), thunk] : [promise(), thunk, logger()];
export default createStore(
reducer,
applyMiddleware(...middleware)
);
After the compile if you look at the generated code from the above redux example.
var middleware = true ? [(0, _reduxPromiseMiddleware2.default)(), _reduxThunk2.default] : [(0, _reduxPromiseMiddleware2.default)(), _reduxThunk2.default, (0, _reduxLogger2.default)()];
process.env.NODE_ENV === "production" have been evaluate to true.
Make one Webpack config for development, and one config for production, which you need anyway. Specify your variable in each config using the DefinePlugin, then you can reference that global variable anywhere in your bundle.

Resources