SlideShare a Scribd company logo
1 of 178
Download to read offline
Deck the halls with:
Grunt, RequireJS & Bower
by your friend:
!

Ryan Weaver
@weaverryan
Who is this jolly guy?
The “Docs” guy
!
!

KnpLabs US - Symfony consulting, training, Kumbaya
!

Writer for KnpUniversity.com
screencasts
Husband of the much more talented
@leannapelham

@weaverryan

knplabs.com
github.com/weaverryan
Shout-out to the Docs team!

@weaverryan
“Hack” with us on Sat!

@weaverryan
!
!

Intro

Your friendly neighborhood
JavaScript developer is all
grown up… and kicking butt
No Node.js
!

5
years
ago
@weaverryan

Minifying and combining
done with a backend
solution
!

No RequireJS, AngularJS
!

SASS/LESS were virtually
non-existent
Node.js for running
server-side JavaScript
!

RequireJS/AMD
!

Today

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

SASS/LESS are very
mature and can compile
themselves
@weaverryan
Your friend Pablo from
ServerGrove is
redeveloping the SG control
panel with a pure-JS
fronted

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

@weaverryan
Maybe

@weaverryan
But we’re Symfony2
Developers…

@weaverryan
… with the highest
standards in PHP

@weaverryan
When we write JavaScript…

@weaverryan
Let’s write great JavaScript

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

it’s on your
Christmas list

http://www.flickr.com/photos/calsidyrose/4183559218/
The Project
* Symfony 2.3 - simple events website
!

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

@weaverryan
base.html.twig
<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
<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>
Pages
* 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)
@weaverryan
!

Node.js and npm

!

Server-side JavaScript
Node.js
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
@weaverryan

3
play.js
sys = require('sys');	
!

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

extra stuff added by Node.js
play.js
OMG!
http://www.flickr.com/photos/nocturnenoir/8305081285/
Node.js gives us the
ability to use JavaScript
as yet-another-serverside-scripting-language
npm
1) Composer for Node.js
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
@weaverryan

3
With Node.js and npm,
we can quickly create small
JavaScript files that use
external modules
With PHP and Composer,
we can quickly create small
PHP files that use
external libraries
Why should we care?
Fronted JavaScript library
build and development
tools are written in
JavaScript, executed with
Node.js
Bower
Composer-lite for client-side
JavaScript
Bower
(and one of those Node.js
libraries installed with npm)
Problem:
How do I get frontend libraries (e.g. jQuery,
Bootstrap) downloaded into my project?

http://www.flickr.com/photos/perry-pics/5251240991/
Bower
1) Downloads frontend libraries (usually JS)
into a directory in your project
2) Reads from a bower.json file
3) Handles dependencies!
@weaverryan

3
Installation
!

sudo npm install -g bower

this means “install it globally” on your
machine - i.e. a bit like how Pear works
.bowerrc
Yo Bower, put the libraries over there:

{	
"directory": "web/assets/vendor"	
}
bower init
creates a bower.json file, with nothing
important in it

bower install bootstrap --save
Download the “bootstrap” library and
adds it as a dependency to bower.json
bower.json
{	
"dependencies": {	
"bootstrap": "~3.0.2"	
}	
}

** yes, this *is* composer.json for Bower
Now, how do we use these
files?
“Requiring” something in PHP

require 'Event.php';	
!

$event = new Event();	
echo $event->getName();
“Requiring” something in JS
<script type="text/javascript" 	
src="Event.js"></script>	
!

<script type="text/javascript">	
console.log(Event.someMethod());	
</script>
Composer does 2 things:
1) Downloads libraries and
their dependencies
!

2) Sets up autoloading so
you don’t need “require”
statements
Bower does 1 thing:
1) Downloads libraries and
their dependencies
!

2) Sets up autoloading so
you don’t need “require”
statements
before
<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>
after
<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>
!

RequireJS

!

Autoloading for client-side
JavaScript
Problem:
Before we reference something in JavaScript, we
need to make sure it’s been included via a
<script> tag

