javascript first-class citizenery
DESCRIPTION
Javascript and first-class citizenry: require.js & node.jsJavascript on web pages is ubiquitous and its problems are legendary. Javascript, seen as a second-class code citizen, is usually hacked together even by seasoned developers. New libraries (jQuery, prototype, backbone, knockout, underscore) and runtime tools (firebug, jasmine) look like they solve many problems - and they do. But they still leave poorly written code as just that. One key problem is that all javascript code lives globally and this results in poorly managed, tested and delivered code.In this session, I will illustrate that we can treat javascript as a first-class citizen using with require.js and node.js: it can be modular, encapsulated and easily unit tested and added to continuous integration cycle. The dependencies between javascript modules can also be managed and packaged just like in C# and Java. In the end, we can resolve many javascript difficulties at compile time rather than waiting until runtime.TRANSCRIPT
Fast, modular code with RequireJS & node.js
JAVASCRIPT & 1ST CLASS CITIZENRY
Size and complexity
As JavaScript applications grow in size and complexity we need a real tool to handle dependencies and keep our code modular. RequireJS give you an easy way to define dependencies on your module and it'll make sure it’s loaded before your code runs. It also has a nice build system which can be used to compress and obfuscate your code using a closure compiler.
Adapted from: http://www.meetup.com/jQuery-Boston/events/16191792/
Agenda
• Global Pollution• Dependencies &
Modularisation• Building and
Testing• Optimisation
In Javascript, global functions are not a good strategy. They pollute. So, too do well known namespaces – but we’ll live with that. Modules provide a way forward against these problems.
Global Pollution
Badness 1: Global functions
<body><button onclick="addMovie()">add
Movie</button><script type="text/javascript”> function addMovie() {
$.observable( movies ).insert ( this.name); } </script></body>
Badness 2: Well-known namespaces<html><head>
<script src=http://code.jquery.com/jquery.js …<script src="jsrender.js" type="text/javascript"></script>
</head><body>
<div id="movieList"><b>Our Movies</b><br /></div>
<script type="text/javascript”>$.templates({ movieTemplate: "#movieList” })
$.template.moveTemplate.render( )</script>
</body></html>
Template Pattern
(function($) { // nothing outside here can call this function function sumWithInterest( el, interset){
return $(el).val() * interest; } $.extend($.fn , sumWithInterest) // except …}(jQuery));
RequireJs (module) pattern
define([“jquery”], function($) { function sumWithInterest( el, interest){
return $(el) .val() * interest; } return sumWithInterest// object or function });
Each module ecapsulates
define(“main”, function(){
//all code in here // is scoped })
require(“main”)
Create Consume
Access in is via a return only
define(“main”, function(){ return {
init: function(){…}
} })
require(“main”).init()
Create Consume
Dependencies are explicit
define(“main”, [‘log’, ‘jquery’], function( log, $ ){ log.debug( $(‘body’).text() ) })
Module loading can be ordered
define(“main”, [‘log’, ‘order!jquery’, ‘order!jsrender’], function( log, $ ){
$.template(‘body’).render()log.debug( $(‘body’).text() )
})
Building & Testing
Build command line
• Rake• Gradle• Psake• Msbuild• Ant• Bat• sh
Test-first via jasmine
Units• Views• Events
Acceptance• Full loading
Test html
describe("Html views", function() { var data = { type: "small", ordered: "1 min ago" } it("has a line item", function() { _requires(["text!coffee/views/_item.html", 'jsrender'], function( text ){ $( "#test" ).html( $.templates( text ).render( data ) ); expect($("li", "#test" ).size()).toEqual(1); }); }
<li> <i>{{:type}}</i> (<abbr class="date”{{:ordered}}</abbr>)</li>
Code template
Test
Test Events
it("should register add order handler”, function() {
_requires([“coffee/loader”])); spyOn(_, 'order').andCallThrough() _.orderHandler( $('#test'), function(){ return {} ) $('#test').click()
expect(_.order).toHaveBeenCalled() });
Test that it actually works!
it("should be able to add new order", function() { _requires(["coffee/main"])); val = $('li', ’#orders').size(); $('button.order', ’#orders').click() expect($('li', ’#orders').size()).toEqual(val++); });
Build and get runtime errors
$ rake buildnode lib/r.js -o app.build.js
Tracing dependencies for: coffee/main
Error: Error evaluating module "undefined" at location "/Users/todd/Documents/src/coffee/build/scripts/coffee/main.js":Unexpected token: punc (,) (line: 4, col: 16, pos: 152)
Throw away the debugger
no break points or step through
… no really, I’m serious
We still need to package code ready for serving. Why wait until run?
Optimisation
Script-tag vomit
In development, it’s good Production, it’s not
Pretty clean
Create bundles & linted
Script and CSS compressed
Ready for deployment
• File paths between development and production
• CSS url paths• Text (html, json) loading without
server• Creating a localhost server for text• Hassle for smaller projects
Gotchas!
• Different AMD implementations• Plugins (page load, internationalisation)• Lazy and dynamic loading• Wrapping non-AMD code• Coffeescript• Data-main loading• Build runners and CI integration
Follow-up areas
Finally, what does this mean?
• We can unit test web-based application at the GUI layer
• We CAN reduce the number of expensive, system tests (eg selenium)
• Modularity and testing are likely to allow smaller pieces of work and potentially increased flow
• All this (coupled with REST) means interesting times!
Useful refs (and referred to materials)
• http://www.angrycoding.com/2011/09/managing-dependencies-with-requirejs.html
• http://patrickmcelhaney.com/AMD-Talk/#microjs• http://projects.haykranen.nl/amsterdamjs• http://www.slideshare.net/JulienZee/parisjs-10-requirejs-9111799• http://www.slideshare.net/tim_doherty/requirejs-8858167 • http://www.slideshare.net/timoxley/testable-client-
sidemvcappsinjavascript• http://www.slideshare.net/sioked/requirejs
Good luck
Todd Brackley
goneopen.comtwitter: toddbNZ
code: github.com/toddb/javascript-amd