Can I use a Gulp task with multiple sources and multiple destinations? - javascript

I have the following in my gulpfile.js:
var sass_paths = [
'./httpdocs-site1/media/sass/**/*.scss',
'./httpdocs-site2/media/sass/**/*.scss',
'./httpdocs-site3/media/sass/**/*.scss'
];
gulp.task('sass', function() {
return gulp.src(sass_paths)
.pipe(sass({errLogToConsole: true}))
.pipe(autoprefixer('last 4 version'))
.pipe(minifyCSS({keepBreaks:true}))
.pipe(rename({ suffix: '.min'}))
.pipe(gulp.dest(???));
});
I'm wanting to output my minified css files to the following paths:
./httpdocs-site1/media/css
./httpdocs-site2/media/css
./httpdocs-site3/media/css
Am I misunderstanding how to use sources/destinations? Or am I trying to accomplish too much in a single task?
Edit: Updated output paths to corresponding site directories.

I guess that the running tasks per folder recipe may help.
Update
Following the ideas in the recipe, and oversimplifying your sample just to give the idea, this can be a solution:
var gulp = require('gulp'),
path = require('path'),
merge = require('merge-stream');
var folders = ['httpdocs-site1', 'httpdocs-site2', 'httpdocs-site3'];
gulp.task('default', function(){
var tasks = folders.map(function(element){
return gulp.src(element + '/media/sass/**/*.scss', {base: element + '/media/sass'})
// ... other steps ...
.pipe(gulp.dest(element + '/media/css'));
});
return merge(tasks);
});

you are going to want to use merge streams if you would like to use multiple srcs but you can have multiple destinations inside of the same one. Here is an example.
var merge = require('merge-stream');
gulp.task('sass', function() {
var firstPath = gulp.src(sass_paths[0])
.pipe(sass({errLogToConsole: true}))
.pipe(autoprefixer('last 4 version'))
.pipe(minifyCSS({keepBreaks:true}))
.pipe(rename({ suffix: '.min'}))
.pipe(gulp.dest('./httpdocs-site1/media/css'))
.pipe(gulp.dest('./httpdocs-site2/media/css'));
var secondPath = gulp.src(sass_paths[1])
.pipe(sass({errLogToConsole: true}))
.pipe(autoprefixer('last 4 version'))
.pipe(minifyCSS({keepBreaks:true}))
.pipe(rename({ suffix: '.min'}))
.pipe(gulp.dest('./httpdocs-site1/media/css'))
.pipe(gulp.dest('./httpdocs-site2/media/css'));
return merge(firstPath, secondPath);
});
I assumed you wanted different paths piped here so there is site1 and site2, but you can do this to as many places as needed. Also you can specify a dest prior to any of the steps if, for example, you wanted to have one dest that had the .min file and one that didn't.

You can use gulp-rename to modify where files will be written.
gulp.task('sass', function() {
return gulp.src(sass_paths, { base: '.' })
.pipe(sass({errLogToConsole: true}))
.pipe(autoprefixer('last 4 version'))
.pipe(minifyCSS({keepBreaks:true}))
.pipe(rename(function(path) {
path.dirname = path.dirname.replace('/sass', '/css');
path.extname = '.min.css';
}))
.pipe(gulp.dest('.'));
});
Important bit: use base option in gulp.src.

For the ones that ask themselves how can they deal with common/specifics css files (works the same for scripts), here is a possible output to tackle this problem :
var gulp = require('gulp');
var concat = require('gulp-concat');
var css = require('gulp-clean-css');
var sheets = [
{ src : 'public/css/home/*', name : 'home.min', dest : 'public/css/compressed' },
{ src : 'public/css/about/*', name : 'about.min', dest : 'public/css/compressed' }
];
var common = {
'materialize' : 'public/assets/materialize/css/materialize.css'
};
gulp.task('css', function() {
sheets.map(function(file) {
return gulp.src([
common.materialize,
file.src + '.css',
file.src + '.scss',
file.src + '.less'
])
.pipe( concat(file.name + '.css') )
.pipe( css() )
.pipe( gulp.dest(file.dest) )
});
});
All you have to do now is to add your sheets as the object notation is constructed.
If you have additionnal commons scripts, you can map them by name on the object common, then add them after materialize for this example, but before the file.src + '.css' as you may want to override the common files with your customs files.
Note that in the src attribute you can also put path like this :
'public/css/**/*.css'
to scope an entire descendence.

