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

53,384 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 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
2 Comments
83 Likes
Statistics
Notes
No Downloads
Views
Total views
53,384
On SlideShare
0
From Embeds
0
Number of Embeds
1,221
Actions
Shares
0
Downloads
327
Comments
2
Likes
83
Embeds 0
No embeds

No notes for slide

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

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

×