http://www.flickr.com/photos/sewtechnicolor/8213938281/
RequireJS
* 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

@weaverryan
RequireJS works by requiring “modules”,
not files.

(though one file will contain one module)
Get it!

bower install requirejs --save
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>
base.html.twig
<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'	
}	
});	

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

!

require(['app/homepage']);	
</script>
base.html.twig
<script	

2) Configure RequireJS

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

</script>	

All modules live relative to this directory

!

<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
<script	

2) Configure RequireJS

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

</script>	
!

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

<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
<script	

2) Configure RequireJS

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

</script>	
!

Loads assets/js/app/homepage.js

<script>	
requirejs.config({	
baseUrl: '/assets/js',	
paths: {	
jquery: '../vendor/jquery/jquery.min',	
bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min'	
}	
});	
!

require(['app/homepage']);	
</script>
app/homepage.js

define([], function() {	
console.log("It's alive!");	
});
But how does it work?
But how!
* 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.
@weaverryan
now, what if we need jQuery?
Remember, jQuery isn’t even downloaded
yet - the global $ is not available

http://www.flickr.com/photos/danaberlith/4207059574
app/homepage.js
define([], function() {	
$ = require('jquery');	
$('...');	
});
… it might look something like this?
app/homepage.js
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
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
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
base.html.twig
requirejs.config({	
baseUrl: '/assets/js',	
paths: {	
jquery: '../vendor/jquery/jquery.min',	
bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min'	
},	
shim: {	
fixes an issue where
bootstrap: ['jquery']	 Bootstrap *needs* jQuery
}	
before it’s downloaded
});

shim is a way for you to configure libraries
that aren’t proper RequireJS modules
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
app/modules/love.js
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();	
});	

}	
}	
});
app/modules/love.js
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/homepage.js
define(	
['jquery', 'app/modules/love'],	
function ($, Love) {	
!

$(document).ready(function() {	
Love.spiritOfXmas();	
});	
});
This takes care of bringing in JavaScript
for the homepage. But what about the
new event page?
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'	
}	
// …	
});	

... and make the module a variable

!

require(['{{ module }}']);	
</script>
2) Add a requirejs block to your <head>

::base.html.twig
{% block requirejs %}{% endblock %}
3) Include the module you need

EventBundle:Event:index.html.twig
{% block requirejs %}	
{{ include('::_requirejs.html.twig', {	
'module': 'app/homepage'	
}) }}	
{% endblock %}
4) Repeat!

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

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

$(document).ready(function() {	
// ...	
});	
});
Optimization
Combining JavaScript files
Problem:
Each module is loaded from an individual file
meaning there are lots of HTTP requests
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.
Let’s start by creating a
common “module” that’s
always loaded
assets/js/common.js
requirejs.config({	
paths: {	
domReady: '../vendor/requirejs-domready/domReady',	

jquery: '../vendor/jquery/jquery.min',	
bootstrap: '../vendor/bootstrap/dist/js/bootstrap.min'	
},	
shim: {	
bootstrap: ['jquery']	
}	
});
::requirejs.html.twig
<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
<script src="{{ asset('/assets/vendor/
requirejs/require.js') }}"></script>	

Setup baseUrl so
<script>	
we can reference
requirejs.config({	
the common
baseUrl: '/assets/js'	
module below
});	
!

!

require(['common'], function (common) {	
require(['{{ module }}']);	
});	
</script>
::requirejs.html.twig
<script src="{{ asset('/assets/vendor/
requirejs/require.js') }}"></script>	

Load common and
<script>	
*then* load our
requirejs.config({	
real module
baseUrl: '/assets/js'	
!

});	
!

require(['common'], function (common) {	
require(['{{ module }}']);	
});	
</script>
Why?
http://www.flickr.com/photos/danaberlith/4207059574
Because now we have a
module (common) that’s
*always* loaded
and we can use the
optimizer to “push” more
modules (e.g. bootstrap,
jquery) into it
Installing the Optimizer
* 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
@weaverryan
Create an empty package.json

