grunt.registerTask can not modify global grunt task settings - javascript

The following code reads the contents of every subdirectory js inside of app/modules/ (eg. app/modules/module1/js/, app/modules/module2/js/, aso.)
this script worked before WITHOUT using the last command grunt.task.run('concat:' + dir);.
for a while now it stopped working so that i had to add a call to the task concat inside of the forEach loop.
normally I would have saved the new configuration inside of the concat configuration and call sometime later the resulting concat task.
grunt.registerTask('preparemodulejs', 'iterates over all module directories and compiles modules js files', function() {
// read all subdirectories from your modules folder
grunt.file.expand('./app/modules/*').forEach(function(dir){
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: [dir + '/js/*.js', '!' + dir + '/js/compiled.js'],
dest: dir + '/js/compiled.js'
};
// save the new concat config
grunt.config.set('concat', concat);
grunt.task.run('concat:' + dir); // this line is new
});
});
what exactly changed in recent versions that i have to add an explicit task.run line?
and is there any way to write the settings of this task into the settings of an existing concat task so that if i have other manual additions to that configuration those won't run for each directory scanned?
thanks for help.

grunt.task.run(); despite it's name, does not run tasks. Grunt is always synchronous so grunt.task.run() will queue tasks to run after the current task has finished.
So I would avoid using grunt.task.run() within an array but rather build a list of tasks/targets to run afterward:
grunt.registerTask('preparemodulejs', 'iterates over all module directories and compiles modules js files', function() {
var tasks = [];
// read all subdirectories from your modules folder
grunt.file.expand('./app/modules/*').forEach(function(dir){
// get the current concat config
var concat = grunt.config.get('concat') || {};
// set the config for this modulename-directory
concat[dir] = {
src: [dir + '/js/*.js', '!' + dir + '/js/compiled.js'],
dest: dir + '/js/compiled.js'
};
// save the new concat config
grunt.config.set('concat', concat);
tasks.push('concat:' + dir);
});
// queues the tasks and run when this current task is done
grunt.task.run(tasks);
});

We can also provide a config directly here for different tasks to run on the go for bigger projects having multiple modules. Even if the we need to process files outside of the root directory:
grunt.registerTask('publishapp', 'uglify ivapp.js and upload to server', function (){
var tasks = [];
grunt.file.expand('../outerdirectory/').forEach(function(dir) {
// config for uglify that needs to execute before uploading on server
var uglify = {
options: {
compress: {
drop_console: true,
},
banner: '/* Banner you want to put above js minified code. */\n'
},
all: {
files: [{
expand: true,
cwd: '../',
src: ['somedir/some.js'],
dest: 'build',
ext: '.js',
extDot: 'last'
}]
}
};
// set grunt config : uglify
grunt.config.set('uglify', uglify);
});
// prepare a tasks list
tasks.push('uglify:all');
tasks.push('exec:publish');
// execute a tasks to perform
grunt.task.run(tasks);
});

Related

How to fix, ENOENT error: no such file or directory when the "fonts" task mentioned below in the gulpfile.js starts

