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

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).

Related

Jest Run All Tests(include only/skip) in CI

While in development we occasionally use skip or only to debug a particular test or test suit. Accidentally, we might forget to revert the cases and push the code for PR. I am looking for a way to detect or automatically run all tests even for skip and only tests in our CI pipeline(using Github action). It can be in either case as follow.
Fail the test when there are skip or only tests.
Run all tests even for skip and only.
Very much appreciate any help.
I came up with a solution for the second part of the question about running all tests even for skip and only. I don't think it's elegant solution, but it works and it's easy to implement.
First of all you need to change test runner to jest-circus if you work with jest bellow 27.x version. We need it so our custom test environment will use handleTestEvent function to watch for setup events. To do so, install jest-circus with npm i jest-circus and then in your jest.config.js set testRunner property:
//jest.config.js
module.exports = {
testRunner: 'jest-circus/runner',
...
}
From Jest 27.0 they changed default test runner to jest-circus so you can skip this step if you have this or higher version.
Then you have to write custom test environment. I suggest to write it based on jsdom so for example we also have access to window object in tests and etc. To do so run in terminal npm i jest-environment-jsdom and then create custom environment like so:
//custom-jsdom-environment.js
const JsDomEnvironment = require('jest-environment-jsdom')
class CustomJsDomEnvironment extends JsDomEnvironment {
async handleTestEvent(event, state) {
if(process.env.IS_CI === 'true' && event.name === 'setup') {
this.global.describe.only = this.global.describe
this.global.describe.skip = this.global.describe
this.global.fdescribe = this.global.describe
this.global.xdescribe = this.global.describe
this.global.it.only = this.global.it
this.global.it.skip = this.global.it
this.global.fit = this.global.it
this.global.xit = this.global.it
this.global.test.only = this.global.test
this.global.test.skip = this.global.test
this.global.ftest = this.global.test
this.global.xtest = this.global.test
}
}
}
module.exports = CustomJsDomEnvironment
And inform jest to properly use it:
//jest.config.js
module.exports = {
testRunner: 'jest-circus/runner',
testEnvironment: 'path/to/custom/jsdom/environment.js',
...
}
Then you just have to setup custom environment value IS_CI in your CI pipeline and from now on all your skipped tests will run.
Also in custom test environment you could watch for skipped test and throw an error when your runner find skip/only. Unfortunately throwing an error in this place won't fail a test. You would need to find a way to fail a test outside of a test.
//custom-jsdom-environment.js
const JsDomEnvironment = require('jest-environment-jsdom')
const path = require('path')
class CustomJsDomEnvironment extends JsDomEnvironment {
constructor(config, context) {
super(config, context)
const testPath = context.testPath
this.testFile = path.basename(testPath)
}
async handleTestEvent(event, state) {
if(process.env.IS_CI === 'true' && event.name === 'add_test') {
if(event.mode === 'skip' || event.mode === 'only') {
const msg = `Run ${event.mode} test: '${event.testName}' in ${this.testFile}`
throw new Error(msg)
}
}
}
}
module.exports = CustomJsDomEnvironment

Set NODE_ENV from Grunt task

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.

How to setup gulp-watch with gulp-connect livereload?