npm init
npm install requirejs --save-dev
package.json
{	
"devDependencies": {	
"requirejs": "~2.1.9"	
}	
}
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
Configuration tells RequireJS how
to minify and combine files
build.js
({	

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']	
}	
]	
})
({	

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

mainConfigFile: 'web/assets/js/common.js',	
These files are then re-written.
baseUrl: reads their
RequireJS './js',	 dependencies
appDir: 'web/assets',	
and includes them in the file
dir: 'web/assets-built',	
automatically
modules: [	
{	
name: 'common',	
include: ['jquery', 'bootstrap']	
},	
{	
name: 'app/homepage',	
exclude: ['common']	
}	
]	
})
({	

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

mainConfigFile: 'web/assets/js/common.js',	
baseUrl: './js',	
appDir: 'web/assets',	
dir: 'web/assets-built',	
modules: [	
{	
Avoids packaging
name: 'common',	
jquery , bootstrap
include: ['jquery', 'bootstrap']	
into homepage since
},	
we now already
{	
have it in common
name: 'app/homepage',	
exclude: ['common']	
}	
]	
})
node node_modules/.bin/r.js -o build.js
Now, just point everything to
assets-built
{% 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>
Not super dynamic yet... but it works!
assets-built is the same as assets
except when we include the common
module, it has jquery and bootstrap
packaged inside it
Compass
Sass with style
Problem:
Static CSS files are *so* 2010

http://www.flickr.com/photos/stevendepolo/8409407391
Compass
* Processes sass files into CSS
!

* A sass “framework”: adds a lot of extra
functionality, including CSS3 mixins, sprites,
etc

@weaverryan
Use Bower to bring in a sass Bootstrap

bower.json
{	
"dependencies": {	
"sass-bootstrap": "~3.0.0"	
"requirejs": "~2.1.9",	
}	
}
bower install
Rename and reorganize CSS into SASS files

web/assets/sass/
* _base.scss
* _event.scss
* _events.scss
* event_form.scss
* layout.scss
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
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
Rename and reorganize CSS into SASS files

web/assets/sass/
* _base.scss
* _event.scss
EventBundle:Event:new.html.twig
* _events.scss
* event_form.scss
base.html.twig
* layout.scss
These are the only CSS files that will be
included directly
base.html.twig
{% block stylesheets %}	
<link rel="stylesheet"	

href="{{ asset('assets/css/layout.css') }}"/>	

{% endblock %}

EventBundle:Event:new.html.twig
{% block stylesheets %}	
{{ parent() }}	
!

<link rel="stylesheet"	

href="{{ asset('assets/css/event_form.css') }}"/>	

{% endblock %}
base.html.twig
{% block stylesheets %}	
<link rel="stylesheet"	

href="{{ asset('assets/css/layout.css') }}"/>	

{% endblock %}

EventBundle:Event:new.html.twig
{% block stylesheets %}	
We link directly to non-existent
{{ parent() }}	
!

CSS files,

which we’ll generate
<link rel="stylesheet"	

href="{{ asset('assets/css/event_form.css') }}"/>	

{% endblock %}
layout.scss
@import
@import
@import
@import

"base";	
"../vendor/sass-bootstrap/lib/bootstrap";	

"event";	
"events";	

!

body {	
// ...	
}
layout.scss
@import
@import
@import
@import

"base";	
"../vendor/sass-bootstrap/lib/bootstrap";	

"event";	
"events";	

!

body {	
// ...	
}

Sets variables and imports mixins
used in all SASS files
layout.scss
@import
@import
@import
@import

"base";	
"../vendor/sass-bootstrap/lib/bootstrap";	

"event";	
"events";	

!

body {	
// ...	
}

Import other files that contain actual CSS
rules. These will eventually be concatenated
into 1 file.
event_form.scss

@import "base";	
!

/* for play, make the inputs super-rounded */	
.form-group input {	
@include border-radius(20px, 20px);	
}
Now, just use more tools
sudo npm install -g compass
compass compile 
--css-dir=web/assets/css 
--sass-dir=web/assets/sass
“partials” (files beginning with “_”) are ignored
compass watch 
--css-dir=web/assets/css 
--sass-dir=web/assets/sass
watches for file changes and regenerates the
necessary CSS files
Grunt
app/console for JavaScript
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
Install the Grunt executable
sudo npm install -g grunt-cli
package.json
{	
"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
{	
"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"	
}	
}
npm install
Grunt works by creating a
Gruntfile.js file, where we
define tasks (like app/console)
Gruntfile.js
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');	
};
grunt -h

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

