In order to build the final JS source for a web app, I am compiling JavaScript templates, CoffeScript sources and vanilla JS vendor scripts. A final task, dependent on the former three, would then concatenate the scripts together into one file.
The following setup, in which I define the last task with dependencies to the first three, the final step never executes. It seems that process doesn't wait until the partial files are written to the disc.
I tried merging event streams as well, but it seems to be an overkill for something like this. I'd appreciate if someone can point me in the right direction here.
// compile JS templates
gulp.task('js-build-template', function() {
gulp.src('./../app/assets/javascripts/**/*.jst.eco')
.pipe(eco({ basePath: 'app/assets/javascripts', namespace: 'JST_ATL' }))
.pipe(concat('_templates.js'))
.pipe(gulp.dest('public/assets/scripts/partials'));
});
// compile CoffeeScript source
gulp.task('js-build-source', function() {
gulp.src(js.source)
.pipe(coffee())
.pipe(concat('_source.js'))
.pipe(gulp.dest('public/assets/scripts/partials'));
});
// compile vendor scripts
gulp.task('js-build-vendor', function() {
gulp.src(js.vendor)
.pipe(concat('_vendor.js'))
.pipe(gulp.dest('public/assets/scripts/partials'));
});
// concatenate and fingerprint files
gulp.task('js-build', [ 'js-build-template', 'js-build-source', 'js-build-vendor' ], function() {
gulp.src([ 'public/assets/scripts/partials/_templates.js', 'public/assets/scripts/partials/_vendor.js', 'public/assets/scripts/partials/_source.js' ])
.pipe(concat('app.js'))
.pipe(gulp.dest('public/assets/scripts'))
.pipe(rev())
.pipe(gulp.dest('public/assets/scripts'))
.pipe(rev.manifest())
.pipe(gulp.dest('public/assets/scripts'));
});
in all 3 of your task you need return statements
gulp.task('js-build-template', function() {
return gulp.src('./../app/assets/javascripts/**/*.jst.eco')
.pipe(eco({ basePath: 'app/assets/javascripts', namespace: 'JST_ATL' }))
.pipe(concat('_templates.js'))
.pipe(gulp.dest('public/assets/scripts/partials'));
});
this will help in letting final task know that they have finished and it needs to start...
Related
EDIT: it appears the pretty:true from Jade is from where the problem comes from. any idea why ?
I've got a bit of trouble here. Let me explain what I'm trying to do :
Here is what is supposed to happens:
file.jade > intermediate.html > index.html
The jade file outputs a html, then the html outpus a minified html. All in the watch function of course.
What I wrote to achieve this effect:
/* ===== TEMPLATES ===== */
gulp.task('templates', function() {
gulp.src(src + '/*.jade')
.pipe(jade({ locals: {data: data}, pretty:true}))
.pipe(rename('intermediate.html'))
.pipe(gulp.dest(src));
})
/* ===== HTML ===== */
gulp.task('html', function() {
// Get file
return gulp.src('./src/intermediate.html')
.pipe(inlineCss({
applyStyleTags: true,
applyLinkTags: true,
removeStyleTags: true,
removeLinkTags: true
}))
.pipe(inject(gulp.src([src + '/responsive.css']), {
starttag: '<!-- inject:head:{{ext}} -->',
transform: function (filePath, file) {
// return file contents as string
return file.contents.toString('utf8')
}
}))
.pipe(rename('index.html'))
.pipe(minifyHTML(options))
// Output file
.pipe(gulp.dest(archive))
.pipe(gulp.dest(dest));
});
/* ===== WATCH ===== */
gulp.task('watch', function() {
// Folders to watch and tasks to execute
gulp.watch([src + '/template.jade'], ['default']);
});
/* ===== DEFAULT ===== */
// Default gulp task ($ gulp)
gulp.task('default', function(callback) {
runSequence('clean',
'templates',
'html',
'notify',
callback);
});
Now what happens when a build happens ?
file.jade outputs intermediate.html
but index.html is produced with the OLD intermediate.html version, before its produced by the jade template. It's a bit tricky to explain.
What I mean is I need to do 2 gulp in a row every time to get the up to date index.html ! Because index.html takes the intermediate.html version that exists BEFORE the template is compiled from jade. I hope I'm being clear enough
I thought it would be ok with a runsequence but it doesn't seem to be !
I'm not a pro but those returns look problematic. If you're doing asynchronous stuff you need to use callbacks. It is likely skipping over the return statements altogether, or running them after everything else.
gulp.task('html', function() {
// Get file
gulp.src('./src/intermediate.html')
.pipe(inlineCss({
applyStyleTags: true,
applyLinkTags: true,
removeStyleTags: true,
removeLinkTags: true
}))
.pipe(inject(gulp.src([src + '/responsive.css']), {
starttag: '<!-- inject:head:{{ext}} -->',
transform: function (filePath, file) {
// return file contents as string
file.contents.toString('utf8')
}
}))
.pipe(rename('index.html'))
.pipe(minifyHTML(options))
// Output file
.pipe(gulp.dest(archive))
.pipe(gulp.dest(dest));
});
Disclaimer: I've never used gulp.
Also, is your console printing any errors or warnings?
This is pretty much a Grunt construction where you output to disk and pick it up in another task. You want to keep these things in one stream without writing the intermediate to disk in Gulp.
Since it all sequential anyway, why not run it through one pipeline? Template and html should be combined into one gulp task imo. The author of Gulp also confirms this here: How can I pipe stream between tasks #69
If you really want the logic to be separated, you could use lazypipe to construct two pipes beforehand (or even return them from a utility).
Option 2
If you're adamant about keeping these tasks as two parts or files, you can write them as two gulp plugins and pipe from one to another. Though this seems overengineering to me, it's a possibility. How to write a gulp plugin.
I've set up some simple Gulp tasks to process my CSS files.
The tasks are put together in one 'master' task:
gulp.task('process-css', ['concatCSS', 'minifyCSS', 'renameCSS']);
Just for reference, the definition of the concrete tasks follows:
gulp.task('minifyCSS', function() {
return gulp.src('themes/my_theme/css/dist/*.css')
.pipe(sourcemaps.init())
.pipe(minifyCSS())
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('themes/my_theme/css/dist/'));
});
gulp.task('concatCSS', function() {
var files = [
'themes/rubbish_taxi/css/bootstrap.css',
'themes/rubbish_taxi/css/custom.css',
'themes/rubbish_taxi/css/responsive.css',
'themes/rubbish_taxi/css/jquery.fancybox.css'
];
return gulp.src(files)
.pipe(concat("bundle.css"))
.pipe(gulp.dest('themes/my_theme/css/dist/'));
});
gulp.task('renameCSS', function() {
gulp.src('themes/my_theme/css/dist/bundle.css')
.pipe(rename(function(path) {
path.basename += ".min";
}))
.pipe(gulp.dest("themes/my_theme/css/"));
});
The tasks complete without an error, but the problem is that minifyCSS does not minify the source file. An unminified version of the files is saved as bundle.min.css. I believe that the reason is that minifyCSS runs before concatCSS completed.
How can I make the tasks be executed synchronously?
Is my only option to specify which tasks should be executed before a give task like this:
gulp.task('minifyCSS', ['concatCSS'], function() {..} ?
It worked when I set it this way, but I wanted to avoid this to make code more readable.
gulp.task('minifyCSS', ['concatCSS'], function() {..} ?
It worked when I set it this way, but I wanted to avoid this to make code more readable.
More readable how? You're stating that minifyCSS is dependent on concatCSS. The line of code I quoted above is how you explain this dependency to gulp.
The alternative is to use something like run-sequence, but I think avoiding functionality built into the tool to solve the exact problem you're facing isn't justified by the desire for a subjective improvement in readability.
So here is my hypothetical config object for a hypothetical fooTask that does something (not relevant to question) to a bunch of JS files
grunt.initConfig({
fooTask: {
app1: 'app1/*.js',
app2: 'app2/*.js',
app3: 'app3/*.js'
}
});
As you can see, with this approach, I have to run fooTask 3 times with each app specified as a target:
grunt fooTask:app1
grunt fooTask:app2
grunt fooTask:app3
Needless to say this does not scale as either the number of apps increase or the number of such foo tasks increase as one has to C&P the same code over and over for each app.
So ideally what I would like to define is just one target with the name of the app passed in as a config variable
grunt.initConfig({
fooTask: {
dist: '<%=appName%>/*.js'
}
});
I would then like to call fooTask 3 times, one for each app, with the right app set as appName
var apps = ['app1', 'app2', 'app3'];
apps.forEach(function(app) {
var currAppName = app;
// Run fooTask but how do I specify the new currAppName config?
grunt.task.run('fooTask');
});
As from code above, I know I can run my fooTask using grunt.task.run but how do I set the appName config for my task?
Note that this question is similar to this other one that also does not have the right answer yet - Pass Grunt config options from task.run
Thanks a lot.
EDIT 2:
So nevermind the garbage below the first edit, leaving as example of what doesn't work. In my case it was really important to be able to set the value within a task at run-time so I settled on the file system. Perhaps it suits your needs.
grunt.initConfig({
someTask: {
someKey: fs.readFileSync('file.txt', { encoding: 'utf8' })
}
});
of course you can do the readFile outside of the task if you need a bunch of different app names.
EDIT:
Hmmm. I swear I had this working when I wrote this...but now it is not. Grunt just sees the extra arguments as additional unfound tasks.
I was trying to figure this out myself, finally a "duh" just moment happened - why not parse process.argv before grunt.initConfig?
module.exports = function(grunt) {
var sourcefile = process.argv[2] || 'default.js'; // <- this
grunt.initConfig({
uglify: {
main: {
src: sourcefile, // <- voila :)
dest: sourcefile.substring(0, sourcefile.length-3) + '.min.js'
}
}
});
grunt.loadNpmTasks('uglify');
grunt.registerTask('default', ['uglify']);
};
and use from command line:
grunt mykillerscript.js
I didn't even try to use grunt.option for the same reason that all the examples only showed directing which task is run, but I wouldn't be surprised if there is a more "grunt" way to do this.
I am currently using Grunt, and as I was trying Gulp, the same problem I encountered first with Grunt occurred to me.
I am trying to process some js files (concat, uglify and minify them), but I don't want all of them to compile into one big file, I want multiple output files, each from the processing of some input files :
scripts =
firstOutput:
outputFilename: 'first.min.js',
inputFiles: ['one.js', 'two.js']
secondOutput:
outputFilename: 'second.min.js',
inputFiles: ['three.js']
thirdOutput:
outputFilename: 'third.min.js',
inputFiles: ['four.js', 'five.js']
The only way I found (for now) to achieve that with Grunt is with multiple watches and multiple uglify tasks (or one uglify task and a listener on watch change to dynamically modify the uglify task src and dest) :
module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-contrib-watch'
grunt.loadNpmTasks 'grunt-contrib-uglify'
grunt.initConfig
watch:
firstOutput:
files: scripts.firstOutput.inputFiles
tasks: ['uglify:firstOutput']
options :
spawn : false
secondOutput:
files: scripts.secondOutput.inputFiles
tasks: ['uglify:secondOutput']
options :
spawn : false
thirdOutput:
files: scripts.thirdOutput.inputFiles
tasks: ['uglify:thirdOutput']
options :
spawn : false
uglify:
firstOutput:
files: scripts.firstOutput.inputFiles
dest: scripts.firstOutput.outputFilename
secondOutput:
files: scripts.secondOutput.inputFiles
dest: scripts.secondOutput.outputFilename
thirdOutput:
files: scripts.thirdOutput.inputFiles
dest: scripts.thirdOutput.outputFilename
grunt.registerTask 'default', 'watch'
And, as you can imagine, this is just an example, in my case of a big web application, there's a lot more than just three output js files, and I also process a few less files into some css files
My Gruntfile is really huge, and I find it has a lot of duplicate code, is there any way to have this code refactored to have one watch and one uglify task, with an automatically guessed src and dest with some kind of dependency (to know that if the four.js file is modified, it has to process the third output) ?
If you have some way to do it with Gulp I'll take it with great pleasure, as I would like to test it in my usual workflow.
Here's how you can do this with gulp + vanilla javascript:
var _ = require("underscore")
, gulp = require("gulp")
, uglify = require("gulp-uglify")
var scripts = [
{
output: 'first.min.js',
input: ['one.js', 'two.js']
}
, {
output: 'second.min.js',
input: ['three.js']
}
, {
output: 'third.min.js',
input: ['four.js', 'five.js']
}
];
function build(files, dest) {
return gulp.src(files)
.pipe(uglify())
.pipe(gulp.dest(dest));
}
gulp.task("watch", function () {
_.each(scripts, function (script, i) {
gulp.watch(script.input, function () {
build(script.input, script.output);
});
});
});
Even better if you can use globs to match sets of files so you don't have to write out the path for every single input set. Something like input: ["one/**/*.js, "other/**/*.js"]
"I am trying to process some js files (concat, uglify and minify
them), but I don't want all of them to compile into one big file"
Can I ask why? The benefit of one larger file is that you save on HTTP requests, every resource you load will cause some slowdown of your website. May I suggest using proper dependency management with RequireJS? That way the optimiser can walk your dependency graph and output optimised files for you.
http://requirejs.org/
There's a grunt task for this too:
https://github.com/gruntjs/grunt-contrib-requirejs
I've written a function which I'd like to use as a Grunt task. I can do this by adding this to the Gruntfile:
grunt.registerTask('foo', function () {
// code here
});
However, it makes more sense to keep the function code in a separate file. I plan to define a bunch of these custom tasks and I don't want to bloat the Gruntfile.
I'm not sure what the preferred way of registering such tasks is. I have found this to work:
grunt.registerTask('foo', function () {
require('./path/to/foo.js')(grunt);
});
So, I'm having the inline function like in the fist example, but this time, I'm loading an external file and invoking it immediately. In that external file, I of course have to write:
module.exports = function (grunt) {
// code here
}
This works, but it feels hackish. Is there a more proper way of doing this?
Short answer: the alternative to this
grunt.registerTask('foo', function () {
require('./path/to/foo.js')(grunt);
});
is http://gruntjs.com/api/grunt#grunt.loadtasks
Long answer:
Normally when you have tasks in external files there are served as other nodejs modules. So, if that is something that you will use in several projects you may want to register it in the registry. Later inside your Gruntfile.js you will have:
grunt.loadNpmTasks('yout-module-here');
The grunt's documentation says:
Load tasks from the specified Grunt plugin. This plugin must be installed locally via npm, and must be relative to the Gruntfile
However, if you don't want to upload anything to the registry you should use loadTasks
grunt.loadTasks('path/to/your/task/directory');
So, once the task is loaded you may use it in your configuration.
Here is a simple grunt task placed in external file:
'use strict';
module.exports = function(grunt) {
grunt.registerMultiTask('nameoftask', 'description', function() {
var self = this;
// this.data here contains your configuration
});
};
And later in Gruntfile.js
grunt.initConfig({
nameoftask: {
task: {
// parameters here
}
}
});
I had a similar problem.
I wanted to modularize my grunt config and custom tasks by functionnalities (big UX/UI blocks) rather than by technical features. AND I wanted to keep the config files next to task files... (better when working on a large legacy codebase with an varied team - 5 persons with varying JS knowledge)
So I externalized my tasks like Krasimir did.
In the gruntfile, I wrote :
//power of globbing for loading tasks
var tasksLocations = ['./grunt-config/default_tasks.js', './grunt-config/**/tasks.js'];
var taskFiles = grunt.file.expand({
filter: "isFile"
}, tasksLocations);
taskFiles.forEach(function(path) {
grunt.log.writeln("=> loading & registering : " + path);
require(path)(grunt);
});
You will find the whole boilerplate gruntfile here (external config and tasks loading) : https://gist.github.com/0gust1/7683132