Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

178
Deck the halls with: Grunt, RequireJS & Bower by your friend: Ryan Weaver @weaverryan

description

Bower, Grunt, and RequireJS are just a few tools that have been re-shaping the frontend development world, replacing cluttered script tags and server-side build solutions with a sophisticated, but sometimes complex approach to dependency management and module loading. In this talk, we'll put on our trendy frontend developer hat and find out how these tools work and how they differ from what we might be used to. Most important, we'll see how using tools like this might look in Symfony2 and how our application can be a friendly place for a frontend guy/gal.

Transcript of Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Page 1: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Deck the halls with: Grunt, RequireJS & Bower

by your friend: !

Ryan Weaver @weaverryan

Page 2: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

The “Docs” guy !!KnpLabs US - Symfony consulting, training, Kumbaya !

Writer for KnpUniversity.com screencasts

knplabs.com github.com/weaverryan@weaverryan

Husband of the much more talented @leannapelham

Who is this jolly guy?

Page 3: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

Shout-out to the Docs team!

Page 4: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

“Hack” with us on Sat!

@weaverryan

Page 5: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

!

!

Your friendly neighborhood JavaScript developer is all grown up… and kicking butt

Intro

Page 6: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

No Node.js !

Minifying and combining done with a backend solution !

No RequireJS, AngularJS !

SASS/LESS were virtually non-existent

@weaverryan

5 years ago

Page 7: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Node.js for running server-side JavaScript !

RequireJS/AMD !

JavaScript task runners (e.g. Grunt) for uglifying and much more !

SASS/LESS are very mature and can compile themselves

@weaverryan

Today

Page 8: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Your friend Pablo from ServerGrove is redeveloping the SG control panel with a pure-JS fronted

@weaverryan

Page 9: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Can we continue to use JavaScript like we have in the past?

@weaverryan

Page 10: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Maybe

@weaverryan

Page 11: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

But we’re Symfony2 Developers…

@weaverryan

Page 12: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

… with the highest standards in PHP

@weaverryan

Page 13: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

When we write JavaScript…

@weaverryan

Page 14: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Let’s write great JavaScript

@weaverryan

Page 15: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Our Goal

Take a traditional Symfony app and make it much more jolly by using Node.js, Bower, RequireJS, Compass and Grunt

Page 16: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

http://www.flickr.com/photos/calsidyrose/4183559218/

Node.js !

it’s on your Christmas list

Page 17: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

* Symfony 2.3 - simple events website !

* The code: http://bit.ly/sfcon-js-github

The Project

Page 18: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<head>{% block stylesheets %} {% stylesheets 'bundles/event/css/event.css' 'bundles/event/css/events.css' 'bundles/event/css/main.css' 'assets/vendor/bootstrap/dist/css/bootstrap.css' 'assets/vendor/bootstrap/dist/css/bootstrap-theme.css' output='css/generated/layout.css' %} <link rel="stylesheet" href="{{ asset_url }}" /> {% endstylesheets %}{% endblock %}</head>

base.html.twig

Page 19: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<body>{% block body %}{% endblock %}!

{% block javascripts %} {% javascripts 'bundles/event/js/jquery.min.js' 'bundles/event/js/bootstrap.js' output='js/generated/main.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %}{% endblock %}</body>

base.html.twig

Page 20: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

* Homepage:

A) Has its own page-specific JS code

!

* New Event

A) Has its own page-specific JS code

B) Has page-specific CSS (event_form.css)

Pages

Page 21: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

!

!

Server-side JavaScript

Node.js and npm

Page 22: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

1) Executes JavaScript code !

2) Adds extra functionality for using

JavaScript to deal with filesystems and other

things that make sense on a server !

3) Similar to the “php” executable that lets

us interpret PHP code

3

Node.js

Page 23: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

sys = require('sys');!

for (i=0; i<5; i++) { sys.puts(i);}

extra stuff added by Node.js

play.js

Page 24: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

play.js

Page 25: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

OMG!

http://www.flickr.com/photos/nocturnenoir/8305081285/

Page 26: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Node.js gives us the ability to use JavaScript as yet-another-server-side-scripting-language

