The "Rails of JavaScript" won't be a framework

The "Rails of JavaScript" won't be a framework

As presented at RailsConf 2014. Video to follow.

Say hello@testdouble.com!

E6c6e133e74c3b83f04d2861deaa1c20?s=128

Justin Searls

April 25, 2014
Tweet

Transcript

  1. the of won't be a framework

  2. the of will be more than a framework

  3. My name is Justin Searls Please tweet me @searls &

    Say hello@testdouble.com
  4. 1

  5. 2 1

  6. 2 1 3

  7. You own a bakery.

  8. A customer walks in.

  9. None
  10. None
  11. None
  12. Honest mistake.

  13. You own a software studio.

  14. A customer walks in.

  15. None
  16. None
  17. None
  18. None
  19. Honest mistake.

  20. Rails is too convenient.

  21. server-side tools

  22. client-side tools

  23. Just kidding.

  24. client-side tools

  25. None
  26. ˑ Provocation ˑ

  27. Non-rubyists write better JavaScript ˑ Provocation ˑ

  28. None
  29. Before Rails was easy

  30. Before Rails was easy JavaScript wasn't hard

  31. Is JavaScript a terrible language?

  32. YES DEFINITELY

  33. Is that why writing JavaScript is terrible?

  34. ASK AGAIN LATER

  35. None
  36. The community changed the world.

  37. Great new tools for the web, over time:

  38. 2005

  39. 2005

  40. 2006

  41. 2007

  42. 2008

  43. 2009

  44. 2010

  45. 2011

  46. 2012

  47. 2013

  48. 2014

  49. 2014

  50. Where are the best tools?

  51. Ruby's tool ecosystem is mature, crowded Where are the best

    tools?
  52. Ruby's tool ecosystem is mature, crowded Node's ecosystem is immature,

    innovative Where are the best tools?
  53. Ruby's tool ecosystem is mature, crowded Node's ecosystem is immature,

    innovative Tool authors aren't immune to trends Where are the best tools?
  54. Ruby's tool ecosystem is mature, crowded Node's ecosystem is immature,

    innovative Tool authors aren't immune to trends Tools address the problems of their day Where are the best tools?
  55. Ruby's tool ecosystem is mature, crowded Node's ecosystem is immature,

    innovative Tool authors aren't immune to trends Tools address the problems of their day Where are the best tools?
  56. Ruby's tool ecosystem is mature, crowded Node's ecosystem is immature,

    innovative Tool authors aren't immune to trends Tools address the problems of their day Web tools in Node better solve today's problems Where are the best tools?
  57. Maddening for those who insist on using only Rails

  58. Hey, speaking of Rails

  59. None
  60. Rails won the war.

  61. Rails won the war? which

  62. Good frameworks are extractions, not inventions. - DHH

  63. Basecamp

  64. Basecamp

  65. None
  66. Routing

  67. Routing Models

  68. Routing Models Persistence

  69. Routing Models Persistence Sessions

  70. Routing Models Persistence Sessions Mailers

  71. Routing Models Persistence Sessions Mailers JavaScript Alternatives

  72. AJAX ERB Tags Routing Models Sessions Mailers Persistence

  73. RJS Routing Models Sessions Mailers Persistence

  74. AJAX ERB Tags Unobtrusive Routing Models Sessions Mailers Persistence

  75. Turbolinks Routing Models Sessions Mailers Persistence

  76. Basecamp

  77. Basecamp

  78. Basecamp

  79. There are Rails apps and there are apps that use

    Rails. -Sandi Metz
  80. HTML UI

  81. HTML UI ! <a href="/items/1">Item 1</a>

  82. HTML UI ! <a href="/items/1">Item 1</a> ! <form action="/items" method="post">

    <input type="submit" value="Submit"/> </form>
  83. JavaScript UI

  84. JavaScript UI ! new ItemView('#items').render()

  85. JavaScript UI ! new ItemView('#items').render() ! $('button').click(function(e){ $('.alert').text('Thanks!').show() });

  86. HTML UI ! app ├── controllers │ └── items_controller.rb ├──

    models │ └── item.rb └── views └── items └── index.html.erb
  87. HTML UI + JS ! app ├── controllers │ └──

    items_controller.rb ├── models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── application.js
  88. HTML UI + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  89. HTML UI + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  90. HTML UI + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  91. HTML UI + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  92. HTML UI + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  93. JSON API + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb │ │ │ └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  94. JSON API + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb │ │ │ └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  95. JSON API + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb │ │ │ └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs vestigial appendage
  96. what's wrong with that vestigial appendage?

  97. app/

  98. app/

  99. app/

  100. app/

  101. ap

  102. ap

  103. ap p/

  104. app1/ ! app2/

  105. app1/ ! app2/

  106. app1/ ! app2/

  107. app1/ ! app2/

  108. app1

  109. app1

  110. app1 app2

  111. late extraction costs more than early abstraction

  112. None
  113. None
  114. None
  115. None
  116. None
  117. None
  118. None
  119. late extraction costs more than early abstraction

  120. JSON API + JS UI ! app ├── controllers │

    └── items_controller.rb ├── models │ └── item.rb │ │ │ └── assets └── javascripts └── more_app ├── controllers │ └── items_controller.js ├── models │ └── item.js └── views └── items └── index.jst.ejs
  121. If you see this:

  122. If you see this: ! <script> </script>

  123. If you see this: ! <script> user = <%= user.to_json

    %> </script>
  124. If you see this: ! <script> user = <%= user.to_json

    %> time = <%= Time.now.to_i %> </script>
  125. If you see this: ! <script> user = <%= user.to_json

    %> time = <%= Time.now.to_i %> items = <%= items.to_json %> </script>
  126. If you see this: ! <script> user = <%= user.to_json

    %> time = <%= Time.now.to_i %> items = <%= items.to_json %> token = "<%= @token %>" </script>
  127. ! <script> user = <%= user.to_json %> time = <%=

    Time.now.to_i %> items = <%= items.to_json %> token = "<%= @token %>" </script> Your yarn is tangled
  128. ! <script> user = <%= user.to_json %> time = <%=

    Time.now.to_i %> items = <%= items.to_json %> token = "<%= @token %>" </script> Your API is a lie
  129. But it's hard not to cheat!

  130. JavaScript apps made easy.

  131. App Framework Convention & Config Build Automation

  132. Rails App Framework Convention & Config Build Automation

  133. Rails Rails App Framework Convention & Config Build Automation

  134. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation
  135. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation B-
  136. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation B- A+
  137. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation B+ B- A+
  138. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation B+ B- A+
  139. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation
  140. Rails App Framework Convention & Config Build Automation

  141. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation
  142. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Backbone
  143. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Backbone Angular
  144. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Backbone Angular Ember
  145. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Backbone Angular Ember ᵇ(´-ʆ)ᵃ

  146. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Node.js Backbone Angular Ember ᵇ(´-ʆ)ᵃ

  147. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Node.js ??? Backbone Angular Ember ᵇ(´-ʆ)ᵃ

  148. Rails Rails Railsy Rake App Framework Convention & Config Build

    Automation Node.js Lineman Backbone Ember Angular ᵇ(´-ʆ)ᵃ

  149. @linemanjs linemanjs.com

  150. ! $ npm install -g lineman Install

  151. ! $ npm install -g lineman Install ! $ lineman

    new my-app Create
  152. None
  153. None
  154. hi() code

  155. hi() code ! save

  156. hi() code ! save loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}")

    grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/ tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland compile
  157. hi() code ! save loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}")

    grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/ tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland compile loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() concat
  158. hi() code ! save loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}")

    grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/ tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland compile loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() concat hello world! play
  159. < 100ms hi() code ! save loadTask = (module) ->

    if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/ tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland compile loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() concat hello world! play
  160. None
  161. < 100ms hi() code ! save loadTask = (module) ->

    if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/ tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland compile loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() concat hello world! play
  162. test < 100ms hi() code ! save loadTask = (module)

    -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/ tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland compile loadTask task for task in npmTasks grunt.renameTask "copy", "images" loadTask "grunt-contrib-copy" # load again so webfonts can use it grunt.renameTask "copy", "webfonts" loadTask "grunt-contrib-copy" # load again to make available in userland loadTask = (module) -> if fs.existsSync("#{process.cwd()}/node_modules/#{module}") grunt.loadNpmTasks(module) else grunt.loadTasks("#{__dirname}/../node_modules/#{module}/tasks") npmTasks = grunt.util._(linemanNpmTasks).chain(). union("grunt-contrib-sass" if config.enableSass). union(config.loadNpmTasks). compact().value() concat
  163. None
  164. Can your server host static files?

  165. ! $ lineman build !

  166. ! $ lineman build ! $ tree dist

  167. ! $ lineman build ! $ tree dist ! dist

    ├── css │ └── app.css ├── index.html └── js └── app.js
  168. ! $ lineman build ! $ tree dist ! dist

    ├── assets.json ├── css │ └── app-6193b1...54592.css ├── index.html └── js └── app-8b750b...57f62.js
  169. ! $ heroku config:set BUILDPACK_URL=http://github.com/ testdouble/heroku-buildpack-lineman.git ! $ git push

    Heroku
  170. ! $ heroku config:set BUILDPACK_URL=http://github.com/ testdouble/heroku-buildpack-lineman.git ! $ git push

    Heroku Builds with Node.js
  171. ! $ heroku config:set BUILDPACK_URL=http://github.com/ testdouble/heroku-buildpack-lineman.git ! $ git push

    Heroku Builds with Node.js Runs without Node.js
  172. Starter Projects

  173. angular Starter Projects

  174. backbone angular Starter Projects

  175. ember backbone angular Starter Projects

  176. ember backbone angular web libs Starter Projects

  177. ember backbone angular web libs blogs Starter Projects

  178. None
  179. Grunt

  180. } Make Cmake Rake Ant / NAnt MSBuild Maven Grunt

  181. None
  182. grunt-contrib-clean grunt-contrib-coffee grunt-contrib-concat grunt-contrib-copy grunt-contrib-handlebars grunt-contrib-jshint grunt-contrib-jst grunt-contrib-less grunt-contrib-sass grunt-contrib-cssmin

    grunt-contrib-uglify grunt-watch-nospawn grunt-contrib-imagemin grunt-jasmine-bundle grunt-contrib-compass
  183. grunt-contrib-clean grunt-contrib-coffee grunt-contrib-concat grunt-contrib-copy grunt-contrib-handlebars grunt-contrib-jshint grunt-contrib-jst grunt-contrib-less grunt-contrib-sass grunt-contrib-cssmin

    grunt-contrib-uglify grunt-watch-nospawn grunt-contrib-imagemin grunt-jasmine-bundle grunt-contrib-compass grunt-*
  184. Comically extensible.

  185. Text goes here. ! $ npm install --save-dev lineman-bower !

    zero-config plugins
  186. Text goes here. ! $ npm install --save-dev lineman-bower !

    $ lineman run ! zero-config plugins
  187. Text goes here. ! $ npm install --save-dev lineman-bower !

    $ lineman run ! ... Running "bower:install" (bower) task >> Installed bower packages ... zero-config plugins
  188. bower

  189. bower grunt-bower-task

  190. bower grunt-bower-task lineman-bower

  191. bower grunt-bower-task lineman-bower does the thing

  192. bower grunt-bower-task lineman-bower automates the thing

  193. bower grunt-bower-task lineman-bower configures the thing

  194. bower grunt-bower-task lineman-bower

  195. bower grunt-bower-task lineman-bower handlebars grunt-ember-templates lineman-ember

  196. bower grunt-bower-task lineman-bower handlebars grunt-ember-templates lineman-ember lineman-{{name}}

  197. bower grunt-bower-task lineman-bower bundles plugins handlebars grunt-ember-templates lineman-ember lineman-{{name}}

  198. bower grunt-bower-task lineman-bower handlebars grunt-ember-templates lineman-ember lineman-{{name}} overrides configuration

  199. None
  200. None
  201. Client Server

  202. Client Server ?

  203. Client Server

  204. Client Server

  205. Client Server

  206. Lineman Sinatra

  207. Lineman Sinatra

  208. Lineman Sinatra

  209. Lineman Sinatra

  210. Lineman Sinatra

  211. Lineman Sinatra

  212. None
  213. Client Server

  214. Client Server

  215. Client Server

  216. Client Server

  217. Lineman Sinatra

  218. Lineman Sinatra

  219. Lineman Sinatra

  220. Lineman Sinatra

  221. None
  222. 30 minute test build

  223. None
  224. 4 minutes

  225. 4 minutes 4 minutes +

  226. 4 minutes 4 minutes + + 2 minutes

  227. 4 minutes 4 minutes + + 2 minutes 10 minute

    test build
  228. It's habit forming

  229. It's habit forming

  230. It's habit forming

  231. None
  232. linemanjs.com/rails.html

  233. screencast @ blog.testdouble.com

  234. None
  235. We'd love to help your team! Say hello@testdouble.com

  236. Like everyone, we're hiring! Just join@testdouble.com

  237. Join us at 1:30 in Superior AB for our JavaScript

    Testing workshop!
  238. My name is Justin Searls Please tweet me @searls &

    Say hello@testdouble.com
  239. noun project attribution Yarn designed by Marie Coons from The

    Noun Project! ! Scale designed by Ritika Khasgiwale from The Noun Project