Set NODE_ENV from Grunt task - node.js

I would like to set the NODE_ENV variable at the beginning of a Grunt task, to development or production, but it looks it's not as simple as I thought.
The reason, why I would like this is that I use grunt-webpack, which expects NODE_ENV to be set correctly to "development" or "production". But I also would like to initialize my tasks exclusively from grunt, if possible.
I created the following test Gruntfile, using the grunt-shell and cross-env modules:
function log(err, stdout, stderr, cb, e) {
if (err) {
cb(err);
return;
}
console.log(process.env.NODE_ENV);
console.log(stdout);
cb();
}
module.exports = function(grunt) {
grunt.initConfig({
shell: {
dev: {
command : 'cross-env NODE_ENV="development"',
options: {
callback: log
}
},
dist: {
command : 'cross-env NODE_ENV="production"',
options: {
callback: log
}
}
}
});
grunt.loadNpmTasks('grunt-shell');
};
Line 6 of log() should echo the actual value of process.env.NODE_ENV, but it constantly says undefined, even if I check it manually in the node console.
If I set it manually from the terminal, like set NODE_ENV=production (set is for Windows), everywhere echoes the value production, as I would like it to.

Your test won't work because grunt-shell runs a child_process and your callback runs after it ends and under the main process.
Same thing would happen with cross-env.
If you want to pass an environment variable to grunt-shell, you should use the options configuration according to the documentation.
For example:
grunt.initConfig({
shell: {
dev: {
command : 'echo %NODE_ENV%', //windows syntax
options: {
execOptions: {
env: {
'NODE_ENV': 'dev'
}
},
callback: log
}
}
}
});
This will still print undefined for process.env.NODE_ENV, but the value of NODE_ENV will be available in the stdout because of the echo.
On a side note, it sounds like you're trying to run a process (grunt-shell), which runs a process (cross-env), which runs a process (webpack or grunt-webpack).
Why not just use the cross-env example usage? It looks pretty close to what you need.
Or you can just define the variable in the task config itself and lose all of these wrappers.

LifeQuery's answer helped me a lot to find out what the problem actually was. I first realized that webpack.DefinePlugin() actually doesn't change anything on process.env.NODE_ENV (and it would be too late anyway, as it transforms code parsed by webpack after all loaders).
After this I created a solution, which does what I want. This is how my customized Gruntfile.js begins:
'use strict';
const path = require('path');
const webpack = require('webpack');
module.exports = function (grunt) {
// Setting the node environment based on the tasks's name or target
let set_NODE_ENV = function () {
const devTasks = ['webpack-dev-server', 'dev', 'hmr', 'watch'],
devTargets = [':dev'],
task = grunt.cli.tasks[0], // The name of the (first) task we initialized grunt with ('webpack-dev-server' if started 'grunt webpack-dev-server)
target = ':'+grunt.option('target'),
devEnv = (devTasks.indexOf(task) > -1 || devTargets.indexOf(target) > -1);
process.env.NODE_ENV = devEnv ? 'development' : 'production';
}();
const webpackConfig = require('../assets/webpack.config');
grunt.initConfig({
// ...usual Gruntfile content
});
};
I created a whitelist of grunt task names and targets when I set the process.env.NODE_ENV. As it is placed before the grunt.initConfig(), the configuration object can use process.env.NODE_ENV with the desired state.
It will set NODE_ENV to "development" if starting definitely webpack-dev-server, dev, hmr or watch tasks, or any other tasks with the :dev target.

Related

NodeJS + WebPack setting client static data