Page 27: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

1) Composer for Node.js

3

npm

2) Can be used to install things globally or

into your project (usually in a node_modules)

directory

3) Reads dependencies from a package.json file

Page 28: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

With Node.js and npm, we can quickly create small JavaScript files that use external modules

Page 29: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

With PHP and Composer, we can quickly create small PHP files that use external libraries

Page 30: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Why should we care?

Page 31: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Fronted JavaScript library build and development tools are written in JavaScript, executed with Node.js

Page 32: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Bower

Composer-lite for client-side JavaScript

Page 33: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Bower

(and one of those Node.js libraries installed with npm)

Page 34: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Problem:How do I get frontend libraries (e.g. jQuery, Bootstrap) downloaded into my project?

http://www.flickr.com/photos/perry-pics/5251240991/

Page 35: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

1) Downloads frontend libraries (usually JS)

into a directory in your project

3

Bower

2) Reads from a bower.json file

3) Handles dependencies!

Page 36: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Installation !

sudo npm install -g bower

this means “install it globally” on your machine - i.e. a bit like how Pear works

Page 37: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 38: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

.bowerrc

Yo Bower, put the libraries over there:

{ "directory": "web/assets/vendor"}

Page 39: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

bower init

bower install bootstrap --save

creates a bower.json file, with nothing important in it

Download the “bootstrap” library and adds it as a dependency to bower.json

Page 40: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{ "dependencies": { "bootstrap": "~3.0.2" }}

bower.json

** yes, this *is* composer.json for Bower

Page 41: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 42: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 43: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 44: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Now, how do we use these files?

Page 45: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

“Requiring” something in PHP

require 'Event.php';!

$event = new Event();echo $event->getName();

Page 46: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

“Requiring” something in JS

<script type="text/javascript" src="Event.js"></script>!

<script type="text/javascript"> console.log(Event.someMethod());</script>

Page 47: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Composer does 2 things:

1) Downloads libraries and their dependencies !

2) Sets up autoloading so you don’t need “require” statements

Page 48: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Bower does 1 thing:

1) Downloads libraries and their dependencies !

2) Sets up autoloading so you don’t need “require” statements

Page 49: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<body>{% block body %}{% endblock %}!

{% block javascripts %} {% javascripts 'bundles/event/js/jquery.min.js' 'bundles/event/js/bootstrap.js' output='js/generated/main.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %}{% endblock %}</body>

before

Page 50: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<body>{% block body %}{% endblock %}!

{% block javascripts %} {% javascripts 'assets/vendor/jquery/jquery.min.js' 'assets/vendor/bootstrap/dist/js/bootstrap.js' output='js/generated/main.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %}{% endblock %}</body>

after

Page 51: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

RequireJS!

!

Autoloading for client-side JavaScript

Page 52: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Before we reference something in JavaScript, we need to make sure it’s been included via a <script> tag

Problem:

http://www.flickr.com/photos/sewtechnicolor/8213938281/

Page 53: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

* A library that allows us to load JavaScript

resources without worrying about script tags

!

* Instead, we use a require function, which is

quite similar to the PHP require statement

RequireJS

Page 54: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

RequireJS works by requiring “modules”, not files.

(though one file will contain one module)

Page 55: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

bower install requirejs --save

Get it!

Page 56: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Remove all the JavaScript!<body>{% block body %}{% endblock %}!

{% block javascripts %} {% javascripts 'bundles/event/js/jquery.min.js' 'bundles/event/js/bootstrap.js' output='js/generated/main.js' %} <script type="text/javascript" src="{{ asset_url }}"></script> {% endjavascripts %}{% endblock %}</body>

Page 57: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset('assets/vendor/requirejs/require.js') }}"></script>!<script>requirejs.config({ baseUrl: 'assets/js', paths: { jquery: '../vendor/jquery/jquery.min', bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min' }});!require(['app/homepage']);</script>

base.html.twig

1) Bring in the require.js file downloaded via Bower using a normal script tag

Page 58: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset('assets/vendor/requirejs/require.js') }}"></script>!<script>requirejs.config({ baseUrl: '/assets/js', paths: { jquery: '../vendor/jquery/jquery.min', bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min' }});!require(['app/homepage']);</script>

