How can the base directory be replaced with gulp? - node.js

I need to alter a stream of files to contain a different base folder name. I thought the gulp-rename plugin would allow for this, but it only seems to replace the glob portion.
Example:
gulp.task("test", function() {
gulp.src("bower_components/**/*", { base: "bower_components", read:false })
.pipe($.rename(function (p) { p.dirname = "X/" + p.dirname; }))
.pipe($.print());
});
outputs:
[gulp] bower_components\X\jquery\test\data\offset\scroll.html
[gulp] bower_components\X\jquery\test\data\offset\static.html
[gulp] bower_components\X\jquery\test\data\offset\table.html
...
I want
[gulp] X\jquery\test\data\offset\scroll.html
[gulp] X\jquery\test\data\offset\static.html
[gulp] X\jquery\test\data\offset\table.html
...
Is there a way to do this with gulp-replace, or some other plugin?

I believe you could do this with gulp-tap to get a hold of the the file instances and alter properties on them before they get printed or use it to print them.
Out of curiosity what are you aiming to do?
Hope that helps!
EDIT-1::
The following is a slightly modified version of the example in the gulp-tap documentation which may work for your use case.
gulp.src("src/**/*.{coffee,js}")
.pipe(tap(function(file, t) {
file.path = 'X/' + file.path;
}))
.pipe($.print())
.pipe(gulp.dest('build'));
EDIT-2::
This is a common task I have set up in my projects for handling external scripts (note; I am using gulp-load-plugins hence invoking my plugins with plugins.<NAME>);
gulp.task('vendor:scripts:publish', function() {
return gulp.src(sources.vendor.js)
.pipe(plugins.plumber())
.pipe(plugins.concat('vendor.js'))
.pipe(gulp.dest(destinations.js))
.pipe(plugins.uglify())
.pipe(plugins.rename(pluginOpts.rename))
.pipe(gulp.dest(destinations.js));
});
destinations and sources are two variables that I have defined in a config file for my gulpfile.
But for clarity, sources.vendor.js points at an array much like the following;
js: [
'src/vendor/jquery/dist/jquery.js',
'src/vendor/lodash/lodash.js',
'src/vendor/backbone/backbone.js'
],
The reason my folder is named vendor and not bower_components is because I've made use of a .bowerrc file to point my bower installation at a different folder.
In addition if you have discrete scripts that you may not want to include all of the time you can look to make use of gulp-utils and gulp-filter to filter out certain scripts when an option is passed or not passed when gulp is invoked on the CLI.
For example; having gulp vendor:scripts:publish include all scripts but gulp vendor:scripts:publish --release omitting discrete scripts.
This then requires modifying your task to declare a filter that is piped in based on an option flag being picked up by gulp-utils.
var isRelease = (plugins.utils.env.release) ? true: false;
gulp.task('vendor:scripts:publish', function() {
var discreteFilter = plugins.filter([
'**/*.js',
'!**/discrete.min.js'
]);
return gulp.src(sources.vendor.js)
.pipe(plugins.plumber())
.pipe(isRelease ? discreteFilter: plugins.utils.noop())
.pipe(plugins.concat('vendor.js'))
.pipe(gulp.dest(destinations.js))
.pipe(plugins.uglify())
.pipe(plugins.rename(pluginOpts.rename))
.pipe(gulp.dest(destinations.js));
});
Hope that helps you out!

Related

change `dest` option on the fly

I have some grunt tasks to compile files and would like to "recycle" them inside different tasks.
I am trying to modify the destination directory without success... My idea is something like:
grunt.registerTask('bower', ['compile:index', 'compile:core'], function(){
this.options({dest: 'dist/*.js'});
});
The compile:index task runs good by itself (i.e. when called alone) and has dest: 'index.js, other tasks have other filenames. I would like to change these inside the bowertask, adding a new directory but keeping the filename defined in the original task.
Is this possible?
You can create a dynamic alias task that configures and then runs tasks like such:
grunt.registerTask('bower', function(target) {
target = target || 'index';
if (target === 'core') {
grunt.config('compile.core.dest', 'dist/core.js');
} else {
grunt.config('compile.index.dest', 'dist/index.js');
// Will call itself after compile:index has ran to configure for compile:core
grunt.task.run(['compile:index', 'bower:core', 'compile:core']);
}
});
Then entering grunt bower or grunt bower:index will dynamically configure/run the compile:index task, then configure/run the compile:core task.

Can Gulp overwrite all src files?

