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

Ryan Weaver
Ryan WeaverSymfony Developer à KnpUniversity
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
1 sur 178

Recommandé

Mastering Grunt par
Mastering GruntMastering Grunt
Mastering GruntSpencer Handley
777 vues74 diapositives
Bower - A package manager for the web par
Bower - A package manager for the webBower - A package manager for the web
Bower - A package manager for the webLarry Nung
847 vues76 diapositives
Bower & Grunt - A practical workflow par
Bower & Grunt - A practical workflowBower & Grunt - A practical workflow
Bower & Grunt - A practical workflowRiccardo Coppola
5.8K vues31 diapositives
Optimising Your Front End Workflow With Symfony, Twig, Bower and Gulp par
Optimising Your Front End Workflow With Symfony, Twig, Bower and GulpOptimising Your Front End Workflow With Symfony, Twig, Bower and Gulp
Optimising Your Front End Workflow With Symfony, Twig, Bower and GulpMatthew Davis
52.6K vues144 diapositives
Meetup Performance par
Meetup PerformanceMeetup Performance
Meetup PerformanceGreg Whalin
21.1K vues56 diapositives
Bower power par
Bower powerBower power
Bower powerEric Carlisle
2.2K vues28 diapositives

Contenu connexe

Tendances

Node.js & Twitter Bootstrap Crash Course par
Node.js & Twitter Bootstrap Crash CourseNode.js & Twitter Bootstrap Crash Course
Node.js & Twitter Bootstrap Crash CourseAaron Silverman
28.7K vues67 diapositives
Advanced WordPress Development Environments par
Advanced WordPress Development EnvironmentsAdvanced WordPress Development Environments
Advanced WordPress Development EnvironmentsBeau Lebens
2.4K vues103 diapositives
WordCamp Ann Arbor 2015 Introduction to Backbone + WP REST API par
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
1.8K vues30 diapositives
WordPress as the Backbone(.js) par
WordPress as the Backbone(.js)WordPress as the Backbone(.js)
WordPress as the Backbone(.js)Beau Lebens
4.9K vues50 diapositives
Let Grunt do the work, focus on the fun! [Open Web Camp 2013] par
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
20.7K vues84 diapositives
Choosing a Javascript Framework par
Choosing a Javascript FrameworkChoosing a Javascript Framework
Choosing a Javascript FrameworkAll Things Open
1K vues101 diapositives

Tendances(20)

Node.js & Twitter Bootstrap Crash Course par Aaron Silverman
Node.js & Twitter Bootstrap Crash CourseNode.js & Twitter Bootstrap Crash Course
Node.js & Twitter Bootstrap Crash Course
Aaron Silverman28.7K vues
Advanced WordPress Development Environments par Beau Lebens
Advanced WordPress Development EnvironmentsAdvanced WordPress Development Environments
Advanced WordPress Development Environments
Beau Lebens2.4K vues
WordCamp Ann Arbor 2015 Introduction to Backbone + WP REST API par Brian Hogg
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
Brian Hogg1.8K vues
WordPress as the Backbone(.js) par Beau Lebens
WordPress as the Backbone(.js)WordPress as the Backbone(.js)
WordPress as the Backbone(.js)
Beau Lebens4.9K vues
Let Grunt do the work, focus on the fun! [Open Web Camp 2013] par Dirk Ginader
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 Ginader20.7K vues
Automating WordPress Theme Development par Hardeep Asrani
Automating WordPress Theme DevelopmentAutomating WordPress Theme Development
Automating WordPress Theme Development
Hardeep Asrani5.7K vues
CodeIgniter PHP MVC Framework par Bo-Yi Wu
CodeIgniter PHP MVC FrameworkCodeIgniter PHP MVC Framework
CodeIgniter PHP MVC Framework
Bo-Yi Wu27.4K vues
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C... par Ryan Weaver
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 Weaver13.7K vues
Instant and offline apps with Service Worker par Chang W. Doh
Instant and offline apps with Service WorkerInstant and offline apps with Service Worker
Instant and offline apps with Service Worker
Chang W. Doh856 vues
Migraine Drupal - syncing your staging and live sites par drupalindia
Migraine Drupal - syncing your staging and live sitesMigraine Drupal - syncing your staging and live sites
Migraine Drupal - syncing your staging and live sites
drupalindia7.7K vues
Keeping the frontend under control with Symfony and Webpack par Ignacio Martín
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
Ignacio Martín6.4K vues
SockJS Intro par Ngoc Dao
SockJS IntroSockJS Intro
SockJS Intro
Ngoc Dao24.2K vues
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史 par Shengyou Fan
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史
[PHP 也有 Day] 垃圾留言守城記 - 用 Laravel 阻擋 SPAM 留言的奮鬥史
Shengyou Fan1.1K vues
遠端團隊專案建立與管理 remote team management 2016 par Caesar Chi
遠端團隊專案建立與管理 remote team management 2016遠端團隊專案建立與管理 remote team management 2016
遠端團隊專案建立與管理 remote team management 2016
Caesar Chi2.9K vues