2) Configure RequireJSAll modules live relative to this directory

base.html.twig

Page 59: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset('assets/vendor/requirejs/require.js') }}"></script>!<script>requirejs.config({ baseUrl: '/assets/js', paths: { jquery: '../vendor/jquery/jquery.min', bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min' }});!require(['app/homepage']);</script>

2) Configure RequireJSExceptions: when I ask for “jquery” look for it here (relative to baseUrl), instead of assets/js/jquery

base.html.twig

Page 60: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset('assets/vendor/requirejs/require.js') }}"></script>!<script>requirejs.config({ baseUrl: '/assets/js', paths: { jquery: '../vendor/jquery/jquery.min', bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min' }});!require(['app/homepage']);</script>

2) Configure RequireJS

Loads assets/js/app/homepage.js

base.html.twig

Page 61: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

app/homepage.js

define([], function() { console.log("It's alive!");});

Page 62: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

But how does it work?

Page 63: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

* All files are loaded by adding script tags

right into the HTML. But these use the async

tag, so do not block the page load.

!

* You’ll commonly see a data-main attribute

in setup. It loads that module. Our setup

does the same thing.

But how!

Page 64: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

now, what if we need jQuery?

Page 65: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

http://www.flickr.com/photos/danaberlith/4207059574

Remember, jQuery isn’t even downloaded yet - the global $ is not available

Page 66: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

define([], function() { $ = require('jquery'); $('...');});

… it might look something like this?

app/homepage.js

Page 67: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

define([], function() { $ = require('jquery'); $('...');});The require function *can’t* work like this. !

Unlike PHP files, scripts need to be downloaded, which takes time. !

Our function can’t run until that happens

app/homepage.js

Page 68: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

define(['jquery'], function ($) { $(document).ready(function() { $('a').on('click', function(e) { e.preventDefault(); alert('Ah ah ah, you didn\'t say the magic word!'); }) });});

Get the jquery module and *then* execute this function

app/homepage.js

Page 69: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

define(['jquery', 'bootstrap'], function ($, Bootstrap) { $(document).ready(function() { $('a').on('click', function(e) { e.preventDefault(); var $nope = $('<div>...</div>'); $nope.text('Ah ah ah, you didn\'t say the magic word!' ); $nope.modal(); }) });}); Get the jquery and bootstrap modules

and *then* execute this function

app/homepage.js

Page 70: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