I had success without needing anything extra, a solution very similar to Anwar Nairi's
const p = {
dashboard: {
css: {
orig: ['public/dashboard/scss/style.scss', 'public/dashboard/styles/*.css'],
dest: 'public/dashboard/css/',
},
},
public: {
css: {
orig: ['public/styles/custom.scss', 'public/styles/*.css'],
dest: 'public/css/',
},
js: {
orig: ['public/javascript/*.js'],
dest: 'public/js/',
},
},
};
gulp.task('default', function(done) {
Object.keys(p).forEach(val => {
// 'val' will go two rounds, as 'dashboard' and as 'public'
return gulp
.src(p[val].css.orig)
.pipe(sourcemaps.init())
.pipe(sass())
.pipe(autoPrefixer())
.pipe(cssComb())
.pipe(cmq({ log: true }))
.pipe(concat('main.css'))
.pipe(cleanCss())
.pipe(sourcemaps.write())
.pipe(gulp.dest(p[val].css.dest))
.pipe(reload({ stream: true }));
});
done(); // <-- to avoid async problems using gulp 4
});

Multiple sources with multiple destinations on gulp without using any extra plugins just doing concatenation on each js and css. Below code works for me. Please try it out.
const gulp = require('gulp');
const concat = require('gulp-concat');
function task(done) {
var theme = {
minifiedCss: {
common: {
src : ['./app/css/**/*.min.css', '!./app/css/semantic.min.css'],
name : 'minified-bundle.css',
dest : './web/bundles/css/'
}
},
themeCss:{
common: {
src : ['./app/css/style.css', './app/css/responsive.css'],
name : 'theme-bundle.css',
dest : './web/bundles/css/'
}
},
themeJs: {
common: {
src: ['./app/js/jquery-2.1.1.js', './app/js/bootstrap.js'],
name: 'theme-bundle.js',
dest: './web/_themes/js/'
}
}
}
Object.keys(theme).map(function(key, index) {
return gulp.src(theme[key].common.src)
.pipe( concat(theme[key].common.name) )
.pipe(gulp.dest(theme[key].common.dest));
});
done();
}
exports.task = task;

Using gulp-if helps me a lot.
The gulp-if first argument. is the gulp-match second argument condition
gulp-if can be found in gulp-if
import {task, src, dest} from 'gulp';
import VinylFile = require("vinyl");
const gulpif = require('gulp-if');
src(['foo/*/**/*.md', 'bar/*.md'])
.pipe(gulpif((file: VinylFile) => /foo\/$/.test(file.base), dest('dist/docs/overview')))
.pipe(gulpif((file: VinylFile) => /bar\/$/.test(file.base), dest('dist/docs/guides')))
});

I think we should create 1 temporary folder for containing all these files. Then gulp.src point to this folder

The destination will have the same directory structure as the source.

Related

Refactored watch task using gulp v4 doesn't work