Following is the gulpfile.js which i have. Am getting ENOENT error: no such file or directory, when the "fonts" task starts, while running "gulp build-qa" command. Howeve, The build is successful every alternate time but not every time consistently. How to fix this issue ? I have made sure that every task has return, even though it is not working.
var gulp = require('gulp'),
del = require('del'),
pump = require('pump'),
flatten = require('gulp-flatten'),
usemin = require('gulp-usemin'),
htmlmin = require('gulp-htmlmin'),
cssmin = require('gulp-clean-css'),
uglifyjs = require('gulp-uglify'),
stripdebug = require('gulp-strip-debug'),
ngannotate = require('gulp-ng-annotate'),
rev = require('gulp-rev'),
concat = require('gulp-concat'),
sourcemaps = require('gulp-sourcemaps'),
connect = require('gulp-connect');
var ENV = process.env.NODE_ENV || 'dev',
APP_DIR = 'app',
BUILD_DIR = 'build',
SRC_DIR = 'source',
ZIP_DIR = 'packed';
gulp.task('clean', function () {
return del(
[BUILD_DIR + '/**', ZIP_DIR + '/**'],
{ dryRun: false })
.then(
// paths => { console.log('Files and folders that would be deleted:\n', paths.join('\n'));}
);
});
gulp.task('build-qa',['clean','fonts','images','partials','usemin-qa'], function(){});
/* Html processing, minifying and copying to build folder */
gulp.task('partials', function () {
return gulp.src(['**/*.html','!index.html'], {cwd: APP_DIR})
.pipe(htmlmin({ collapseWhitespace: true }))
.pipe(gulp.dest(BUILD_DIR));
});
/* Images copying to build folder */
gulp.task('images', function () {
return gulp.src('**/*.{jpg,png,gif,svg}')
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/images'));
});
/* Find all font type files in 'fonts' folders and move to fonts build folder with flattened structure */
gulp.task('fonts', function () {
return gulp.src('**/fonts/*.{ttf,woff,eot,svg}')
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/fonts'));
});
gulp.task('usemin-qa', function () {
return gulp.src('app/index.html')
.pipe(usemin({
// pipelines are named in the HTML, like js_lib and js_app below; html is the src file
html: [htmlmin({ collapseWhitespace: true, quotes: true, empty: true, spare: true, loose: true })],
css_lib: [
cssmin(),
'concat',
rev()
],
css: [
cssmin(),
'concat',
rev()
],
js_lib: [
ngannotate({remove: false, add: true}),
uglifyjs(),
rev()
],
js_app: [
sourcemaps.init(),
'concat',
ngannotate({remove: true, add: true}),
uglifyjs(),
rev()
]
}))
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest(BUILD_DIR))
});
ENOENT errors in gulp are almost always caused by some kind of race condition.
The first problem is that all your tasks are sourcing files not just from your source folders. For example '**/fonts/*.{ttf,woff,eot,svg}' selects font files from anywhere in your project including your BUILD_DIR. You need to exclude BUILD_DIR in all your tasks:
gulp.task('fonts', function () {
return gulp.src(['**/fonts/*.{ttf,woff,eot,svg}',
'!'+BUILD_DIR+'/**'])
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/fonts'));
});
The next problem is this line:
gulp.task('build-qa',['clean','fonts','images','partials','usemin-qa'], function(){});
This doesn't do what you think it does. All those tasks aren't executed in order, they're all executed at the same time. That means your clean task is deleting files in BUILD_DIR while your other tasks are busy copying files into that same directory.
You have two options:
(1) Place a dependency hint on all the other tasks. For example your fonts task would have to look like this:
gulp.task('fonts', ['clean'], function () {
return gulp.src(['**/fonts/*.{ttf,woff,eot,svg}',
'!' + BUILD_DIR + '/**'])
.pipe(flatten())
.pipe(gulp.dest(BUILD_DIR+'/fonts'));
});
This makes sure that all the other tasks run only after the clean task has finished.
(2) Use run-sequence in your build-qa task:
var runSequence = require('run-sequence');
gulp.task('build-qa', function(cb) {
runSequence('clean',['fonts','images','partials','usemin-qa'], cb);
});
This runs the clean task first and then all the other tasks in parallel.

Gulp dest: warn if 2 tasks try to write to the same destination

