On Github bengladwell / codemash2014
othermike - reddit
$ npm init $ npm install --save express $ npm install --save express-hbs $ npm install --save serve-static
{ "name": "fridgewords", "version": "0.0.1", "description": "CodeMash 2015 - how to do frontend with gulp.js", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "https://github.com/bengladwell/fridgewords.git" }, "author": "Ben Gladwell", "license": "MIT", "bugs": { "url": "https://github.com/bengladwell/fridgewords/issues" }, "homepage": "https://github.com/bengladwell/fridgewords", "dependencies": { "express": "^4.10.6", "express-hbs": "^0.7.11", "serve-static": "^1.7.1" } }
"use strict"; var express = require('express'), serveStatic = require('serve-static'), hbs = require('express-hbs'), app = express(); app.engine('hbs', hbs.express3()); app.set('views', __dirname + '/server/views'); app.set('view engine', 'hbs'); app.use(serveStatic(__dirname + '/public')); app.get('/*', function (req, res, next) { res.render('app'); }); app.listen(3000, 'localhost', function () { console.log('Listening at http://localhost:3000'); });
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Fridgewords</title> <link href="/css/app.css" rel="stylesheet" type="text/css"> </head> <body> <script type="text/javascript" src="/js/vendor.js"></script> <script type="text/javascript" src="/js/app.js"></script> </body> </html>
$ npm install -g gulp $ npm install --save-dev gulp
$ npm install --save-dev gulp-less
gulpfile.js
var gulp = require('gulp'), less = require('gulp-less'); gulp.task('less', function () { return gulp.src('src/less/app.less') .pipe(less()) .pipe(gulp.dest('public/css/')); });
|-- bower_components | |-- jquery-ui | | `-- bower.json -> jquery-ui.js | `-- jquery | `-- bower.json -> jquery.js | |-- public `-- js `-- vendor.js
$ npm install -g bower $ bower init
$ bower install --save jquery-ui
$ npm install --save-dev gulp-concat $ npm install --save-dev main-bower-files
"use strict"; var gulp = require('gulp'), concat = require('gulp-concat'), mbf = require('main-bower-files'); gulp.task('bower', function () { gulp.src(mbf().filter(function (f) { return f.substr(-2) === 'js'; })) .pipe(concat('vendor.js') .pipe(gulp.dest('public/js/')); });
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Fridgewords</title> <link href="/css/app.css" rel="stylesheet" type="text/css"> </head> <body> <script type="text/javascript" src="/js/vendor.js"></script> <script type="text/javascript" src="/js/app.js"></script> </body> </html>
$(function () { var availableWordsView = new AvailableWordsView(); $('body').append(availableWordsView.render().el); });
Where to put the definition of AvailableWordsView?
window.App = {}; App.Views = {}; App.Views.AvailableWordsView = Backbone.View.extend({ ... });
src/js/app.js
"use strict"; var $ = window.jQuery, Backbone = window.Backbone, LayoutView = require('./views/Layout'), AppRouter = require('./routers/App'); $(function () { var layoutView = new LayoutView(), router = new AppRouter({ layout: layoutView }); $('body').append(layoutView.render().el); if (!Backbone.history.start({pushState: true})) { router.navigate("", {trigger: true}); } });
src/js/routers/App.js
"use strict"; var _ = window._, Backbone = window.Backbone, GameView = require('../views/Game'), SettingsView = require('../views/Settings'), AvailableWordsCollection = require('../collections/AvailableWords'), PhrasesCollection = require('../collections/Phrases'); module.exports = Backbone.Router.extend({ initialize: function (options) { _.extend(this, _.pick(options, 'layout')); }, routes: { "": "game", "settings": "settings" }, game: function () { var availableWordsCollection = new AvailableWordsCollection(), phrasesCollection = new PhrasesCollection(); Backbone.$.when([availableWordsCollection.fetch(), phrasesCollection.fetch()]).then(_.bind(function () { this.layout.setView(new GameView({ available: availableWordsCollection, phrases: phrasesCollection }), { linkTo: GameView.linkTo }); }, this)); }, settings: function () { this.layout.setView(new SettingsView(), { linkTo: SettingsView.linkTo }); } });
$ npm install --save-dev browserify $ npm install --save-dev vinyl-transform
gulpfile.js
... browserify = require('browserify'), transform = require('vinyl-transform'), ... gulp.task('browserify', function () { return gulp.src(['src/js/app.js']) .pipe(transform(function (f) { return browserify({ entries: f, debug: true }).bundle(); })) .pipe(gulp.dest('public/js/')); });explain benefits of "require" build - unused code never in build show source maps
var Backbone = window.Backbone, template = require('../templates/layout'); module.exports = Backbone.View.extend({ ... render: function () { this.$el.html(template()); return this; } });assumptions in grunt-contrib-handlebars vs gulp way
$ bower install --save handlebars
bower.json
... "overrides": { "handlebars": { "main": "handlebars.runtime.js" } }
$ gulp bower
$ npm install --save-dev gulp-handlebars $ npm install --save-dev gulp-wrap
... handlebars = require('gulp-handlebars'), wrap = require('gulp-wrap'), ... gulp.task('templates', function () { return gulp.src('src/hbs/**/*.hbs') .pipe(handlebars()) .pipe(wrap('module.exports = Handlebars.template(<%= contents %>);')) .pipe(gulp.dest('src/js/templates/')); }); gulp.task('browserify', ['templates'], function () { ...
src/hbs/layout.hbs --> src/js/templates/layout.js
module.exports = Handlebars.template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) { return "<div class="\"page-header\"">\n <h1>Fridgewords</h1>\n <div class="\"link-to\""></div>\n</div>\n<div class="\"outlet\"">\n</div>\n"; },"useData":true});
gulpfile.js
gulp.task('watch', ['browserify', 'less'], function () { gulp.watch(['src/js/**/*.js'], [ 'browserify' ]); gulp.watch('src/less/**/*.less', [ 'less' ]); gulp.watch('src/hbs/**/*.hbs', [ 'templates' ]); });
$ npm install --save-dev gulp-livereload
gulpfile.js
... livereload = require('gulp-livereload'); ... gulp.task('watch', ['browserify', 'less'], function () { gulp.watch(['src/js/**/*.js'], [ 'browserify' ]); gulp.watch('src/less/**/*.less', [ 'less' ]); gulp.watch('src/hbs/**/*.hbs', [ 'templates' ]); livereload.listen(); gulp.watch('public/**').on('change', livereload.changed); });
Add to livereload.js to the page (node)
$ npm install --save-dev connect-livereload
index.js
... if (process.env.NODE_ENV === 'development') { app.use(require('connect-livereload')()); } ...
Launch:
$ NODE_ENV=development node index.js
$ npm install --save-dev gulp-jshint $ npm install --save-dev jshint-stylish
gulpfile.js
... jshint = require('gulp-jshint'); ... gulp.task('jshint', function () { return gulp.src(['src/js/**/*.js', '!src/js/templates/**/*.js']) .pipe(jshint(process.env.NODE_ENV === 'development' ? {devel: true, debug: true} : {})) .pipe(jshint.reporter('jshint-stylish')) .pipe(jshint.reporter('fail')); }); ... gulp.task('browserify', ['jshint', 'templates'], function () {
$ npm install --save-dev testem $ npm install --save-dev mocha $ npm install --save-dev chai $ npm install --save-dev sinon
src/js/tests/unit/routers/App.js
describe('routers/App', function () { "use strict"; var expect = require('chai').expect, AppRouter = require('../../../routers/App'), LayoutView = require('../../../views/Layout'), GameView = require('../../../views/Game'), router; beforeEach(function () { router = new AppRouter({ layout: new LayoutView() }); }); it('should load a GameView for the root route', function (cb) { // router.game() returns a promise router.game().then(function () { expect(router.layout._view).to.be.instanceOf(GameView); cb(); }); }); });
gulpfile.js
gulp.task('tests', function () { return gulp.src([ 'src/js/tests/**/*.js' ]) .pipe(transform(function (f) { return browserify({ entries: f, debug: true }).bundle(); })) .pipe(gulp.dest('tmp/')); });
testem.json
{ "framework": "mocha", "src_files": [ "public/js/vendor.js", "tmp/**/*.js" ] }
package.json
"scripts": { "test": "node node_modules/testem/testem.js -l PhantomJS" }