I'm refactoring my gulpfile now I'm using gulp v4 and am having an issue with gulp watch not running my stylesCompileIncremental function. Any help or pointers would be much appreciated.
My refactoring includes:
Switching to using functions instead of gulp.task
Using series and parallel as per the docs
Exporting public tasks at the bottom of my gulpfile ie exports.stylesWatch = stylesWatch;
Adding callbacks in functions to tell Gulp the function is complete
The code for the affected tasks is as follows (directory paths are stored in package.json file hence pathConfig.ui... values):
// Compile only particular Sass file that has import of changed file
function stylesCompileIncremental(cb) {
sassCompile({
source: getResultedFilesList(changedFilePath),
dest: pathConfig.ui.core.sass.dest,
alsoSearchIn: [pathConfig.ui.lib.resources]
});
cb();
}
// Compile all Sass files and watch for changes
function stylesWatch(cb) {
createImportsGraph();
var watcher = gulp.watch(pathConfig.ui.core.sass.src + '**/*.scss', gulp.parallel(devServReloadStyles));
watcher.on('change', function(event) {
changedFilePath = event;
});
cb();
}
// reload css separated into own function. No callback needed as returning event stream
function reloadCss() {
return gulp.src(generateFilePath)
.pipe($.connect.reload()); // css only reload
}
function devServReloadStyles(cb) {
gulp.series(stylesCompileIncremental, reloadCss);
cb();
}
When I run gulp stylesWatch using my refactored code I get the below output (notice the stylesCompileIncremental task is not run):
So my watch tasking is successfully running but there's something wrong when the devServReloadStyles is run for the stylesCompileIncremental function to not kick in.
The original code before refactoring (when using gulp v3) is below:
// Compile only particular Sass file that has import of changed file
gulp.task('styles:compile:incremental', () => {
return sassCompile({
source: getResultedFilesList(changedFilePath),
dest: pathConfig.ui.core.sass.dest,
alsoSearchIn: [pathConfig.ui.lib.resources]
});
});
// Compile all Sass files and watch for changes
gulp.task('styles:watch', () => {
createImportsGraph();
gulp.watch(
pathConfig.ui.core.sass.src + '**/*.scss',
['devServ:reload:styles']
).on('change', event => changedFilePath = event.path);
});
// Reload the CSS links right after 'styles:compile:incremental' task is returned
gulp.task('devServ:reload:styles', ['styles:compile:incremental'], () => {
return gulp.src(generateFilePath) // css only reload
.pipe($.connect.reload());
});
The original task output when running styles:watch is this:
And this is the sassCompile variable used inside stylesCompileIncremental which I've currently not changed in anyway.
/**
* Configurable Sass compilation
* #param {Object} config
*/
const sassCompile = config => {
const sass = require('gulp-sass');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const postProcessors = [
autoprefixer({
flexbox: 'no-2009'
})
];
return gulp.src(config.source)
.pipe($.sourcemaps.init({
loadMaps: true,
largeFile: true
}))
.pipe(sass({
includePaths: config.alsoSearchIn,
sourceMap: false,
outputStyle: 'compressed',
indentType: 'tab',
indentWidth: '1',
linefeed: 'lf',
precision: 10,
errLogToConsole: true
}))
.on('error', function (error) {
$.util.log('\x07');
$.util.log(error.message);
this.emit('end');
})
.pipe(postcss(postProcessors))
.pipe($.sourcemaps.write('.'))
.pipe(gulp.dest(config.dest));
};
UPDATE
This is due to an issue with my devServReloadStyles function, although I'm still unsure why. If I change my stylesWatch function to use the original devServ:reload:styles task stylesCompileIncremental gets run.
// Compile all Sass files and watch for changes
function stylesWatch(cb) {
createImportsGraph();
var watcher = gulp.watch(pathConfig.ui.core.sass.src + '**/*.scss', gulp.parallel('devServ:reload:styles'));
watcher.on('change', function(event) {
changedFilePath = event;
});
cb();
}
It would still be good to not use the old task and have this as a function though.
Can anybody tell me why my refactored version doesn't work and have any suggestions as to how this should look?
I've fixed this now.
gulp.series and gulp.parallel return functions so there was no need to wrap stylesCompileIncremental and reloadCss inside another function ie. devServReloadStyles.
As per Blaine's comment here.
So my function:
function devServReloadStyles(cb) {
gulp.series(stylesCompileIncremental, reloadCss);
cb();
}
Can just be assigned to a variable:
const devServReloadStyles = gulp.series(stylesCompileIncremental, reloadCss);
And my stylesWatch task is already calling devServReloadStyles:
// Compile all Sass files and watch for changes
function stylesWatch(cb) {
createImportsGraph();
var watcher = gulp.watch(pathConfig.ui.core.sass.src + '**/*.scss', gulp.parallel(devServReloadStyles));
watcher.on('change', function(event) {
changedFilePath = event;
});
cb();
}
So running gulp stylesWatch now runs the stylesCompileIncremental job (notice how devServReloadStyles doesn't show as it's not a function).