I wonder if there is an easy way to detect if two tasks write to the same file.
In this example there is a /js directory alongside a /ts directory. The /ts will get transpiled to the same directory as the /js. There shouldn't be any collisions. The ask is that, if there are collisions, the ts will win; but, I would like to warn that there is a collision.
gulp.task('js', function() {
return es.concat(
gulp.src(config.src.path('js', '**', '*.js'))
.pipe(gulp.dest(config.build.path(app, 'js')))
//, ....
);
});
gulp.task('ts', ['js'], function() {
var tsResult = gulp.src(config.src.path('ts', '**', '*.ts'))
.pipe(ts({
declaration: true,
noExternalResolve: true
}));
return es.concat([
tsResult.dts.pipe(gulp.dest(
config.build.path(app, 'definitions'))),
tsResult.js.pipe(gulp.dest(
config.build.path(app, 'js'))) // <--- same dest as js task
]);
})
Can I detect that the ts task is overwriting a file that the js task just put in place?
Just an idea. You can pass a callback to gulp.dest like this:
gulp.src('lib/*.js')
.pipe(uglify())
.pipe(gulp.src('styles/*.css'))
.pipe(gulp.dest(function(file) {
if (fs.existsSync('something here')) { // it's a deprecated call, use a newer one
console.warn("File exists", file);
}
// I don't know, you can do something cool here
return 'build/whatever';
}));
The feature is available since Gulp 3.8: https://github.com/gulpjs/gulp/blob/master/CHANGELOG.md#380
Other resources:
https://stackoverflow.com/a/29437418/99256
https://stackoverflow.com/a/29817916/99256

how to copy a single file in each directory

