How do I register multiple grunt-exec tasks? - node.js

I want to define a tree of shell commands in my Gruntfile.js, using grunt-exec to write shell commands.
I can do this for a single command, but I can't figure out the syntax for writing multiple sets of commands, and being able to call them one-by-one, or in groups.
Could someone please provide a fully fleshed out example Gruntfile.js with the following task structure?
test: `npm test`
lint:
jshint: `jshint .`
csslint: `csslint .`
I'm coming from the world of make, so I really want to shove this workflow into my Node.js projects, damn the Node community conventions.
I want grunt to default to npm test
I want grunt lint to perform both jshint . and csslint .
I want grunt jshint to perform only jshint .

A grunt multi-task supports multiple targets, but they can't be nested. But you can register tasks that run other tasks.
Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
exec: {
test: "npm test",
jshint: "jshint .",
csslint: "csslint ."
}
});
grunt.loadNpmTasks("grunt-exec");
grunt.registerTask("default", ["exec:test"]);
grunt.registerTask("test", ["exec:test"]);
grunt.registerTask("lint", ["exec:jshint", "exec:csslint"]);
};
Note that there are already a lot of grunt tasks available, like grunt-contrib-jshint, which make things much simpler.

Grunt lets you register tasks by name, and tasks of tasks. Easily done via arrays of string literal task names, e.g.:
grunt.registerTask('default', ['test']);
grunt.registerTask('lint', ['jshint', 'csslint']);
jshint should already be registered if you've defined it as jshint inside your grunt.initConfig. Keep in mind that, unusual for a node program, everything is highly synchronous. The tasks will run in the order you give them.
edit: in case you need it later, you can also registerTask a function instead of an array of task names. This is how you could go about writing a task that does async work, or if you have very strict ordering requirements that may use control flow logic. Try to stick with just registering based on task name if you can.

Related

How to get full coverage of a CLI nodeJS app with istanbul?