En vedette

Introduction to maven, its configuration, lifecycle and relationship to JS world par
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
691 vues91 diapositives
Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ... par
 Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ... Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ...
Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ...Mikko Ohtamaa
3.6K vues32 diapositives
Introduction to WAMP, a protocol enabling PUB/SUB and RPC over Websocket par
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
367K vues32 diapositives
Surprising failure factors when implementing eCommerce and Omnichannel eBusiness par
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
161.4K vues30 diapositives
Magento scalability from the trenches (Meet Magento Sweden 2016) par
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
166.5K vues25 diapositives
Omnichannel Customer Experience par
Omnichannel Customer ExperienceOmnichannel Customer Experience
Omnichannel Customer ExperienceDivante
166.6K vues27 diapositives

En vedette(6)

Introduction to maven, its configuration, lifecycle and relationship to JS world par Dmitry Bakaleinik
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... par Mikko Ohtamaa
 Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ... Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ...
Beautiful Maintainable ModularJavascript Codebase with RequireJS - HelsinkiJ...
Mikko Ohtamaa3.6K vues
Introduction to WAMP, a protocol enabling PUB/SUB and RPC over Websocket par sametmax
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
sametmax367K vues
Surprising failure factors when implementing eCommerce and Omnichannel eBusiness par Divante
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
Divante161.4K vues
Magento scalability from the trenches (Meet Magento Sweden 2016) par Divante
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)
Divante166.5K vues
Omnichannel Customer Experience par Divante
Omnichannel Customer ExperienceOmnichannel Customer Experience
Omnichannel Customer Experience
Divante166.6K vues

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

Practical Use of MongoDB for Node.js par
Practical Use of MongoDB for Node.jsPractical Use of MongoDB for Node.js
Practical Use of MongoDB for Node.jsasync_io
19.9K vues54 diapositives
Once upon a time, there were css, js and server-side rendering par
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
574 vues44 diapositives
Modern Web Application Development Workflow - EclipseCon France 2014 par
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
3.5K vues147 diapositives
Modern Web Application Development Workflow - EclipseCon Europe 2014 par
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
863 vues154 diapositives
Frontend Workflow par
Frontend WorkflowFrontend Workflow
Frontend WorkflowDelphiCon
464 vues201 diapositives
Integrating Browserify with Sprockets par
Integrating Browserify with SprocketsIntegrating Browserify with Sprockets
Integrating Browserify with SprocketsSpike Brehm
1.8K vues34 diapositives

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