How I replace values in HTML attributes via config.json file with Gulp?

Let's say I have a JSON file with the pairs:
{
"Table":{
"fullwidth": "680",
"color": "#33d025",
"margin1": "30",
"margin2": "60",
"padding": "20"
}
}
then, I want to read those values and use them to replace attributes in an html file that looks like this:
<table width="{{Table.fullwidth}}" bgcolor="{{Table.color}}" style="margin: {{Table.margin1}}px {{Table.margin2}}px;">
<tr>
<td style="padding: {{Table.padding}}px;">
<img src="a.jpg">
</td>
</tr>
</table>
So, with the html file in a "temp/" path, after gulping, I obtain a valid html file in "dist/" with the attributes changed looking like this:
<table width="680" bgcolor="#33d025" style="margin: 30px 60px;">
<tr>
<td style="padding: 20px;">
<img src="a.jpg">
</td>
</tr>
</table>
I have already tried gulp-token-replace but after running it once, it won't work again if I save new values in the json file, even when it triggers the watch function, forcing me to restart the "gulp".
Is there a gulp plugin that can do this? or a technique that can replace the gulp-token-replace?
Maybe just javascript, but, can I run something like that from inside a gulp process (running watch to refresh it)?
Gulpfile.js as requested:
// Include gulp
var gulp = require('gulp'),
// Include plugins
fileinclude = require('gulp-file-include'),
rename = require('gulp-rename'),
images = require('gulp-imagemin'),
cache = require('gulp-cache'),
browserSync = require('browser-sync').create(),
reload = browserSync.reload,
runSequence = require('run-sequence'),
del = require('del'),
notify = require('gulp-notify'),
gtr = require('gulp-token-replace')
// Default Task
gulp.task('default', function (cb) {
runSequence('clean', ['AA', 'BB', 'CC', 'watch'], cb);
});
// TASKS
// Clean 'dist'
gulp.task('clean', function () {
return del(['HTMLTemplates/*.html', 'HTMLTemplates/img', 'Temp/*.html']);
});
// Compress images
gulp.task('BB', function () {
gulp.src('templates/img/*.{gif,jpg,png}')
.pipe(cache(images({
optimizationLevel: 4,
progressive: true,
interlaced: true
})))
.pipe(gulp.dest('Templates/img/'));
});
// Reload browser
gulp.task('reload', function () {
browserSync.reload();
});
// Prepare Browser-sync
gulp.task('CC', ['AA'], function () {
browserSync.init({
// browserSync.init(['templates/*/*.html'], {
//proxy: 'your_dev_site.url'
server: {
baseDir: './HTMLTemplates'
}
});
});
// MAIN TASKS
gulp.task('AA', function (cbk) {
runSequence('fileinclude', 'trp', cbk);
});
// Force to run fileinclude first before replacing the tokens
gulp.task('trp', ['fileinclude'], function (done) {
function onFinish(event) {
if (event.task === 'tokenreplace') {
gulp.removeListener('task_stop', onFinish);
done();
}
}
gulp.on('task_stop', onFinish);
gulp.start('tokenreplace');
});
// Include partial files into email template (fileinclude)
gulp.task('fileinclude', function () {
// grab 'template'
return gulp.src('templates/layouts/*.tpl.html')
// include partials
.pipe(fileinclude({
basepath: 'templates/components/'
}))
// remove .tpl.html extension name
.pipe(rename({
extname: ""
}))
// add new extension name
.pipe(rename({
extname: ".html"
}))
// move file to folder
.pipe(gulp.dest('Temp/'))
.pipe(notify({
message: 'Template file includes complete'
}));
});
// Replace tokens in the index.html created by fileinclude
gulp.task('tokenreplace', ['fileinclude'], function (doit) {
var config = require('./templates/components/000 vars/config.json');
return gulp.src('Temp/index.html')
.pipe(gtr({
global: config
}))
.pipe(gulp.dest('HTMLTemplates/'))
// notify to say the task has complete
.pipe(browserSync.stream())
.pipe(notify({
message: 'Vars includes complete'
})), doit();
});
// END of MAIN TASKS
// WATCH
// Watch files for changes in html/css/tpl.html/images
gulp.task('watch', function () {
gulp.watch(['templates/components/**/*.html'], ['AA']);
gulp.watch(['templates/components/**/*.css'], ['AA']);
gulp.watch(['templates/layouts/*.tpl.html'], ['AA']);
gulp.watch(['templates/components/000 vars/*.json'], ['trp']);
gulp.watch(['HTMLTemplates/*.html'], ['reload']);
gulp.watch('templates/img/*', ['BB']);
});
I received the answer directly from the developer of the Gulp Token Replace plugin, so I'm answering my own question for archive purposes.
Replace this:
// Replace tokens in the index.html created by fileinclude
gulp.task('tokenreplace', ['fileinclude'], function (doit) {
var config = require('./templates/components/000 vars/config.json');
return gulp.src('Temp/index.html')
.pipe(gtr({
global: config
}))
with this:
// Replace tokens in the index.html created by fileinclude
gulp.task('tokenreplace', ['fileinclude'], function (doit) {
delete require.cache[require.resolve('./templates/components/000 vars/config.json')]
var config = require('./templates/components/000 vars/config.json');
return gulp.src('Temp/index.html')
.pipe(gtr({
global: config
}))
And now it works like a charm!

Gulp injection missing files

I'm trying to use gulp-inject but something is not syncing up properly. I think it has to do with my cleaning task.
gulp.task("clean", function () {
return gulp.src([
"tmp/client/**/*",
"wwwroot/**/*.css",
"wwwroot/**/*.js"
], { read: false })
.pipe(plumber())
.pipe(clean());
});
This runs before my compile tasks, such as:
gulp.task("dev:tsc", ["clean"], function () {
return tsResult.js
.pipe(sourceMaps.write("."))
.pipe(gulp.dest("wwwroot/js"));
});
Then all my compile steps run before a build task:
function injectTask(assets) {
var layout = gulp.src("Views/Shared/_Layout.cshtml");
var sources = gulp.src(assets, { read: false });
return layout.pipe(inject(sources, {
ignorePath: "wwwroot",
addPrefix: "~",
addRootSlash: false
})).pipe(gulp.dest("Views/Shared"));
}
gulp.task("dev:build", ["dev:tsc", "...other tasks..."], function () {
var vendorSources = vendorConfig.map(function (vendor) {
return ["wwwroot/js", vendor.srcFile].join("/");
});
return injectTask(vendorSources.concat([
"wwwroot/css/*.css",
"wwwroot/js/*.js"
]));
});
Sometimes all my files get injected, sometimes some are missing. Sometimes I get an error about a file missing. I suspect I'm missing some step to correctly declare dependencies on the streams and prevent them from overlapping, I just can't see what it is. The complete gulpfile is here. Any ideas?

gulp-task output in console is not printing expected result

I have the following gulp task which inserts .css and .js files in the html file:
gulp.task('inject', function () {
log('Injecting the JS and CSS files into index.html');
var wiredep = require('wiredep').stream;
var options = config.getWiredepDefaultOptions();
return gulp.src(config.index)
.pipe(wiredep(options))
.pipe($.inject(gulp.src(config.customFiles), {ignorePath: options.ignorePath}))
.pipe(gulp.dest(config.client));
});
and my gulp.config.js:
module.exports = function () {
var client = './public';
var config = {
allJS: [
'*.js',
'public/js/*.js',
'public/js/**/*.js'
],
client: client,
index: client + '/index.html',
customFiles: [
'./public/css/*.css',
'./public/js/*.js',
'./public/js/**/*.js'
],
bower: {
json: require('./bower.json'),
directory: './public/lib',
ignorePath: '/public/'
},
};
config.getWiredepDefaultOptions = function () {
var options = {
bowerJson: config.bower.json,
directory: config.bower.directory,
ignorePath: config.bower.ignorePath
};
return options;
};
return config;
};
This works as expected, but when I run the task I get this:
It always says gulp-inject 3 files into index.html, even though no new files was added.
Is there something wrong with my gulp file?
Well if you are using gulp-inject this is what I found.
If you look at the code in gulp-inject you can see it just spits out the file count unless it gets opt.quiet set. I didn't see a option in the docs for this setting but if you look at the tests it shows an example it being used.
Enabling quiet mode link line 505
inject(sources, {quiet: true});
Source where it generates the log statement. link line 109
function getNewContent(target, collection, opt) {
var logger = opt.quiet ? noop : function (filesCount) {
if (filesCount) {
log(cyan(filesCount) + ' files into ' + magenta(target.relative) + '.');
} else {
log('Nothing to inject into ' + magenta(target.relative) + '.');
}
};

grunt load dynamic JSON file

Is it possible to load a JSON file from a dynamic source? I want to do some localisation
grunt.file.readJSON('src/locales/<%= grunt.task.current.args[0] %>/i18n.json');
A fuller example of the Gruntfile looks like:
module.exports = function(grunt) {
var i18n = {
locales: ['en', 'fr', 'de', 'es'],
default: 'en',
replacements: function(locale){
var content = grunt.file.readJSON('src/locales/<%= grunt.task.current.args[0] %>/i18n.json');
var arr = [];
for(i in content){
var replacement = {
from: i,
to: content[i].value
};
arr.push(replacement);
}
return arr;
}
};
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
replace: {
build: {
src: ['local/en/**/*.html'], // source files array (supports minimatch)
dest: 'local/<%= grunt.task.current.args[0] %>/', // destination directory or file
replacements: i18n.replacements('<%= grunt.task.current.args[0] %>')
}
},
While registering the task looks like:
grunt.registerTask('localise', function(){
var tasks = [];
for(i in i18n.locales){
if(i18n.locales[i] !== i18n.default){
tasks.push('replace:build:' + i18n.locales[i]);
}
}
grunt.task.run(tasks);
});
Everything works as I'd hoped except loading the JSON to actually do the replacements.
I've also tried:
grunt.file.readJSON('src/locales/'+locale+'/i18n.json');
which didn't work either, leaving me a little stumped.
Anyone able to help?
Thanks
Try:
'src/locales/' + grunt.task.current.args[0] + '/i18n.json'
Ok, I got it working after much trial and error:
I updated the function returning the data to:
var i18n = {
locales: ['en', 'fr', 'de', 'es'],
default: 'en',
replacements: function(locale){
var content = grunt.file.readJSON('src/locales/'+ locale +'/i18n.json');
var arr = [{from: "/" + locale, to: "/en"}, {from: "Test", to: locale}];
for(i in content){
var replacement = {
from: i,
to: content[i].value
};
arr.push(replacement);
}
console.log(arr);
return arr;
}
};
Then set an empty array in the default task:
replace: {
build: {
src: ['local/en/**/*.html'], // source files array (supports minimatch)
dest: 'local/<%= grunt.task.current.args[0] %>/', // destination directory or file
replacements: []
}
},
Which is updated using it's own task
grunt.registerTask('updateConf', function(locale){
var content = i18n.replacements(locale);
grunt.config('replace.build.replacements', content);
});
That is run just before the replace task:
grunt.registerTask('localise', function(){
var tasks = [];
for(i in i18n.locales){
if(i18n.locales[i] !== i18n.default){
tasks.push('updateConf:' + i18n.locales[i]);
tasks.push('replace:build:' + i18n.locales[i]);
}
}
grunt.task.run(tasks);
});
Giving the right output. Probably not the most elegant of solutions but it works!

Categories

Resources