Skip to content

Commit

Permalink
Add server-side rendering with Node.js/Express; big refactoring
Browse files Browse the repository at this point in the history
- Add Node.js/Express web server (see ./src/server.js)
- Remove Flux store base class
- Remove PageStore in favor of AppStore
- Refactor AppStore to use EventEmitter3 (see ./src/stores/AppStore.js)
- Refactor `serve` Gulp task to use nodemon
- Add __SERVER__ env variable
- Move HTML template for React component(s) to ./src/index.html
- Refactor client-side startup script (see ./src/app.js)
- Add CHANGE_LOCATION, LOAD_PAGE action types
- Add NavigationMixin to be used in the top-level component
- Remove Index.js, Privacy.js React components
- Add HomePage, ContentPage, NotFoundPage, ErrorPage React components
- Replace <Link> with <a>
- Remove PageActions, RouteActions in favor of AppActions
  • Loading branch information
vishwanatharondekar authored and Vishwanath Arondekar committed Feb 8, 2015
1 parent 889360b commit 1a047dc
Show file tree
Hide file tree
Showing 38 changed files with 864 additions and 653 deletions.
3 changes: 2 additions & 1 deletion .jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"globals": {
"require": false,
"__dirname": false,
"__DEV__": false
"__DEV__": false,
"__SERVER__": false
}
}
8 changes: 6 additions & 2 deletions config/webpack.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,18 @@ module.exports = function(release) {
plugins: release ? [
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
'__DEV__': false
'__DEV__': false,
'__SERVER__': false
}),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.AggressiveMergingPlugin()
] : [
new webpack.DefinePlugin({'__DEV__': true})
new webpack.DefinePlugin({
'__DEV__': true,
'__SERVER__': false
})
],

