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
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
.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
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
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
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
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 ...]	
}	
}
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
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'	
}	
}
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
We have 2 sub-tasks:
1) compass:dist for deployment
2) compass:dev for development
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 %>'	
}	
]	
}	
},
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
And even JsHint
jshint: {	
all: [	
'Gruntfile.js',	
'<%= appDir %>/js/{,*/}*.js'	
]	
},
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Roll these up into some
grouped commands

http://www.flickr.com/photos/gazeronly/8206753938
// 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']	
}	
}
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
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

Anypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPAAnypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPAshyamraj55
 
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...DianaGray10
 
UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6DianaGray10
 
Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )Brian Pichman
 
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCostKubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCostMatt Ray
 
IaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdf
IaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdfIaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdf
IaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdfDaniel Santiago Silva Capera
 
UiPath Platform: The Backend Engine Powering Your Automation - Session 1
UiPath Platform: The Backend Engine Powering Your Automation - Session 1UiPath Platform: The Backend Engine Powering Your Automation - Session 1
UiPath Platform: The Backend Engine Powering Your Automation - Session 1DianaGray10
 
AI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just MinutesAI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just MinutesMd Hossain Ali
 
Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024D Cloud Solutions
 
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfUiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfDianaGray10
 
OpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability AdventureOpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability AdventureEric D. Schabell
 
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...Aggregage
 
Bird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystemBird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystemAsko Soukka
 
20230202 - Introduction to tis-py
20230202 - Introduction to tis-py20230202 - Introduction to tis-py
20230202 - Introduction to tis-pyJamie (Taka) Wang
 
Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.YounusS2
 
Empowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership BlueprintEmpowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership BlueprintMahmoud Rabie
 
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsIgniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsSafe Software
 
UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8DianaGray10
 
UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...
UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...
UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...UbiTrack UK
 

Recently uploaded (20)

Anypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPAAnypoint Code Builder , Google Pub sub connector and MuleSoft RPA
Anypoint Code Builder , Google Pub sub connector and MuleSoft RPA
 
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
Connector Corner: Extending LLM automation use cases with UiPath GenAI connec...
 
UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6UiPath Studio Web workshop series - Day 6
UiPath Studio Web workshop series - Day 6
 
Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )Building Your Own AI Instance (TBLC AI )
Building Your Own AI Instance (TBLC AI )
 
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCostKubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
KubeConEU24-Monitoring Kubernetes and Cloud Spend with OpenCost
 
IaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdf
IaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdfIaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdf
IaC & GitOps in a Nutshell - a FridayInANuthshell Episode.pdf
 
UiPath Platform: The Backend Engine Powering Your Automation - Session 1
UiPath Platform: The Backend Engine Powering Your Automation - Session 1UiPath Platform: The Backend Engine Powering Your Automation - Session 1
UiPath Platform: The Backend Engine Powering Your Automation - Session 1
 
AI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just MinutesAI Fame Rush Review – Virtual Influencer Creation In Just Minutes
AI Fame Rush Review – Virtual Influencer Creation In Just Minutes
 
Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024Artificial Intelligence & SEO Trends for 2024
Artificial Intelligence & SEO Trends for 2024
 
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdfUiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
UiPath Solutions Management Preview - Northern CA Chapter - March 22.pdf
 
OpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability AdventureOpenShift Commons Paris - Choose Your Own Observability Adventure
OpenShift Commons Paris - Choose Your Own Observability Adventure
 
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
The Data Metaverse: Unpacking the Roles, Use Cases, and Tech Trends in Data a...
 
Bird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystemBird eye's view on Camunda open source ecosystem
Bird eye's view on Camunda open source ecosystem
 
20230202 - Introduction to tis-py
20230202 - Introduction to tis-py20230202 - Introduction to tis-py
20230202 - Introduction to tis-py
 
Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.Basic Building Blocks of Internet of Things.
Basic Building Blocks of Internet of Things.
 
20150722 - AGV
20150722 - AGV20150722 - AGV
20150722 - AGV
 
Empowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership BlueprintEmpowering Africa's Next Generation: The AI Leadership Blueprint
Empowering Africa's Next Generation: The AI Leadership Blueprint
 
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration WorkflowsIgniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
 
UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8UiPath Studio Web workshop series - Day 8
UiPath Studio Web workshop series - Day 8
 
UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...
UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...
UWB Technology for Enhanced Indoor and Outdoor Positioning in Physiological M...
 

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