I have a NodeJS/React/WebPack application that I'm trying to take environment variables that are present at build time and export them as variables that are available to the client without having to request them with AJAX.
What is currently setup is a /browser/index.js file with a method that is exported however the variables are not getting expanded when webpack runs.
function applicationSetup()
{
const config = JSON.parse(process.env.CONFIG);
const APPLICATION_ID = process.env.APPLICATION_ID;
.........
}
During the build process we run node node_modules/webpack/bin/webpack.js --mode production with npm.
What do I need to do in order to expand the environment variable to be their actual values when webpack creates the .js file?
Edit 8/23
I've tried adding it in the webpack.DefinePlugin section of the webpack.config.js file however it's still doesn't seem to be available in the client side code. What am I missing?
Edit #2 (webpack.config.js)
const getClientConfig = (env, mode) => {
return {
plugins: [
new webpack.DefinePlugin({
__isBrowser__: 'false',
__Config__: process.env.CONFIG,
__ApplicationID__:process.env.APPLICATION_ID
})]
}
module.exports = (env, options) => {
const configs = [
getClientConfig(options.env, options.mode)
];
return configs;
};

How to handle different .env files in Next?

What I want
I have created a new Next project, and I want to manage app behavior according to the NODE_ENV variable. The application must load different variables located in different .env files. eje. if I load NODE_ENV=development, the application should to load the variables located in .env.development file. What is the most efficient and safe way to do it in Next.
What I have
package.json
In the dev script I pass the environment type:
"scripts": {
"dev": "cross-env NODE_ENV=development next",
"build": "next build",
"start": "next start",
},
next.config.js
In the next configuration I load environment variables from correct .env file with dotenv library according to NODE_ENV variable pass in devscript in package.json.
const path = require('path');
const withOffline = require('next-offline');
const webpack = require('webpack');
require('dotenv').config({
path: path.resolve(
__dirname,
`.env.${process.env.NODE_ENV}`,
),
});
module.exports = withOffline({
webpack: (config) => {
// Returns environment variables as an object
const env = Object.keys(process.env).reduce((acc, curr) => {
acc[`process.env.${curr}`] = JSON.stringify(process.env[curr]);
return acc;
}, {});
// Allows you to create global constants which can be configured
// at compile time, which in our case is our environment variables
config.plugins.push(new webpack.DefinePlugin(env));
return config;
},
});
.env.development
TITLE=modo development
pages/index.js
function HomePage() {
return <div>{process.env.TITLE}</div>
}
export default HomePage
With this aproach...
This is the most efficient and safe way to handle diferent .env files in Next?
Nextjs supports env by default without the need to use of webpack.DefinePlugin, just pass it to the env property of next.config.js.
So your code will become:
// next.conf.js
const path = require('path');
const withOffline = require('next-offline');
const webpack = require('webpack');
require('dotenv').config({
path: path.resolve(
__dirname,
`.env.${process.env.NODE_ENV}`,
),
});
module.exports = withOffline({
env: {
VAR_1: process.env.VAR_1
...
// List all the variables that you want to expose to the client
}
});
PAY ATTENTION: these env variables may be exposed to the client side (if you use them in one of your app page).
For example, if your process.env is containing secrets, and by mistake you are using one of them in one of the pages / components that are used by pages, they will be inside js files that are downloaded to the client side.
Since Next.js 9.4, there is a built in .env loading functionality, read about it here https://nextjs.org/docs/basic-features/environment-variables
Agree with the #felixmosh that Nextjs supports env by default without the need to use of webpack.DefinePlugin, but...
It may be seen as limited and confused while loading different configurations on each environment. You can see common problem here
enter link description here
You can solve this problem easily by following these small steps.
You’ll need to create a folder config on the root, with all environment stages you’d like to have.
You can add the initial/common configuration in default.js like this.
API: {
API_URL: process.env.API_URL || '<http://localhost:4000>',
ENDPOINT: '********',
IS_MOCKING_ENABLED: false,
},
}```
Include above config files in the Next.config.js file by publicRuntimeConfig. If you’d like to have it just on the server-side use just serverRuntimeConfig like this.
const APIConfig = config.get('API')
const nextConfig = {
publicRuntimeConfig: {
APIConfig,
},
}
module.exports = nextConfig ```
Usage in any file.
const { publicRuntimeConfig } = getConfig()
const APIConfig = publicRuntimeConfig.APIConfig
[...] ```
Finally, in your package.json. You can inject the environment variables to load the appropriate configuration.
"start:local": "NODE_ENV=development run-p dev"
Reference: enter link description here for detail explanation.

How to use the Node debug module with Grunt?

How can I use Node's debug module with Grunt?
I know that to use the module I need to set the DEBUG env var in the command line like so:
$ DEBUG=* node app.js
But, I am struggling to figure out how to do it with Grunt.
I used grunt-env to help set env variable in grunt as below:
grunt.loadNpmTasks('grunt-env');
// Define the configuration for all the tasks
grunt.initConfig({
// Env-specific configuration
env: {
options: {
//Shared Options Hash
},
dev: {
NODE_ENV: 'development',
DEBUG: 'wukong:*,loopback:security:role,-not_this'
},
test: {
NODE_ENV: 'test',
DEBUG: 'wukong:*,-not_this'
}
},
// ...
}
However, it doesn't work because debug module load and save process.env.DEBUG at the very beginning of module initialization: https://github.com/visionmedia/debug/blob/master/node.js#L209
Thus, I played a trick to load the DEBUG variable again via its "hidden" public API - enable as:
const debug = require('debug');
debug.enable(process.env.DEBUG);
module.exports = debug;
When this debug wrapper is used instead, grunt could print out debug message as expected.
Hope this workaround works for you as well. ;)

gulp.js+browserify: Dynamically generate development-specific files

I have an application that has some development-specific debugging code in it. Currently, all development code is guarded by a variable called dev at the top of the file. Here's an example of what my app does:
var dev = true;
if (dev) {
console.log("Hello developer");
} else {
console.log("Hello production");
}
When I go to deploy my application, I have to manually change the dev variable form true to false. This sucks.
I'm in the middle of migrating from hand-rolled builds to gulp.js and I want to solve this development vs. production build problem cleanly. I'm thinking about the following:
// Inside main.js
var dev = require('./isdev');
if (dev) //...
// Inside isdev.js:
module.exports = true;
Now, when I build for production, instead of manually setting the dev flag to false, I want to replace isdev.js from module.exports = true; to module.exports = false;. My specific question is, how do I automate gulp such that gulp development produces a file with dev = true and gulp production produces a file with dev = false.
Here's an update to those who are curious.
First, I have an options.js:
exports.dev = false;
I also have a options_dev.js:
exports.dev = true;
Inside of gulpfile.js, I have the following code that parses input arguments:
// Parse the arguments. Use `gulp --prod` to build a production extension
var argv = parseArgs(process.argv.slice(2));
var dev = !argv['prod']; // Whether to build a development extension or not
Finally, when I pipe to browserify, I have the following:
var resolve = require('browser-resolve');
// ...
.pipe(browserify({
debug: dev,
resolve: function(pkg, opts) {
// Replace options.js with options_dev.js if this is a dev build
if (dev) {
opts.modules['./options'] = 'src/options_dev.js';
}
return resolve.apply(this, arguments);
}
}))
The magic happens by using a custom resolve function, dynamically swapping ./options with options_dev for development builds. The browserify docs say:
You can give browserify a custom opts.resolve() function or by default it uses browser-resolve.
When we run gulp, we build a development version. When we run gulp --prod, we build a production version. The value of require('./options').dev allows us to dynamically change things like server endpoints, etc. Cool!
The way that I've seen this done is to set the environment variable on the command line before the execution command. An example of doing this with the Node.JS CLI (in a bash-like environment) would be:
ENV=dev node
> process.env.ENV
'dev'
Then in your code, you could do:
var dev = process.env.ENV === 'dev'
So with gulp, you could use:
ENV=dev gulp <task name>
I tested this out with the following snippet, and it works:
gulp.task('dev', function(){
if (process.env.ENV === 'dev')
console.log("IT WORKED");
else
console.log("NO DICE");
});
Edit:
You can write out the environment to the file isdev right before building:
var fs = require('fs');
gulp.task('build', function(){
if (process.env.ENV === 'dev')
fs.writeFileSync('isdev', 'module.exports = true');
else
fs.writeFileSync('isdev', 'module.exports = false');
// kick off build
});
Now, the correct value will be present in isdev for any require call in the built bundle. You could extend this to other specified environments as well (or to other configuration flags).

Nodemon-like task in Grunt : execute node process and watch

I feel like I'm missing something.
Here is what I want to achieve :
Having a grunt task that executes my server.js and runs watch task in parallel. It feels to me that this is precisely one of the tasks grunt was designed for but I can't achieve this configuration.
Among others, I have read this :
Running Node app through Grunt
but I still can't make it.
Here is my Gruntfile.js :
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
watch: {
scripts: {
files: ['*.js'],
tasks: ['start'],
options: {
nospawn: true
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.registerTask('start', function() {
grunt.util.spawn({
cmd: 'node',
args: ['server.js']
});
grunt.task.run('watch');
});
grunt.registerTask('default', 'start');
};
I have "grunt-contrib-watch": "~0.3.1" which should be higher version than grunt-contrib-watch#0.3.0 as in the previously mentioned post.
If you could help me achieve the proper configuration, I would be extremely grateful. But more in general, I don't understand why there is no official grunt-contrib-nodemon-like package and task since I have the feeling it would be another great reason to use grunt (which I really like as a tool !)
Thanks
Edit: grunt-nodemon
since writing this, a nice person developed that.
I was having a lot of trouble using grunt.util.spawn to fire off new processes. They would run, but they wouldn't give me any output back. Perhaps you can figure out what I could not in these docs. http://gruntjs.com/api/grunt.util#grunt.util.spawn
Two problems I see with what you have:
I think grunt.registerTask() has to take three arguments when you use a callback function to run your task.
I don't think you can just call node server.js over and over again everytime a file changes. It will work on the first time, for it to really work you'd have to manage the server as a child process, killing and restarting it on subsequent file changes.
For the registerTask arguments try this, just to see if you can get something to work in your current implementation.
http://gruntjs.com/api/grunt.task#grunt.task.registertask
It takes (taskName, description, taskFunction) like so:
grunt.registerTask('start', 'My start task description', function() {
grunt.util.spawn({
cmd: 'node',
args: ['server.js']
});
grunt.task.run('watch');
});
That might at least get your watch to run node server.js the first time a file changes.
Here's what I would do instead.
Either just use nodemon $ nodemon server.js as is
or...
Read the source and use grunt-develop
He is managing the server as a child process, might be what you're looking for.
or...
Get grunt-shell
npm install grunt-shell --save-dev
And use it to run nodemon for you:
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
serverFile: 'server.js',
shell: {
nodemon: {
command: 'nodemon <%= serverFile %>',
options: {
stdout: true,
stderr: true
}
}
},
watch: { /* nothing to do in watch anymore */ }
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-shell');
grunt.registerTask('default', 'shell:nodemon');
};
$ grunt shell:nodemon
I sincerely hope that helps. Good luck!
Hi I also came across this problem and here is my solution (based on nackjicholson's answer). This uses grunt-nodemon in a spawned process. so I can:
Reload nodejs
Watch for changes to e.g. .less files
Get output of both tasks
grunt.loadNpmTasks('grunt-nodemon');
grunt.initConfig({
nodemon: {
dev: {
options: {
file: 'server.js',
nodeArgs: ['--debug'],
env: {
PORT: '8282'
}
}
}
},
});
grunt.registerTask('server', function (target) {
// Running nodejs in a different process and displaying output on the main console
var nodemon = grunt.util.spawn({
cmd: 'grunt',
grunt: true,
args: 'nodemon'
});
nodemon.stdout.pipe(process.stdout);
nodemon.stderr.pipe(process.stderr);
// here you can run other tasks e.g.
// grunt.task.run([ 'watch' ]);
});
Use grunt-concurrent
The issue is that tasks like watch and nodemon will never terminate, so grunt will never reach them. You need to spawn a new process.
You can do this easily using grunt-concurrent:
https://github.com/sindresorhus/grunt-concurrent
For example:
module.exports = function(grunt) {
grunt.initConfig({
...
concurrent: {
dev: {
tasks: ['nodemon', 'watch'],
options: {
logConcurrentOutput: true
}
}
}
});
};
The two will now run happily side by side.

Resources