How to move globbed gulp.src files into a nested gulp.dest folder - gulp

QUESTION PART 1: OUTPUTTING TO A NESTED DYNAMIC FOLDER
I use Gulp.js for graphic email development. My employer is switching to a different marketing platform which requires our email templates to be in a different folder structure. I'm having trouble outputting to nested folders when gulp.src uses globbing. I'd appreciate your help!
Here is a simplified example the gulp.src folder:
build/template1/template1.html
build/template2/template2.html
build/template3/template4.html
build/template4/template4.html
Here is a simplified example the gulp.src folder:
build/template1/theme/html/template1.html
build/template2/theme/html/template2.html
build/template3/theme/html/template4.html
build/template4/theme/html/template4.html
I want to do something like a wildcard for the dynamic template folders ...
gulp.task('moveFile', function(){
return gulp.src('./build/*/*.html')
.pipe(gulp.dest('./build/*/theme/html'));
});
... But globbing only works in the gulp.src. How can I output to a dynamic folder when using a globbed gulp.src? the closest I can get is putting the /theme folder at the same level as the template folders, not inside each as desired.
Thank you for your help!
QUESTION PART 2: OUTPUTTING A *RENAMED FILE* TO A NESTED DYNAMIC FOLDER
Mark's answered my question (Thanks #Mark!), but I over-simplified my use case so I'm adding a Part 2.
In addition to nesting the file, I need to rename it. (I had this part working originally, but can't get the 2 parts to work together.) Referring to the gulp-rename documentation, I made 3 different attempts. It's so close but I'd appreciate a little more help. :-)
// ATTEMPT 1: Using gulp-rename mutating function method
gulp.task('createTwig', function(){
return gulp.src('./build/*/*.html')
.pipe(rename(
function (path) {
path.basename = "email";
path.extname = ".html.twig";
},
function (file) {
console.log(file.dirname);
file.dirname = nodePath.join(file.dirname, 'theme/html');
}
))
.pipe(gulp.dest('./build/'));
});
// ATTEMPT 2: Using gulp-rename fixed object method
gulp.task('createTwig', function(){
return gulp.src('./build/*/*.html', { base: process.cwd() })
.pipe(rename(
{
basename: "email",
extname: ".html.twig"
},
function (file) {
console.log(file.dirname);
file.dirname = nodePath.join(file.dirname, 'theme/html');
}
))
.pipe(gulp.dest('./build/'));
});
// ATTEMPT 3: Using gulp-rename mutating function method
gulp.task('createTwig', function(){
return gulp.src('./build/*/*.html')
.pipe(rename(
function (path, file) {
path.basename = "email";
path.extname = ".html.twig";
console.log(file.dirname);
file.dirname = nodePath.join(file.dirname, 'theme/html');
}
))
.pipe(gulp.dest('./build/'));
});

This works:
const rename = require("gulp-rename");
const path = require("path");
gulp.task('moveFile', function(){
return gulp.src(['build/**/*.html'])
.pipe(rename(function (file) {
console.log(file.dirname);
file.dirname = path.join(file.dirname, 'theme/html');
}))
.pipe(gulp.dest('build')) // build/template1/theme/html
});
I tried a few ways, including trying the base option and gulp-flatten and using a function in gulp.dest but this was the easiest.
Question Part #2:
gulp.task('createTwig', function(){
return gulp.src(['build/**/*.html'])
.pipe(rename(function (file) {
file.basename = "email";
file.extname = ".html.twig";
file.dirname = path.join(file.dirname, 'theme/html');
}))
.pipe(gulp.dest('build')) // build/template1/theme/html
});
path.basename/extname are just "getters", you cannot set those values.

Related

How to unzip multiple files in the same folder with Gulp

I'd like to unzip multiple zip files that are inside a single folder. Every unzipped file will be unpacked into a folder with the same name as the original zip file and added as a sub folder to the original folder containing the original zips.
Something like this:
parent(folder)
-a.zip
-b.zip
-c.zip
would become:
parent(folder)
-a(folder)
--a.zip contents here
-b(folder)
--b.zip contents here
-c(folder)
--c.zip contents here
I believe the code i have so far is a nice try but seems like it's executing asynchronously in the pipeline (i'm obviously not a Gulp expert). All the zip files are being looked at but only the last one seems to get all the content, and then some from other zips. Run it with one zip file in the folder and it works perfectly.
var zipsPath = 'src/';
var currentZipFileName;
function getZips(dir) {
return fs.readdirSync(dir)
.filter(function (file) {
return file.indexOf(".zip") > 0;
});
}
gulp.task('init', function (done) {
var zips = getZips(zipsPath);
var tasks = zips.map(function (zip) {
console.log("zip", zip, path.join(zipsPath, zip));
return gulp.src(path.join(zipsPath, zip), {
base: '.'
})
.pipe(tap(function (file, t) {
currentZipFileName = path.basename(file.path);
}))
.pipe(unzip({ keepEmpty : true }))
.pipe(gulp.dest(function (path) {
var folderName = currentZipFileName.replace(".zip", "");
var destination = "./src/" + folderName;
//console.log("destination", destination);
return destination;
}))
.on('end', function() {
console.log('done');
done();
});
});
return tasks;
});
Expected results: all the zip files should be unpacked.
Actual results: most of the content is being dumped into the last zip file looked at.
Thanks for the help
Your problem lies here:
.pipe(tap(function (file, t) {
currentZipFileName = path.basename(file.path);
}))
You are trying to set a variable in one pipe to use in a later pipe. That doesn't work, there are a few questions here about it, but it just doesn't work - your variable will probably have the last value in it when the gulp.dests start firing or undefined - I think it is based on unpredictable timing.
In any case you don't need to set that variable - you already have the value of the desired folder name in your zips.map(zip) {} the zip item. You can use that in the gulp.dest just fine.
gulp.task('init', function (done) {
var zips = getZips(zipsPath);
var tasks = zips.map(function (zip) {
return gulp.src(zipsPath + "/" + zip)
// .pipe(tap(function (file, t) {
// currentZipFileName = path.basename(file.path);
// }))
.pipe(unzip({ keepEmpty: true }))
.pipe(gulp.dest(path.join("src", path.basename(zip, ".zip"))))
.on('end', function() {
done();
});
});
return tasks;
});
Also avoid using path.join in your gulp.src for the reasons stated here: gulpjs docs on glob separators:
The separator in a glob is always the / character - regardless of the operating system - even in Windows where the path separator is \\. In a glob, \\ is reserved as the escape character.
Avoid using Node's path methods, like path.join, to create globs. On Windows, it produces an invalid glob because Node uses \\ as the separator. Also avoid the __dirname global, __filename global, or process.cwd() for the same reasons.

