Whoops! There was a problem previewing this document. Retrying... Download ... below to open or edit this item. Manning
A Build First Approach
Nicolas Bevacqua FOREWORD BY Addy Osmani
MANNING www.it-ebooks.info
JavaScript Application Design
www.it-ebooks.info
www.it-ebooks.info
JavaScript Application Design A Build First Approach NICOLAS BEVACQUA
MANNING SHELTER ISLAND
www.it-ebooks.info
iv For online information and ordering of this and other Manning books, please visit www.manning.com. The publisher offers discounts on this book when ordered in quantity. For more information, please contact Special Sales Department Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964 Email:
[email protected]
©2015 by Manning Publications Co. All rights reserved.
No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher.
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps.
Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid-free paper, and we exert our best efforts to that end. Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without elemental chlorine.
Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964
Development editor: Technical development editor: Copyeditor: Proofreader: Technical proofreaders:
Susan Conant Douglas Duncan Katie Petito Alyson Brener Deepak Vohra Valentin Crettaz Typesetter: Marija Tudor Cover designer: Marija Tudor
ISBN: 9781617291951 Printed in the United States of America 1 2 3 4 5 6 7 8 9 10 – EBM – 20 19 18 17 16 15
www.it-ebooks.info
To Marian, for withstanding the birth of this book, your unconditional love, and your endless patience. I love you! Will you marry me?
www.it-ebooks.info
vi
www.it-ebooks.info
brief contents PART 1
PART 2
BUILD PROCESSES ........................................................ 1 1
■
Introduction to Build First 3
2
■
Composing build tasks and flows
3
■
Mastering environments and the development workflow
4
■
Release, deployment, and monitoring
23 50
71
MANAGING COMPLEXITY ........................................... 97 Embracing modularity and dependency management 99
5
■
6
■
7
■
Leveraging the Model-View-Controller
8
■
Testing JavaScript components
9
■
REST API design and layered service architectures
Understanding asynchronous flow control methods in JavaScript 131
vii
www.it-ebooks.info
166
211 251
viii
BRIEF CONTENTS
www.it-ebooks.info
contents foreword xv preface xvii acknowledgments xix about this book xxi about the author xxv about the cover illustration
PART 1
1
xxvi
BUILD PROCESSES ............................................ 1 Introduction to Build First 3 1.1
When things go wrong 4 How to lose $172,222 a second for 45 minutes Build First 5 Rites of initiation 6
5
■
1.2
Planning ahead with Build First Core principles in Build First
1.3 1.4 1.5
7
Build processes 9 Handling application complexity and design Diving into Build First 15 Keeping code quality in check line 19
1.6
7
Summary
22 ix
www.it-ebooks.info
16
■
11
Lint in the command
x
CONTENTS
2
Composing build tasks and flows 2.1
Introducing Grunt
23
24
Installing Grunt 26 Setting up your first Grunt task Using Grunt to manage the build process 29
28
■
2.2
Preprocessing and static asset optimization
31
Discussing preprocessing 31 Doing LESS 34 Bundling static assets 37 Static asset minification Implementing image sprites 41 ■
■
2.3
Setting up code integrity
43
Cleaning up your working directory 43 Automating unit testing 45
2.4 2.5 2.6
3
38
■
Lint, lint, lint!
44
Writing your first build task 46 Case study: , ; rel="last"
Note that the endpoints must be absolute so the consumer can parse the Link header and query them directly. The rel attribute describes the relationship between the requested page and the linked page. If you now request the second page, /api/products/?p=2, you should get a similar Link header, this time letting you know that “previous” and “first” related pages are also available: Link: ; rel="first", ; rel="prev", ; rel="next", ; rel="last"
Cases exist where >%s', name), '' ].join('');
With the array builder style, you can also push parts of the snippet and then join everything together at the end. This is what string templating engines such as Jade12 prefer to do.
D.2.1
Variable declaration Always declare variables in a consistent manner, and at the top of their scope. Keeping variable declarations to one-per-line is encouraged. Comma-first, a single var statement, multiple var statements, it’s all fine, but be consistent across the project. Ensure that everyone on your team follows the style guide, for consistency. INCONSISTENT DECLARATION var foo = 1, bar = 2; var baz; var pony; var a , b;
11 12
The documentation for Node’s util.format can be found at http://bevacqua.io/bf/util.format. Learn more about templating in Jade by visiting their GitHub repository at http://bevacqua.io/bf/jade.
www.it-ebooks.info
Conditionals
297
or var foo = 1; if (foo > 1) { var bar = 2; }
Note that the following example is okay not only because of its style, but also because the statements are consistent with each other. CONSISTENT DECLARATIONS var foo = 1; var bar = 2; var baz; var pony; var a; var b; var foo = 1; var bar; if (foo > 1) { bar = 2; }
Variable declarations not immediately assigned a value can share the same line of code. ACCEPTABLE DECLARATION var a = 'a'; var b = 2; var i, j;
D.3
Conditionals Brackets are enforced. This, together with a reasonable spacing strategy, will help you avoid mistakes such as Apple’s SSL/TLS bug.13 BAD CONDITIONALS if (err) throw err; GOOD CONDITIONALS if (err) { throw err; }
Avoid using == and != operators; always favor === and !==. These operators are called the “strict equality operators,” whereas their counterparts will attempt to cast the operands14 into the same value type. If possible, try to keep even single-statement conditionals in a multiline format. BAD COERCING EQUALITY function isEmptyString (text) { return text == ''; 13 14
A detailed report on Apple’s “GOTO Fail” bug can be found at http://bevacqua.io/bf/gotofail. Equality operators have a dedicated page on MDN at http://bevacqua.io/bf/equality.
www.it-ebooks.info
298
APPENDIX D
JavaScript code quality guide
} isEmptyString(0); // sum(1, 3); } function sum (x, y) return x + y; } Or function sum (x, y) return x + y; } if (Math.random() > sum(1, 3); }
0.5) {
{
{
0.5) {
If you need a “no-op” method, you can use either Function.prototype, or function noop () {}. Ideally a single reference to noop is used throughout the application. Whenever you have to manipulate the arguments object, or other array-likes, cast them to an array. BAD ARRAY-LIKE LOOP var divs = document.querySelectorAll('div'); for (i = 0; i < divs.length; i++) { console.log(divs[i].innerHTML); } GOOD ARRAY-LIKE LOOP var divs = document.querySelectorAll('div'); [].slice.call(divs).forEach(function (div) { console.log(div.innerHTML); }); 19 20
John Resig explains how to partially apply functions on his blog at http://bevacqua.io/bf/partial-application. Variable hoisting is explained in the code samples found at http://bevacqua.io/bf/hoisting.
www.it-ebooks.info
300
APPENDIX D
JavaScript code quality guide
However, be aware that there’s a substantial performance hit21 in V8 environments when using this approach on arguments. If performance is a major concern, avoid casting arguments with slice and use a for loop instead. BAD ARGUMENTS ACCESSOR var args = [].slice.call(arguments); BETTER ARGUMENTS ACCESSOR var i; var args = new Array(arguments.length); for (i = 0; i < args.length; i++) { args[i] = arguments[i]; }
Never declare functions inside of loops. BAD INLINE FUNCTIONS var values = [1, 2, 3]; var i; for (i = 0; i < values.length; i++) { setTimeout(function () { console.log(values[i]); }, 1000 * i); }
or var values = [1, 2, 3]; var i; for (i = 0; i < values.length; i++) { setTimeout(function (i) { return function () { console.log(values[i]); }; }(i), 1000 * i); } BETTER EXTRACT THE FUNCTION var values = [1, 2, 3]; var i; for (i = 0; i < values.length; i++) { wait(i); } function wait (i) { setTimeout(function () { console.log(values[i]); }, 1000 * i); }
Or even better, use .forEach, which doesn’t have the same caveats as declaring functions in for loops.
21
See a great article on optimizing manipulation of function arguments at http://bevacqua.io/bf/arguments.
www.it-ebooks.info
Conditionals
301
EVEN BETTER, FUNCTIONAL ARRAYS WITH FOREACH [1, 2, 3].forEach(function (value, i) { setTimeout(function () { console.log(value); }, 1000 * i); }); NAMED FUNCTION VS ANONYMOUS
Whenever a method is nontrivial, make the effort to use a named function expression rather than an anonymous function. This makes it easier to pinpoint the root cause of an exception when analyzing stack traces. BAD, ANONYMOUS FUNCTIONS function once (fn) { var ran = false; return function () { if (ran) { return }; ran = true; fn.apply(this, arguments); }; } GOOD, NAMED FUNCTION function once (fn) { var ran = false; return function run () { if (ran) { return }; ran = true; fn.apply(this, arguments); }; }
Avoid keeping indentation levels from raising more than necessary by using guard clauses instead of flowing if statements. BAD if (car) { if (black) { if (turbine) { return 'batman!'; } } }
or if (condition) { // 10+ lines of code } GOOD if (!car) { return; } if (!black) { return;
www.it-ebooks.info
302
APPENDIX D
JavaScript code quality guide
} if (!turbine) { return; } return 'batman!';
or if (!condition) { return; } // 10+ lines of code
D.3.3
Prototypes Hacking the prototype of native types should be avoided at all costs; use methods instead. If you must extend the functionality in a native type, try using poser22 instead. Poser provides out-of-context native type references that you can safely build upon and extend. BAD String.prototype.half = function () { return this.substr(0, this.length / 2); }; GOOD function half (text) { return text.substr(0, text.length / 2); }
Avoid prototypical inheritance models unless you have a good performance reason to justify yourself: ■ ■ ■ ■
D.3.4
They are way more verbose than using plain objects. They cause headaches when creating new objects. They need a closure to hide valuable private state of instances. Just use plain objects instead.
Object literals Instantiate using the Egyptian notation {}. Use factories instead of constructors. Here’s a proposed pattern for you to implement objects in general: function util (options) { // private methods and state go here var foo; function add () { return foo++; } function reset () { // note that this method isn't publicly exposed foo = options.start || 0;
22
Poser provides out-of-context native type references that you can safely build upon and extend. For more information, see http://bevacqua.io/bf/poser.
www.it-ebooks.info
Regular expressions
303
} reset(); return { // public interface methods go here uuid: add }; }
D.3.5
Array literals Instantiate using the square bracketed notation []. If you have to declare a fixeddimension array for performance reasons, then it’s fine to use the new Array(length) notation instead. Arrays in JavaScript have a rich API that you should take advantage of. You can start with array manipulation basics23 and then move on to more advanced use cases. For example, you could use the .forEach method to iterate over all of the items in a collection. The following list shows basic operations you can perform on arrays: ■
■
■
Use .push to insert items at the end of a collection or .shift to insert them at the beginning. Use .pop to get the last item and remove it from the collection at the same time or use .unshift to do the same for the first item. Master .splice to remove items by index, or to insert items at a specific index, or to do both at the same time!
Also learn about and use the functional collection manipulation methods! These can save you a ton of time that you’d otherwise spend doing the operations by hand. Here are a few examples of things you can do: ■ ■ ■ ■ ■ ■
Use .filter to discard uninteresting values. Use .map to transpolate array values into something else. Use .reduce to iterate over an array and produce a single result. Use .some and .every to assert whether all array items meet a condition. Use .sort to arrange the elements in a collection. Use .reverse to invert the order in the array.
The Mozilla Developer Network (MDN) has thoroughly documented all of these methods and more at https://developer.mozilla.org/.
D.4
Regular expressions Keep regular expressions in variables; don’t use them inline. This will vastly improve readability.
23
An introductory article on JavaScript arrays is available on my blog at http://bevacqua.io/bf/arrays.
www.it-ebooks.info
304
APPENDIX D
JavaScript code quality guide
BAD REGULAR EXPRESSIONS if (/\d+/.test(text)) { console.log('so many numbers!'); } GOOD REGULAR EXPRESSIONS var numeric = /\d+/; if (numeric.test(text)) { console.log('so many numbers!'); }
Also, learn to write regular expressions24 and what they do. Then you can also visualize them online.25
D.4.1
Debugging statements Preferably put your console statements into a service that can easily be disabled in production. Alternatively, don’t ship any console.log printing statements to production distributions.
D.4.2
Comments Comments aren’t meant to explain what the code does. Good code is supposed to be self-explanatory. If you’re thinking of writing a comment to explain what a piece of code does, chances are you need to change the code itself. The exception to that rule is explaining what a regular expression does. Good comments are supposed to explain why code does something that may not seem to have a clear-cut purpose. BAD COMMENTS // create the centered container var p = $('
'); p.center(div); p.text('foo'); GOOD COMMENTS var container = $('
'); var contents = 'foo'; container.center(parent); container.text(contents); megaphone.on('data', function (value) { container.text(value); // the megaphone periodically emits updates for container });
or var numeric = /\d+/; // one or more digits somewhere in the string if (numeric.test(text)) { console.log('so many numbers!'); }
24 25
There’s an introductory article on regular expressions on my blog at http://bevacqua.io/bf/regex. Regexper lets you visualize how any regular expression works at http://bevacqua.io/bf/regexper.
www.it-ebooks.info
Regular expressions
305
Commenting out entire blocks of code should be avoided entirely; that’s why you have version control systems in place!
D.4.3
Variable naming Variables must have meaningful names so that you don’t have to resort to commenting what a piece of functionality does. Instead, try to be expressive while succinct, and use meaningful variable names: BAD NAMING function a (x, y, z) { return z * y / x; } a(4, 2, 6); //