13 avr. 2014

Famo.us with Gulp.js and Browserify in CoffeeScript

Introduction

Famo.us is a new framework with a new radical approach to HTML5 development. Basically, everything is treated as surfaces which are seen by the browser as accelerated virtual DOM components. This is all achieved under the JS files and not via CSS3 files via transforms and animations. Therefore, it avoids pollution of your CSS which is solely used for theming. As virtual DOM, the real HTML's DOM is not polluted either with DIVs inserted only for animation purposes. The animations and screen dynamics are kept separated in your JS files. But, it is not all that is provided out-of-the-box. Every tricks that needs to be inserted in our codes for being compatible with all mobiles, tablets and desktops have been inserted directly in the library. HTML5 starts to really shine using Famo.us.

Why an HelloWorld?

Currently, Famo.us is distributed either as source file in their Github repository or as a Yeoman generator that uses Grunt. This last option is incredibly nice and will surely be the most preferred one. It is rare enough to note it: Famo.us has done a great job towards its users. There are really few frameworks which provides so much as soon as they are launched. Plus, it has been done 'state-of-the-art' for most users who like writing plain HTML, CSS and JS. Really nice. Kudos to the Famo.us team.

But, if you have read this blog entries, you should know that I don't like writing vanilla codes. I generally prefer relying on preprocessors. So, with this little tutorial, I will show you my best moves to integrate Sass, Jade, CoffeeScript and Famo.us using Gulp.js and Browserify. Once done, it becomes easy to start playing with the great examples that Famo.us provides: Collection of Famo.us examples on Github.

I've shared all the source code of this tutorial into a little Github repository: PEM--/hellofamousgulped.

Creating the folder hierarchy

The tree of files is as following:
.
├── app
│   ├── css
│   │   └── main.sass
│   ├── index.jade
│   └── js
│       └── main.coffee
├── bower.json
├── gulpfile.coffee
├── gulpfile.js
└── package.json

To create it, use this classic command:
mkdir -p app/css app/js

Import Famo.us using Bower

Now, we import Famo.us thanks to Bower. Here is the content of my bower.json file:
{
  "name": "HelloFamousGulped",
  "version": "0.0.0",
  "authors": [
    "PEM--"
  ],
  "license": "MIT",
  "ignore": [
    "**/.*",
    "node_modules",
    "bower_components",
    "test",
    "tests"
  ],
  "dependencies": {
    "famous": "git@github.com:Famous/famous.git#v0_1_1"
  }
}
Once copied, use Bower to import Famo.us and all its submodules:
bower install

Import Gulp.js and Browserify using NPM

No JS on your desktop without the powerful Node Package Management. To import all our required build tools and plugins, use my package.json or get inspiration from it:
{
  "name": "HelloFamousGulped",
  "version": "0.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "PEM--",
  "license": "MIT",
  "dependencies": {},
  "devDependencies": {
    "gulp": "^3.6.0",
    "gulp-load-plugins": "^0.5.0",
    "gulp-util": "^2.2.14",
    "gulp-autoprefixer": "0.0.6",
    "gulp-imagemin": "^0.2.0",
    "gulp-clean": "^0.2.4",
    "gulp-cache": "^0.1.3",
    "gulp-ruby-sass": "^0.4.3",
    "gulp-connect": "^2.0.5",
    "gulp-cssmin": "^0.1.4",
    "gulp-jade": "^0.5.0",
    "gulp-concat": "^2.2.0",
    "gulp-plumber": "^0.6.1",
    "gulp-watch": "^0.5.4",
    "gulp-changed": "^0.3.0",
    "gulp-bower": "^0.0.4",
    "gulp-open": "^0.2.8",
    "coffeeify": "^0.6.0",
    "browserify": "^3.41.0",
    "deamdify": "^0.1.1",
    "vinyl-source-stream": "^0.1.1",
    "debowerify": "^0.7.0",
    "uglifyify": "^2.1.1"
  },
  "engines": {
    "node": ">=0.10.0"
  }
}
Once copied and adapted to suit your needs, let NPM do its magic:
npm install

Create the build scripts

Actually as I've already blogged about it (A gulp of coffee: your gulpfile in coffeescript), I use a JS wrapper to call Gulp.js within CoffeeScript. It has the strong advantage to shorten your Gulpfile a lot. Here is the wrapper gulpufile.js:
'use strict';
require('coffee-script/register');
require('./gulpfile.coffee');

And now, the real build script gulpfile.coffee:
'use strict'
gulp       = require 'gulp'
gp         = (require 'gulp-load-plugins') lazy: false
path       = require 'path'
browserify = require 'browserify'
source     = require 'vinyl-source-stream'

# HTML
gulp.task 'html', ->
  gulp.src 'app/index.jade'
    .pipe gp.plumber()
    .pipe gp.jade locals: pageTitle: 'Famo.us built with Gulp'
    .pipe gulp.dest 'www'

# JS
gulp.task 'js', ->
  browserify
    entries: ['./app/js/main.coffee']
    extensions: ['.coffee', '.js']
  .transform 'coffeeify'
  .transform 'deamdify'
  .transform 'debowerify'
  .transform 'uglifyify'
  .bundle()
  # Pass desired file name to browserify with vinyl
  .pipe source 'main.js'
  # Start piping stream to tasks!
  .pipe gulp.dest 'www/js'