How can I use gulp to replace a string in a particular source file using a config object?

How to achieve a replacing a string on a particular source file while the source files will be concatenated.
var gulp = require('gulp'),
rename = require('gulp-rename'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
replace = require('gulp-replace');
var config = {
cssConcatFiles: [
'one.css',
'two.css',
'three.css'
]
};
gulp.task('css-concat', function() {
return gulp.src(config.cssConcatFiles)
.pipe(replace('url\(\'', 'url\(\'../images/fancybox/'))
// I want to perform this replace to a particular file which is "two.css"
.pipe(concat('temp.css'))
.pipe(uglyfycss())
.pipe(rename('temp.min.css'))
.on('error', errorLog)
.pipe(gulp.dest('public/css/plugins/fancybox'));
});
This should work. gulp-if
const gulpIF = require('gulp-if');
gulp.task('css-concat', function() {
return gulp.src(config.cssConcatFiles)
//.pipe(replace('url\(\'', 'url\(\'../images/fancybox/'))
// I want to perform this replace to a particular file which is "two.css"
.pipe(gulpIF((file) => file.path.match('two.css') , replace('url\(\'', 'url\(\'../images/fancybox/')))
.pipe(concat('temp.css'))
.pipe(uglyfycss())
.pipe(rename('temp.min.css'))
.on('error', errorLog)
.pipe(gulp.dest('public/css/plugins/fancybox'));
});
You could use the following pipe instead of the gulp-if call:
.pipe(replace(/url\(\'/g, function(match) {
if (this.file.relative === "two.css") {
return 'url\(\'../images/fancybox/';
}
else return match;
}))
since gulp-replace will take a function as an argument and provide that function with a vinyl file reference (this.file) which you can use to test for which file is passing through the stream. You must, however, return something from the function call even when you want to do nothing - so return the original match.
I recommend using gulp-if, much cleaner in your case.

convert gulp.src stream to array?

Is it possible to get the list of files coming from a gulp.src stream as an array, e.g.:
var files = convertToArray(gulp.src('**/*.js'));
Update:
I was trying to move away from the gulp-karma plugin:
gulp.task('test', function () {
return gulp.src(files)
.pipe($.order(ordering))
.pipe($.karma({
karma.conf.js'
});
});
So my idea was:
gulp.task('test', function (done) {
var karmaFiles = convertToArray(gulp.src(files)
.pipe($.order(ordering)));
new Server({
configFile: karma.conf.js',
files: karmaFiles
}, done).start();
});
But as pointed out, this won't work because of it being async. Here's my solution:
gulp.task('test', function (done) {
gulp.src(files)
.pipe($.order(ordering)))
.pipe(gutil.buffer())
.on('data', function(data) {
var karmaFiles = data.map(function(f) { return f.path; });
new Server({
configFile: __dirname + '/karma.conf.js',
files: karmaFiles
}, done).start();
});
});
Gulp streams are always asynchronous so your hypothetical convertToArray function (which takes a stream and returns an array) is impossible.
The only way to get all the files in a stream is through some kind of callback function. The gulp-util package, which bundles various helper functions, provides the nice gutil.buffer() :
var gutil = require('gulp-util');
gulp.src('**/*.js').pipe(gutil.buffer(function(err, files) {
console.log('Path of first file:');
console.log(files[0].path);
console.log('Contents of first file:');
console.log(files[0].contents.toString());
}));
In the above files will be an array of vinyl files. That means for each file you have access to both the contents and the path of the file.
If you don't care about the file contents and only want the path of each file you shouldn't be using gulp.src() at all. You should be using glob instead (which is what gulp is using internally). It gives you a synchronous method that returns an array of matching file paths:
var glob = require('glob');
var files = glob.sync('**/*.js');
console.log(files);

Gulp - Delete empty folders recursively

I want to delete all folders and subfolders inside of a given directory if they only contain folders and no files. Is there an easy way to do that?
What I found until now:
https://www.npmjs.com/package/gulp-recursive-folder
https://www.npmjs.com/package/gulp-count
https://www.npmjs.com/package/gulp-path
You can use delete-empty:
gulp.task('delete-empty-directories', function() {
deleteEmpty.sync('foo/');
});
This recursively deletes all empty folders below foo/.
Here's a rough start, so you'll just need to loop for recursion I guess.
var modules = {
gulp : require('gulp'),
fs : require('fs'),
path : require('path'),
del : require('del'),
map : require('map-stream')
};
modules.gulp.task('folder-delete', function() {
// get folder list inside of the dir passed in
function getFolders(dir) {
return modules.fs.readdirSync(dir)
.filter(function(file) {
return modules.fs.statSync(modules.path.join(dir, file)).isDirectory();
});
}
var dir = '../src/', // (update with your path to the root folder)
folders = getFolders(dir),
hasFile = 0;
var folderMap = folders.map(function(folder) {
hasFile = 0; // reset for each folder
return modules.gulp.src(dir + folder + '/**/*')
.pipe(modules.map(function(file, cb) {
hasFile = 1;
cb(null, file);
}))
.on('end', function() {
console.log(hasFile, ' - ', folder);
if (!hasFile) {
modules.del([dir + folder], { force: true }).then(function() {
console.log('Deleted ' + dir + folder);
});
}
})
});
return folderMap;
});
Basically, this is setting the directory at ../src/, getting the folders at the root of that dir, then runs the src under those directories. It then uses map to see if there was a file added to the stream, then updates a variable if so. After the task finishes and if the variable has not been updated, then it will delete the folder.
As stated above, you could probably just loop through the directories for the recursion (or you could use one of the plugins you've mentioned).
The accepted solution, which uses the delete-empty package, doesn't work for me (and for others too). Plus, the creator seems to have stopped maintaining the package (last update 3 years ago).
What works just fine, and probably is much more future save, is a combination of sync-exec and the OS find command.
const syncExec = require("sync-exec")
syncExec("find sample/directory/ -type d -empty -delete")

How to set gulp.dest() in same directory as pipe inputs?

I need all the found images in each of the directories to be optimized and recorded into them without setting the path to the each folder separately. I don't understand how to make that.
var gulp = require('gulp');
var imageminJpegtran = require('imagemin-jpegtran');
gulp.task('optimizeJpg', function () {
return gulp.src('./images/**/**/*.jpg')
.pipe(imageminJpegtran({ progressive: true })())
.pipe(gulp.dest('./'));
});
Here are two answers.
First: It is longer, less flexible and needs additional modules, but it works 20% faster and gives you logs for every folder.
var merge = require('merge-stream');
var folders =
[
"./pictures/news/",
"./pictures/product/original/",
"./pictures/product/big/",
"./pictures/product/middle/",
"./pictures/product/xsmall/",
...
];
gulp.task('optimizeImgs', function () {
var tasks = folders.map(function (element) {
return gulp.src(element + '*')
.pipe(sometingToDo())
.pipe(gulp.dest(element));
});
return merge(tasks);
});
Second solution: It's flexible and elegant, but slower. I prefer it.
return gulp.src('./pictures/**/*')
.pipe(somethingToDo())
.pipe(gulp.dest(function (file) {
return file.base;
}));
Here you go:
gulp.task('optimizeJpg', function () {
return gulp.src('./images/**/**/*.jpg')
.pipe(imageminJpegtran({ progressive: true })())
.pipe(gulp.dest('./images/'));
});
Gulp takes everything that's a wildcard or a globstar into its virtual file name. So all the parts you know you want to select (like ./images/) have to be in the destination directory.
You can use the base parameter:
gulp.task('uglify', function () {
return gulp.src('./dist/**/*.js', { base: "." })
.pipe(uglify())
.pipe(gulp.dest('./'));
});