We all have faced challenges with Webpack, and even though we know it is a blessing, sometimes it challenges us a lot. In this post, I will share a simple solution for a problem that I spent hours trying to solve and even got-4o couldn't solve.
The key to resolving the process.env issue after webpack lies in harnessing the power of 'webpack.DefinePlugin({ 'process.env': 'process.env', })'. This simple step can empower you to take control of your webpack setup, ensuring that your process.env is no longer empty. It's a game-changer that can boost your confidence and capabilities as a developer.
TL;DR: If you need your minified nodeJS to dynamically use a .env file and you need webpack for some reason (usually because of the TypeScript), jump straight to the code.
Package.json
In short, yarn 4.2.2, webpack 5, and typescript 5.4:
{
(...),
"packageManager": "yarn@4.2.2",
"dependencies": {
(...),
"dotenv": "^16.4.5",
"express": "^4.19.2",
},
"devDependencies": {
(...),
"@types/express": "^4.17.21",
"@types/node": "^20.12.12",
"module-alias": "^2.2.3",
"node-polyfill-webpack-plugin": "^3.0.0",
"nodemon": "^3.1.0",
"terser": "^5.31.0",
"terser-webpack-plugin": "^5.3.10",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.4.5",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-node-externals": "^3.0.0"
}
}
Explanation:
This will tell webpack that you want the proccess.env to be dynamic and accessible after the minification and will solve the issue of the process.env empty after webpack.
I must thank Doug, a user from StackOverflow who shared this solution.
Even though it could be more intuitive, the documentation can be found at Webpack DefinePlugin.
But how does it work, and what is so special about this?
We have basically three options:
to inform the .env values
to pass a .env file
to get control over the process.env to make it dynamic
In cases 1 and 2, the webpack will replace the values it finds in your code with the values you declared or that were found in your .env file. It will disable the process.env, so even if you set a variable before running your minified script, it won't be available.
As I'm sharing how to setup the webpack.DefinePlugin, let's dive into the possibilities and the outcomes:
1. Informing the values
This can be achieved by this setting in your webpack.config.js:
export default {
...,
plugins: [
...,
new webpack.DefinePlugin({
'process.env': {
'SOME_VARIABLE': 'some value',
'SOME_OTHER_VARIABLE': JSON.stringify(true),
},
})
],
}
The outcome is the same as if you inform the .env file: webpack will do a find/replace in your code, and every occurrence of YOUR_VARIABLE will have the value you informed. Example:
// original code
const sample = process.env.SOME_VARIABLE;
console.log(sample);
// after webpack
const sample = 'some value';
console.log(sample);
If you minified your code and did not add process.env in mangle.reserved, your code will then look like this (scroll to see more about mangle.reserved):
const r = 'some value';console.log(r);
Other ways to inform the variables include extracting the keys/values from the env file. Feel free to contact me if you want to know more. You can see my LinkedIn here.
2. Informing the .env file
The result is the same, and the webpack will do a find/replace. To inform your file location, you can do this:
export default {
...,
plugins: [
...,
new webpack.DefinePlugin({
'process.env': '.env', // or `.env.${process.env.NODE_ENV}`
})
],
}
3. Make your .env file dynamic
To avoid having an empty process.env file when you run your application, you want to inform 'process.env' (as a string), and the webpack will not apply any change, making it dynamic.
export default {
...,
plugins: [
...,
new webpack.DefinePlugin({
'process.env': 'process.env',
})
],
}
IMPORTANT: When doing this, the setting mangles.reserved = ['process', 'env'] won't make any difference.
Webpack.config Settings:
export default {
...,
optimization: {
minimize: false,
minimizer: [new TerserPlugin({
extractComments: false,
terserOptions: {
ecma: 2017,
output: {
comments: false
},
mangle: {
// reserved: ['process', 'env'], // having this or not won't affect
toplevel: true
}
}
})]
},
plugins: [
...,
new webpack.DefinePlugin({
'process.env': 'process.env',
})
],
}
Output Example (even without the mangle.reserved:
(...) return e.status(200).send({is_online:s,date_time:new Date,response:r.text,version:process.env.SERVER_VERSION})}))); (...)
Full Example:
/* eslint-disable no-undef */
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import nodeExternals from 'webpack-node-externals';
import NodePolyfillPlugin from 'node-polyfill-webpack-plugin';
import TerserPlugin from 'terser-webpack-plugin';
import webpack from 'webpack';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const mode = process.env.NODE_ENV === 'prod' ? 'production' : 'development';
export default {
entry: './src/index.ts',
output: {
path: resolve('dist'),
filename: 'hmx-app.min.js'
},
mode,
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
target: 'node',
externals: [nodeExternals()],
resolve: {
extensions: ['.ts', '.js'],
alias: {
'apis': resolve(__dirname, 'src/apis'),
'services': resolve(__dirname, 'src/services'),
'utils': resolve(__dirname, 'src/utils'),
}
},
optimization: {
minimize: false,
minimizer: [new TerserPlugin({
extractComments: false,
terserOptions: {
ecma: 2017,
output: {
comments: false
},
mangle: {
toplevel: true
}
}
})]
},
plugins: [
new NodePolyfillPlugin(),
new webpack.DefinePlugin({
'process.env': 'process.env',
})
],
};
Let me know if you have any questions and happy coding!
Daniel
Bình luận