Practical Use of MongoDB for Node.js par async_io
Practical Use of MongoDB for Node.jsPractical Use of MongoDB for Node.js
Practical Use of MongoDB for Node.js
async_io19.9K vues
Once upon a time, there were css, js and server-side rendering par Andrea Giannantonio
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 par Stéphane Bégaudeau
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 par Stéphane Bégaudeau
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 par DelphiCon
Frontend WorkflowFrontend Workflow
Frontend Workflow
DelphiCon464 vues
Integrating Browserify with Sprockets par Spike Brehm
Integrating Browserify with SprocketsIntegrating Browserify with Sprockets
Integrating Browserify with Sprockets
Spike Brehm1.8K vues
Modern Web Application Development Workflow - web2day 2014 par Stéphane Bégaudeau
Modern Web Application Development Workflow - web2day 2014Modern Web Application Development Workflow - web2day 2014
Modern Web Application Development Workflow - web2day 2014
JavaScript Modules Done Right par Mariusz Nowak
JavaScript Modules Done RightJavaScript Modules Done Right
JavaScript Modules Done Right
Mariusz Nowak5.4K vues
20130528 solution linux_frousseau_nopain_webdev par Frank Rousseau
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev
Frank Rousseau1.3K vues
Angular Part 3 (Basic knowledge) par Rohit Singh
Angular Part 3 (Basic knowledge)Angular Part 3 (Basic knowledge)
Angular Part 3 (Basic knowledge)
Rohit Singh54 vues
A Introduction to the World of Node, Javascript & Selenium par James Eisenhauer
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
James Eisenhauer799 vues
Web development - technologies and tools par Yoann Gotthilf
Web development - technologies and toolsWeb development - technologies and tools
Web development - technologies and tools
Yoann Gotthilf1.1K vues
TDC2017 | Florianopolis - Trilha DevOps How we figured out we had a SRE team ... par tdc-globalcode
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-globalcode42 vues
Dcjq node.js presentation par async_io
Dcjq node.js presentationDcjq node.js presentation
Dcjq node.js presentation
async_io5.1K vues
Building a Single Page Application with VueJS par danpastori
Building a Single Page Application with VueJSBuilding a Single Page Application with VueJS
Building a Single Page Application with VueJS
danpastori340 vues
Consegi 2010 - Dicas de Desenvolvimento Web com Ruby par Fabio Akita
Consegi 2010 - Dicas de Desenvolvimento Web com RubyConsegi 2010 - Dicas de Desenvolvimento Web com Ruby
Consegi 2010 - Dicas de Desenvolvimento Web com Ruby
Fabio Akita1.3K vues
Introduction of webpack 4 par Vijay Shukla
Introduction of webpack 4Introduction of webpack 4
Introduction of webpack 4
Vijay Shukla124 vues

Plus de Ryan Weaver

Webpack Encore Symfony Live 2017 San Francisco par
Webpack Encore Symfony Live 2017 San FranciscoWebpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San FranciscoRyan Weaver
2.8K vues94 diapositives
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017 par
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
1.4K vues85 diapositives
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more par
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
32.1K vues114 diapositives
Symfony: Your Next Microframework (SymfonyCon 2015) par
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Ryan Weaver
7.6K vues90 diapositives
Guard Authentication: Powerful, Beautiful Security par
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful SecurityRyan Weaver
9.4K vues102 diapositives
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat par
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
2.3K vues123 diapositives

Plus de Ryan Weaver(20)

