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

Node.js & Twitter Bootstrap Crash Course
Node.js & Twitter Bootstrap Crash CourseNode.js & Twitter Bootstrap Crash Course
Node.js & Twitter Bootstrap Crash CourseAaron Silverman
 
Advanced WordPress Development Environments
Advanced WordPress Development EnvironmentsAdvanced WordPress Development Environments
Advanced WordPress Development EnvironmentsBeau Lebens
 
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 APIBrian Hogg
 
WordPress as the Backbone(.js)
WordPress as the Backbone(.js)WordPress as the Backbone(.js)
WordPress as the Backbone(.js)Beau Lebens
 
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]Dirk Ginader
 
Choosing a Javascript Framework
Choosing a Javascript FrameworkChoosing a Javascript Framework
Choosing a Javascript FrameworkAll Things Open
 
Automating WordPress Theme Development
Automating WordPress Theme DevelopmentAutomating WordPress Theme Development
Automating WordPress Theme DevelopmentHardeep Asrani
 
CodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC FrameworkBo-Yi Wu
 
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...Ryan Weaver
 
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 WorkerChang W. Doh
 
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 sitesdrupalindia
 
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 WebpackIgnacio Martín
 
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!Derek Willian Stavis
 
SockJS Intro
SockJS IntroSockJS Intro
SockJS IntroNgoc Dao
 
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史Shengyou Fan
 
遠端團隊專案建立與管理 remote team management 2016
遠端團隊專案建立與管理 remote team management 2016遠端團隊專案建立與管理 remote team management 2016
遠端團隊專案建立與管理 remote team management 2016Caesar Chi
 

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

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 worldDmitry Bakaleinik
 
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...Mikko Ohtamaa
 
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 Websocketsametmax
 
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 eBusinessDivante
 
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)Divante
 
Omnichannel Customer Experience
Omnichannel Customer ExperienceOmnichannel Customer Experience
Omnichannel Customer ExperienceDivante
 

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

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.jsasync_io
 
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 renderingAndrea Giannantonio
 
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 2014Stéphane Bégaudeau
 
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 2014Stéphane Bégaudeau
 
Frontend Workflow
Frontend WorkflowFrontend Workflow
Frontend WorkflowDelphiCon
 
Integrating Browserify with Sprockets
Integrating Browserify with SprocketsIntegrating Browserify with Sprockets
Integrating Browserify with SprocketsSpike Brehm
 
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 2014Stéphane Bégaudeau
 
JavaScript Modules Done Right
JavaScript Modules Done RightJavaScript Modules Done Right
JavaScript Modules Done RightMariusz Nowak
 
20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdevFrank Rousseau
 
Angular Part 3 (Basic knowledge)
Angular Part 3 (Basic knowledge)Angular Part 3 (Basic knowledge)
Angular Part 3 (Basic knowledge)Rohit Singh
 
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 & SeleniumJames Eisenhauer
 
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 yeomanMakarand Bhatambarekar
 
Web development - technologies and tools
Web development - technologies and toolsWeb development - technologies and tools
Web development - technologies and toolsYoann Gotthilf
 
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 ...tdc-globalcode
 
Dcjq node.js presentation
Dcjq node.js presentationDcjq node.js presentation
Dcjq node.js presentationasync_io
 
Introduction to node.js by jiban
Introduction to node.js by jibanIntroduction to node.js by jiban
Introduction to node.js by jibanJibanananda Sana
 
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 VueJSdanpastori
 
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 RubyFabio Akita
 
Introduction of webpack 4
Introduction of webpack 4Introduction of webpack 4
Introduction of webpack 4Vijay Shukla
 

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

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 FranciscoRyan Weaver
 
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 2017Ryan Weaver
 
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 moreRyan Weaver
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Ryan Weaver
 
Guard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityRyan Weaver
 
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 BehatRyan Weaver
 
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!Ryan Weaver
 
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 SilexRyan Weaver
 
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 SymfonyRyan Weaver
 
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 itRyan Weaver
 
The Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsRyan Weaver
 
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 appRyan Weaver
 
Symfony2: Get your project started
Symfony2: Get your project startedSymfony2: Get your project started
Symfony2: Get your project startedRyan Weaver
 
Symony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP FrameworkSymony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP FrameworkRyan Weaver
 
Hands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkHands-on with the Symfony2 Framework
Hands-on with the Symfony2 FrameworkRyan Weaver
 
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)Ryan Weaver
 
Being Dangerous with Twig
Being Dangerous with TwigBeing Dangerous with Twig
Being Dangerous with TwigRyan Weaver
 
Doctrine2 In 10 Minutes
Doctrine2 In 10 MinutesDoctrine2 In 10 Minutes
Doctrine2 In 10 MinutesRyan Weaver
 
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 youRyan Weaver
 
The Art of Doctrine Migrations
The Art of Doctrine MigrationsThe Art of Doctrine Migrations
The Art of Doctrine MigrationsRyan 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

Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Igalia
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilV3cube
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonAnna Loughnan Colquhoun
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationSafe Software
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Paola De la Torre
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024The Digital Insurer
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonetsnaman860154
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsEnterprise Knowledge
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...Martijn de Jong
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Miguel Araújo
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountPuma Security, LLC
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityPrincipled Technologies
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerThousandEyes
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxKatpro Technologies
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Enterprise Knowledge
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsRoshan Dwivedi
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 

Recently uploaded (20)

Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101Salesforce Community Group Quito, Salesforce 101
Salesforce Community Group Quito, Salesforce 101
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
IAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI SolutionsIAC 2024 - IA Fast Track to Search Focused AI Solutions
IAC 2024 - IA Fast Track to Search Focused AI Solutions
 
2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...2024: Domino Containers - The Next Step. News from the Domino Container commu...
2024: Domino Containers - The Next Step. News from the Domino Container commu...
 
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
 
Breaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path MountBreaking the Kubernetes Kill Chain: Host Path Mount
Breaking the Kubernetes Kill Chain: Host Path Mount
 
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
Neo4j - How KGs are shaping the future of Generative AI at AWS Summit London ...
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
How to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected WorkerHow to Troubleshoot Apps for the Modern Connected Worker
How to Troubleshoot Apps for the Modern Connected Worker
 
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptxFactors to Consider When Choosing Accounts Payable Services Providers.pptx
Factors to Consider When Choosing Accounts Payable Services Providers.pptx
 
Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...Driving Behavioral Change for Information Management through Data-Driven Gree...
Driving Behavioral Change for Information Management through Data-Driven Gree...
 
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live StreamsTop 5 Benefits OF Using Muvi Live Paywall For Live Streams
Top 5 Benefits OF Using Muvi Live Paywall For Live Streams
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 

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