Your SlideShare is downloading. ×
Cool like a Frontend Developer: Grunt, RequireJS, Bower and other Tools
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

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

42,478
views

Published on

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 …

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.

Published in: Technology

1 Comment
56 Likes
Statistics
Notes
No Downloads
Views
Total Views
42,478
On Slideshare
0
From Embeds
0
Number of Embeds
9
Actions
Shares
0
Downloads
226
Comments
1
Likes
56
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. Deck the halls with: Grunt, RequireJS & Bower by your friend: ! Ryan Weaver @weaverryan
  • 2. 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
  • 3. Shout-out to the Docs team! @weaverryan
  • 4. “Hack” with us on Sat! @weaverryan
  • 5. ! ! Intro Your friendly neighborhood JavaScript developer is all grown up… and kicking butt
  • 6. No Node.js ! 5 years ago @weaverryan Minifying and combining done with a backend solution ! No RequireJS, AngularJS ! SASS/LESS were virtually non-existent
  • 7. 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
  • 8. Your friend Pablo from ServerGrove is redeveloping the SG control panel with a pure-JS fronted @weaverryan
  • 9. Can we continue to use JavaScript like we have in the past? @weaverryan
  • 10. Maybe @weaverryan
  • 11. But we’re Symfony2 Developers… @weaverryan
  • 12. … with the highest standards in PHP @weaverryan
  • 13. When we write JavaScript… @weaverryan
  • 14. Let’s write great JavaScript @weaverryan
  • 15. Our Goal Take a traditional Symfony app and make it much more jolly by using Node.js, Bower, RequireJS, Compass and Grunt
  • 16. Node.js ! it’s on your Christmas list http://www.flickr.com/photos/calsidyrose/4183559218/
  • 17. The Project * Symfony 2.3 - simple events website ! * The code: http://bit.ly/sfcon-js-github @weaverryan
  • 18. 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>
  • 19. 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>
  • 20. 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
  • 21. ! Node.js and npm ! Server-side JavaScript
  • 22. 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
  • 23. play.js sys = require('sys'); ! for (i=0; i<5; i++) { sys.puts(i); } extra stuff added by Node.js
  • 24. play.js
  • 25. OMG! http://www.flickr.com/photos/nocturnenoir/8305081285/
  • 26. Node.js gives us the ability to use JavaScript as yet-another-serverside-scripting-language
  • 27. 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
  • 28. With Node.js and npm, we can quickly create small JavaScript files that use external modules
  • 29. With PHP and Composer, we can quickly create small PHP files that use external libraries
  • 30. Why should we care?
  • 31. Fronted JavaScript library build and development tools are written in JavaScript, executed with Node.js
  • 32. Bower Composer-lite for client-side JavaScript
  • 33. Bower (and one of those Node.js libraries installed with npm)
  • 34. Problem: How do I get frontend libraries (e.g. jQuery, Bootstrap) downloaded into my project? http://www.flickr.com/photos/perry-pics/5251240991/
  • 35. 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
  • 36. Installation ! sudo npm install -g bower this means “install it globally” on your machine - i.e. a bit like how Pear works
  • 37. .bowerrc Yo Bower, put the libraries over there: { "directory": "web/assets/vendor" }
  • 38. 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
  • 39. bower.json { "dependencies": { "bootstrap": "~3.0.2" } } ** yes, this *is* composer.json for Bower
  • 40. Now, how do we use these files?
  • 41. “Requiring” something in PHP require 'Event.php'; ! $event = new Event(); echo $event->getName();
  • 42. “Requiring” something in JS <script type="text/javascript" src="Event.js"></script> ! <script type="text/javascript"> console.log(Event.someMethod()); </script>
  • 43. Composer does 2 things: 1) Downloads libraries and their dependencies ! 2) Sets up autoloading so you don’t need “require” statements
  • 44. Bower does 1 thing: 1) Downloads libraries and their dependencies ! 2) Sets up autoloading so you don’t need “require” statements
  • 45. 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>
  • 46. 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>
  • 47. ! RequireJS ! Autoloading for client-side JavaScript
  • 48. 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/
  • 49. 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
  • 50. RequireJS works by requiring “modules”, not files. (though one file will contain one module)
  • 51. Get it! bower install requirejs --save
  • 52. 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>
  • 53. 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>
  • 54. 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>
  • 55. 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>
  • 56. 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>
  • 57. app/homepage.js define([], function() { console.log("It's alive!"); });
  • 58. But how does it work?
  • 59. 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
  • 60. now, what if we need jQuery?
  • 61. Remember, jQuery isn’t even downloaded yet - the global $ is not available http://www.flickr.com/photos/danaberlith/4207059574
  • 62. app/homepage.js define([], function() { $ = require('jquery'); $('...'); }); … it might look something like this?
  • 63. 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
  • 64. 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
  • 65. 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
  • 66. 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
  • 67. 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
  • 68. 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(); }); } } });
  • 69. 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”
  • 70. app/homepage.js define( ['jquery', 'app/modules/love'], function ($, Love) { ! $(document).ready(function() { Love.spiritOfXmas(); }); });
  • 71. This takes care of bringing in JavaScript for the homepage. But what about the new event page?
  • 72. 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>
  • 73. 2) Add a requirejs block to your <head> ::base.html.twig {% block requirejs %}{% endblock %}
  • 74. 3) Include the module you need EventBundle:Event:index.html.twig {% block requirejs %} {{ include('::_requirejs.html.twig', { 'module': 'app/homepage' }) }} {% endblock %}
  • 75. 4) Repeat! EventBundle:Event:new.html.twig {% block requirejs %} {{ include('::_requirejs.html.twig', { 'module': 'app/event_new' }) }} {% endblock %}
  • 76. app/event_new.js define(['jquery'], function ($) { ! $(document).ready(function() { // ... }); });
  • 77. Optimization Combining JavaScript files
  • 78. Problem: Each module is loaded from an individual file meaning there are lots of HTTP requests
  • 79. 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.
  • 80. Let’s start by creating a common “module” that’s always loaded
  • 81. 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'] } });
  • 82. ::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>
  • 83. ::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>
  • 84. ::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>
  • 85. Why? http://www.flickr.com/photos/danaberlith/4207059574
  • 86. Because now we have a module (common) that’s *always* loaded
  • 87. and we can use the optimizer to “push” more modules (e.g. bootstrap, jquery) into it
  • 88. 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
  • 89. Create an empty package.json npm init
  • 90. npm install requirejs --save-dev
  • 91. package.json { "devDependencies": { "requirejs": "~2.1.9" } }
  • 92. 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
  • 93. Configuration tells RequireJS how to minify and combine files build.js
  • 94. ({ 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'] } ] })
  • 95. ({ 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'] } ] })
  • 96. ({ 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'] } ] })
  • 97. ({ 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'] } ] })
  • 98. ({ 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'] } ] })
  • 99. node node_modules/.bin/r.js -o build.js
  • 100. Now, just point everything to assets-built
  • 101. {% 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>
  • 102. Not super dynamic yet... but it works!
  • 103. assets-built is the same as assets except when we include the common module, it has jquery and bootstrap packaged inside it
  • 104. Compass Sass with style
  • 105. Problem: Static CSS files are *so* 2010 http://www.flickr.com/photos/stevendepolo/8409407391
  • 106. Compass * Processes sass files into CSS ! * A sass “framework”: adds a lot of extra functionality, including CSS3 mixins, sprites, etc @weaverryan
  • 107. Use Bower to bring in a sass Bootstrap bower.json { "dependencies": { "sass-bootstrap": "~3.0.0" "requirejs": "~2.1.9", } }
  • 108. bower install
  • 109. Rename and reorganize CSS into SASS files web/assets/sass/ * _base.scss * _event.scss * _events.scss * event_form.scss * layout.scss
  • 110. 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
  • 111. 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
  • 112. 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
  • 113. 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 %}
  • 114. 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 %}
  • 115. layout.scss @import @import @import @import "base"; "../vendor/sass-bootstrap/lib/bootstrap"; "event"; "events"; ! body { // ... }
  • 116. 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
  • 117. 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.
  • 118. event_form.scss @import "base"; ! /* for play, make the inputs super-rounded */ .form-group input { @include border-radius(20px, 20px); }
  • 119. Now, just use more tools
  • 120. sudo npm install -g compass
  • 121. compass compile --css-dir=web/assets/css --sass-dir=web/assets/sass
  • 122. “partials” (files beginning with “_”) are ignored
  • 123. compass watch --css-dir=web/assets/css --sass-dir=web/assets/sass
  • 124. watches for file changes and regenerates the necessary CSS files
  • 125. Grunt app/console for JavaScript
  • 126. 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
  • 127. Install the Grunt executable sudo npm install -g grunt-cli
  • 128. 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" } }
  • 129. 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" } }
  • 130. npm install
  • 131. Grunt works by creating a Gruntfile.js file, where we define tasks (like app/console)
  • 132. 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'); };
  • 133. grunt -h Eventually we can run grunt RequireJS but we need to configure each command
  • 134. Gruntfile.js Use Grunt to run the RequireJS optimizer Remove the RequireJS build.js and moves its contents here
  • 135. 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 ...] } }
  • 136. 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 ...] } }
  • 137. 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 ...] } }
  • 138. 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 ...] } }
  • 139. Repeat for Compass!
  • 140. compass: { dist: { options: { sassDir: '<%= builtDir %>/sass', cssDir: '<%= builtDir %>/css', environment: 'production', outputStyle: 'compressed' } }, dev: { options: { sassDir: '<%= appDir %>/sass', cssDir: '<%= appDir %>/css', outputStyle: 'expanded' } }
  • 141. We have 2 sub-tasks: 1) compass:dist for deployment 2) compass:dev for development
  • 142. Repeat for Uglify (to minimize our JS files)! ** The RequireJS optimizer can uglify, but using uglify directly gives us a bit more control
  • 143. uglify: { build: { files: [ { expand: true, cwd: '<%= builtDir %>', src: 'js/*.js', dest: '<%= builtDir %>' } ] } },
  • 144. And even JsHint
  • 145. jshint: { all: [ 'Gruntfile.js', '<%= appDir %>/js/{,*/}*.js' ] },
  • 146. Roll these up into some grouped commands http://www.flickr.com/photos/gazeronly/8206753938
  • 147. // task for development grunt.registerTask('dev', [ 'jshint', 'compass:dev' ]); ! // task for before deployment grunt.registerTask('production', [ 'jshint', 'requirejs', 'uglify', 'compass:dist' ]);
  • 148. ! ! ! ! ! 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) ]);
  • 149. What about “watching”
  • 150. watch: { scripts: { files: [ '<%= appDir %>/js/*.js', // ... ], tasks: ['jshint'] }, compass: { files: '<%= appDir %>/sass/*.scss', tasks: ['compass:dev'] } }
  • 151. assets versus assets-dist How to handle in Symfony
  • 152. 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?
  • 153. Simple Solution
  • 154. config.yml parameters: assets_directory: 'assets' ! twig: # ... globals: assetsPath: %assets_directory%
  • 155. config_prod.yml parameters: assets_directory: 'assets-prod'
  • 156. ::requirejs.html.twig <script src="{{ asset(assetsPath~'/vendor/ requirejs/require.js') }}"></script> <script> requirejs.config({ baseUrl: '/{{ assetsPath }}/js' }); ! // ... </script>
  • 157. ::base.html.twig {% block stylesheets %} <link rel="stylesheet" href="{{ asset(assetsPath~'/css/layout.css') }}"/> {% endblock %}
  • 158. Manual, but straightforward
  • 159. 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()
  • 160. 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
  • 161. When developing: ! grunt watch
  • 162. When deploying: ! grunt production
  • 163. and make your own Grunt tasks for other processing
  • 164. grunt.registerTask('symfonycon', function() { sys = require('sys'); sys.puts('Thanks everyone!'); });
  • 165. JavaScript is a first-class tool in your stack @weaverryan
  • 166. Treat it with the same care and quality as everything else @weaverryan
  • 167. And (code) be cool like a frontend developer @weaverryan
  • 168. Ho ho ho, thanks! Brutal Feedback appreciated https://joind.in/10372 The code: http://bit.ly/sfcon-js-github Keep learning: KnpUniversity.com @weaverryan