Webpack Encore Symfony Live 2017 San Francisco par Ryan Weaver
Webpack Encore Symfony Live 2017 San FranciscoWebpack Encore Symfony Live 2017 San Francisco
Webpack Encore Symfony Live 2017 San Francisco
Ryan Weaver2.8K vues
The Coolest Symfony Components you’ve never heard of - DrupalCon 2017 par Ryan Weaver
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
Ryan Weaver1.4K vues
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more par Ryan Weaver
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
Ryan Weaver32.1K vues
Symfony: Your Next Microframework (SymfonyCon 2015) par Ryan Weaver
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
Ryan Weaver7.6K vues
Guard Authentication: Powerful, Beautiful Security par Ryan Weaver
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
Ryan Weaver9.4K vues
Grand Rapids PHP Meetup: Behavioral Driven Development with Behat par Ryan Weaver
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
Ryan Weaver2.3K vues
Twig: Friendly Curly Braces Invade Your Templates! par Ryan Weaver
Twig: Friendly Curly Braces Invade Your Templates!Twig: Friendly Curly Braces Invade Your Templates!
Twig: Friendly Curly Braces Invade Your Templates!
Ryan Weaver3K vues
Master the New Core of Drupal 8 Now: with Symfony and Silex par Ryan Weaver
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
Ryan Weaver4.7K vues
Silex: Microframework y camino fácil de aprender Symfony par Ryan Weaver
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
Ryan Weaver1.4K vues
Drupal 8: Huge wins, a Bigger Community, and why you (and I) will Love it par Ryan Weaver
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
Ryan Weaver3.2K vues
The Wonderful World of Symfony Components par Ryan Weaver
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony Components
Ryan Weaver47.4K vues
A PHP Christmas Miracle - 3 Frameworks, 1 app par Ryan Weaver
A PHP Christmas Miracle - 3 Frameworks, 1 appA PHP Christmas Miracle - 3 Frameworks, 1 app
A PHP Christmas Miracle - 3 Frameworks, 1 app
Ryan Weaver11.8K vues
Symfony2: Get your project started par Ryan Weaver
Symfony2: Get your project startedSymfony2: Get your project started
Symfony2: Get your project started
Ryan Weaver19.1K vues
Symony2 A Next Generation PHP Framework par Ryan Weaver
Symony2 A Next Generation PHP FrameworkSymony2 A Next Generation PHP Framework
Symony2 A Next Generation PHP Framework
Ryan Weaver7.5K vues
Hands-on with the Symfony2 Framework par Ryan Weaver
Hands-on with the Symfony2 FrameworkHands-on with the Symfony2 Framework
Hands-on with the Symfony2 Framework
Ryan Weaver9.3K vues
Being Dangerous with Twig (Symfony Live Paris) par Ryan Weaver
Being Dangerous with Twig (Symfony Live Paris)Being Dangerous with Twig (Symfony Live Paris)
Being Dangerous with Twig (Symfony Live Paris)
Ryan Weaver14K vues
Being Dangerous with Twig par Ryan Weaver
Being Dangerous with TwigBeing Dangerous with Twig
Being Dangerous with Twig
Ryan Weaver11.2K vues
Doctrine2 In 10 Minutes par Ryan Weaver
Doctrine2 In 10 MinutesDoctrine2 In 10 Minutes
Doctrine2 In 10 Minutes
Ryan Weaver4.5K vues
Dependency Injection: Make your enemies fear you par Ryan Weaver
Dependency Injection: Make your enemies fear youDependency Injection: Make your enemies fear you
Dependency Injection: Make your enemies fear you
Ryan Weaver2K vues
The Art of Doctrine Migrations par Ryan Weaver
The Art of Doctrine MigrationsThe Art of Doctrine Migrations
The Art of Doctrine Migrations
Ryan Weaver15K vues

Dernier

Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ... par
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...ShapeBlue
121 vues15 diapositives
State of the Union - Rohit Yadav - Apache CloudStack par
State of the Union - Rohit Yadav - Apache CloudStackState of the Union - Rohit Yadav - Apache CloudStack
State of the Union - Rohit Yadav - Apache CloudStackShapeBlue
218 vues53 diapositives
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T par
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TCloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TShapeBlue
81 vues34 diapositives
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R... par
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...ShapeBlue
105 vues15 diapositives
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti... par
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...ShapeBlue
69 vues29 diapositives
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue par
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlueShapeBlue
75 vues23 diapositives

