1st class web development with lineman

E6c6e133e74c3b83f04d2861deaa1c20?s=47 Justin Searls
November 08, 2013

1st class web development with lineman

As was presented at Øredev 2013. This talk is more fun in video form: http://blog.testdouble.com/posts/2013-11-12-1st-class-web-development-with-lineman.html

Check out Lineman at http://linemanjs.com

E6c6e133e74c3b83f04d2861deaa1c20?s=128

Justin Searls

November 08, 2013
Tweet

Transcript

  1. 1st class web development with lineman

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

    Say hello@testdouble.com
  3. @linemanjs linemanjs.com

  4. 1

  5. 1 2

  6. 1 3 2

  7. None
  8. Java Developers

  9. Java Developers

  10. Ruby Developers

  11. Ruby Developers

  12. .NET Developers

  13. .NET Developers

  14. JavaScript Developers

  15. JavaScript Developers

  16. JavaScript Developers

  17. JavaScript Developers

  18. JavaScript Developers

  19. not soluble

  20. Inefficiency

  21. None
  22. Browser capability

  23. Browser capability Opportunity cost

  24. Browser capability Opportunity cost

  25. None
  26. Rails won the war.

  27. Rails won the war? which

  28. "Good frameworks are extractions, not inventions." - DHH

  29. None
  30. Routing

  31. Routing Models

  32. Routing Models Persistence

  33. Routing Models Persistence Sessions

  34. Routing Models Persistence Sessions Mailers

  35. Routing Models Persistence Sessions Mailers JS-free Dynamism

  36. HTML UI

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

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

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

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

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

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

    models │ └── item.rb └── views └── items └── index.html.erb
  43. HTML UI ! app ├── controllers │ └── items_controller.rb ├──

    models │ └── item.rb ├── views │ └── items │ └── index.html.erb └── assets └── javascripts └── application.js
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. what's wrong with vestigial appendages?

  51. app/

  52. app/

  53. app/

  54. app/

  55. ap

  56. ap

  57. ap p/

  58. app1/ ! app2/

  59. app1/ ! app2/

  60. app1/ ! app2/

  61. app1/ ! app2/

  62. app1

  63. app1

  64. app1 app2

  65. late extraction costs more than early abstraction

  66. None
  67. None
  68. None
  69. None
  70. None
  71. None
  72. None
  73. late extraction costs more than early abstraction

  74. When you see this:

  75. When you see this: ! <script> </script>

  76. When you see this: ! <script> user = <%= user.to_json

    %> </script>
  77. When you see this: ! <script> user = <%= user.to_json

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

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

    %> time = <%= Time.now.to_i %> items = <%= items.to_json %> token = "<%= @token %>" </script>
  80. When you see this: ! <script> user = <%= user.to_json

    %> time = <%= Time.now.to_i %> items = <%= items.to_json %> token = "<%= @token %>" </script> your API is broken
  81. None
  82. We think we're

  83. None
  84. But sometimes we're

  85. None
  86. mature ≈ convenient

  87. Before Rails was easy

  88. Before Rails was easy JavaScript wasn't hard

  89. Convenience

  90. Server-side

  91. Client-side

  92. Just kidding.

  93. Client-side

  94. None
  95. Do the Simplest Thing

  96. Do the Simplest Thing 1. Show data in a table

  97. Do the Simplest Thing 1. Show data in a table

    2. Now render it as a graph
  98. Do the Simplest Thing 1. Show data in a table

    2. Now render it as a graph 3. (ͮŇ㷩㷩Ň)ͮ
  99. Do the Simplest Thing 1. Show data in a table

    2. Now render it as a graph 3. (ͮŇ㷩㷩Ň)ͮ 4. Now add filters, sliders, & zoom to the graph!
  100. Do the Simplest Thing 1. Show data in a table

    2. Now render it as a graph 3. (ͮŇ㷩㷩Ň)ͮ 4. Now add filters, sliders, & zoom to the graph! 5. (ಠ_ಠ)
  101. the simple cliff

  102. the simple cliff

  103. the simple cliff

  104. the simple cliff

  105. the simple cliff

  106. the simple cliff (›°□°ʣ›ớ

  107. None
  108. None
  109. None
  110. Compete on easy.

  111. App Framework Convention & Config Build Automation

  112. Rails App Framework Convention & Config Build Automation

  113. Rails Rails App Framework Convention & Config Build Automation

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

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

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

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

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

    Automation Backbone Ember Angular ᵇ(´-ʆ)ᵃ

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

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

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

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

  121. ! $ npm install -g lineman Install

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

    new my-app Create
  123. None
  124. None
  125. None
  126. None
  127. None
  128. None
  129. None
  130. None
  131. None
  132. None
  133. None
  134. None
  135. None
  136. None
  137. None
  138. ;?

  139. ;?❤️!

  140. ;?❤️! # @ ;

  141. ;?❤️! # @ ; ☕-script!

  142. None
  143. Grunt

  144. } make Cmake Rake Ant / NAnt MSBuild Maven Grunt

  145. Easy peasy ! $ cat tasks/hello.js !

  146. Easy peasy ! $ cat tasks/hello.js ! module.exports = function(grunt)

    { };
  147. Easy peasy ! $ cat tasks/hello.js ! module.exports = function(grunt)

    { grunt.registerTask("hello",function(){ }); };
  148. Easy peasy ! $ cat tasks/hello.js ! module.exports = function(grunt)

    { grunt.registerTask("hello",function(){ grunt.log.writeln("Hello!"); }); };
  149. Easy peasy ! $ grunt hello !

  150. Easy peasy ! $ grunt hello ! Running "hello" task

    Hello! ! Done, without errors.
  151. None
  152. tasks

  153. config tasks

  154. None
  155. 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
  156. 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-*
  157. 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-*
  158. None
  159. None
  160. Client Server

  161. Client Server ?

  162. Client Server

  163. Client Server

  164. Lineman Sinatra

  165. Lineman Sinatra

  166. Lineman Sinatra

  167. Lineman Sinatra

  168. Lineman Sinatra

  169. None
  170. Client Server

  171. Client Server

  172. Client Server

  173. Client Server

  174. Lineman Sinatra

  175. Lineman Sinatra

  176. Lineman Sinatra

  177. Lineman Sinatra

  178. None
  179. 30 minute test build

  180. None
  181. 4 minutes

  182. 4 minutes 4 minutes +

  183. 4 minutes 4 minutes + + 2 minutes

  184. 4 minutes 4 minutes + + 2 minutes 10 minute

    test build
  185. It's habit forming.

  186. It's habit forming.

  187. A good start

  188. None
  189. hi() code

  190. hi() code % save

  191. 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
  192. 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
  193. 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
  194. < 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
  195. None
  196. < 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
  197. 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 test < 100ms
  198. None
  199. Can your server host static files?

  200. ! $ lineman build !

  201. ! $ lineman build ! $ tree dist

  202. ! $ lineman build ! $ tree dist ! dist

    ├── css │ └── app.css ├── index.html └── js └── app.js
  203. ! $ heroku config:set BUILDPACK_URL=http://github.com/ testdouble/heroku-buildpack-lineman.git ! $ git push

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

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

    Heroku Builds with Node.js Runs without Node.js
  206. Other Starter Projects

  207. angular Other Starter Projects

  208. backbone angular Other Starter Projects

  209. ember backbone angular Other Starter Projects

  210. ember backbone angular web libs Other Starter Projects

  211. ember backbone angular web libs markdown blogs Other Starter Projects

  212. Eminently extensible

  213. Add a dependency ! $ npm install --save-dev grunt-typescript !

  214. Add a dependency ! $ npm install --save-dev grunt-typescript !

    $ cat package.json
  215. Add a dependency ! $ npm install --save-dev grunt-typescript !

    $ cat package.json ... "devDependencies": { ... "grunt-typescript": "0.1.6" }
  216. ! loadNpmTasks: ['grunt-typescript'], config/application.js

  217. ! loadNpmTasks: ['grunt-typescript'], ! prependTasks: { common: ['typescript'] }, config/application.js

  218. ! loadNpmTasks: ['grunt-typescript'], ! prependTasks: { common: ['typescript'] }, !

    typescript: { compile: { src: 'app/js/**/*.ts', dest: 'generated/js/app.ts.js' } } config/application.js
  219. ! $ lineman run ! ... Running "typescript:compile" (typescript) task

    js: 0 files, map: 0 files, declaration: 0 files ... ! Run the task
  220. JavaScript made easy.

  221. None
  222. My name is Justin Searls Please tweet me @searls &

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

    Noun Project! ! Scale designed by Ritika Khasgiwale from The Noun Project! ! Business Man designed by Piotrek Chuchla from The Noun Project! ! Business Man designed by Toke Thieden from The Noun Project! ! Airplane designed by Sven Gabriel from The Noun Project! ! Brick Wall designed by Juan Pablo Bravo from The Noun Project