Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

Task Order

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $6.00

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?

There is no Order to Dependent Tasks

Log a message once fonts is done:

134 lines gulpfile.js
... 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:

134 lines gulpfile.js
... 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:

134 lines gulpfile.js
... 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:

134 lines gulpfile.js
... 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

  • so it just runs watch right away. So, return app.copy from the fonts task, since app.copy returns a Gulp stream.

134 lines gulpfile.js
... 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:

133 lines gulpfile.js
... 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():

133 lines gulpfile.js
... 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.

Leave a comment!

12
Login or Register to join the conversation
Default user avatar
Default user avatar Eduard Pleh | posted 5 years ago

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!

1 Reply

Wow, this is really great work! Thanks so much for sharing - a working example is worth 1000 (or more) words :). Cheers!

Reply
Nicolas-S Avatar
Nicolas-S Avatar Nicolas-S | posted 5 years ago

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 ?

Reply

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!

Reply
Nicolas-S Avatar

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 !

Reply
Default user avatar
Default user avatar Samuel Vicent | posted 5 years ago

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

Reply

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!

Reply
Default user avatar
Default user avatar David Thorne | posted 5 years ago

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?

Reply

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!

Reply
Default user avatar
Default user avatar s.molinari | posted 5 years ago

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

Reply

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!

Reply
Default user avatar

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

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// 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
    }
}
userVoice