If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
Look at the default
task. The array defines task dependencies: Gulp runs
each of these first, waits for them to finish, and then executes the callback
for the task, if there is one. And based on the output, it looks like it
runs them in order: clean
, then styles
, then scripts
.
Is that true?
Log a message once fonts
is done:
... lines 1 - 112 | |
gulp.task('fonts', function() { | |
app.copy( | |
config.bowerDir+'/font-awesome/fonts/*', | |
'web/fonts' | |
).on('end', function() {console.log('finished fonts!')}); | |
}); | |
... lines 119 - 134 |
And also add a message right when the watch
task starts:
... lines 1 - 126 | |
gulp.task('watch', function() { | |
console.log('starting watch!'); | |
gulp.watch(config.assetsDir+'/'+config.sassPattern, ['styles']); | |
gulp.watch(config.assetsDir+'/js/**/*.js', ['scripts']); | |
}); | |
... lines 132 - 134 |
The default task defines fonts
then watch
, and I want to see if that
order matters.
Ok, try it!
gulp
It exploded! It say we're calling on
on something undefined. This happens
with our code because up in app.copy
, we're not returning the stream. So
yea, that would be undefined:
... lines 1 - 48 | |
app.copy = function(srcFiles, outputDir) { | |
return gulp.src(srcFiles) | |
.pipe(gulp.dest(outputDir)); | |
}; | |
... lines 53 - 134 |
Ok, now try it. It's all out of order! Even though fonts
is listed
before watch
in the dependency list, watch
starts way before fonts
finishes. In reality, Gulp reads the dependent tasks for default
, then
starts them all at once. Once they all finish, default
runs.
But what if we needed fonts
to finish before watch
started? Well, it's
the same trick: add fonts
as a dependency to watch
:
... lines 1 - 126 | |
gulp.task('watch', ['fonts'], function() { | |
... lines 128 - 130 | |
}); | |
... lines 132 - 134 |
Try it out:
gulp
But surprise! It's still running out of order. Here's the reason: if you're
dependent on a task like fonts
, that task must return a Promise or a
Gulp stream. If it doesn't, Gulp actually has no idea when fonts
finishes
watch
right away. So, return app.copy
from the fonts
task, since app.copy
returns a Gulp stream.... lines 1 - 112 | |
gulp.task('fonts', function() { | |
return app.copy( | |
... lines 115 - 116 | |
).on('end', function() {console.log('finished fonts!')}); | |
}); | |
... lines 119 - 134 |
Now, Gulp can know when fonts
truly finishes its work.
Ok, try it once more:
gulp
There it is! fonts
finishes, and then watch
starts. And there's one
more thing: Gulp finally prints "Finished 'fonts'" in the right place, after
fonts
does its work.
Why? It's not that Gulp was lying before about when things finished. It's that Gulp can't report when a task finishes unless that task returns a Promise or a Gulp stream. This means we should return one of these from every task.
We don't need the fonts
dependency, so take it off. And remove the logging:
... lines 1 - 112 | |
gulp.task('fonts', function() { | |
return app.copy( | |
config.bowerDir+'/font-awesome/fonts/*', | |
'web/fonts' | |
); | |
}); | |
... lines 119 - 126 | |
gulp.task('watch', function() { | |
... lines 128 - 129 | |
}); | |
... lines 131 - 133 |
So if we should always return a stream or promise, how can we do that for
styles
? It doesn't have a single stream - it has two that are combined
into the pipeline. We need to wait until both of them are finished.
Oh, the answer is so nice: just return pipeline.run()
:
... lines 1 - 84 | |
gulp.task('styles', function() { | |
var pipeline = new Pipeline(); | |
... lines 87 - 98 | |
return pipeline.run(app.addStyle); | |
}); | |
gulp.task('scripts', function() { | |
var pipeline = new Pipeline(); | |
... lines 104 - 109 | |
return pipeline.run(app.addScript); | |
}); | |
... lines 112 - 133 |
This isn't magic. I wrote the Pipeline code, and I made run()
return a
Promise that resolves once everything is done. And if you know anything
about promises, the guts should make sense to you. But if you have questions,
just ask in the comments.
Make sure we didn't break anything.
gulp
Yep, it all still looks great. So if you eventually need to create a task
that's dependent on styles
or scripts
finishing first, it'll work.
Wow, this is really great work! Thanks so much for sharing - a working example is worth 1000 (or more) words :). Cheers!
Hey Ryan,
I notice you don't talk about the ordering issue for the 'clean' task. How do you know it will be finished before the other tasks start ? If not, what tells us it is not going to erase newly compiled assets ?
Hey Nicolas!
GREAT question - so great that I also forgot why this works! The answer is that I'm using del.sync exclusively inside clean - this is actually a synchronous function - it blocks the "clean" task from finishing *until* all of these lines have run. Because of this, the next task can't start. This was done by design (idea adapted from Laravel's Elixir): it appears that Gulp doesn't exactly "start all the tasks" at the same moment. Instead, I believe it calls all of the tasks one by one, waiting for each callback function to fully execute. Of course, *most* callback functions to asynchronous work, but if it works synchronously, then it in fact blocks the next tasks from having their callback function executed.
If you're seeing anything different, let me know!
Cheers!
Thanks Ryan ! I am seeing exactly what you are describing :)
On a side note, seems like Gulp 4 will be deprecating sync tasks and adding easier ways to manager sequences, but your solution works great for me !
Hi,
I wonder if you know it there is any tool that allows optimizing css first time a page is visited.
The idea would be using some plugin like uncss in conjunction with assetic.
The thing is that a site can be very big, with several languages, domains, routes... instead of pre-generating the css, the idea is generate one single css per page but optimized without having to set up a configuration with all routes of the system.
Thanks in advance!
Samuel
Hi Samuel!
This approach makes sense, but I haven't seen anything like this in action before (doesn't mean that it doesn't exist, however). One thing to watch out for is that you really don't want 1 single, completely independent CSS file per page. That reason is that a large amount of CSS rules will likely be shared across *every* page, and if you re-package that CSS into a different file for each page, you're actually making your user download those CSS rules repeatedly.
So, you'd still probably want 1 consistent "site.css" file that is included on every page, and a separate page-specific CSS file for each page that needs it. In theory, you could get some system to automatically set that up (I've just not seen this yet). In practice, what I often do (or try to do) is create a main CSS file and then include page-specific CSS when I know I have certain CSS files that only apply to certain pages. This helps reduce the file of that "main" CSS file, but adds a little bit of complexity (you need to manually remember to include the page-specific CSS files on pages where you're using CSS selectors form it). I saw a presentation by Jonathan Snook once (https://twitter.com/snookca) who basically recommended only having 1 CSS file for your entire site. The reason is that they organize their CSS so well (by building re-usable components, instead of one-off CSS styles) that they don't really have enough "extra" stuff to merit needing to add extra optimization.
Cheers!
Really great Gulp series. I'll post a new comment (Or edit this if editing works when I get round to it) with my updates. I like Laravel Elixir's "notify" showing when things worked/failed (Although more for the failure than the success) so I plan to add in that part. If you are like me, you'll notice not necessarily notice anything has failed - even with plumber(). A tidy notification on error is quite useful I find. Mine is also slightly adjusted for Zurb Foundation 6 (http://foundation.zurb.com/) over Twitter Bootstrap/Font-Awesome (work/personal preference)
Thanks again for these series Ryan. Any chance of more front-end stuff for us backend developers who are occasionally forced to dabble in the realms of front end?
Hey David!
I totally agree with the notifications. When I first saw that, I thought it was totally unnecessary. But, when things fail 1 hour into your "watch" task running in the background... it starts to make more sense :D. I would love if you posted your solution here (or on gist) - I might be able to integrate part of it into an eventual update of this tutorial.
And yea, I think you nailed a big direction for us coming up: "Frontend for backend devs" - because it's not as easy as it once was, and *most* of us still don't have a dedicated frontend team to "dump stuff off to". Wouldn't that be nice :D. No concrete plans for that, but watch for them.
Cheers!
Hey Ryan,
Thanks for the run through with Gulp. I enjoyed it.
I was wondering, would working with Gulp be a better way to work with public asset files with Symfony instead of using Assetic and scssphp? Which would you recommend?
We are also probably going to be using a javascript framework, which also uses Gulp and Bower (or jspm). So, it almost makes more sense to use Node JS and Gulp for our public assets too. It just seems a bit like a sacrilege to use another server side language and server though. LOL!
Scott
Hey Scott!
Ah, glad you enjoyed it! You even found you way to the final few chapters that aren't totally finished yet (like this one) ;). We'll have the videos up for those very shortly.
My overall opinion is that the nodejs tools have matured so far that they *are* the best way to do things now. They're also young, so it means that more stuff is still being "figured out", so certain things end up being difficult or confusing. Still, I think for many people, this stuff is the right choice. Assetic is still good if you're really new to this nodejs stuff still or have a simple setup. If you make the leap, I think you will hit some speed bumps, but I think it'll be worth it. For years, bigger companies I've worked with have tried Assetic, then used something else (e.g. Compass directly) after awhile.
I hear you on the server-side language thing - the fact that I promote "server side JavaScript" on a PHP tutorial site doesn't escape me ;).
Cheers!
Yep.:D Exactly what I meant and thought too. But who are we to stand in the way of progress? Thanks!
Edit - on a completely different subject. I just actually won a version of PHPStorm and was playing with it and noticed it isn't "dumbed down" like how your version is showing it. (You do use PHPStorm in the videos, correct?) Is that "mode" or customization also how you use it to develop? Because, to be honest, for me, getting rid of all the clutter that you aren't currently actually using simply helps with concentration and probably why so many devs just use a plain editor to code with, instead of IDEs. LOL!
Not sure it would work, but could you share your config for PHPStorm (export settings)? I am currently a Netbeans user (which I have customized considerably, but it is also still cluttered) and was hoping to get PHPStorm down to like how you show it in the videos. However, customizing PHPStorm isn't the easiest of tasks. (and neither is customizing Netbeans either;-)), but why invent the wheel a second time?:-D
Scott
// composer.json
{
"require": {
"php": ">=5.3.3",
"symfony/symfony": "2.6.*", // v2.6.4
"doctrine/orm": "~2.2,>=2.2.3", // v2.4.6
"doctrine/doctrine-bundle": "~1.2", // v1.2.0
"twig/extensions": "~1.0", // v1.2.0
"symfony/assetic-bundle": "~2.3", // v2.5.0
"symfony/swiftmailer-bundle": "~2.3", // v2.3.7
"symfony/monolog-bundle": "~2.4", // v2.6.1
"sensio/distribution-bundle": "~3.0", // v3.0.9
"sensio/framework-extra-bundle": "~3.0", // v3.0.3
"incenteev/composer-parameter-handler": "~2.0", // v2.1.0
"hautelook/alice-bundle": "~0.2" // 0.2
},
"require-dev": {
"sensio/generator-bundle": "~2.3" // v2.4.0
}
}
Hey Ryan. Very detailed tutorial, thanks. Switched from Assetic to Gulp.
I have experienced small problem with browserSync and cache busting - browserSync forces full page reload with versioning in dev environment. (Instead of Css injection) So, I have modified config: (All files are commented)
https://github.com/nix23/gu...
Shortly, I have added/modified following:
1) Added ability to execute console commands in order with Pipeline class; (If some command fails, command execution stops)
2) Added browserSync Gulp integration with Css injection support;
3) Modified AssetExtension class - now css/js assets are versioned only in prod environment, because dynamic name change will force browserSync to perform full page reload instead of Css injection on sass/css file changes;
4) Extended FosJsRoutingBundle to override initialize method in dump command and inject target arg to InputInterface object. (We want apply versioning to 'fos_js_routes.js' file as well on production environment);
Hope, this helps someone. ;)
Cheers!