Use Grunt to run the RequireJS optimizer
Remove the RequireJS build.js and moves its
contents here
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 ...]	
}	
}
grunt.initConfig({	
appDir: 'web/assets',	
builtDir: 'web/assets-built',	
We can setup
requirejs: {	
main: {	
variables and use
options: {	
them
mainConfigFile: '<%= appDir %>/js/
common.js',	
appDir: '<%= appDir %>',	
baseUrl: './js',	
dir: '<%= builtDir %>',	
optimizeCss: "none",	
optimize: "none",	
modules: [... same as build.js ...]	
}	
}
grunt.initConfig({	
appDir: 'web/assets',	
builtDir: 'web/assets-built',	
requirejs: {	 RequireJS *can* uglify CSS
and JS, but we’ll leave this
main: {	
options: {	to Uglify and Compass
mainConfigFile: '<%= appDir %>/js/
common.js',	
appDir: '<%= appDir %>',	
baseUrl: './js',	
dir: '<%= builtDir %>',	
optimizeCss: "none",	
optimize: "none",	
modules: [... same as build.js ...]	
}	
}
grunt.initConfig({	
appDir: 'web/assets',	
builtDir: 'web/assets-built',	 We can
This is a sub-task.
requirejs: {	
now run grunt requirejs:main
main: {	
or grunt requirejs to run all
options: {	
sub-tasks '<%= appDir %>/js/
mainConfigFile:
common.js',	
appDir: '<%= appDir %>',	
baseUrl: './js',	
dir: '<%= builtDir %>',	
optimizeCss: "none",	
optimize: "none",	
modules: [... same as build.js ...]	
}	
}
Repeat for Compass!
compass: {	
dist: {	
options: {	
sassDir: '<%= builtDir %>/sass',	
cssDir: '<%= builtDir %>/css',	
environment: 'production',	
outputStyle: 'compressed'	
}	
},	
dev: {	
options: {	
sassDir: '<%= appDir %>/sass',	
cssDir: '<%= appDir %>/css',	
outputStyle: 'expanded'	
}	
}
We have 2 sub-tasks:
1) compass:dist for deployment
2) compass:dev for development
Repeat for Uglify (to
minimize our JS files)!
** The RequireJS optimizer can uglify,
but using uglify directly gives us a bit
more control
uglify: {	
build: {	
files: [	
{	
expand: true,	
cwd: '<%= builtDir %>',	
src: 'js/*.js',	
dest: '<%= builtDir %>'	
}	
]	
}	
},
And even JsHint
jshint: {	
all: [	
'Gruntfile.js',	
'<%= appDir %>/js/{,*/}*.js'	
]	
},
Roll these up into some
grouped commands

http://www.flickr.com/photos/gazeronly/8206753938
// task for development	
grunt.registerTask('dev', [	
'jshint',	
'compass:dev'	
]);	
!

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

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

!

// task for before deployment	
grunt.registerTask('production', [	
1) 'jshint',	
2)'requirejs',	
3)'uglify',	
'compass:dist'	
4)
]);
What about “watching”
watch: {	
scripts: {	
files: [	
'<%= appDir %>/js/*.js',	
// ...	
],	
tasks: ['jshint']	
},	
compass: {	
files: '<%= appDir %>/sass/*.scss',	
tasks: ['compass:dev']	
}	
}
assets versus assets-dist
How to handle in Symfony
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?
Simple Solution
config.yml
parameters:	
assets_directory: 'assets'	
!

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

parameters:	
assets_directory: 'assets-prod'
::requirejs.html.twig
<script src="{{ asset(assetsPath~'/vendor/
requirejs/require.js') }}"></script>	
<script>	
requirejs.config({	
baseUrl: '/{{ assetsPath }}/js'	
});	
!

// ...	
</script>
::base.html.twig

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

{% endblock %}
Manual, but straightforward
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()
Bower downloads JS
dependencies
!

Modules included via
RequireJS

Now

!

Compass compiles our
SASS files
!

@weaverryan

Grunt optimizes for
RequireJS, Uglifies, runs
Compass, and watches for
changes
When developing:
!

grunt watch
When deploying:
!

grunt production
and make your own Grunt
tasks for other processing
grunt.registerTask('symfonycon',
function() {	
sys = require('sys');	
sys.puts('Thanks everyone!');	
});
JavaScript is a first-class
tool in your stack

@weaverryan
Treat it with the same care
and quality as everything
else

@weaverryan
And (code) be cool like a
frontend developer

@weaverryan
Ho ho ho, thanks!
Brutal Feedback appreciated
https://joind.in/10372
The code:
http://bit.ly/sfcon-js-github

Keep learning: KnpUniversity.com
@weaverryan

More Related Content

What's hot

Advanced WordPress Development Environments
Advanced WordPress Development EnvironmentsAdvanced WordPress Development Environments
Advanced WordPress Development Environments
Beau Lebens
 
SockJS Intro
SockJS IntroSockJS Intro
SockJS Intro
Ngoc Dao
 

What's hot (20)

Node.js & Twitter Bootstrap Crash Course
Node.js & Twitter Bootstrap Crash CourseNode.js & Twitter Bootstrap Crash Course
Node.js & Twitter Bootstrap Crash Course
 
Advanced WordPress Development Environments
Advanced WordPress Development EnvironmentsAdvanced WordPress Development Environments
Advanced WordPress Development Environments
 
WordCamp Ann Arbor 2015 Introduction to Backbone + WP REST API
WordCamp Ann Arbor 2015 Introduction to Backbone + WP REST APIWordCamp Ann Arbor 2015 Introduction to Backbone + WP REST API
WordCamp Ann Arbor 2015 Introduction to Backbone + WP REST API
 
WordPress as the Backbone(.js)
WordPress as the Backbone(.js)WordPress as the Backbone(.js)
WordPress as the Backbone(.js)
 
Let Grunt do the work, focus on the fun! [Open Web Camp 2013]
Let Grunt do the work, focus on the fun! [Open Web Camp 2013]Let Grunt do the work, focus on the fun! [Open Web Camp 2013]
Let Grunt do the work, focus on the fun! [Open Web Camp 2013]
 
Choosing a Javascript Framework
Choosing a Javascript FrameworkChoosing a Javascript Framework
Choosing a Javascript Framework
 
Automating WordPress Theme Development
Automating WordPress Theme DevelopmentAutomating WordPress Theme Development
Automating WordPress Theme Development
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC Framework
 
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
 
Instant and offline apps with Service Worker
Instant and offline apps with Service WorkerInstant and offline apps with Service Worker
Instant and offline apps with Service Worker
 
Mojolicious
MojoliciousMojolicious
Mojolicious
 
Migraine Drupal - syncing your staging and live sites
Migraine Drupal - syncing your staging and live sitesMigraine Drupal - syncing your staging and live sites
Migraine Drupal - syncing your staging and live sites
 
Front-end tools
Front-end toolsFront-end tools
Front-end tools
 
Keeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackKeeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and Webpack
 
SocketStream
SocketStreamSocketStream
SocketStream
 
Forget Grunt and Gulp! Webpack and NPM rule them all!
Forget Grunt and Gulp! Webpack and NPM rule them all!Forget Grunt and Gulp! Webpack and NPM rule them all!
Forget Grunt and Gulp! Webpack and NPM rule them all!
 
SockJS Intro
SockJS IntroSockJS Intro
SockJS Intro
 
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史
 
遠端團隊專案建立與管理 remote team management 2016
遠端團隊專案建立與管理 remote team management 2016遠端團隊專案建立與管理 remote team management 2016
遠端團隊專案建立與管理 remote team management 2016
 
Write php deploy everywhere
Write php deploy everywhereWrite php deploy everywhere
Write php deploy everywhere
 

Viewers also liked

Viewers also liked (6)

Introduction to maven, its configuration, lifecycle and relationship to JS world
Introduction to maven, its configuration, lifecycle and relationship to JS worldIntroduction to maven, its configuration, lifecycle and relationship to JS world
Introduction to maven, its configuration, lifecycle and relationship to JS world
 
Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ...
 Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ... Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ...
Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ...
 
Introduction to WAMP, a protocol enabling PUB/SUB and RPC over Websocket
Introduction to WAMP, a protocol enabling PUB/SUB and RPC over WebsocketIntroduction to WAMP, a protocol enabling PUB/SUB and RPC over Websocket
Introduction to WAMP, a protocol enabling PUB/SUB and RPC over Websocket
 
Surprising failure factors when implementing eCommerce and Omnichannel eBusiness
Surprising failure factors when implementing eCommerce and Omnichannel eBusinessSurprising failure factors when implementing eCommerce and Omnichannel eBusiness
Surprising failure factors when implementing eCommerce and Omnichannel eBusiness
 
Magento scalability from the trenches (Meet Magento Sweden 2016)
Magento scalability from the trenches (Meet Magento Sweden 2016)Magento scalability from the trenches (Meet Magento Sweden 2016)
Magento scalability from the trenches (Meet Magento Sweden 2016)
 
Omnichannel Customer Experience
Omnichannel Customer ExperienceOmnichannel Customer Experience
Omnichannel Customer Experience
 

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

Integrating Browserify with Sprockets
Integrating Browserify with SprocketsIntegrating Browserify with Sprockets
Integrating Browserify with Sprockets
Spike Brehm
 
20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev
Frank Rousseau
 

Similar to Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools (20)

Practical Use of MongoDB for Node.js
Practical Use of MongoDB for Node.jsPractical Use of MongoDB for Node.js
Practical Use of MongoDB for Node.js
 
Once upon a time, there were css, js and server-side rendering
Once upon a time, there were css, js and server-side renderingOnce upon a time, there were css, js and server-side rendering
Once upon a time, there were css, js and server-side rendering
 
Modern Web Application Development Workflow - EclipseCon France 2014
Modern Web Application Development Workflow - EclipseCon France 2014Modern Web Application Development Workflow - EclipseCon France 2014
Modern Web Application Development Workflow - EclipseCon France 2014
 
Modern Web Application Development Workflow - EclipseCon Europe 2014
Modern Web Application Development Workflow - EclipseCon Europe 2014Modern Web Application Development Workflow - EclipseCon Europe 2014
Modern Web Application Development Workflow - EclipseCon Europe 2014
 
Frontend Workflow
Frontend WorkflowFrontend Workflow
Frontend Workflow
 
Integrating Browserify with Sprockets
Integrating Browserify with SprocketsIntegrating Browserify with Sprockets
Integrating Browserify with Sprockets
 
Modern Web Application Development Workflow - web2day 2014
Modern Web Application Development Workflow - web2day 2014Modern Web Application Development Workflow - web2day 2014
Modern Web Application Development Workflow - web2day 2014
 
JavaScript Modules Done Right
JavaScript Modules Done RightJavaScript Modules Done Right
JavaScript Modules Done Right
 
20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev
 
Angular Part 3 (Basic knowledge)
Angular Part 3 (Basic knowledge)Angular Part 3 (Basic knowledge)
Angular Part 3 (Basic knowledge)
 
A Introduction to the World of Node, Javascript & Selenium
A Introduction to the World of Node, Javascript & SeleniumA Introduction to the World of Node, Javascript & Selenium
A Introduction to the World of Node, Javascript & Selenium
 
Bootstrapping angular js with bower grunt yeoman
Bootstrapping angular js with bower grunt yeomanBootstrapping angular js with bower grunt yeoman
Bootstrapping angular js with bower grunt yeoman
 
Web development - technologies and tools
Web development - technologies and toolsWeb development - technologies and tools
Web development - technologies and tools
 
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ...
 
Dcjq node.js presentation
Dcjq node.js presentationDcjq node.js presentation
Dcjq node.js presentation
 
Introduction to node.js by jiban
Introduction to node.js by jibanIntroduction to node.js by jiban
Introduction to node.js by jiban
 
Building a Single Page Application with VueJS
Building a Single Page Application with VueJSBuilding a Single Page Application with VueJS
Building a Single Page Application with VueJS
 
Consegi 2010 - Dicas de Desenvolvimento Web com Ruby
Consegi 2010 - Dicas de Desenvolvimento Web com RubyConsegi 2010 - Dicas de Desenvolvimento Web com Ruby
Consegi 2010 - Dicas de Desenvolvimento Web com Ruby
 
Nodejs
NodejsNodejs
Nodejs
 
Introduction of webpack 4
Introduction of webpack 4Introduction of webpack 4
Introduction of webpack 4
 

More from Ryan Weaver

Symfony2: Get your project started
Symfony2: Get your project startedSymfony2: Get your project started
Symfony2: Get your project started
Ryan Weaver
 
Doctrine2 In 10 Minutes
Doctrine2 In 10 MinutesDoctrine2 In 10 Minutes
Doctrine2 In 10 Minutes
Ryan Weaver
 

More from Ryan Weaver (20)

Webpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San FranciscoWebpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San Francisco
 
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
 
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat
Grand Rapids PHP Meetup: Behavioral Driven Development with BehatGrand Rapids PHP Meetup: Behavioral Driven Development with Behat
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat
 
Twig: Friendly Curly Braces Invade Your Templates!
Twig: Friendly Curly Braces Invade Your Templates!Twig: Friendly Curly Braces Invade Your Templates!
Twig: Friendly Curly Braces Invade Your Templates!
 
Master the New Core of Drupal 8 Now: with Symfony and Silex
Master the New Core of Drupal 8 Now: with Symfony and SilexMaster the New Core of Drupal 8 Now: with Symfony and Silex
Master the New Core of Drupal 8 Now: with Symfony and Silex
 
Silex: Microframework y camino fácil de aprender Symfony
Silex: Microframework y camino fácil de aprender SymfonySilex: Microframework y camino fácil de aprender Symfony
Silex: Microframework y camino fácil de aprender Symfony
 
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love itDrupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony Components
 
A PHP Christmas Miracle - 3 Frameworks, 1 app
A PHP Christmas Miracle - 3 Frameworks, 1 appA PHP Christmas Miracle - 3 Frameworks, 1 app
A PHP Christmas Miracle - 3 Frameworks, 1 app
 
Symfony2: Get your project started
Symfony2: Get your project startedSymfony2: Get your project started
Symfony2: Get your project started
 
Symony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP FrameworkSymony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP Framework
 
Hands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkHands-on with the Symfony2 Framework
Hands-on with the Symfony2 Framework
 
Being Dangerous with Twig (Symfony Live Paris)
Being Dangerous with Twig (Symfony Live Paris)Being Dangerous with Twig (Symfony Live Paris)
Being Dangerous with Twig (Symfony Live Paris)
 
Being Dangerous with Twig
Being Dangerous with TwigBeing Dangerous with Twig
Being Dangerous with Twig
 
Doctrine2 In 10 Minutes
Doctrine2 In 10 MinutesDoctrine2 In 10 Minutes
Doctrine2 In 10 Minutes
 
Dependency Injection: Make your enemies fear you
Dependency Injection: Make your enemies fear youDependency Injection: Make your enemies fear you
Dependency Injection: Make your enemies fear you
 
The Art of Doctrine Migrations
The Art of Doctrine MigrationsThe Art of Doctrine Migrations
The Art of Doctrine Migrations
 

Recently uploaded

Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
WSO2
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Safe Software
 

Recently uploaded (20)

Introduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDMIntroduction to use of FHIR Documents in ABDM
Introduction to use of FHIR Documents in ABDM
 
Six Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal OntologySix Myths about Ontologies: The Basics of Formal Ontology
Six Myths about Ontologies: The Basics of Formal Ontology
 
[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf[BuildWithAI] Introduction to Gemini.pdf
[BuildWithAI] Introduction to Gemini.pdf
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ..."I see eyes in my soup": How Delivery Hero implemented the safety system for ...
"I see eyes in my soup": How Delivery Hero implemented the safety system for ...
 
TEST BANK For Principles of Anatomy and Physiology, 16th Edition by Gerard J....
TEST BANK For Principles of Anatomy and Physiology, 16th Edition by Gerard J....TEST BANK For Principles of Anatomy and Physiology, 16th Edition by Gerard J....
TEST BANK For Principles of Anatomy and Physiology, 16th Edition by Gerard J....
 
Quantum Leap in Next-Generation Computing
Quantum Leap in Next-Generation ComputingQuantum Leap in Next-Generation Computing
Quantum Leap in Next-Generation Computing
 
Choreo: Empowering the Future of Enterprise Software Engineering
Choreo: Empowering the Future of Enterprise Software EngineeringChoreo: Empowering the Future of Enterprise Software Engineering
Choreo: Empowering the Future of Enterprise Software Engineering
 
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost SavingRepurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
Repurposing LNG terminals for Hydrogen Ammonia: Feasibility and Cost Saving
 
WSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering DevelopersWSO2's API Vision: Unifying Control, Empowering Developers
WSO2's API Vision: Unifying Control, Empowering Developers
 
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
AI+A11Y 11MAY2024 HYDERBAD GAAD 2024 - HelloA11Y (11 May 2024)
 
Modernizing Legacy Systems Using Ballerina
Modernizing Legacy Systems Using BallerinaModernizing Legacy Systems Using Ballerina
Modernizing Legacy Systems Using Ballerina
 
Architecting Cloud Native Applications
Architecting Cloud Native ApplicationsArchitecting Cloud Native Applications
Architecting Cloud Native Applications
 
Platformless Horizons for Digital Adaptability
Platformless Horizons for Digital AdaptabilityPlatformless Horizons for Digital Adaptability
Platformless Horizons for Digital Adaptability
 
WSO2 Micro Integrator for Enterprise Integration in a Decentralized, Microser...
WSO2 Micro Integrator for Enterprise Integration in a Decentralized, Microser...WSO2 Micro Integrator for Enterprise Integration in a Decentralized, Microser...
WSO2 Micro Integrator for Enterprise Integration in a Decentralized, Microser...
 
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers:  A Deep Dive into Serverless Spatial Data and FMECloud Frontiers:  A Deep Dive into Serverless Spatial Data and FME
Cloud Frontiers: A Deep Dive into Serverless Spatial Data and FME
 
DBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor PresentationDBX First Quarter 2024 Investor Presentation
DBX First Quarter 2024 Investor Presentation
 
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​Elevate Developer Efficiency & build GenAI Application with Amazon Q​
Elevate Developer Efficiency & build GenAI Application with Amazon Q​
 
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
Navigating the Deluge_ Dubai Floods and the Resilience of Dubai International...
 
How to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cfHow to Check CNIC Information Online with Pakdata cf
How to Check CNIC Information Online with Pakdata cf
 

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