Browserify All The Things

Browserify All The Things

This talk is about how to use browserify to develop front-end modular code using Common.JS, and how those modules should be documented, designed, and released using an automated build system. In order to explain these concepts I'll walk you through a few of my own open-source creations, highlighting interesting points as we go along.

550d0153dbeee2fcaede98f906e55d02?s=128

Nicolás Bevacqua

August 28, 2014
Tweet

Transcript

  1. Browserify All The Things

  2. Hello! I’m Nico nzgb

  3. Hello! I’m Nico nzgb bevacqua

  4. Hello! I’m Nico nzgb bevacqua ponyfoo.com

  5. bevacqua.io/bf Quality

  6. bevacqua.io/bf Quality Processes

  7. bevacqua.io/bf Quality Processes Modularity

  8. bevacqua.io/bf Quality Processes Modularity 100~ samples!

  9. Modules! are good for you

  10. Getting Closure ~function(){}()

  11. ~function () { var foo = 1; alert(foo); }();

  12. Modularity? To some extent.

  13. ~function (window) { function sum (a, b) { return a

    + b; } window.math = {sum: sum}; }(this);
  14. ~function (math) { alert(math.sum(2, 5)); }(math); !

  15. Dependencies? Hand-crafted dependency graph!

  16. <script src='foo.js'></script> <script src='bar.js'></script> <script src='baz.js'></script> <script src='sin.js'></script> <script src='cos.js'></script>

    <script src='tan.js'></script> <script src='circle.js'></script> <script src='event.js'></script>
  17. (){}() It’s making a face!

  18. Closures - Abuse global variables

  19. Closures - Abuse global variables - Fine for tiny projects

  20. Closures - Abuse global variables - Fine for tiny projects

    - Modularity is up to you
  21. Closures - Abuse global variables - Fine for tiny projects

    - Modularity is up to you - Manual dependency management
  22. AMD Modules

  23. None
  24. Dependencies? Yay!

  25. define([], function () { function sum (a, b) { return

    a + b; } return { sum: sum }; }); math.js
  26. require(['math'], function (math) { alert(math.sum(2, 5)); }); ! ! main.js

  27. Modularity… But at what cost?

  28. AMD Modules - Resolves dependency graph

  29. AMD Modules - Resolves dependency graph - Lacks a straightforward

    API
  30. AMD Modules - Resolves dependency graph - Lacks a straightforward

    API - Drastic changes after you build
  31. AMD Modules - Resolves dependency graph - Lacks a straightforward

    API - Drastic changes after you build - So. Much. Clutter.
  32. ES6 Modules Great! (on paper)

  33. function sum (a, b) { return a + b; }

    ! export default {sum: sum}; math.js
  34. import math from 'math'; ! alert(math.sum(2,5)); main.js

  35. ES6 Modules - The Way Forward ™

  36. ES6 Modules - The Way Forward ™ - To use

    today, add a transpilation step
  37. ES6 Modules - The Way Forward ™ - To use

    today, add a transpilation step - Hard to interact with CJS modules
  38. ES6 Modules - The Way Forward ™ - To use

    today, add a transpilation step - Hard to interact with CJS modules - Not generally available / popular yet
  39. Stay positive!

  40. CommonJS Modules

  41. CommonJS Modules

  42. CommonJS Modules

  43. CommonJS Modules

  44. function sum (a, b) { return a + b; }

    module.exports = {sum:sum}; math.js
  45. var math = require('./math'); ! alert(math.sum(2,5)); ! main.js

  46. CommonJS Modules API similar to ES6

  47. CommonJS Modules API similar to ES6 Takes advantage of npm

  48. CommonJS Modules API similar to ES6 Takes advantage of npm

    Browserify for the client
  49. npm install -g browserify

  50. browserify main.js

  51. None
  52. browserify main.js -o all.js <script src='all.js'></script>

  53. None
  54. Quality great for everyone!

  55. insert-rule Everything begins with a simple idea

  56. Consistent Results Flexible API

  57. insertRule(selector, styles) styles: String, Object Adds a CSS rule

  58. var camel = /([a-z])([A-Z])/g; var hyphens = '$1-$2'; function parseStyles

    (styles) { if (typeof styles === 'string') { return styles; } return Object.keys(styles).map(function (key) { var prop = key.replace(camel, hyphens).toLowerCase(); return prop + ':' + styles[key]; }).join(';'); }
  59. var camel = /([a-z])([A-Z])/g; var hyphens = '$1-$2'; function parseStyles

    (styles) { if (typeof styles === 'string') { return styles; } return Object.keys(styles).map(function (key) { var prop = key.replace(camel, hyphens).toLowerCase(); return prop + ':' + styles[key]; }).join(';'); }
  60. var camel = /([a-z])([A-Z])/g; var hyphens = '$1-$2'; function parseStyles

    (styles) { if (typeof styles === 'string') { return styles; } return Object.keys(styles).map(function (key) { var prop = key.replace(camel, hyphens).toLowerCase(); return prop + ':' + styles[key]; }).join(';'); }
  61. var camel = /([a-z])([A-Z])/g; var hyphens = '$1-$2'; function parseStyles

    (styles) { if (typeof styles === 'string') { return styles; } return Object.keys(styles).map(function (key) { var prop = key.replace(camel, hyphens).toLowerCase(); return prop + ':' + styles[key]; }).join(';'); }
  62. var camel = /([a-z])([A-Z])/g; var hyphens = '$1-$2'; function parseStyles

    (styles) { if (typeof styles === 'string') { return styles; } return Object.keys(styles).map(function (key) { var prop = key.replace(camel, hyphens).toLowerCase(); return prop + ':' + styles[key]; }).join(';'); }
  63. var camel = /([a-z])([A-Z])/g; var hyphens = '$1-$2'; function parseStyles

    (styles) { if (typeof styles === 'string') { return styles; } return Object.keys(styles).map(function (key) { var prop = key.replace(camel, hyphens).toLowerCase(); return prop + ':' + styles[key]; }).join(';'); }
  64. module.exports = function (selector, styles) { var css = parseStyles(styles);

    var sheets = document.styleSheets; var s = sheets[sheets.length - 1]; var key = s.cssRules ? s.cssRules: s.rules; if (s.insertRule) { sheet.insertRule(selector + '{' + css + '}', key.length); } else if (sheet.addRule) { sheet.addRule(selector, css, key.length); } };
  65. module.exports = function (selector, styles) { var css = parseStyles(styles);

    var sheets = document.styleSheets; var s = sheets[sheets.length - 1]; var key = s.cssRules ? s.cssRules: s.rules; if (s.insertRule) { sheet.insertRule(selector + '{' + css + '}', key.length); } else if (sheet.addRule) { sheet.addRule(selector, css, key.length); } };
  66. module.exports = function (selector, styles) { var css = parseStyles(styles);

    var sheets = document.styleSheets; var s = sheets[sheets.length - 1]; var key = s.cssRules ? s.cssRules: s.rules; if (s.insertRule) { sheet.insertRule(selector + '{' + css + '}', key.length); } else if (sheet.addRule) { sheet.addRule(selector, css, key.length); } };
  67. module.exports = function (selector, styles) { var css = parseStyles(styles);

    var sheets = document.styleSheets; var s = sheets[sheets.length - 1]; var key = s.cssRules ? s.cssRules: s.rules; if (s.insertRule) { sheet.insertRule(selector + '{' + css + '}', key.length); } else if (sheet.addRule) { sheet.addRule(selector, css, key.length); } };
  68. module.exports = function (selector, styles) { var css = parseStyles(styles);

    var sheets = document.styleSheets; var s = sheets[sheets.length - 1]; var key = s.cssRules ? s.cssRules: s.rules; if (s.insertRule) { sheet.insertRule(selector + '{' + css + '}', key.length); } else if (sheet.addRule) { sheet.addRule(selector, css, key.length); } };
  69. Extensive Documentation

  70. None
  71. None
  72. Automate All The Builds!

  73. Build distribution

  74. Build distribution Bump package

  75. Build distribution Bump package Tag in git

  76. Build distribution Bump package Tag in git Publish to npm

  77. Build distribution Bump package Tag in git Publish to npm

  78. npm install gulp -g

  79. npm install gulp -D

  80. ! gulp.task('build', ...)

  81. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  82. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  83. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  84. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  85. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  86. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  87. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  88. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  89. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  90. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  91. function build () { var pkg = require('./package.json'); return browserify('./src/lib.js')

    .bundle({ debug: true, standalone: 'lib' }) .pipe(source('lib.js')) .pipe(streamify(header(ext, { pkg: pkg }))) .pipe(gulp.dest('./dist')) .pipe(streamify(rename('lib.min.js'))) .pipe(streamify(uglify())) .pipe(streamify(header(min, { pkg: pkg }))) .pipe(streamify(size())) .pipe(gulp.dest('./dist')); }
  92. gulp.task('build', build);

  93. gulp.task('watch', ...)

  94. gulp.task('watch', function() { var pattern = { glob: 'src/**/*.js' };

    watch(pattern, function () { gulp.start('build'); }); });
  95. gulp.task('watch', function() { var pattern = { glob: 'src/**/*.js' };

    watch(pattern, function () { gulp.start('build'); }); });
  96. gulp.task('watch', function() { var pattern = { glob: 'src/**/*.js' };

    watch(pattern, function () { gulp.start('build'); }); });
  97. gulp.task('watch', function() { var pattern = { glob: 'src/**/*.js' };

    watch(pattern, function () { gulp.start('build'); }); });
  98. ! gulp.task('bump', ...)

  99. gulp.task('bump-only', function () { // major.minor.patch var bumpType = process.env.BUMP

    || 'patch'; ! return gulp.src(['./package.json', './bower.json']) .pipe(bump({ type: bumpType })) .pipe(gulp.dest('./')); }); ! gulp.task('bump', ['bump-only'], build);
  100. gulp.task('bump-only', function () { // major.minor.patch var bumpType = process.env.BUMP

    || 'patch'; ! return gulp.src(['./package.json', './bower.json']) .pipe(bump({ type: bumpType })) .pipe(gulp.dest('./')); }); ! gulp.task('bump', ['bump-only'], build);
  101. gulp.task('bump-only', function () { // major.minor.patch var bumpType = process.env.BUMP

    || 'patch'; ! return gulp.src(['./package.json', './bower.json']) .pipe(bump({ type: bumpType })) .pipe(gulp.dest('./')); }); ! gulp.task('bump', ['bump-only'], build);
  102. gulp.task('bump-only', function () { // major.minor.patch var bumpType = process.env.BUMP

    || 'patch'; ! return gulp.src(['./package.json', './bower.json']) .pipe(bump({ type: bumpType })) .pipe(gulp.dest('./')); }); ! gulp.task('bump', ['bump-only'], build);
  103. gulp.task('bump-only', function () { // major.minor.patch var bumpType = process.env.BUMP

    || 'patch'; ! return gulp.src(['./package.json', './bower.json']) .pipe(bump({ type: bumpType })) .pipe(gulp.dest('./')); }); ! gulp.task('bump', ['bump-only'], build);
  104. gulp.task('bump-only', function () { // major.minor.patch var bumpType = process.env.BUMP

    || 'patch'; ! return gulp.src(['./package.json', './bower.json']) .pipe(bump({ type: bumpType })) .pipe(gulp.dest('./')); }); ! gulp.task('bump', ['bump-only'], build);
  105. ! gulp.task('tag', ...)

  106. gulp.task('tag', ['bump'], function () { var pkg = require('./package.json'); var

    v = 'v' + pkg.version; var message = 'Release ' + v; ! return gulp.src('./') .pipe(git.commit(message)) .pipe(git.tag(v, message)) .pipe(git.push('origin', 'master', '--tags')) .pipe(gulp.dest('./')); });
  107. gulp.task('tag', ['bump'], function () { var pkg = require('./package.json'); var

    v = 'v' + pkg.version; var message = 'Release ' + v; ! return gulp.src('./') .pipe(git.commit(message)) .pipe(git.tag(v, message)) .pipe(git.push('origin', 'master', '--tags')) .pipe(gulp.dest('./')); });
  108. gulp.task('tag', ['bump'], function () { var pkg = require('./package.json'); var

    v = 'v' + pkg.version; var message = 'Release ' + v; ! return gulp.src('./') .pipe(git.commit(message)) .pipe(git.tag(v, message)) .pipe(git.push('origin', 'master', '--tags')) .pipe(gulp.dest('./')); });
  109. gulp.task('tag', ['bump'], function () { var pkg = require('./package.json'); var

    v = 'v' + pkg.version; var message = 'Release ' + v; ! return gulp.src('./') .pipe(git.commit(message)) .pipe(git.tag(v, message)) .pipe(git.push('origin', 'master', '--tags')) .pipe(gulp.dest('./')); });
  110. gulp.task('tag', ['bump'], function () { var pkg = require('./package.json'); var

    v = 'v' + pkg.version; var message = 'Release ' + v; ! return gulp.src('./') .pipe(git.commit(message)) .pipe(git.tag(v, message)) .pipe(git.push('origin', 'master', '--tags')) .pipe(gulp.dest('./')); });
  111. gulp.task('tag', ['bump'], function () { var pkg = require('./package.json'); var

    v = 'v' + pkg.version; var message = 'Release ' + v; ! return gulp.src('./') .pipe(git.commit(message)) .pipe(git.tag(v, message)) .pipe(git.push('origin', 'master', '--tags')) .pipe(gulp.dest('./')); });
  112. gulp.task('tag', ['bump'], function () { var pkg = require('./package.json'); var

    v = 'v' + pkg.version; var message = 'Release ' + v; ! return gulp.src('./') .pipe(git.commit(message)) .pipe(git.tag(v, message)) .pipe(git.push('origin', 'master', '--tags')) .pipe(gulp.dest('./')); });
  113. ! gulp.task('npm', ...)

  114. var cp = require('child_process'); var spawn = cp.spawn; ! gulp.task('npm',['tag'],function(done){

    spawn('npm', ['publish'], { stdio: 'inherit' }).on('close', done); });
  115. var cp = require('child_process'); var spawn = cp.spawn; ! gulp.task('npm',['tag'],function(done){

    spawn('npm', ['publish'], { stdio: 'inherit' }).on('close', done); });
  116. var cp = require('child_process'); var spawn = cp.spawn; ! gulp.task('npm',['tag'],function(done){

    spawn('npm', ['publish'], { stdio: 'inherit' }).on('close', done); });
  117. var cp = require('child_process'); var spawn = cp.spawn; ! gulp.task('npm',['tag'],function(done){

    spawn('npm', ['publish'], { stdio: 'inherit' }).on('close', done); });
  118. ! gulp.task('release', ['npm']); ! gulp watch gulp release

  119. Use Cases also good for you

  120. Poser Extend natives safely

  121. var doc = global.document; var tag = 'iframe'; function poser

    (type) { var frame = doc.createElement(tag); frame.style.display = 'none'; doc.body.appendChild(frame); return frame.contentWindow[type]; } module.exports = poser;
  122. var doc = global.document; var tag = 'iframe'; function poser

    (type) { var frame = doc.createElement(tag); frame.style.display = 'none'; doc.body.appendChild(frame); return frame.contentWindow[type]; } module.exports = poser;
  123. var doc = global.document; var tag = 'iframe'; function poser

    (type) { var frame = doc.createElement(tag); frame.style.display = 'none'; doc.body.appendChild(frame); return frame.contentWindow[type]; } module.exports = poser;
  124. var doc = global.document; var tag = 'iframe'; function poser

    (type) { var frame = doc.createElement(tag); frame.style.display = 'none'; doc.body.appendChild(frame); return frame.contentWindow[type]; } module.exports = poser;
  125. var doc = global.document; var tag = 'iframe'; function poser

    (type) { var frame = doc.createElement(tag); frame.style.display = 'none'; doc.body.appendChild(frame); return frame.contentWindow[type]; } module.exports = poser;
  126. Poser Extend natives safely

  127. var vm = require('vm'); function poser (type) { var box

    = {}; var code = 'stolen=' + type; vm.runInNewContext(code, box, 'vm'); return box.stolen; } module.exports = poser;
  128. var vm = require('vm'); function poser (type) { var box

    = {}; var code = 'stolen=' + type; vm.runInNewContext(code, box, 'vm'); return box.stolen; } module.exports = poser;
  129. var vm = require('vm'); function poser (type) { var box

    = {}; var code = 'stolen=' + type; vm.runInNewContext(code, box, 'vm'); return box.stolen; } module.exports = poser;
  130. var vm = require('vm'); function poser (type) { var box

    = {}; var code = 'stolen=' + type; vm.runInNewContext(code, box, 'vm'); return box.stolen; } module.exports = poser;
  131. var vm = require('vm'); function poser (type) { var box

    = {}; var code = 'stolen=' + type; vm.runInNewContext(code, box, 'vm'); return box.stolen; } module.exports = poser;
  132. { "main": "src/node.js", "browser": "./src/browser.js" } "./src/node": "./src/browser" } Semantics

    in package.json
  133. { "main": "src/node.js", "browser": "./src/browser.js" } "./src/node": "./src/browser" } Semantics

    in package.json
  134. { "main": "src/node.js", "browser": { "./src/node": "./src/browser" } } Semantics

    in package.json
  135. Taunus Shared Rendering

  136. View Templates: function (model) Shared Functionality View Routing: JSON Module

  137. Server Side GET /author/compose Accept: HTML? Full Layout + Partial

    Accept: JSON? Partial View Model
  138. Client Side On first load GET request established by the

    browser Server-side renders layout and view Client-side controller gets executed
  139. Client Side On navigation - after first load AJAX request

    for partial model Client-side renders view Client-side controller gets executed
  140. Wrapping Up!

  141. Modularity in JS

  142. Modularity in JS API Design Quality

  143. Modularity in JS API Design Quality Documentation

  144. Modularity in JS API Design Quality Documentation Build Automation

  145. Thanks! nzgb bevacqua

  146. Thanks! nzgb bevacqua All The Questions?