Dernier(20)

Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ... par ShapeBlue
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...
Backroll, News and Demo - Pierre Charton, Matthias Dhellin, Ousmane Diarra - ...
ShapeBlue121 vues
State of the Union - Rohit Yadav - Apache CloudStack par ShapeBlue
State of the Union - Rohit Yadav - Apache CloudStackState of the Union - Rohit Yadav - Apache CloudStack
State of the Union - Rohit Yadav - Apache CloudStack
ShapeBlue218 vues
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T par ShapeBlue
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&TCloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
CloudStack and GitOps at Enterprise Scale - Alex Dometrius, Rene Glover - AT&T
ShapeBlue81 vues
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R... par ShapeBlue
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...
Setting Up Your First CloudStack Environment with Beginners Challenges - MD R...
ShapeBlue105 vues
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti... par ShapeBlue
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...
DRaaS using Snapshot copy and destination selection (DRaaS) - Alexandre Matti...
ShapeBlue69 vues
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue par ShapeBlue
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue
2FA and OAuth2 in CloudStack - Andrija Panić - ShapeBlue
ShapeBlue75 vues
Data Integrity for Banking and Financial Services par Precisely
Data Integrity for Banking and Financial ServicesData Integrity for Banking and Financial Services
Data Integrity for Banking and Financial Services
Precisely76 vues
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda... par ShapeBlue
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...
Hypervisor Agnostic DRS in CloudStack - Brief overview & demo - Vishesh Jinda...
ShapeBlue93 vues
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT par ShapeBlue
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBITUpdates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT
Updates on the LINSTOR Driver for CloudStack - Rene Peinthor - LINBIT
ShapeBlue138 vues
NTGapps NTG LowCode Platform par Mustafa Kuğu
NTGapps NTG LowCode Platform NTGapps NTG LowCode Platform
NTGapps NTG LowCode Platform
Mustafa Kuğu287 vues
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or... par ShapeBlue
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...
Zero to Cloud Hero: Crafting a Private Cloud from Scratch with XCP-ng, Xen Or...
ShapeBlue128 vues
Extending KVM Host HA for Non-NFS Storage - Alex Ivanov - StorPool par ShapeBlue
Extending KVM Host HA for Non-NFS Storage -  Alex Ivanov - StorPoolExtending KVM Host HA for Non-NFS Storage -  Alex Ivanov - StorPool
Extending KVM Host HA for Non-NFS Storage - Alex Ivanov - StorPool
ShapeBlue56 vues
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue par ShapeBlue
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlueCloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue
CloudStack Object Storage - An Introduction - Vladimir Petrov - ShapeBlue
ShapeBlue63 vues
Automating a World-Class Technology Conference; Behind the Scenes of CiscoLive par Network Automation Forum
Automating a World-Class Technology Conference; Behind the Scenes of CiscoLiveAutomating a World-Class Technology Conference; Behind the Scenes of CiscoLive
Automating a World-Class Technology Conference; Behind the Scenes of CiscoLive
The Power of Heat Decarbonisation Plans in the Built Environment par IES VE
The Power of Heat Decarbonisation Plans in the Built EnvironmentThe Power of Heat Decarbonisation Plans in the Built Environment
The Power of Heat Decarbonisation Plans in the Built Environment
IES VE67 vues
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue par ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlueMigrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
Migrating VMware Infra to KVM Using CloudStack - Nicolas Vazquez - ShapeBlue
ShapeBlue147 vues
Business Analyst Series 2023 - Week 4 Session 7 par DianaGray10
Business Analyst Series 2023 -  Week 4 Session 7Business Analyst Series 2023 -  Week 4 Session 7
Business Analyst Series 2023 - Week 4 Session 7
DianaGray10110 vues
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue par ShapeBlue
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlueVNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue
VNF Integration and Support in CloudStack - Wei Zhou - ShapeBlue
ShapeBlue134 vues
Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P... par ShapeBlue
Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P...Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P...
Developments to CloudStack’s SDN ecosystem: Integration with VMWare NSX 4 - P...
ShapeBlue120 vues

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