I would like to configure and execute a task that copies a specified file in each dir recursively. In specific I would like to have "index.php" copied in each directory and subdirectory of my web project, if that doesn't exist. I've tried to use multidest and copy but multidestdoesn't seem to allow to specify paths by means of a wildcard (too bad!). How could I solve this? Thank you
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
multidest: {
tst: {
tasks: ['copy:indexphp'],
dest: '**/' //THIS DOESN'T WORK, I SHOULD SPECIFY ANY DIR/SUBDIR/ETC..
},
},
copy: {
indexphp: {
expand: true,
cwd: 'classes',
src: 'index.php',
filter: function (filepath) {
// NPM load file path module.
var path = require('path');
// Construct the destination file path.
var dest = path.join(
grunt.task.current.data.dest,
path.basename(filepath)
);
// Return false if the file exists.
return !(grunt.file.exists(dest));
}
},
[...]

Grunt reloading JSON from file and updating task options

I am using a configuration file with JSON format containing names of other files which I concatenate when one of the files is changed. It works great so far, but I want to make the Grunt to reload the configuration file once it's changed (I remove or add something to the JSON) and update other task options (the concat task).
Here is my simplified Gruntfile.js:
module.exports = function(grunt) {
function getModules() {
var modules = grunt.file.readJSON('src/js/modules.json').modules;
for ( var module in modules ) {
if ( modules.hasOwnProperty(module) ) {
modules[module] = 'src/js/modules/' + modules[module] + '.js';
}
}
modules.push('src/js/scripts.js');
return modules;
}
var modules = getModules();
grunt.initConfig({
concat: {
options: {
separator: ';',
},
dist: {
src: modules,
dest: 'assets/js/scripts.min.js',
},
},
watch: {
js_modules: {
files: ['src/js/modules.json'],
tasks: ['reload_modules', 'concat'],
options: {
spawn: false,
livereload: true,
},
}
}
});
grunt.registerTask('reload_modules', "Reload JavaScript modules", function() {
modules = getModules();
});
};
As you can see I have some attempt to solve my problem, but the updated modules variable is not used in the concat task. The task uses the variable value loaded when the grunt default task is started.
You should be able to overwrite the config using grunt.config.merge:
var config = {
// ...
};
grunt.config.init(config);
grunt.registerTask('reload_modules', "Reload JavaScript modules", function() {
config.concat.options.dist.src = getModules();
grunt.config.merge(config);
});
http://gruntjs.com/api/grunt.config

Use Global Variable to Set Build Output Path in Grunt

I have a couple grunt tasks and I am trying to share global variables across those tasks and I am running into issues.
I have written a some custom tasks which set the proper output path depending on the build type. This seems to be setting things correctly.
// Set Mode (local or build)
grunt.registerTask("setBuildType", "Set the build type. Either build or local", function (val) {
// grunt.log.writeln(val + " :setBuildType val");
global.buildType = val;
});
// SetOutput location
grunt.registerTask("setOutput", "Set the output folder for the build.", function () {
if (global.buildType === "tfs") {
global.outputPath = MACHINE_PATH;
}
if (global.buildType === "local") {
global.outputPath = LOCAL_PATH;
}
if (global.buildType === "release") {
global.outputPath = RELEASE_PATH;
}
if (grunt.option("target")) {
global.outputPath = grunt.option("target");
}
grunt.log.writeln("Output folder: " + global.outputPath);
});
grunt.registerTask("globalReadout", function () {
grunt.log.writeln(global.outputPath);
});
So, I'm trying to then reference global.outputPath in a subsequent task, and running into errors.
If I call grunt test from the command line, it outputs the correct path no problem.
However, if I have a task like this:
clean: {
release: {
src: global.outputPath
}
}
It throws the following error:
Warning: Cannot call method 'indexOf' of undefined Use --force to continue.
Also, my constants in the setOutput task are set at the top of my Gruntfile.js
Any thoughts? Am I doing something wrong here?
So, I was on the right path. The issue is that the module exports before those global variables get set, so they are all undefined in subsequent tasks defined within the initConfig() task.
The solution I came up with, although, there may be better, is to overwrite a grunt.option value.
I have an optional option for my task --target
working solution looks like this:
grunt.registerTask("setOutput", "Set the output folder for the build.", function () {
if (global.buildType === "tfs") {
global.outputPath = MACHINE_PATH;
}
if (global.buildType === "local") {
global.outputPath = LOCAL_PATH;
}
if (global.buildType === "release") {
global.outputPath = RELEASE_PATH;
}
if (grunt.option("target")) {
global.outputPath = grunt.option("target");
}
grunt.option("target", global.outputPath);
grunt.log.writeln("Output path: " + grunt.option("target"));
});
And the task defined in initConfig() looked like this:
clean: {
build: {
src: ["<%= grunt.option(\"target\") %>"]
}
}
Feel free to chime in if you have a better solution. Otherwise, perhaps this may help someone else.
I have a way to do this that allows you to specify the output path using values like --dev. So far it's working very well, I quite like it. Thought I'd share it, as someone else may like it, too.
# Enum for target switching behavior
TARGETS =
dev: 'dev'
dist: 'dist'
# Configurable paths and globs
buildConfig =
dist: "dist"
dev: '.devServer'
timestamp: grunt.template.today('mm-dd_HHMM')
grunt.initConfig
cfg: buildConfig
cssmin:
crunch:
options: report: 'min'
files: "<%= grunt.option('target') %>/all-min.css": "/**/*.css"
# Set the output path for built files.
# Most tasks will key off this so it is a prerequisite
setPath = ->
if grunt.option 'dev'
grunt.option 'target', buildConfig.dev
else if grunt.option 'dist'
grunt.option 'target', "#{buildConfig.dist}/#{buildConfig.timestamp}"
else # Default path
grunt.option 'target', buildConfig.dev
grunt.log.writeln "Output path set to: `#{grunt.option 'target'}`"
grunt.log.writeln "Possible targets:"
grunt.log.writeln target for target of TARGETS
setPath()
With this setup, you can run commands like:
grunt cssmin --dist #sent to dist target
grunt cssmin --dev #sent to dev target
grunt cssmin --dev #sent to default target (dev)
This is an older question, I just thought to throw in my 5 cents.
If you need config variable to be accessible from any task, just define it in your main (the one that you'll always load) config file like this:
module.exports = function(grunt)
{
//
// Common project configuration
//
var config =
{
pkg: grunt.file.readJSON('package.json'),
options: // for 'project'
{
dist:
{
outputPath: '<%= process.cwd() %>/lib',
},
dev:
{
outputPath: '<%= process.cwd() %>/build',
},
},
}
grunt.config.merge( config )
}
Then you can simply access value like this:
in config file(s)
...
my_thingie:
[
ends_up_here: '<%= options.dev.outputPath %>/bundle',
],
...
in tasks
// as raw value
grunt.config.data.options.dist.outputPath
// after (eventual) templates have been processed
grunt.config('options.dist.outputPath')
I used key options here just to be in line with convention, but you can use anything as long as you remember not to register a task named 'options' or whatever you used for the key :)

Categories

Resources