requirejs.config({ baseUrl: '/assets/js', paths: { jquery: '../vendor/jquery/jquery.min', bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min' }, shim: { bootstrap: ['jquery'] }});

fixes an issue where Bootstrap *needs* jQuery before it’s downloaded

shim is a way for you to configure libraries that aren’t proper RequireJS modules

base.html.twig

Page 71: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Let’s create a new module (Love) that takes down the grinch before he steals Christmas.

http://en.wikipedia.org/wiki/File:The_Grinch_(That_Stole_Christmas).jpg

Page 72: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

app/modules/love.jsdefine(['jquery', 'bootstrap'], function ($, Boots) { return { spiritOfXmas: function() { $('a').on('click', function(e) { e.preventDefault(); var $love = $('<div>...</div>'); $love.text('The Grinch\’s heart grew three sizes that day' ); $nope.modal(); }); } }});

Page 73: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

define(['jquery', 'bootstrap'], function ($, Boots) { return { spiritOfXmas: function() { $('a').on('click', function(e) { e.preventDefault(); var $love = $('<div>...</div>'); $love.text('The Grinch\’s heart grew three sizes that day' ); $nope.modal(); }); } }}); Return some value (usually an object) that

will be the app/modules/newman “module”

app/modules/love.js

Page 74: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

define( ['jquery', 'app/modules/love'], function ($, Love) {!

$(document).ready(function() { Love.spiritOfXmas(); });});

app/homepage.js

Page 75: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

This takes care of bringing in JavaScript for the homepage. But what about the new event page?

Page 76: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

1) Move the RequireJS code to a new template

::requirejs.html.twig

<script src="{{ asset('assets/vendor/requirejs/require.js') }}"></script><script>requirejs.config({ baseUrl: '/assets/js', paths: { domReady: '../vendor/requirejs-domready/domReady', jquery: '../vendor/jquery/jquery.min', bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min' } // …});!require(['{{ module }}']);</script>

... and make the module a variable

Page 77: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

2) Add a requirejs block to your <head>

::base.html.twig

{% block requirejs %}{% endblock %}

Page 78: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

3) Include the module you need

EventBundle:Event:index.html.twig

{% block requirejs %} {{ include('::_requirejs.html.twig', { 'module': 'app/homepage'}) }}{% endblock %}

Page 79: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

4) Repeat!

EventBundle:Event:new.html.twig

{% block requirejs %} {{ include('::_requirejs.html.twig', { 'module': 'app/event_new'}) }}{% endblock %}

Page 80: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

app/event_new.js

define(['jquery'], function ($) {!

$(document).ready(function() { // ... });});

Page 81: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Optimization

Combining JavaScript files

Page 82: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Problem:

Each module is loaded from an individual file meaning there are lots of HTTP requests

Page 83: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Solution:

When we include the file containing moduleA, let’s also packaged moduleB and moduleC in there so when we need them, we already have them.

Page 84: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Let’s start by creating a common “module” that’s always loaded

Page 85: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

requirejs.config({ paths: { domReady: '../vendor/requirejs-domready/domReady', jquery: '../vendor/jquery/jquery.min', bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min' }, shim: { bootstrap: ['jquery'] }});

assets/js/common.js

Page 86: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset('/assets/vendor/requirejs/require.js') }}"></script>!

<script> requirejs.config({ baseUrl: '/assets/js' });!

require(['common'], function (common) { require(['{{ module }}']); });</script>

::requirejs.html.twig

Page 87: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset('/assets/vendor/requirejs/require.js') }}"></script>!

<script> requirejs.config({ baseUrl: '/assets/js' });!

require(['common'], function (common) { require(['{{ module }}']); });</script>

Setup baseUrl so we can reference the common module below

::requirejs.html.twig

Page 88: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset('/assets/vendor/requirejs/require.js') }}"></script>!

<script> requirejs.config({ baseUrl: '/assets/js' });!

require(['common'], function (common) { require(['{{ module }}']); });</script>

Load common and *then* load our real module

::requirejs.html.twig

Page 89: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Why?http://www.flickr.com/photos/danaberlith/4207059574

Page 90: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Because now we have a module (common) that’s *always* loaded

Page 91: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

and we can use the optimizer to “push” more modules (e.g. bootstrap, jquery) into it

Page 92: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

* Optimization is a server-side JavaScript tool

!

* In other words it’s a node library installed

via npm

!

* We’ll install it into our project, by defining

our project’s dependencies in package.json

Installing the Optimizer

Page 93: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

npm init

Create an empty package.json

Page 94: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

npm install requirejs --save-dev

Page 95: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{ "devDependencies": { "requirejs": "~2.1.9" }}

package.json

Page 96: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

and we also now have a node_modules directory in our project with requirejs in it

** We could have also installed it globally, like we did with Bower. All we really need is the r.js executable

Page 97: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

build.js

Configuration tells RequireJS how to minify and combine files

Page 98: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

({ mainConfigFile: 'web/assets/js/common.js', baseUrl: './js', appDir: 'web/assets', dir: 'web/assets-built', modules: [ { name: 'common', include: ['jquery', 'bootstrap'] }, { name: 'app/homepage', exclude: ['common'] } ]})

Page 99: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

({ mainConfigFile: 'web/assets/js/common.js', baseUrl: './js', appDir: 'web/assets', dir: 'web/assets-built', modules: [ { name: 'common', include: ['jquery', 'bootstrap'] }, { name: 'app/homepage', exclude: ['common'] } ]})

The entire web/assets directory is first copied to web/assets-built

Page 100: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

({ mainConfigFile: 'web/assets/js/common.js', baseUrl: './js', appDir: 'web/assets', dir: 'web/assets-built', modules: [ { name: 'common', include: ['jquery', 'bootstrap'] }, { name: 'app/homepage', exclude: ['common'] } ]})

These files are then re-written. RequireJS reads their dependencies and includes them in the file automatically

Page 101: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

({ mainConfigFile: 'web/assets/js/common.js', baseUrl: './js', appDir: 'web/assets', dir: 'web/assets-built', modules: [ { name: 'common', include: ['jquery', 'bootstrap'] }, { name: 'app/homepage', exclude: ['common'] } ]})

... plus we can manually include more modules

Page 102: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

({ mainConfigFile: 'web/assets/js/common.js', baseUrl: './js', appDir: 'web/assets', dir: 'web/assets-built', modules: [ { name: 'common', include: ['jquery', 'bootstrap'] }, { name: 'app/homepage', exclude: ['common'] } ]})

Avoids packaging jquery , bootstrap into homepage since we now already have it in common

Page 103: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

node node_modules/.bin/r.js -o build.js

Page 104: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 105: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Now, just point everything to assets-built

Page 106: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{% set assetsPath = 'assets-built' %}!

<script src="{{ asset(assetsPath~'/vendor/requirejs/require.js') }}"></script><script>!

requirejs.config({ baseUrl: '/{{ assetsPath }}/js' });!

require(['common'], function (common) { require(['{{ module }}']);!

});</script>

Page 107: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Not super dynamic yet... but it works!

Page 108: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

assets-built is the same as assets

except when we include the common module, it has jquery and bootstrap packaged inside it

Page 109: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Compass

Sass with style

Page 110: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Problem:

Static CSS files are *so* 2010

http://www.flickr.com/photos/stevendepolo/8409407391

Page 111: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@weaverryan

* Processes sass files into CSS

!

* A sass “framework”: adds a lot of extra

functionality, including CSS3 mixins, sprites,

etc

Compass

Page 112: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{ "dependencies": { "sass-bootstrap": "~3.0.0" "requirejs": "~2.1.9", }}

bower.json

Use Bower to bring in a sass Bootstrap

Page 113: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

bower install

Page 114: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Rename and reorganize CSS into SASS files

web/assets/sass/ * _base.scss * _event.scss * _events.scss * event_form.scss * layout.scss

Page 115: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Rename and reorganize CSS into SASS files

web/assets/sass/ * _base.scss * _event.scss * _events.scss * event_form.scss * layout.scss

(was event.css)

(was events.css)

(was main.css)

** these files were included on every page

Page 116: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Rename and reorganize CSS into SASS files

web/assets/sass/ * _base.scss * _event.scss * _events.scss * event_form.scss * layout.scss

(was event_form.css)

** included only on the “new event” page

Page 117: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Rename and reorganize CSS into SASS files

web/assets/sass/ * _base.scss * _event.scss * _events.scss * event_form.scss * layout.scss

These are the only CSS files that will be included directly

EventBundle:Event:new.html.twig

base.html.twig

Page 118: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{% block stylesheets %} <link rel="stylesheet" href="{{ asset('assets/css/layout.css') }}"/>{% endblock %}

{% block stylesheets %} {{ parent() }}!

<link rel="stylesheet" href="{{ asset('assets/css/event_form.css') }}"/>{% endblock %}

base.html.twig

EventBundle:Event:new.html.twig

Page 119: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{% block stylesheets %} <link rel="stylesheet" href="{{ asset('assets/css/layout.css') }}"/>{% endblock %}

{% block stylesheets %} {{ parent() }}!

<link rel="stylesheet" href="{{ asset('assets/css/event_form.css') }}"/>{% endblock %}

We link directly to non-existent CSS files, which we’ll generate

base.html.twig

EventBundle:Event:new.html.twig

Page 120: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@import "base";@import "../vendor/sass-bootstrap/lib/bootstrap";@import "event";@import "events";!

body { // ...}

layout.scss

Page 121: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@import "base";@import "../vendor/sass-bootstrap/lib/bootstrap";@import "event";@import "events";!

body { // ...}

Sets variables and imports mixins used in all SASS files

layout.scss

Page 122: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@import "base";@import "../vendor/sass-bootstrap/lib/bootstrap";@import "event";@import "events";!

body { // ...}Import other files that contain actual CSS rules. These will eventually be concatenated into 1 file.

layout.scss

Page 123: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

@import "base";!

/* for play, make the inputs super-rounded */.form-group input { @include border-radius(20px, 20px);}

event_form.scss

Page 124: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Now, just use more tools

Page 125: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

sudo npm install -g compass

Page 126: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

compass compile \ --css-dir=web/assets/css \ --sass-dir=web/assets/sass

Page 127: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

“partials” (files beginning with “_”) are ignored

Page 128: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

compass watch \ --css-dir=web/assets/css \ --sass-dir=web/assets/sass

Page 129: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

watches for file changes and regenerates the necessary CSS files

Page 130: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Grunt

app/console for JavaScript

Page 131: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Problem:

We have an increasing number of “build” operations we need to run for JavaScript & CSS

1) Before deploy, run the RequireJS optimizer 2) Before deploy, run Compass 3) During development, watch Compass

Page 132: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

sudo npm install -g grunt-cli

Install the Grunt executable

Page 133: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{ "devDependencies": { "requirejs": "~2.1.9", "grunt": "~0.4.2", "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-requirejs": "~0.4.1", "grunt-contrib-compass": "~0.6.0", "grunt-contrib-watch": "~0.5.3" }}

package.json

Page 134: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{ "devDependencies": { "requirejs": "~2.1.9", "grunt": "~0.4.2", "grunt-contrib-jshint": "~0.6.3", "grunt-contrib-uglify": "~0.2.2", "grunt-contrib-requirejs": "~0.4.1", "grunt-contrib-compass": "~0.6.0", "grunt-contrib-watch": "~0.5.3" }}

Install Grunt into your project + some modules

Page 135: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

npm install

Page 136: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Grunt works by creating a Gruntfile.js file, where we define tasks (like app/console)

Page 137: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

module.exports = function (grunt) { grunt.initConfig({ });!

grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-jshint'); grunt.loadNpmTasks('grunt-contrib-requirejs'); grunt.loadNpmTasks('grunt-contrib-compass'); grunt.loadNpmTasks('grunt-contrib-watch');};

Gruntfile.js

Page 138: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

grunt -h

Eventually we can run grunt RequireJS but we need to configure each command

Page 139: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Remove the RequireJS build.js and moves its contents here

Gruntfile.js

Use Grunt to run the RequireJS optimizer

Page 140: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

grunt.initConfig({ appDir: 'web/assets', builtDir: 'web/assets-built', requirejs: { main: { options: { mainConfigFile: '<%= appDir %>/js/common.js', appDir: '<%= appDir %>', baseUrl: './js', dir: '<%= builtDir %>', optimizeCss: "none", optimize: "none", modules: [... same as build.js ...] } } }

Page 141: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

grunt.initConfig({ appDir: 'web/assets', builtDir: 'web/assets-built', requirejs: { main: { options: { mainConfigFile: '<%= appDir %>/js/common.js', appDir: '<%= appDir %>', baseUrl: './js', dir: '<%= builtDir %>', optimizeCss: "none", optimize: "none", modules: [... same as build.js ...] } } }

We can setup variables and use them

Page 142: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

grunt.initConfig({ appDir: 'web/assets', builtDir: 'web/assets-built', requirejs: { main: { options: { mainConfigFile: '<%= appDir %>/js/common.js', appDir: '<%= appDir %>', baseUrl: './js', dir: '<%= builtDir %>', optimizeCss: "none", optimize: "none", modules: [... same as build.js ...] } } }

RequireJS *can* uglify CSS and JS, but we’ll leave this to Uglify and Compass

Page 143: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

grunt.initConfig({ appDir: 'web/assets', builtDir: 'web/assets-built', requirejs: { main: { options: { mainConfigFile: '<%= appDir %>/js/common.js', appDir: '<%= appDir %>', baseUrl: './js', dir: '<%= builtDir %>', optimizeCss: "none", optimize: "none", modules: [... same as build.js ...] } } }

This is a sub-task. We can now run grunt requirejs:main or grunt requirejs to run all sub-tasks

Page 144: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 145: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Repeat for Compass!

Page 146: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

compass: { dist: { options: { sassDir: '<%= builtDir %>/sass', cssDir: '<%= builtDir %>/css', environment: 'production', outputStyle: 'compressed' } }, dev: { options: { sassDir: '<%= appDir %>/sass', cssDir: '<%= appDir %>/css', outputStyle: 'expanded' } }}

Page 147: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 148: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

We have 2 sub-tasks: 1) compass:dist for deployment 2) compass:dev for development

Page 149: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Repeat for Uglify (to minimize our JS files)!

** The RequireJS optimizer can uglify, but using uglify directly gives us a bit more control

Page 150: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

uglify: { build: { files: [ { expand: true, cwd: '<%= builtDir %>', src: 'js/*.js', dest: '<%= builtDir %>' } ] }},

Page 151: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 152: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

And even JsHint

Page 153: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

jshint: { all: [ 'Gruntfile.js', '<%= appDir %>/js/{,*/}*.js' ]},

Page 154: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 155: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Roll these up into some grouped commands

http://www.flickr.com/photos/gazeronly/8206753938

Page 156: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

// task for developmentgrunt.registerTask('dev', [ 'jshint', 'compass:dev']);!

// task for before deploymentgrunt.registerTask('production', [ 'jshint', 'requirejs', 'uglify', 'compass:dist']);

Page 157: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

!

!

!

!

!

!

// task for before deploymentgrunt.registerTask('production', [ 'jshint', 'requirejs', 'uglify', 'compass:dist']);

1) 2) 3) 4)

1) syntax and style check our JS 2) copies assets to assets-dist and compiles some files 3) uglifies all JS files in assets-dist 4) compiles all assets-dist/sass files

Page 158: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

What about “watching”

Page 159: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

watch: { scripts: { files: [ '<%= appDir %>/js/*.js', // ... ], tasks: ['jshint'] }, compass: { files: '<%= appDir %>/sass/*.scss', tasks: ['compass:dev'] }}

Page 160: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Page 161: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

assets versus assets-dist

How to handle in Symfony

Page 162: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Problem:

Grunt perfectly copies assets to assets-dist and processes it. But how do we load our JS and CSS files from the correct directory?

Page 163: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Simple Solution

Page 164: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

parameters: assets_directory: 'assets'!

twig: # ... globals: assetsPath: %assets_directory%

config.yml

Page 165: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

parameters: assets_directory: 'assets-prod'

config_prod.yml

Page 166: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

<script src="{{ asset(assetsPath~'/vendor/requirejs/require.js') }}"></script><script> requirejs.config({ baseUrl: '/{{ assetsPath }}/js' });!

// ...</script>

::requirejs.html.twig

Page 167: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

{% block stylesheets %}<link rel="stylesheet" href="{{ asset(assetsPath~'/css/layout.css') }}"/>{% endblock %}

::base.html.twig

Page 168: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Manual, but straightforward

Page 169: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

If you wish, a fancier solution exists, do it!

1) Extend the asset() function to change the directory !

2) Create a new Twig function to replace asset()

Page 170: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Bower downloads JS dependencies !

Modules included via RequireJS !

Compass compiles our SASS files !

Grunt optimizes for RequireJS, Uglifies, runs Compass, and watches for changes@weaverryan

Now

Page 171: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

When developing: !

grunt watch

Page 172: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

When deploying: !

grunt production

Page 173: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

and make your own Grunt tasks for other processing

Page 174: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

grunt.registerTask('symfonycon', function() { sys = require('sys'); sys.puts('Thanks everyone!');});

Page 175: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

JavaScript is a first-class tool in your stack

@weaverryan

Page 176: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Treat it with the same care and quality as everything else

@weaverryan

Page 177: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

And (code) be cool like a frontend developer

@weaverryan

Page 178: Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools

Ho ho ho, thanks!

@weaverryan

Brutal Feedback appreciated https://joind.in/10372

The code: http://bit.ly/sfcon-js-github

Keep learning: KnpUniversity.com