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

Post on 08-Sep-2014

29.568 views 0 download

description

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

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

Deck the halls with: Grunt, RequireJS & Bower

by your friend: !

Ryan Weaver @weaverryan

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

Writer for KnpUniversity.com screencasts

knplabs.com github.com/weaverryan@weaverryan

Husband of the much more talented @leannapelham

Who is this jolly guy?

@weaverryan

Shout-out to the Docs team!

“Hack” with us on Sat!

@weaverryan

!

!

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

Intro

No Node.js !

Minifying and combining done with a backend solution !

No RequireJS, AngularJS !

SASS/LESS were virtually non-existent

@weaverryan

5 years ago

Node.js for running server-side JavaScript !

RequireJS/AMD !

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

SASS/LESS are very mature and can compile themselves

@weaverryan

Today

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

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

Node.js !

it’s on your Christmas list

@weaverryan

* Symfony 2.3 - simple events website !

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

The Project

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

base.html.twig

@weaverryan

* Homepage:

A) Has its own page-specific JS code

!

* New Event

A) Has its own page-specific JS code

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

Pages

!

!

Server-side JavaScript

Node.js and npm

@weaverryan

1) Executes JavaScript code !

2) Adds extra functionality for using

JavaScript to deal with filesystems and other

things that make sense on a server !

3) Similar to the “php” executable that lets

us interpret PHP code

3

Node.js

sys = require('sys');!

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

extra stuff added by Node.js

play.js

play.js

OMG!

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

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

@weaverryan

1) Composer for Node.js

3

npm

2) Can be used to install things globally or

into your project (usually in a node_modules)

directory

3) Reads dependencies from a package.json file

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/

@weaverryan

1) Downloads frontend libraries (usually JS)

into a directory in your project

3

Bower

2) Reads from a bower.json file

3) Handles dependencies!

Installation !

sudo npm install -g bower

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

.bowerrc

Yo Bower, put the libraries over there:

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

bower init

bower install bootstrap --save

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

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

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

bower.json

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

Now, how do we use these files?

“Requiring” something in PHP

require 'Event.php';!

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

“Requiring” something in JS

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

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

Composer does 2 things:

1) Downloads libraries and their dependencies !

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

Bower does 1 thing:

1) Downloads libraries and their dependencies !

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

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

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

before

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

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

after

RequireJS!

!

Autoloading for client-side JavaScript

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

Problem:

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

@weaverryan

* A library that allows us to load JavaScript

resources without worrying about script tags

!

* Instead, we use a require function, which is

quite similar to the PHP require statement

RequireJS

RequireJS works by requiring “modules”, not files.

(though one file will contain one module)

bower install requirejs --save

Get it!

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>

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

base.html.twig

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

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

2) Configure RequireJSAll modules live relative to this directory

base.html.twig

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

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

base.html.twig

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

2) Configure RequireJS

Loads assets/js/app/homepage.js

base.html.twig

app/homepage.js

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

But how does it work?

@weaverryan

* All files are loaded by adding script tags

right into the HTML. But these use the async

tag, so do not block the page load.

!

* You’ll commonly see a data-main attribute

in setup. It loads that module. Our setup

does the same thing.

But how!

now, what if we need jQuery?

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

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

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

app/homepage.js

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

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

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

base.html.twig

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

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

will be the app/modules/newman “module”

app/modules/love.js

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

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

app/homepage.js

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' } // …});!require(['{{ module }}']);</script>

... and make the module a variable

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

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

assets/js/common.js

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

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

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

Setup baseUrl so we can reference the common module below

::requirejs.html.twig

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

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

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

Load common and *then* load our real module

::requirejs.html.twig

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

@weaverryan

* Optimization is a server-side JavaScript tool

!

* In other words it’s a node library installed

via npm

!

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

our project’s dependencies in package.json

Installing the Optimizer

npm init

Create an empty package.json

npm install requirejs --save-dev

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

package.json

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

build.js

Configuration tells RequireJS how to minify and combine files

({ 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: [ { name: 'common', include: ['jquery', 'bootstrap'] }, { name: 'app/homepage', exclude: ['common'] } ]})

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

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

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

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

... plus we can manually include more modules

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

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

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

Now, just point everything to assets-built

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

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

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

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

});</script>

Not super dynamic yet... but it works!

assets-built is the same as assets

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

Compass

Sass with style

Problem:

Static CSS files are *so* 2010

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

@weaverryan

* Processes sass files into CSS

!

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

functionality, including CSS3 mixins, sprites,

etc

Compass

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

bower.json

Use Bower to bring in a sass Bootstrap

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 * _events.scss * event_form.scss * layout.scss

These are the only CSS files that will be included directly

EventBundle:Event:new.html.twig

base.html.twig

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

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

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

base.html.twig

EventBundle:Event:new.html.twig

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

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

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

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

base.html.twig

EventBundle:Event:new.html.twig

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

body { // ...}

layout.scss

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

body { // ...}

Sets variables and imports mixins used in all SASS files

layout.scss

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

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

layout.scss

@import "base";!

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

event_form.scss

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

sudo npm install -g grunt-cli

Install the Grunt executable

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

package.json

{ "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

npm install

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

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

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

Gruntfile.js

grunt -h

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

Remove the RequireJS build.js and moves its contents here

Gruntfile.js

Use Grunt to run the RequireJS optimizer

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', requirejs: { main: { options: { mainConfigFile: '<%= appDir %>/js/common.js', appDir: '<%= appDir %>', baseUrl: './js', dir: '<%= builtDir %>', optimizeCss: "none", optimize: "none", modules: [... same as build.js ...] } } }

We can setup variables and use them

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

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

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

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

Repeat for Compass!

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

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

Repeat for Uglify (to minimize our JS files)!

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

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

And even JsHint

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

Roll these up into some grouped commands

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

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

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

!

!

!

!

!

!

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

1) 2) 3) 4)

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

What about “watching”

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

assets versus assets-dist

How to handle in Symfony

Problem:

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

Simple Solution

parameters: assets_directory: 'assets'!

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

config.yml

parameters: assets_directory: 'assets-prod'

config_prod.yml

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

// ...</script>

::requirejs.html.twig

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

::base.html.twig

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 !

Compass compiles our SASS files !

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

Now

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!

@weaverryan

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

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

Keep learning: KnpUniversity.com