From 238ff4206e5504918bcd871244137e1b78008649 Mon Sep 17 00:00:00 2001 From: Matthew Dressman Date: Mon, 9 Mar 2015 21:57:51 -0700 Subject: [PATCH] basic Porch app framework --- .gitignore | 5 +- .jshintrc | 96 +++++++++++++++++++++++++++++++ README.md | 12 +++- gulp/config.js | 34 +++++++++++ gulp/tasks/browserify.js | 83 ++++++++++++++++++++++++++ gulp/tasks/build.js | 3 + gulp/tasks/clean.js | 8 +++ gulp/tasks/default.js | 3 + gulp/tasks/less.js | 36 ++++++++++++ gulp/tasks/lint-js.js | 18 ++++++ gulp/tasks/lint.js | 3 + gulp/tasks/set-watch.js | 7 +++ gulp/tasks/watch.js | 39 +++++++++++++ gulp/util/bundle-logger.js | 23 ++++++++ gulp/util/handle-errors.js | 19 ++++++ gulpfile.js | 17 ++++++ package.json | 79 +++++++++++++++++++++++++ server/flux-middleware.js | 47 +++++++++++++++ server/index.js | 39 +++++++++++++ src/actions/load-home-page.js | 13 +++++ src/app.js | 23 ++++++++ src/app.less | 1 + src/client.js | 20 +++++++ src/components/html-component.jsx | 37 ++++++++++++ src/pages/homepage/home-page.jsx | 18 ++++++ src/pages/homepage/home-page.less | 1 + src/routes.js | 11 ++++ 27 files changed, 690 insertions(+), 5 deletions(-) create mode 100644 .jshintrc create mode 100644 gulp/config.js create mode 100644 gulp/tasks/browserify.js create mode 100644 gulp/tasks/build.js create mode 100644 gulp/tasks/clean.js create mode 100644 gulp/tasks/default.js create mode 100644 gulp/tasks/less.js create mode 100644 gulp/tasks/lint-js.js create mode 100644 gulp/tasks/lint.js create mode 100644 gulp/tasks/set-watch.js create mode 100644 gulp/tasks/watch.js create mode 100644 gulp/util/bundle-logger.js create mode 100644 gulp/util/handle-errors.js create mode 100644 gulpfile.js create mode 100644 package.json create mode 100644 server/flux-middleware.js create mode 100644 server/index.js create mode 100644 src/actions/load-home-page.js create mode 100644 src/app.js create mode 100644 src/app.less create mode 100644 src/client.js create mode 100644 src/components/html-component.jsx create mode 100644 src/pages/homepage/home-page.jsx create mode 100644 src/pages/homepage/home-page.less create mode 100644 src/routes.js diff --git a/.gitignore b/.gitignore index 59d842b..746c124 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +/dist/ + # Logs logs *.log @@ -13,9 +15,6 @@ lib-cov # Coverage directory used by tools like istanbul coverage -# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) -.grunt - # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..d51979c --- /dev/null +++ b/.jshintrc @@ -0,0 +1,96 @@ + +{ + // JSHint Default Configuration File (as on JSHint website) + // See http://jshint.com/docs/ for more details + + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : false, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : true, // true: Require triple equals (===) for comparison + "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. + "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : 4, // {int} Number of spaces to use for indentation + "latedef" : false, // true: Require variables/functions to be defined before being used + "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : false, // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : true, // true: Requires all functions run in ES5 Strict Mode + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : false, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : false, // true: Tolerate assignments where comparisons would be expected + "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : false, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : false, // true: Tolerate use of `eval` and `new Function()` + "expr" : false, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : false, // true: Tolerate defining variables inside control statements + "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') + "iterator" : false, // true: Tolerate using the `__iterator__` property + "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : false, // true: Tolerate possibly unsafe line breakings + "laxcomma" : false, // true: Tolerate comma-first style coding + "loopfunc" : false, // true: Tolerate functions being defined in loops + "multistr" : false, // true: Tolerate multi-line strings + "noyield" : false, // true: Tolerate generator functions with no yield statement in them. + "notypeof" : false, // true: Tolerate invalid typeof operator values + "proto" : false, // true: Tolerate using the `__proto__` property + "scripturl" : false, // true: Tolerate script-targeted URLs + "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : false, // true: Tolerate using this in a non-constructor function + + // Environments + "browser" : true, // Web Browser (window, document, etc) + "browserify" : false, // Browserify (node.js code in the browser) + "couch" : false, // CouchDB + "devel" : true, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jasmine" : false, // Jasmine + "jquery" : false, // jQuery + "mocha" : true, // Mocha + "mootools" : false, // MooTools + "node" : true, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "qunit" : false, // QUnit + "rhino" : false, // Rhino + "shelljs" : false, // ShellJS + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Custom Globals + "globals" : { + "module" : true, + "require" : true, + "Promise" : true, + "jest" : true, + "expect" : true, + "__ROOT__" : true, + "supertest" : true + } +} diff --git a/README.md b/README.md index ad45a31..eec61c6 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ -# isomorphic-porch-demo -Isomorphic React+Flux application architecture w Fluxible +# isomorphic-porch + +Isomorphic React + Flux demo application + +## Tools and libraries + - [fluxible](http://fluxible.io) Yahoo's open-source implementation of an isomorphic Flux architecture. Makes use of plugins wrapping their lower level libraries, below: + - [dispatchr](https://github.com/yahoo/dispatchr) + - [routr](https://github.com/yahoo/routr) + - [fetchr](https://github.com/yahoo/fetchr) + diff --git a/gulp/config.js b/gulp/config.js new file mode 100644 index 0000000..83e5cd1 --- /dev/null +++ b/gulp/config.js @@ -0,0 +1,34 @@ +module.exports = { + // our js files for linting + jsFiles: [ + './src/**/*.js', + './server/**/*.js', + './gulp/**/*.js', + '!server/index.js' + ], + jsxFiles: [ + './src/**/*.jsx' + ], + less: { + src: [ + './src/app.less' + ], + lessFiles: [ + './src/**/*.less' + ], + dest: './dist' + }, + browserify: { + // Enable source maps + debug: true, + // Additional file extentions to make optional + extensions: ['.jsx'], + // A separate bundle will be generated for each + // bundle config in the list below + bundleConfigs: [{ + entries: './src/client.js', + dest: './dist', + outputName: 'bundle.js' + }] + } +}; diff --git a/gulp/tasks/browserify.js b/gulp/tasks/browserify.js new file mode 100644 index 0000000..6413ecf --- /dev/null +++ b/gulp/tasks/browserify.js @@ -0,0 +1,83 @@ +/* browserify task + --------------- + Bundle javascripty things with browserify! + + This task is set up to generate multiple separate bundles, from + different sources, and to use Watchify when run from the default task. + + See browserify.bundleConfigs in gulp/config.js +*/ + +"use strict"; + +var browserify = require('browserify'); +var bundleLogger = require('../util/bundle-logger'); +var gulp = require('gulp'); +var sourcemaps = require('gulp-sourcemaps'); +var handleErrors = require('../util/handle-errors'); +var source = require('vinyl-source-stream'); +var buffer = require('vinyl-buffer'); +var config = require('../config').browserify; +var path = require('path'); + +gulp.task('browserify', function(callback) { + + var bundleQueue = config.bundleConfigs.length; + + var browserifyThis = function(bundleConfig) { + + var bundler = browserify({ + // Required watchify args + cache: {}, packageCache: {}, fullPaths: false, + // Specify the entry point of your app + entries: bundleConfig.entries, + // Add file extentions to make optional in your requires + extensions: config.extensions, + // Enable source maps! + debug: true, + // Search path to resolve absolute paths in require statements + paths: [path.resolve(__dirname, '../../src')], + basedir: path.resolve(__dirname, '../..') + }); + + var bundle = function() { + // Log when bundling starts + bundleLogger.start(bundleConfig.outputName); + + bundler + .bundle() + // Use vinyl-source-stream to make the + // stream gulp compatible. Specifiy the + // desired output filename here. + .pipe(source(bundleConfig.outputName)) + // Uglify doesn't support streams, so buffer + .pipe(buffer()) + .pipe(sourcemaps.init({loadMaps: true})) + .pipe(sourcemaps.write('./', {sourceRoot: '/'})) + // Specify the output destination + .pipe(gulp.dest(bundleConfig.dest)) + // Report compile errors + .on('error', handleErrors) + .on('end', reportFinished); + }; + + var reportFinished = function() { + // Log when bundling completes + bundleLogger.end(bundleConfig.outputName); + + if (bundleQueue) { + bundleQueue--; + if (bundleQueue === 0) { + // If queue is empty, tell gulp the task is complete. + // https://github.com/gulpjs/gulp/blob/master/docs/API.md#accept-a-callback + callback(); + } + } + }; + + return bundle(); + }; + + config.bundleConfigs.forEach(browserifyThis); + +}); diff --git a/gulp/tasks/build.js b/gulp/tasks/build.js new file mode 100644 index 0000000..9fdf375 --- /dev/null +++ b/gulp/tasks/build.js @@ -0,0 +1,3 @@ +var gulp = require('gulp'); + +gulp.task('build', ['lint', 'browserify', 'less']); diff --git a/gulp/tasks/clean.js b/gulp/tasks/clean.js new file mode 100644 index 0000000..6bbe834 --- /dev/null +++ b/gulp/tasks/clean.js @@ -0,0 +1,8 @@ +"use strict"; + +var gulp = require('gulp'); +var del = require('del'); + +gulp.task('clean', function(callback) { + del(['dist/**'], callback); +}); diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js new file mode 100644 index 0000000..4ce4052 --- /dev/null +++ b/gulp/tasks/default.js @@ -0,0 +1,3 @@ +var gulp = require('gulp'); + +gulp.task('default', ['clean', 'build']); diff --git a/gulp/tasks/less.js b/gulp/tasks/less.js new file mode 100644 index 0000000..60b50dc --- /dev/null +++ b/gulp/tasks/less.js @@ -0,0 +1,36 @@ +"use strict"; + +var handleErrors = require('../util/handle-errors'); +var gulp = require('gulp'); +var sourcemaps = require('gulp-sourcemaps'); +var concat = require('gulp-concat'); +var less = require('gulp-less'); +var postcss = require('gulp-postcss'); +var autoprefixer = require('autoprefixer-core'); +var mqpacker = require('css-mqpacker'); +var csswring = require('csswring'); +var config = require('../config.js').less; + +gulp.task('less', [], function () { + + var useUglify = (process.env.NODE_ENV && process.env.NODE_ENV !== 'development'); + + var postProcessors = [ + autoprefixer({browsers: ['> 1%', 'last 2 versions', 'Firefox ESR', 'ie 8', 'ie 9'], cascade: false}), + mqpacker + ]; + if (useUglify) { + postProcessors.push(csswring); + } + + var stream = gulp.src(config.src) + .pipe(sourcemaps.init()) + .pipe(less()) + .pipe(concat('app.css')) + .pipe(postcss(postProcessors)) + .pipe(sourcemaps.write('.', {sourceRoot: '/src'})) + .pipe(gulp.dest(config.dest)) + .on('error', handleErrors); + + return stream; +}); diff --git a/gulp/tasks/lint-js.js b/gulp/tasks/lint-js.js new file mode 100644 index 0000000..9537b92 --- /dev/null +++ b/gulp/tasks/lint-js.js @@ -0,0 +1,18 @@ +"use strict"; + +var config = require('../config'); +var gulp = require('gulp'); +var jshint = require('gulp-jshint'); +var react = require('gulp-react'); +var reporter = 'jshint-stylish'; + +gulp.task('lintJs', function() { + return gulp.src(config.jsFiles) + .pipe(react()) + .on('error', function(e) { + console.error(e.message + '\n in ' + e.fileName); + }) + .pipe(jshint('.jshintrc')) + .pipe(jshint.reporter(reporter)) + .pipe(jshint.reporter('fail')); +}); diff --git a/gulp/tasks/lint.js b/gulp/tasks/lint.js new file mode 100644 index 0000000..92531e8 --- /dev/null +++ b/gulp/tasks/lint.js @@ -0,0 +1,3 @@ +var gulp = require('gulp'); + +gulp.task('lint', ['lintJs']); diff --git a/gulp/tasks/set-watch.js b/gulp/tasks/set-watch.js new file mode 100644 index 0000000..553e947 --- /dev/null +++ b/gulp/tasks/set-watch.js @@ -0,0 +1,7 @@ +"use strict"; + +var gulp = require('gulp'); + +gulp.task('setWatch', function() { + global.isWatching = true; +}); diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js new file mode 100644 index 0000000..8d31c16 --- /dev/null +++ b/gulp/tasks/watch.js @@ -0,0 +1,39 @@ +"use strict"; + +var gulp = require('gulp'); +var config= require('../config'); +var supervisor = require('gulp-supervisor'); +var clc = require('cli-color'); +var watch = require('gulp-watch'); +var batch = require('gulp-batch'); + + +function batchTask(task) { + return batch(function () { + console.log("RUNNING TASK: " + task); + gulp.start(task); + }); +} + +gulp.task('watch', ['build', 'setWatch'], function() { + + // be nice to new folks, let them know why this may be failing + if(typeof(process.env.NODE_ENV) === 'undefined'){ + console.log(clc.red("NODE_ENV is not set! ") + "You should probably add an environment variable named NODE_ENV and " + + "set to 'development'"); + return; + } + + watch(config.jsFiles, batchTask('lint')); + watch(config.images.src, batchTask('images')); + watch(config.less.lessFiles, batchTask('less')); + watch(config.jsxFiles, batchTask('browserify')); + + + supervisor( 'server', { + watch: 'server', + pollInterval: 200 + }); + + +}); diff --git a/gulp/util/bundle-logger.js b/gulp/util/bundle-logger.js new file mode 100644 index 0000000..7d055df --- /dev/null +++ b/gulp/util/bundle-logger.js @@ -0,0 +1,23 @@ +/* bundleLogger + ------------ + Provides gulp style logs to the bundle method in browserify.js +*/ + +"use strict"; + +var gutil = require('gulp-util'); +var prettyHrtime = require('pretty-hrtime'); +var startTime; + +module.exports = { + start: function(filepath) { + startTime = process.hrtime(); + gutil.log('Bundling', gutil.colors.green(filepath) + '...'); + }, + + end: function(filepath) { + var taskTime = process.hrtime(startTime); + var prettyTime = prettyHrtime(taskTime); + gutil.log('Bundled', gutil.colors.green(filepath), 'in', gutil.colors.magenta(prettyTime)); + } +}; diff --git a/gulp/util/handle-errors.js b/gulp/util/handle-errors.js new file mode 100644 index 0000000..546549a --- /dev/null +++ b/gulp/util/handle-errors.js @@ -0,0 +1,19 @@ +"use strict"; + +var notify = require("gulp-notify"); + +module.exports = function() { + + var args = Array.prototype.slice.call(arguments); + + // Send error to notification center with gulp-notify + notify.onError({ + title: "Compile Error", + message: "<%= error.message %>" + }).apply(this, args); + + console.error('Compile Error: ', args); + + // Keep gulp from hanging on this task + this.emit('end'); +}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..59686d1 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,17 @@ +/* + gulpfile.js + =========== + Rather than manage one giant configuration file responsible + for creating multiple tasks, each task has been broken out into + its own file in gulp/tasks. Any files in that directory get + automatically required below. + + To add a new task, simply add a new task file that directory. + gulp/tasks/default.js specifies the default set of tasks to run + when you run `gulp`. +*/ + +var requireDir = require('require-dir'); + +// Require all tasks in gulp/tasks, including subfolders +requireDir('./gulp/tasks', { recurse: true }); diff --git a/package.json b/package.json new file mode 100644 index 0000000..1e593dc --- /dev/null +++ b/package.json @@ -0,0 +1,79 @@ +{ + "name": "isomorphic-porch", + "version": "0.0.1", + "description": "Isomorphic React and Flux demo application", + "repository": { + "type": "git", + "url": "https://github.com/mdressman/isomorphic-porch.git" + }, + "author": "Matthew Dressman ", + "bugs": { + "url": "https://github.com/mdressman/isomorphic-porch/issues" + }, + "scripts": { + "start": "node server", + "test": "node_modules/mocha/bin/mocha" + }, + "browserify": { + "transform": [ + [ + "reactify", + { + "es6": true + } + ], + "envify" + ] + }, + "devDependencies": { + "autoprefixer-core": "^4.0.0", + "chai": "^1.9.2", + "cli-color": "^0.3.2", + "css-mqpacker": "^1.1.2", + "csswring": "hail2u/node-csswring", + "del": "^0.1.3", + "envify": "^3.2.0", + "gulp": "^3.8.8", + "gulp-autoprefixer": "^2.0.0", + "gulp-batch": "^1.0.5", + "gulp-concat": "^2.4.1", + "gulp-debug": "^2.0.1", + "gulp-jshint": "^1.8.5", + "gulp-less": "^1.3.6", + "gulp-newer": "^0.4.0", + "gulp-notify": "^2.0.0", + "gulp-postcss": "^3.0.0", + "gulp-react": "^2.0.0", + "gulp-sourcemaps": "1.1.0", + "gulp-supervisor": "^0.1.2", + "gulp-util": "^3.0.0", + "gulp-watch": "^4.1.1", + "jshint-stylish": "^1.0.1", + "node-jsx": "^0.12.0", + "pretty-hrtime": "^1.0.0", + "require-dir": "^0.1.0", + "vinyl-buffer": "^1.0.0", + "vinyl-source-stream": "^1.0.0" + }, + "dependencies": { + "body-parser": "^1.10.0", + "browserify": "^9.0.3", + "console-stamp": "^0.1.3", + "debug": "^2.1.1", + "express": "^4.9.5", + "flux-router-component": "^0.5.8", + "fluxible": "^0.2.1", + "fluxible-plugin-fetchr": "0.2.2", + "fluxible-plugin-routr": "0.3.0", + "highlight.js": "8.4.0", + "http-proxy": "^1.6.2", + "react": "^0.12.0", + "reactify": "^1.0.0", + "request": "^2.47.0", + "routr": "^0.1.0", + "rsvp": "3.0.14", + "serialize-javascript": "^1.0.0", + "string": "^3.0.0", + "underscore": "^1.7.0" + } +} diff --git a/server/flux-middleware.js b/server/flux-middleware.js new file mode 100644 index 0000000..c30b48b --- /dev/null +++ b/server/flux-middleware.js @@ -0,0 +1,47 @@ +'use strict'; + +var React = require('react'); +var serialize = require('serialize-javascript'); +var debug = require('debug')('fluxMiddleware'); +var navigateAction = require('flux-router-component').navigateAction; +var app = require('../src/app'); +var HtmlComponent = React.createFactory(require('../src/components/html-component.jsx')); + +module.exports = function(req, res) { + + var context = app.createContext({ + req: req, // The fetchr plugin depends on this + xhrContext: { // Used as query params for all XHR calls + lang: 'en-US', // make sure XHR calls receive the same lang as the initial request + _csrf: 'a3fc2d' // CSRF token to validate on the server using your favorite library + } + }); + + var actionContext = context.getActionContext(); + + debug('Executing navigate action'); + actionContext.executeAction(navigateAction, { + url: req.url + }, function (err) { + if (err) { + res.status(500); + res.send("five hundo. sad panda."); + return; + } + + debug('Exposing context state to client as window.App'); + var exposed = 'window.App=' + serialize(app.dehydrate(context)) + ';'; + + debug('Rendering Application component into html'); + var Component = app.getComponent(); + + var html = React.renderToStaticMarkup(HtmlComponent({ + context: context.getComponentContext(), + state: exposed, + markup: React.renderToString(Component({context:context.getComponentContext()})) + })); + + debug('Sending markup'); + res.send(html); + }); +}; diff --git a/server/index.js b/server/index.js new file mode 100644 index 0000000..30c86fa --- /dev/null +++ b/server/index.js @@ -0,0 +1,39 @@ +'use strict'; + +require('node-jsx').install({extension: '.jsx', harmony: true}); // parses JSX and enables ES6/7 transpiling + +var express = require('express'); +var bodyParser = require('body-parser'); +var app = require('../src/app'); // Fluxible app +var path = require('path'); // path util + +// Make our node process bulletproof +process.on('uncaughtException', function(err) { + console.error(err.stack); +}); + +// initialize our server +var server = express(); +server.set('state namespace', 'App'); + +// serve our 'build' dir assets under '/v2/assets' +server.use('/v2/assets', express.static(path.join(__dirname, '..', 'dist'))); + +// automatically parse any encoding of JSON +server.use(bodyParser.json()); + +// Get access to the fetchr plugin instance +var fetchrPlugin = app.getPlugin('FetchrPlugin'); + +// Set up the fetchr middleware +server.use(fetchrPlugin.getXhrPath(), fetchrPlugin.getMiddleware()); + +// attach our flux middleware (this is our actual app) +server.use(require('./flux-middleware')); + +// ok, ready: run the server +var port = 9312; +server.listen(port, function() { + var env = process.env.NODE_ENV || 'development'; + console.log('Listening on port ' + port + ' [' + env + ']'); +}); diff --git a/src/actions/load-home-page.js b/src/actions/load-home-page.js new file mode 100644 index 0000000..eca82e6 --- /dev/null +++ b/src/actions/load-home-page.js @@ -0,0 +1,13 @@ +'use strict'; + +/** + * executes the navigation to the home page + * + * @param {Context} context + * @param {Object} payload: the route object + * @param {Fn} done + */ +module.exports = function (context, payload, done) { + + done(); +}; diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..7953dd0 --- /dev/null +++ b/src/app.js @@ -0,0 +1,23 @@ +'use strict'; +var React = require('react'); +var FluxibleApp = require('fluxible'); +var fetchrPlugin = require('fluxible-plugin-fetchr'); +var routrPlugin = require('fluxible-plugin-routr'); + +/* +* This is our porch global flux app. It registers all of our pages, stores, +* and handles the basic routing and page loading. +*/ +var app = new FluxibleApp({ + component: React.createFactory(require('./pages/homepage/home-page')) +}); + +app.plug(fetchrPlugin({ + xhrPath: '/napi/' +})); + +app.plug(routrPlugin({ + routes: require('./routes') +})); + +module.exports = app; diff --git a/src/app.less b/src/app.less new file mode 100644 index 0000000..709af50 --- /dev/null +++ b/src/app.less @@ -0,0 +1 @@ +@import "src/pages/homepage/home-page"; diff --git a/src/client.js b/src/client.js new file mode 100644 index 0000000..a7b2d18 --- /dev/null +++ b/src/client.js @@ -0,0 +1,20 @@ +'use strict'; + +var app = require('./app'); + +var React = require('react'); +var dehydratedState = window.App; // sent from the server +window.React = React; // for chrome dev tool support + +app.rehydrate(dehydratedState, function (err, context) { + if (err) { + throw err; + } + window.context = context; + + var mountNode = document.getElementById('app'); + + React.render(app.getComponent()({context: context.getComponentContext()}), mountNode, function () { + console.log('React client-side rendered.'); + }); +}); \ No newline at end of file diff --git a/src/components/html-component.jsx b/src/components/html-component.jsx new file mode 100644 index 0000000..fbe52d0 --- /dev/null +++ b/src/components/html-component.jsx @@ -0,0 +1,37 @@ +"use strict"; + +var React = require('react/addons'); +var FluxibleMixin = require('fluxible').Mixin; + +var Html = React.createClass({ + + mixins: [ FluxibleMixin ], + + getInitialState: function () { + return {}; + }, + + render: function () { + + return ( + + + + Isomorphic React + Flux at Porch.com + + + + +
+ + + {/* dehydrated json state of all the stores */} + + + + + ); + } +}); + +module.exports = Html; diff --git a/src/pages/homepage/home-page.jsx b/src/pages/homepage/home-page.jsx new file mode 100644 index 0000000..91eccc4 --- /dev/null +++ b/src/pages/homepage/home-page.jsx @@ -0,0 +1,18 @@ +"use strict"; + +var React = require('react/addons'); +var FluxibleMixin = require('fluxible').Mixin; + +var HomePage = React.createClass({ + mixins: [ FluxibleMixin ], + + render: function() { + return ( +
+

Isomorphic React + Flux at Porch.com

+
+ ); + } +}); + +module.exports = HomePage; diff --git a/src/pages/homepage/home-page.less b/src/pages/homepage/home-page.less new file mode 100644 index 0000000..3b2432d --- /dev/null +++ b/src/pages/homepage/home-page.less @@ -0,0 +1 @@ +.homePage {} \ No newline at end of file diff --git a/src/routes.js b/src/routes.js new file mode 100644 index 0000000..b7eeb16 --- /dev/null +++ b/src/routes.js @@ -0,0 +1,11 @@ +"use strict"; + +module.exports = { + + HomePage: { + path: '/', + method: 'get', + action: require('./actions/load-home-page') + } + +};