Let's say I want to replace the version number in a bunch of files, many of which live in subdirectories. I will pipe the files through gulp-replace to run the regex-replace function; but I will ultimately want to overwrite all the original files.
The task might look something like this:
gulp.src([
'./bower.json',
'./package.json',
'./docs/content/data.yml',
/* ...and so on... */
])
.pipe(replace(/* ...replacement... */))
.pipe(gulp.dest(/* I DONT KNOW */);
So how can I end it so that each src file just overwrites itself, at its original location? Is there something I can pass to gulp.dest() that will do this?
I can think of two solutions:
Add an option for base to your gulp.src like so:
gulp.src([...files...], {base: './'}).pipe(...)...
This will tell gulp to preserve the entire relative path. Then pass './' into gulp.dest() to overwrite the original files. (Note: this is untested, you should make sure you have a backup in case it doesn't work.)
Use functions. Gulp's just JavaScript, so you can do this:
[...files...].forEach(function(file) {
var path = require('path');
gulp.src(file).pipe(rename(...)).pipe(gulp.dest(path.dirname(file)));
}
If you need to run these asynchronously, the first will be much easier, as you'll need to use something like event-stream.merge and map the streams into an array. It would look like
var es = require('event-stream');
...
var streams = [...files...].map(function(file) {
// the same function from above, with a return
return gulp.src(file) ...
};
return es.merge.apply(es, streams);
Tell gulp to write to the base directory of the file in question, just like so:
.pipe(
gulp.dest(function(data){
console.log("Writing to directory: " + data.base);
return data.base;
})
)
(The data argument is a vinyl file object)
The advantage of this approach is that if your have files from multiple sources each nested at different levels of the file structure, this approach allows you to overwrite each file correctly. (As apposed to set one base directory in the upstream of your pipe chain)
if you are using gulp-rename, here's another workaround:
var rename = require('gulp-rename');
...
function copyFile(source, target){
gulp.src(source)
.pipe(rename(target))
.pipe(gulp.dest("./"));
}
copyFile("src/js/app.js","dist/js/app.js");
and if you want source and target to be absolute paths,
var rename = require('gulp-rename');
...
function copyFile(source, target){
gulp.src(source.replace(__dirname,"."))
.pipe(rename(target.replace(__dirname,".")))
.pipe(gulp.dest("./"));
}
copyFile("/Users/me/Documents/Sites/app/src/js/app.js","/Users/me/Documents/Sites/app/dist/js/app.js");
I am not sure why people complicate it but by just starting your Destination path with "./" does the job.
Say path is 'dist/css' Then you would use it like this
.pipe(gulp.dest("./dist/css"));
That's it, I use this approach on everyone of my projects.

How to use jasmine with gulp.watch

I'm trying to make my tests run each time I'm saving some files. Here is the gulp watch:
gulp.task('jasmine', function() {
gulp.src('spec/nodejs/*Spec.js')
.pipe(jasmine({verbose:true, includeStackTrace: true}));
});
gulp.task('watch', function () {
gulp.watch(['app/*.js', 'app/!(embed)**/*.js','spec/nodejs/*.js'], ['jasmine']);
});
To test for example app/maps.js I'm creating a spec/nodejs/mapsSpec.js file like this:
'use strict';
var maps = require('../../app/maps');
describe('/maps related routes', function(){
it('should ...', function(){...}
...
If I change a spec file everything is working well, if I modify app/maps.js file the change trigger the test. if I modify it again tests are tiggered but the modifications do not taking effect. For example if I add a console.log('foo') in a second time, I will not see it until I relaunch gulp watch and save it again. So only one run of jasmine is ok when using it with gulp.watch.
I guess it's because require is cached by nodejs in the gulp process. So how should I do ?
I took a look at the code of gulp-jasmine. The problem is that the only file from the cache is the Specs.js file. The cache of the children(the reqquired files to test) aren't cleared.
Within the index.js of gulp-jasmine is a row which deletes the cache:
delete require.cache[require.resolve(path.resolve(file.path))];
If you put the next block of code before the delete, you will delete all the children's cache and will it run correctly after every time you save your file.
var files = require.cache[require.resolve(path.resolve(file.path))];
if( typeof files !== 'undefined' ) {
for( var i in files.children ) {
delete require.cache[ files.children[i].id ];
}
}
You can change this in the node_modules.
I will go for a pull request, so maybe in the near future this will be solved permanently.
Also wrote a post about it on: http://navelpluisje.nl/entry/fix-cache-problem-jasmine-tests-with-gulp
I haven't found a fix for this issue, but you can work around it via the gulp-shell task.
npm install gulp-shell --save-dev
then
var shell = require('gulp-shell');
...
gulp.task('jasmine', function() {
gulp.src('spec/nodejs/*Spec.js')
.pipe(shell('minijasminenode spec/*Spec.js'));
});
You'll also need jasmine installed as a direct dependency (gulp-jasmine uses minijasminenode)

How task names are mapped in grunt?

I cannot understand how grunt matches tasks with Gruntfile.js:
module.exports = function (grunt) {
grunt.initConfig({
concat: {
dist: {
src: ['src/*.js'],
dest: 'dest/all.js'
}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.registerTask('default', ['concat']);
};
It's a valid config. But I don't know how grunt match 'concat' to 'grunt-contrib-concat'.
Does grunt trim the 'grunt-contrib-' prefix to match 'concat' to 'grunt-contrib-concat'?
First, we look inside grunt-contrib-concat source code:
grunt.registerMultiTask('concat', 'Concatenate files.', function() {
Looking inside grunt creating tasks docs, the first argument passed into a task registration function is the name of the task:
grunt.registerMultiTask(taskName, [description, ] taskFunction)
grunt.registerTask(taskName, [description, ] taskFunction)
Consclusion
There is no "magic" names nor "grunt keywords"
There is no difference between your custom tasks and task plugins ( even grunt-contrib.. )
The API for creating tasks is simple as that.
contrib-less, contrib-jade, contrib-concat .... so contrib is just to signifies that these plugins are contributed by Grunt community developers and less, jade, concat indicate the modules which you want to work on your project as mentioned in gruntfile.js.
So when you say:
grunt.loadNpmTasks('grunt-contrib-concat')
It loads the mentioned module.
But in order to make it work when you fire grunt, you actually have to register it.
grunt.registerTask('default', ['concat','jade','less']);
grunt.registerTask('test', ['concat','jade','less']);
grunt.registerTask('dist', ['concat','jade','less','uglify']);
So as you can see in production we may want to uglify, so we can register our task in by 'dist'.

How to modify grunt watch tasks based on the file changed?

I'm writing a node.js program that will watch a directory filled with a large (300-ish) amount of scss projects. Grunt-watch (run either through the node module or on its own, whatever works) will be configured so that whenever a scss file is changed, it will be compiled with compass, the output file moved to a separate directory, for example:
./1234/style.scss was changed >> grunt-watch runs grunt-compass >> /foo/bar/baz/1234/style.css updated
The project directory that the file was in is obviously very important (if grunt-compass sent all the compiled files to the same directory, they would be jumbled and unusable and the grunt automation would be purposeless). I order to make sure all files are routed to the correct place, I am dynamically changing the grunt-compass settings every time a css file is updated.
Sample gruntfile:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
watch: {
files: './*/*.scss',
tasks: ['compass']
},
compass: {
origin:{
options: {
//temportary settings to be changed later
sassDir: './',
cssDir: './bar',
specify: './foo.scss'
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-compass');
grunt.event.on('watch', function(action, filepath, target) {
var path = require('path');
grunt.log.writeln(target + ': ' + filepath + ' might have ' + action);
var siteDirectory = path.dirname(filepath);
//changes sass directory to that of the changed file
var option = 'compass.origin.options.sassDir';
var result = __dirname + '/' + siteDirectory;
grunt.log.writeln(option + ' changed to ' + result);
grunt.config(option, result);
//customizes css output directory so that file goes to correct place
option = 'compass.origin.options.cssDir';
result = path.resolve(__dirname, '../', siteDirectory);
grunt.log.writeln(option + ' changed to ' + result);
grunt.config(option, result);
//grunt.task.run(['compass']);
});
};
However this doesn't work. If you run 'grunt watch' in verbose mode, you will see that grunt runs both the grunt.event.on function and the watch task in separate processes. The second parsing of the gruntfile reverts all my event.on config changes to the defaults above, and compass fails to run.
As seen in the event.on comments, I attempted to add a grunt.task.run() to make sure that compass was run in the same process as the event.on function, which would preserve my config changes. However the task refused to run, likely because I'm doing it wrong.
Unfortunately, the grunt.event.on variables are not sent to the defined grunt-watch task, otherwise I could write a custom function that would change the compass settings and then run compass in the same process.
I've tried implementing this without grunt, using the watch function build into compass, however compass can only store one static output path per project and can only watch one project at once.
I have currently gotten around this issue by adding a node program that takes the site name as a parameter, rewrites the grunfile.js by running using fs, and then running 'grunt watch' via an exec function. This however has it's own drawbacks (I can't view the grunt.log data) and is horribly convoluted, so I'd like to change it.
Thank you so much for any insight.
You need to specify
options : { nospawn : true }
in your watch task config to have the watch run in the same context:
watch: {
files: './*/*.scss',
tasks: ['compass'],
options : { nospawn : true }
}
See this section of documentation for more info on this.

Resources