# CSS
gulp.task 'css', ->
  gulp.src 'app/css/*.sass'
    .pipe gp.plumber()
    .pipe gp.rubySass style: 'compressed', loadPath: ['bower_components', '.']
    .pipe gp.cssmin keepSpecialComments: 0
    .pipe gp.autoprefixer 'last 1 version'
    .pipe gulp.dest 'www/css'

# Images
gulp.task 'img', ->
  gulp.src ['app/img/*.jpg', 'app/img/*.png']
    .pipe gp.cache gp.imagemin
      optimizationLevel: 3
      progressive: true
      interlaced: true
    .pipe gulp.dest 'www/img'

# Clean
gulp.task 'clean', ->
  gulp.src ['www', 'tmp'], read: false
    .pipe gp.clean force: true

# Build
gulp.task 'build', ['html', 'js', 'css']

# Default task
gulp.task 'default', ['clean'], -> gulp.start 'build'

# Connect
gulp.task 'connect', ['default'], ->
  gp.connect.server
    root: 'www'
    port: 9000
    livereload: true

# Watch
gulp.task 'watch', ['connect'], ->
  gulp.watch 'app/**/*', read:false, (event) ->
    ext = path.extname event.path
    taskname = null
    reloadasset = null
    switch ext
      when '.jade'
        taskname = 'html'
        reloadasset = 'www/index.html'
      when '.sass'
        taskname = 'css'
        reloadasset = 'www/css/main.css'
      when '.coffee', '.js'
        taskname = 'js'
        reloadasset = 'www/js/main.js'
      else
        taskname = 'img'
        reloadasset = "www/img/#{path.basename event.path}"
    gulp.task 'reload', [taskname], ->
      gulp.src reloadasset
        .pipe gp.connect.reload()
    gulp.start 'reload'

Now we are ready to code!

The app/index.jade, a Jade file that produces our www/index.html, is really kept as its minimum:
doctype html
html(lang="en")
  head
    meta(charset="UTF-8")
    title= pageTitle
    meta(name="viewport", content="width=device-width, maximum-scale=1, user-scalable=no")
    meta(name="mobile-web-app-capable", content="yes")
    meta(name="apple-mobile-web-app-capable", content="yes")
    meta(name="apple-mobile-web-app-status-bar-style", content="black")
    link(rel="stylesheet", href="css/main.css")
  body
    script(src="js/main.js")

It's Famo.us that will populate the DOM.

Now, just to reset our CSS and gives a nice clean look to our little app, here is the content of our app/css/main.sass, a Sass file that produces our www/css/main.css:
// From this gist: https://gist.github.com/trey/3524
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p,
blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em,
img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i,
center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption,
tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section,
summary, time, mark, audio, video
  margin: 0
  padding: 0
  border: 0
  font-size: 100%
  font: inherit
  vertical-align: baseline

article, aside, details, figcaption, figure,  footer, header, hgroup, menu,
nav, section
  display: block

body
  line-height: 1
 
ol, ul
  list-style: none
 
blockquote, q
  quotes: none
 
blockquote:before, blockquote:after,
q:before, q:after
  content: ''
  content: none
 
table
  border-collapse: collapse
  border-spacing: 0

// Import Famo.us's CSS
@import bower_components/famous/core/famous.css

// Real theming happens hereafter
$bgColor: #FF851B
$mainColor: white

html, body, .bgColor
  background-color: $bgColor 

body
  color: $mainColor
  font-family: Helvetica, Arial, sans-serif

Now comes the real fun. Our app/js/main.coffee, a CoffeeScript that produces the www/js/main.js single file is where are required all the dependencies. If you plan on splitting your app into multiple files or importing new Famo.us components or installing new dependencies thanks to BowerBrowserify will do all the heavy lifting importing and building everything into a single file. So, here is simple hello world in regular CoffeeScript:
Engine = require 'famous/core/Engine'
Surface = require 'famous/core/Surface'
Modifier = require 'famous/core/Modifier'

# Create the main context
mainContext = Engine.createContext()

outline = new Surface
  size: [200, 200]
  content: 'Hello world in Famo.us'
  classes: ['bgColor']
  properties:
    lineHeight: '200px'
    textAlign: 'center'

outlineModifier = new Modifier
  origin: [0.5, 0.5]

mainContext
  .add outlineModifier
  .add outline

If you haven't installed Gulp.js and CoffeeScript yet, install them globally:
npm -g install gulp coffee-script

It is now time to check our little hello world in Famo.us. Hit the next command:
gulp watch

Now, open your favorite browser at http://localhost:9000.As our Gulpfile is loaded with Livereload, you can edit the source code live and see all the magic brought to us thanks to Famo.us. You can even open this app with your tablet or smartphone. It works like a charm.

Conclusion

Famo.us brings a new interesting way of creating rich HTML5 apps that are almost as fast as native development without the burdens of redeveloping everything on each platform.

Though, with this new shift in code design, we have some old constraints that arise again. SEO, for instance. Rethinking RWD is another one. Still with this powerful framework in our hands, it is easy to figure out how. Solving this little issues is not a big deal when compared to the huge problems that Famo.us has solved.