I am using this config :Istanbul/Mocha/Chai/supertest(for http tests)/sinon (for timer tests) but I am having some problem with testing CLI tools
My question is simple: How can I test my cli program and achieve at the same time 100% code coverage with istanbul? No matter what tool you are using, I would like to understand how you are doing it please!
I found this article which was very helpful at the beginning but
It was written in 2014
The module mock-utf8-stream does not seem standard
It does not explain clearly the code architecture
cheers
This will be done in 2 steps:
Make sure your test suite is set up to correctly spawn the CLI execution
Set up nyc (reason for switching from istanbul to nyc explained below) to go through the script files behind your CLI tool
Setting up your tests to run spawn subprocesses
I had to set up some CLI tests a few months ago on Fulky (the project is paused right now but it's temporary) and wrote my test suite as such:
const expect = require('chai').expect;
const spawnSync = require('child_process').spawnSync;
describe('Executing my CLI tool', function () {
// If your CLI tool is taking some expected time to start up / tear down, you
// might want to set this to avoid slowness warnings.
this.slow(600);
it('should pass given 2 arguments', () => {
const result = spawnSync(
'./my-CLI-tool',
['argument1', 'argument2'],
{ encoding: 'utf-8' }
);
expect(result.status).to.equal(0);
expect(result.stdout).to.include('Something from the output');
});
});
You can see an example here but bear in mind that this is a test file run with Mocha, that runs Mocha in a spawned process.
A bit Inception for your need here so it might be confusing, but it's testing a Mocha plugin hence the added brain game. That should apply to your use case though if you forget about that complexity.
Setting up coverage
You will then want to install nyc with npm i nyc --save-dev, nowadays' CLI tool for Istanbul, because as opposed to the previous CLI (istanbul itself), it allows coverage for applications that spawn subprocesses.
Good thing is it's still the same tool, the same team, etc. behind nyc, so the switch is really trivial (for example, see this transition).
In your package.json, then add to your scripts:
"scripts": {
"coverage": "nyc mocha"
}
You will then get a report with npm run coverage (you will probably have to set the reporter option in .nycrc) that goes through your CLI scripts as well.
I haven't set up this coverage part with the project mentioned above, but I have just applied these steps locally and it works as expected so I invite you to try it out on your end.

Gulp Watching Creates Infinite Loop Without Changing Files

Similar to other questions, in this very watered-down snippet, running the default gulp task (via npm start which runs gulp); this snippet creates an infinite loop running the scripts task over and over. Here is the gulpfile.js (literally the whole thing at the moment):
'use strict';
const gulp = require('gulp');
// COPY SCRIPTS TO BUILD FOLDER
gulp.task('scripts', function() {
return gulp.src('./scripts/**/*.js')
.pipe(gulp.dest('build/scripts'))
});
// WATCH FILES
gulp.task('watch', function() {
gulp.watch('./scripts/**/*.js', ['scripts']);
});
// DEFAULT
gulp.task('default', ['watch']);
The extra odd thing is that whether the build folder is built anew or not, the scripts task will be executed immediately after calling npm start! And the loop begins.
In case you're curious, here is the pasted (and only) scripts object in my package.json:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "gulp"
},
The only other thing in my directory is a scripts folder with an app.js and an home.js file in it. Obviously once this task is run, the build folder is created (if it wasn't already there yet) and the two aforementioned files are copied into it.
You can see I'm only looking for scripts in the root directory's first level folder called scripts, so I shouldn't have an infinite loop by referencing changes on the same set of scripts. Also, even if I'm explicit, and point to exactly one particular file with a relative path such as ./scripts/home.js this still happens.
I'm anticipating being embarrassed, but I'm utterly confused.
A few things I've picked up on which could be causing some errors.
EDIT -
Try the watch plugin npm install --save-dev gulp-watch
// Try and declare your plugins like this for now.
var gulp = require('gulp'),
watch = require('gulp-watch');
// Provide a callback, cb
gulp.task('scripts', function(cb) {
// Dont use ./ on your src as watch can have a problem with this
return gulp.src('scripts/**/*.js')
.pipe(gulp.dest('./build/scripts'), cb); // call cb and dont forget ;
});
// remove ./ on watch
gulp.task('watch', function() {
gulp.watch('scripts/**/*.js', ['scripts']);
});
gulp.task('default', ['watch']);
So that is pretty weird behaviour but this should do the trick.
The only time I use ./ within gulp is on my dest.
Also just remember that gulpfile is just a JS so remember your semicolon, etc.
I cannot guarantee the resolution here, but I had a two variables that changed when this was resolved:
I upgraded my Parallels VM application (on an Apple PowerBook) from
version 10 -> 11.
I reinstalled Windows 10 using another license for a current version
(the previous one was a licensed dev or early release version).
My code, Node version, devDependencies and versions were identical.

Gulp + Webpack or JUST Webpack?

I see people using gulp with webpack. But then I read webpack can replace gulp? I'm completely confused here...can someone explain?
UPDATE
in the end I started with gulp. I was new to modern front-end and just wanted to get up and running quick. Now that I've got my feet quite wet after more than a year, I'm ready to move to webpack. I suggest the same route for people who start off in the same shoes. Not saying you can't try webpack but just sayin if it seems complicated start with gulp first...nothing wrong with that.
If you don't want gulp, yes there's grunt but you could also just specify commands in your package.json and call them from the command-line without a task runner just to get up and running initially. For example:
"scripts": {
"babel": "babel src -d build",
"browserify": "browserify build/client/app.js -o dist/client/scripts/app.bundle.js",
"build": "npm run clean && npm run babel && npm run prepare && npm run browserify",
"clean": "rm -rf build && rm -rf dist",
"copy:server": "cp build/server.js dist/server.js",
"copy:index": "cp src/client/index.html dist/client/index.html",
"copy": "npm run copy:server && npm run copy:index",
"prepare": "mkdir -p dist/client/scripts/ && npm run copy",
"start": "node dist/server"
},
This answer might help. Task Runners (Gulp, Grunt, etc) and Bundlers (Webpack, Browserify). Why use together?
...and here's an example of using webpack from within a gulp task. This goes a step further and assumes that your webpack config is written in es6.
var gulp = require('gulp');
var webpack = require('webpack');
var gutil = require('gutil');
var babel = require('babel/register');
var config = require(path.join('../..', 'webpack.config.es6.js'));
gulp.task('webpack-es6-test', function(done){
webpack(config).run(onBuild(done));
});
function onBuild(done) {
return function(err, stats) {
if (err) {
gutil.log('Error', err);
if (done) {
done();
}
} else {
Object.keys(stats.compilation.assets).forEach(function(key) {
gutil.log('Webpack: output ', gutil.colors.green(key));
});
gutil.log('Webpack: ', gutil.colors.blue('finished ', stats.compilation.name));
if (done) {
done();
}
}
}
}
I think you'll find that as your app gets more complicated, you might want to use gulp with a webpack task as per example above. This allows you to do a few more interesting things in your build that webpack loaders and plugins really don't do, ie. creating output directories, starting servers, etc. Well, to be succinct, webpack actually can do those things, but you might find them limited for your long term needs. One of the biggest advantages you get from gulp -> webpack is that you can customize your webpack config for different environments and have gulp do the right task for the right time. Its really up to you, but there's nothing wrong with running webpack from gulp, in fact there's some pretty interesting examples of how to do it. The example above is basically from jlongster.
NPM scripts can do the same as gulp, but in about 50x less code. In fact, with no code at all, only command line arguments.
For example, the use case you described where you want to have different code for different environments.
With Webpack + NPM Scripts, it's this easy:
"prebuild:dev": "npm run clean:wwwroot",
"build:dev": "cross-env NODE_ENV=development webpack --config config/webpack.development.js --hot --profile --progress --colors --display-cached",
"postbuild:dev": "npm run copy:index.html && npm run rename:index.html",
"prebuild:production": "npm run clean:wwwroot",
"build:production": "cross-env NODE_ENV=production webpack --config config/webpack.production.js --profile --progress --colors --display-cached --bail",
"postbuild:production": "npm run copy:index.html && npm run rename:index.html",
"clean:wwwroot": "rimraf -- wwwroot/*",
"copy:index.html": "ncp wwwroot/index.html Views/Shared",
"rename:index.html": "cd ../PowerShell && elevate.exe -c renamer --find \"index.html\" --replace \"_Layout.cshtml\" \"../MyProject/Views/Shared/*\"",
Now you simply maintain two webpack config scripts, one for development mode, webpack.development.js, and one for production mode, webpack.production.js. I also utilize a webpack.common.js which houses webpack config shared on all environments, and use webpackMerge to merge them.
Because of the coolness of NPM scripts, it allows for easy chaining, similar to how gulp does Streams/pipes.
In the example above, to build for developement, you simply go to your command line and execute npm run build:dev.
NPM would first run prebuild:dev,
Then build:dev,
And finally postbuild:dev.
The pre and post prefixes tell NPM which order to execute in.
If you notice, with Webpack + NPM scripts, you can run a native programs, such as rimraf, instead of a gulp-wrapper for a native program such as gulp-rimraf. You can also run native Windows .exe files as I did here with elevate.exe or native *nix files on Linux or Mac.
Try doing the same thing with gulp. You'll have to wait for someone to come along and write a gulp-wrapper for the native program you want to use. In addition, you'll likely need to write convoluted code like this: (taken straight from angular2-seed repo)
Gulp Development code
import * as gulp from 'gulp';
import * as gulpLoadPlugins from 'gulp-load-plugins';
import * as merge from 'merge-stream';
import * as util from 'gulp-util';
import { join/*, sep, relative*/ } from 'path';
import { APP_DEST, APP_SRC, /*PROJECT_ROOT, */TOOLS_DIR, TYPED_COMPILE_INTERVAL } from '../../config';
import { makeTsProject, templateLocals } from '../../utils';
const plugins = <any>gulpLoadPlugins();
let typedBuildCounter = TYPED_COMPILE_INTERVAL; // Always start with the typed build.
/**
* Executes the build process, transpiling the TypeScript files (except the spec and e2e-spec files) for the development
* environment.
*/
export = () => {
let tsProject: any;
let typings = gulp.src([
'typings/index.d.ts',
TOOLS_DIR + '/manual_typings/**/*.d.ts'
]);
let src = [
join(APP_SRC, '**/*.ts'),
'!' + join(APP_SRC, '**/*.spec.ts'),
'!' + join(APP_SRC, '**/*.e2e-spec.ts')
];
let projectFiles = gulp.src(src);
let result: any;
let isFullCompile = true;
// Only do a typed build every X builds, otherwise do a typeless build to speed things up
if (typedBuildCounter < TYPED_COMPILE_INTERVAL) {
isFullCompile = false;
tsProject = makeTsProject({isolatedModules: true});
projectFiles = projectFiles.pipe(plugins.cached());
util.log('Performing typeless TypeScript compile.');
} else {
tsProject = makeTsProject();
projectFiles = merge(typings, projectFiles);
}
result = projectFiles
.pipe(plugins.plumber())
.pipe(plugins.sourcemaps.init())
.pipe(plugins.typescript(tsProject))
.on('error', () => {
typedBuildCounter = TYPED_COMPILE_INTERVAL;
});
if (isFullCompile) {
typedBuildCounter = 0;
} else {
typedBuildCounter++;
}
return result.js
.pipe(plugins.sourcemaps.write())
// Use for debugging with Webstorm/IntelliJ
// https://github.com/mgechev/angular2-seed/issues/1220
// .pipe(plugins.sourcemaps.write('.', {
// includeContent: false,
// sourceRoot: (file: any) =>
// relative(file.path, PROJECT_ROOT + '/' + APP_SRC).replace(sep, '/') + '/' + APP_SRC
// }))
.pipe(plugins.template(templateLocals()))
.pipe(gulp.dest(APP_DEST));
};
Gulp Production code
import * as gulp from 'gulp';
import * as gulpLoadPlugins from 'gulp-load-plugins';
import { join } from 'path';
import { TMP_DIR, TOOLS_DIR } from '../../config';
import { makeTsProject, templateLocals } from '../../utils';
const plugins = <any>gulpLoadPlugins();
const INLINE_OPTIONS = {
base: TMP_DIR,
useRelativePaths: true,
removeLineBreaks: true
};
/**
* Executes the build process, transpiling the TypeScript files for the production environment.
*/
export = () => {
let tsProject = makeTsProject();
let src = [
'typings/index.d.ts',
TOOLS_DIR + '/manual_typings/**/*.d.ts',
join(TMP_DIR, '**/*.ts')
];
let result = gulp.src(src)
.pipe(plugins.plumber())
.pipe(plugins.inlineNg2Template(INLINE_OPTIONS))
.pipe(plugins.typescript(tsProject))
.once('error', function () {
this.once('finish', () => process.exit(1));
});
return result.js
.pipe(plugins.template(templateLocals()))
.pipe(gulp.dest(TMP_DIR));
};
The actual gulp code is much more complicated that this, as this is only 2 of the several dozen gulp files in the repo.
So, which one is easier to you?
In my opinion, NPM scripts far surpasses gulp and grunt, in both effectiveness and ease of use, and all front-end developers should consider using it in their workflow because it is a major time saver.
UPDATE
There is one scenario I've encountered where I wanted to use Gulp in combination with NPM scripts and Webpack.
When I need to do remote debugging on an iPad or Android device for example, I need to start up extra servers. In the past I ran all the servers as separate processes, from within IntelliJ IDEA (Or Webstorm) that is easy with the "Compound" Run Configuration. But if I need to stop and restart them, it was tedious to have to close 5 different server tabs, plus the output was spread across the different windows.
One of the benefits of gulp is that is can chain all the output from separate independent processes into one console window, which becomes the parent of all the child servers.
So I created a very simple gulp task that just runs my NPM scripts or the commands directly, so all the output appears in one window, and I can easily end all 5 servers at once by closing the gulp task window.
Gulp.js
/**
* Gulp / Node utilities
*/
var gulp = require('gulp-help')(require('gulp'));
var utils = require('gulp-util');
var log = utils.log;
var con = utils.colors;
/**
* Basic workflow plugins
*/
var shell = require('gulp-shell'); // run command line from shell
var browserSync = require('browser-sync');
/**
* Performance testing plugins
*/
var ngrok = require('ngrok');
// Variables
var serverToProxy1 = "localhost:5000";
var finalPort1 = 8000;
// When the user enters "gulp" on the command line, the default task will automatically be called. This default task below, will run all other tasks automatically.
// Default task
gulp.task('default', function (cb) {
console.log('Starting dev servers!...');
gulp.start(
'devserver:jit',
'nodemon',
'browsersync',
'ios_webkit_debug_proxy'
'ngrok-url',
// 'vorlon',
// 'remotedebug_ios_webkit_adapter'
);
});
gulp.task('nodemon', shell.task('cd ../backend-nodejs && npm run nodemon'));
gulp.task('devserver:jit', shell.task('npm run devserver:jit'));
gulp.task('ios_webkit_debug_proxy', shell.task('npm run ios-webkit-debug-proxy'));
gulp.task('browsersync', shell.task(`browser-sync start --proxy ${serverToProxy1} --port ${finalPort1} --no-open`));
gulp.task('ngrok-url', function (cb) {
return ngrok.connect(finalPort1, function (err, url) {
site = url;
log(con.cyan('ngrok'), '- serving your site from', con.yellow(site));
cb();
});
});
// gulp.task('vorlon', shell.task('vorlon'));
// gulp.task('remotedebug_ios_webkit_adapter', shell.task('remotedebug_ios_webkit_adapter'));
Still quite a bit of code just to run 5 tasks, in my opinion, but it works for the purpose. One caveate is that gulp-shell doesn't seem to run some commands correctly, such as ios-webkit-debug-proxy. So I had to create an NPM Script that just executes the same command, and then it works.
So I primarily use NPM Scripts for all my tasks, but occasionally when I need to run a bunch of servers at once, I'll fire up my Gulp task to help out. Pick the right tool for the right job.
UPDATE 2
I now use a script called concurrently which does the same thing as the gulp task above. It runs multiple CLI scripts in parallel and pipes them all to the same console window, and its very simple to use. Once again, no code required (well, the code is inside the node_module for concurrently, but you don't have to concern yourself with that)
// NOTE: If you need to run a command with spaces in it, you need to use
// double quotes, and they must be escaped (at least on windows).
// It doesn't seem to work with single quotes.
"run:all": "concurrently \"npm run devserver\" nodemon browsersync ios_webkit_debug_proxy ngrok-url"
This runs all 5 scripts in parallel piped out to one terminal. Awesome! So that this point, I rarely use gulp, since there are so many cli scripts to do the same tasks with no code.
I suggest you read these articles which compare them in depth.
How to Use NPM as a Build Tool
Why we should stop using Grunt & Gulp
Why I Left Gulp and Grunt for NPM Scripts
I used both options in my different projects.
Here is one boilerplate that I put together using gulp with webpack - https://github.com/iroy2000/react-reflux-boilerplate-with-webpack.
I have some other project used only webpack with npm tasks.
And they both works totally fine. And I think it burns down to is how complicated your task is, and how much control you want to have in your configuration.
For example, if you tasks is simple, let's say dev, build, test ... etc ( which is very standard ), you are totally fine with just simple webpack with npm tasks.
But if you have very complicated workflow and you want to have more control of your configuration ( because it is coding ), you could go for gulp route.
But from my experience, webpack ecosystem provides more than enough plugins and loaders that I will need, and so I love using the bare minimum approach unless there is something you can only do in gulp. And also, it will make your configuration easier if you have one less thing in your system.
And a lot of times, nowadays, I see people actually replacing gulp and browsify all together with webpack alone.
The concepts of Gulp and Webpack are quite different. You tell Gulp how to put front-end code together step-by-step, but you tell Webpack what you want through a config file.
Here is a short article (5 min read) I wrote explaining my understanding of the differences: https://medium.com/#Maokai/compile-the-front-end-from-gulp-to-webpack-c45671ad87fe
Our company moved from Gulp to Webpack in the past year. Although it took some time, we figured out how to move all we did in Gulp to Webpack. So to us, everything we did in Gulp we can also do through Webpack, but not the other way around.
As of today, I'd suggest just use Webpack and avoid the mixture of Gulp and Webpack so you and your team do not need to learn and maintain both, especially because they are requiring very different mindsets.
Honestly I think the best is to use both.
Webpack for all javascript related.
Gulp for all css related.
I still have to find a decent solution for packaging css with webpack, and so far I am happy using gulp for css and webpack for javascript.
I also use npm scripts as #Tetradev as described. Especially since I am using Visual Studio, and while NPM Task runner is pretty reliable Webpack Task Runner is pretty buggy.

Yeoman + Grunt Disable Uglify

Background:
I'm working on a chrome extension. I used the yeoman generator. It worked like a charm. After I deployed the extension, I needed to debug a few issues.
Problem:
The code is uglified. I can't set break points. I can hardly read it. It is also optimized. This makes it hard to read as well. I would like to tell Grunt to skip uglifying the code.
Attempted Solutions:
I tried to comment out the uglify task in the Grunt file. If I do this, not only is uglify not executed, but most of the scripts fail to copy into the "dist" directory.
I can deploy the application from the "app" directory. If I do this, my human written code is loaded rather than the "dist" values. While this works, I wish to learn more about the inner workings of Grunt. It seems likely that there is some mechanism by which uglifying may be disabled while preserving copying.
It's the usemin task that supplies targets to the uglify task. When you comment out the uglify task usemin can't complete its flow (by default concat and uglify) and the scripts never get copied.
So you must configure the flow in useminPrepare options. Like this:
[...]
useminPrepare: {
options: {
stripBanners: true,
dest: '<%= config.dist %>',
flow: {
steps: {
js: ['concat'], css: ['concat', 'cssmin']
},
post: {}
}
},
[...]
This way you can remove the uglify task from the build sequence (you must, as it will complaint that have no targets and fail).
Documentation here: https://github.com/yeoman/grunt-usemin#flow

heroku -- npm postinstall script to run grunt task depending on enviro

I've got two heroku node.js apps, one for prod and one for dev, and I also have a Gruntfile with dev- and prod-specific tasks. I know you can set up package.json to run grunt as a postinstall hook for npm, but can you specify somehow different tasks to be run depending on what enviro you're in?
Here's what the relevant section of my package.json looks like so far:
"scripts": {
"postinstall": "./node_modules/grunt/bin/grunt default"
},
Rather than run grunt default every time, I'd love to run "grunt production" if NODE_ENV is production, etc.
Is this possible?
Sadly there's no difference like postInstall and postInstallDev. You can make an intermediate script to handle the difference though. For example, if you have the following:
"scripts": { "postinstall": "node postInstall.js" },
Then in this script you could check the environment variable and execute the correct Grunt task from there:
// postInstall.js
var env = process.env.NODE_ENV;
if (env === 'development') {
// Spawn a process or require the Gruntfile directly for the default task.
return;
}
if (env === 'production') {
// Spawn a process or require the Gruntfile directly to the prod task.
return;
}
console.error('No task for environment:', env);
process.exit(1);
A couple of peripherally related points...
Try not to have Grunt and co. as dependencies. Keep them to devDependencies to avoid having to install all that stuff in production. Having an intermediary script in vanilla Node like the above will allow you to do this. I like to use a postInstall script like this to install git hook scripts too (but also only on development environments).
You don't have to use ./node_modules/grunt/bin/grunt default. If grunt-cli is a dependency or devDependency, npm knows where to look and grunt default will work fine.
For some reason, my dev environment was never running my "development" if statement. I sent a ticket to Heroku support, and this was their answer: "By default, your environment is not available during slug compilation. If you would like to make this available, you can enable an experimental feature called "user-env-compile". Please see the following article for details:
http://devcenter.heroku.com/articles/labs-user-env-compile". Good to know. So, I went another route using the heroku-buildpack-nodejs-grunt buildpack, and then creating a heroku:development grunt task.

Resources