On Github andrewharlandedx / codemash2015
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"
}