Most questions and answers on this site do not contain an easy-to follow general approach to using these two libraries together.
So, being that we use the gulp-connect npm package, and we want to make use of the gulp-watch npm package, how do we set it up so that we can:
watch changes in some files
perform some operation, like building / compiling those files
live-reload the server once the building is done
First, you will define your build task. This can have pre-required tasks, can be a task of some sort, it doesn't matter.
gulp.task('build', ['your', 'tasks', 'here']);
Then, you will need to activate the connect server. It is important that you are serving the result of the compilation (in this example, the dist directory) and you're enabling livereload with the livereload: true parameter.
const connect = require('gulp-connect');
gulp.task('server', function() {
return connect.server({
root: 'dist',
livereload: true
});
});
Finally, you will setup your watch logic. Note that we're using watch and not gulp.watch. If you decide to change it, notice that their APIs are different and they have different capabilities. This example uses gulp-watch.
const watch = require('gulp-watch');
gulp.task('watch-and-reload', ['build'], function() {
watch(['src/**'], function() {
gulp.start('build');
}).pipe(connect.reload());
});
gulp.task('watch', ['build', 'watch-and-reload', 'server']);
The watch-and-reload task will depend on the build task, so that it ensures to run at least one build.
Then, it will watch for your source files, and in the callback, it will start the build task. This callback gets executed every time that a file is changed in the directory. You could pass an options object to the watch method to be more specific. Check the usage API in their repository.
Also, you will need to start the build action, for which we're using gulp.start. This is not the recommended approach, and will be deprecated eventually, but so far it works. Most questions with these issues in StackOverflow will look for an alternative workaround that changes the approach. (See related questions.)
Notice that gulp.start is called synchronously. This is what you want, since you want to allow the build task to finish before you proceed with the event stream.
And finally, you can use the event stream to reload the page. The event stream will correctly capture what files changed and will reload those.
Bringing up to speed, as per current stable gulp release
gulp.task API isn't the recommended pattern anymore. Use exports object to make public tasks
From official documentation: https://gulpjs.com/docs/en/api/task#task
To Configure watch and livereload you need following
gulp.watch
gulp-connect
watch function is available in gulp module itself
install gulp-connect using npm install --save-dev gulp-connect
To configure gulp-connect server for livereload we need to set property livereload to true
Run all tasks followed by task that calls watch function in which globs and task are given. Any changes to files that match globs trigger task passed to watch().
task passed to watch() should signal async complection else task will not be run a second time. Simple works: should call callback or return stream or promise
Once watch() is configured, append .pipe(connect.reload()) followed by pipe(dest(..)) where ever you think created files by dest are required to reload
Here is simple working gulpfile.js with connect lifereload
const {src, dest, watch, series, parallel } = require("gulp");
const htmlmin = require("gulp-htmlmin");
const gulpif = require("gulp-if");
const rename = require('gulp-rename');
const connect = require("gulp-connect");
//environment variable NODE_ENV --> set NODE_ENV=production for prouduction to minify html and perform anything related to prod
mode = process.env.NODE_ENV || 'dev';
var outDir = (mode != 'dev') ? 'dist/prod': 'dist/';
const htmlSources = ['src/*.html'];
function html() {
return src(htmlSources)
.pipe(gulpif(
mode.toLowerCase() != 'dev',
htmlmin({
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})
)
)
.pipe(dest(outDir))
.pipe(connect.reload());
}
function js(){
return src('src/*.js')
.pipe(uglify())
.pipe(rename({ extname: '.min.js' }))
.pipe(dest(outDir))
.pipe(connect.reload());
}
function server() {
return connect.server({
port: 8000,
root: outDir,
livereload: true
})
}
function watchReload() {
let tasks = series(html, js);
watch(["src/**"], tasks);
}
exports.html = html;
exports.js = js;
exports.dev = parallel(html, js, server, watchReload);
Configure connect server with livereload property
function server() {
return connect.server({
port: 8000,
root: outDir,
livereload: true //essential for live reload
})
}
Notice .pipe(connect.reload()) in the above code. It is essential that stream of required files to be piped to connect.reload() else it may not work if you call connect.reload() arbitrarily
function html() {
return src(htmlSources)
.pipe(gulpif(
mode.toLowerCase() != 'dev',
htmlmin({
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
})
)
)
.pipe(dest(outDir))
.pipe(connect.reload()); //Keep it if you want livereload else discard
}
Since we configure public task dev following command will execute all tasks followed by connect and watchReload
gulp dev

process.env is an empty object

I am using Gulp / Browserify / Node on Windows, and I want to only include debugging information when in development.
I have a dependant task that is being run before anything else
gulp.task('set-dev-node-env', function() {
process.env.NODE_ENV = 'development'
}
Yet when I try and access this in my code I am finding process.env is an empty object.
console.log("process.env",process.env)
How can I get this to work?
I found a solution was to use envify
var envify = require('envify/custom')
and add it as a transform on my browserify() call
.transform(envify({
NODE_ENV: 'development'
}))

Meteor: How to separate local environment from production?

I have two pieces of code: one that must only be run in a local environment and the other in production. What is the best way to do that in Meteor?
You can check do
(server side)
var isDevelopment = function() {
var i = WebApp.clientProgram.manifest.length;
while(i--) {
if('sourceMap' in WebApp.clientProgram.manifest[i]) return true;
}
return false;
}
if(isDevelopment()) {
console.log("Dev mode");
}else{
console.log("Production");
}
The idea is to check for JS Source Maps, which are only available in dev mode. This should work out of the box with your meteor app without any special configuration.
I prefer to set an environment variable which the server can read. For example:
$ METEOR_ENV="production" meteor
Then on the server:
if (process.env.METEOR_ENV === 'production') {
// production code here
} else {
// dev code here
}
If you have only two states, you could assume !production = dev.
Use this package and you will haveMeteor.isdevelopment only in development. There is other packages out there too that do the same thing but differently. this is the simplest

Resources