resolve: {
Expand Down
146 changes: 40 additions & 106 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,9 @@ var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var del = require('del');
var path = require('path');
var merge = require('merge-stream');
var runSequence = require('run-sequence');
var webpack = require('webpack');
var browserSync = require('browser-sync');
var pagespeed = require('psi');
var fs = require('fs');
var url = require('url');
var ReactTools = require('react-tools');
var argv = require('minimist')(process.argv.slice(2));

// Settings
Expand All @@ -42,30 +37,6 @@ var AUTOPREFIXER_BROWSERS = [ // https://github.com/ai/autoprefi

var src = {};
var watch = false;
var pkgs = (function() {
var pkgs = {};
var map = function(source) {
for (var key in source) {
pkgs[key.replace(/[^a-z0-9]/gi, '')] = source[key].substring(1);
}
};
map(require('./package.json').dependencies);
return pkgs;
}());

// Configure JSX Harmony transform in order to be able
// require .js files with JSX (see 'pages' task)
var originalJsTransform = require.extensions['.js'];
var reactTransform = function(module, filename) {
if (filename.indexOf('node_modules') === -1) {
var src = fs.readFileSync(filename, {encoding: 'utf8'});
src = ReactTools.transform(src, {harmony: true, stripTypes: true});
module._compile(src, filename);
} else {
originalJsTransform(module, filename);
}
};
require.extensions['.js'] = reactTransform;

// The default task
gulp.task('default', ['serve']);
Expand All @@ -75,12 +46,8 @@ gulp.task('clean', del.bind(null, [DEST]));

// 3rd party libraries
gulp.task('vendor', function() {
return merge(
gulp.src('./node_modules/jquery/dist/**')
.pipe(gulp.dest(DEST + '/vendor/jquery-' + pkgs.jquery)),
gulp.src('./node_modules/bootstrap/dist/fonts/**')
.pipe(gulp.dest(DEST + '/fonts'))
);
return gulp.src('./node_modules/bootstrap/dist/fonts/**')
.pipe(gulp.dest(DEST + '/fonts'));
});

// Static files
Expand All @@ -105,42 +72,6 @@ gulp.task('images', function() {
.pipe($.size({title: 'images'}));
});

// HTML pages
gulp.task('pages', function() {
src.pages = ['src/components/pages/**/*.js', 'src/components/pages/404.html'];

var currentPage = {};
var Dispatcher = require('./src/core/Dispatcher');
var ActionTypes = require('./src/constants/ActionTypes');

// Capture document.title and other page metadata changes
Dispatcher.register(function(payload) {
if (payload.action.actionType == ActionTypes.SET_CURRENT_PAGE)
{
currentPage = payload.action.page;
}
return true;
});

var render = $.render({
template: './src/components/pages/index.html',
data: function() { return currentPage; }
})
.on('error', function(err) { console.log(err); render.end(); });

return gulp.src(src.pages)
.pipe($.changed(DEST, {extension: '.html'}))
.pipe($.if('*.js', render))
.pipe($.replace('UA-XXXXX-X', GOOGLE_ANALYTICS_ID))
.pipe($.if(RELEASE, $.htmlmin({
removeComments: true,
collapseWhitespace: true,
minifyJS: true
}), $.jsbeautifier()))
.pipe(gulp.dest(DEST))
.pipe($.size({title: 'pages'}));
});

// CSS style sheets
gulp.task('styles', function() {
src.styles = 'src/styles/**/*.{css,less}';
Expand Down Expand Up @@ -188,51 +119,54 @@ gulp.task('bundle', function(cb) {

// Build the app from source code
gulp.task('build', ['clean'], function(cb) {
runSequence(['vendor', 'assets', 'images', 'pages', 'styles', 'bundle'], cb);
runSequence(['vendor', 'assets', 'images', 'styles', 'bundle'], cb);
});

// Launch a lightweight HTTP Server
gulp.task('serve', function(cb) {

var nodemon = require('nodemon');
var browserSync = require('browser-sync');

watch = true;

runSequence('build', function() {
browserSync({
notify: false,
// Customize the BrowserSync console logging prefix
logPrefix: 'RSK',
// Run as an https by uncommenting 'https: true'
// Note: this uses an unsigned certificate which on first access
// will present a certificate warning in the browser.
// https: true,
server: {
baseDir: DEST,
// Allow web page requests without .html file extension in URLs
middleware: function(req, res, cb) {
var uri = url.parse(req.url);
if (uri.pathname.length > 1 &&
uri.pathname.lastIndexOf('/browser-sync/', 0) !== 0 &&
!fs.existsSync(DEST + uri.pathname)) {
if (fs.existsSync(DEST + uri.pathname + '.html')) {
req.url = uri.pathname + '.html' + (uri.search || '');
} else {
res.statusCode = 404;
req.url = '/404.html' + (uri.search || '');
}
}
cb();
}
}
});

gulp.watch(src.assets, ['assets']);
gulp.watch(src.images, ['images']);
gulp.watch(src.pages, ['pages']);
gulp.watch(src.styles, ['styles']);
gulp.watch(DEST + '/**/*.*', function(file) {
browserSync.reload(path.relative(__dirname, file.path));
var server = require('nodemon')({
script: 'src/server.js',
watch: [path.join(__dirname, 'src/**/*.js')],
env: {NODE_ENV: 'development'}
}).on('log', function(log) {
$.util.log('nodemon', $.util.colors.green(log.message));
}).on('crash', function() {
$.util.log($.util.colors.red('nodemon crashed'));
}).once('start', function() {
browserSync({
notify: false,
// Customize the BrowserSync console logging prefix
logPrefix: 'QC',
// Run as an https by setting 'https: true'
// Note: this uses an unsigned certificate which on first access
// will present a certificate warning in the browser.
https: false,
// Informs browser-sync to proxy our Express app which would run
// at the following location
proxy: 'http://localhost:5000'
});

process.on('exit', function () {
browserSync.exit();
server.emit('exit');
});

gulp.watch(src.assets, ['assets']);
gulp.watch(src.images, ['images']);
gulp.watch(src.styles, ['styles']);
gulp.watch(DEST + '/**/*.*', function (file) {
browserSync.reload(path.relative(__dirname, file.path));
});
cb();
});
cb();
});
});

Expand Down
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
"repository": "https://github.com/kriasoft/react-starter-kit",
"license": "MIT",
"dependencies": {
"bootstrap": "^3.3.1",
"director": "^1.2.7",
"flux": "^2.0.1",
"react": "^0.12.2"
"bootstrap": "3.3.1",
"director": "1.2.7",
"eventemitter3": "0.1.6",
"express": "4.10.7",
"flux": "2.0.1",
"front-matter": "0.2.1",
"jade": "1.9.0",
"lodash": "2.4.1",
"react": "0.12.2",
"superagent": "0.21.0"
},
"devDependencies": {
"autoprefixer-loader": "^1.1.0",
Expand Down Expand Up @@ -44,8 +50,8 @@
"jsx-loader": "^0.12.2",
"less": "^2.2.0",
"less-loader": "^2.0.0",
"merge-stream": "^0.1.7",
"minimist": "^1.1.0",
"nodemon": "^1.2.1",
"protractor": "^1.6.0",
"psi": "^1.0.4",
"react-tools": "^0.12.2",
Expand Down
45 changes: 45 additions & 0 deletions src/actions/AppActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* React.js Starter Kit
* Copyright (c) 2014 Konstantin Tarkus (@koistya), KriaSoft LLC.
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
*/

'use strict';

var Dispatcher = require('../core/Dispatcher');
var ActionTypes = require('../constants/ActionTypes');
var ExecutionEnvironment = require('react/lib/ExecutionEnvironment');
var http = require('superagent');

module.exports = {

navigateTo(path) {
if (ExecutionEnvironment.canUseDOM) {
window.history.pushState({}, document.title, path);
}

Dispatcher.handleViewAction({
actionType: ActionTypes.CHANGE_LOCATION, path: path
});
},

loadPage(path, cb) {
Dispatcher.handleViewAction({
actionType: ActionTypes.LOAD_PAGE, path: path
});

http.get('/api/page' + path)
.accept('application/json')
.end((err, res) => {
Dispatcher.handleServerAction({
actionType: ActionTypes.LOAD_PAGE, path: path, err: err, page: res
});
if (cb) {
cb();
}
});
}

};
29 changes: 0 additions & 29 deletions src/actions/PageActions.js

This file was deleted.

27 changes: 0 additions & 27 deletions src/actions/RouteActions.js

This file was deleted.

Loading

0 comments on commit 1a047dc

Please sign in to comment.