From 1a047dc5ce1db941c2aac6054c8105ab4be48b7f Mon Sep 17 00:00:00 2001 From: Vishwanath Arondekar Date: Fri, 16 Jan 2015 10:55:20 +0300 Subject: [PATCH] Add server-side rendering with Node.js/Express; big refactoring - 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 with - Remove PageActions, RouteActions in favor of AppActions --- .jshintrc | 3 +- config/webpack.js | 8 +- gulpfile.js | 146 +++++------------- package.json | 16 +- src/actions/AppActions.js | 45 ++++++ src/actions/PageActions.js | 29 ---- src/actions/RouteActions.js | 27 ---- src/app.js | 82 +++++----- src/components/App/App.js | 93 +++++++++++ src/components/App/App.less | 9 ++ src/components/App/NavigationMixin.js | 93 +++++++++++ src/components/App/package.json | 6 + src/components/Application/Application.js | 85 ---------- src/components/Application/Application.less | 0 src/components/Application/package.json | 6 - src/components/ContentPage/ContentPage.js | 30 ++++ src/components/ContentPage/ContentPage.less | 9 ++ src/components/ContentPage/package.json | 6 + src/components/HomePage/HomePage.js | 28 ++++ src/components/HomePage/HomePage.less | 9 ++ src/components/HomePage/package.json | 6 + src/components/Link/Link.js | 4 +- src/components/Navbar/Navbar.js | 5 +- src/components/NotFoundPage/NotFoundPage.js | 30 ++++ src/components/NotFoundPage/NotFoundPage.less | 48 ++++++ src/components/NotFoundPage/package.json | 6 + src/components/pages/404.html | 59 ------- src/components/pages/Index.js | 64 -------- src/components/pages/Privacy.js | 85 ---------- src/constants/ActionTypes.js | 9 +- src/core/Store.js | 84 ---------- src/{components/pages => }/index.html | 6 +- src/pages/about.jade | 46 ++++++ src/pages/index.jade | 25 +++ src/pages/privacy.jade | 51 ++++++ src/server.js | 116 ++++++++++++++ src/stores/AppStore.js | 96 ++++++++++++ src/stores/PageStore.js | 47 ------ 38 files changed, 864 insertions(+), 653 deletions(-) create mode 100644 src/actions/AppActions.js delete mode 100644 src/actions/PageActions.js delete mode 100644 src/actions/RouteActions.js create mode 100644 src/components/App/App.js create mode 100644 src/components/App/App.less create mode 100644 src/components/App/NavigationMixin.js create mode 100644 src/components/App/package.json delete mode 100644 src/components/Application/Application.js delete mode 100644 src/components/Application/Application.less delete mode 100644 src/components/Application/package.json create mode 100644 src/components/ContentPage/ContentPage.js create mode 100644 src/components/ContentPage/ContentPage.less create mode 100644 src/components/ContentPage/package.json create mode 100644 src/components/HomePage/HomePage.js create mode 100644 src/components/HomePage/HomePage.less create mode 100644 src/components/HomePage/package.json create mode 100644 src/components/NotFoundPage/NotFoundPage.js create mode 100644 src/components/NotFoundPage/NotFoundPage.less create mode 100644 src/components/NotFoundPage/package.json delete mode 100644 src/components/pages/404.html delete mode 100644 src/components/pages/Index.js delete mode 100644 src/components/pages/Privacy.js delete mode 100644 src/core/Store.js rename src/{components/pages => }/index.html (91%) create mode 100644 src/pages/about.jade create mode 100644 src/pages/index.jade create mode 100644 src/pages/privacy.jade create mode 100644 src/server.js create mode 100644 src/stores/AppStore.js delete mode 100644 src/stores/PageStore.js diff --git a/.jshintrc b/.jshintrc index 3077266bb..8ab2fd567 100644 --- a/.jshintrc +++ b/.jshintrc @@ -15,6 +15,7 @@ "globals": { "require": false, "__dirname": false, - "__DEV__": false + "__DEV__": false, + "__SERVER__": false } } diff --git a/config/webpack.js b/config/webpack.js index 151e4b8d3..9c0f89222 100644 --- a/config/webpack.js +++ b/config/webpack.js @@ -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: { diff --git a/gulpfile.js b/gulpfile.js index 09eaa750a..1437828d3 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -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 @@ -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']); @@ -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 @@ -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}'; @@ -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(); }); }); diff --git a/package.json b/package.json index 5875412e9..abaeba5aa 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/src/actions/AppActions.js b/src/actions/AppActions.js new file mode 100644 index 000000000..9737cda37 --- /dev/null +++ b/src/actions/AppActions.js @@ -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(); + } + }); + } + +}; diff --git a/src/actions/PageActions.js b/src/actions/PageActions.js deleted file mode 100644 index 2f3597176..000000000 --- a/src/actions/PageActions.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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 pageDefaults = require('../constants/Settings').defaults.page; -var assign = require('react/lib/Object.assign'); - -module.exports = { - - /** - * Set metadata for the current page (title, description, keywords etc.). - * @param {object} The page object. - */ - set(page) { - Dispatcher.handleViewAction({ - actionType: ActionTypes.SET_CURRENT_PAGE, - page: assign({}, pageDefaults, page) - }); - } - -}; diff --git a/src/actions/RouteActions.js b/src/actions/RouteActions.js deleted file mode 100644 index 70de2af38..000000000 --- a/src/actions/RouteActions.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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'); - -module.exports = { - - /** - * Set the current route. - * @param {string} route Supply a route value, such as `todos/completed`. - */ - setRoute(route) { - Dispatcher.handleViewAction({ - actionType: ActionTypes.SET_CURRENT_ROUTE, - route: route - }); - } - -}; diff --git a/src/app.js b/src/app.js index 4c3e93255..6989b3a57 100644 --- a/src/app.js +++ b/src/app.js @@ -9,54 +9,56 @@ 'use strict'; var React = require('react'); -var ExecutionEnvironment = require('react/lib/ExecutionEnvironment'); -var {Router} = require('director'); +var App = require('./components/App'); var Dispatcher = require('./core/Dispatcher'); +var AppActions = require('./actions/AppActions'); var ActionTypes = require('./constants/ActionTypes'); -var router; // Export React so the dev tools can find it (window !== window.top ? window.top : window).React = React; -Dispatcher.register((payload) => { - - var action = payload.action; - - switch (action.actionType) - { - case ActionTypes.SET_CURRENT_ROUTE: - router.setRoute(action.route); - break; - - case ActionTypes.SET_CURRENT_PAGE: - if (ExecutionEnvironment.canUseDOM) { - document.title = action.page.title; +// Initial properties and callbacks +// which should be passed into the top-level React component (App) +var props = { + path: decodeURI(window.location.pathname), + onSetTitle: (title) => { + document.title = title; + }, + onSetMeta: (name, content) => { + // Remove and create a new tag in order to make it work + // with bookmarks in Safari + var elements = document.getElementsByTagName('meta'); + [].slice.call(elements).forEach((element) => { + if (element.getAttribute('name') === name) { + element.parentNode.removeChild(element); } - break; - } + }); + var meta = document.createElement('meta'); + meta.setAttribute('name', name); + meta.setAttribute('content', content); + document.getElementsByTagName('head')[0].appendChild(meta); + }, + onPageNotFound: () => { /* do nothing */ } +}; - return true; // No errors. Needed by promise in Dispatcher. -}); +// Render application when DOM is ready +function startup() { + // Render the top-level React component and mount it to the `document.body` + var app = React.render(React.createElement(App, props), document.body); -/** - * Check if Page component has a layout property; and if yes, wrap the page - * into the specified layout, then mount to document.body. - */ -function render(page) { - var layout = null, child = null, props = {}; - while ((layout = page.type.layout || (page.defaultProps && page.defaultProps.layout))) { - child = React.createElement(page, props, child); - page = layout; - } - React.render(React.createElement(page, props, child), document.body); + // Set Application.path property when `window.location` is changed + Dispatcher.register((payload) => { + if (payload.action.actionType === ActionTypes.CHANGE_LOCATION) { + app.setProps({path: decodeURI(payload.action.path)}); + } + }); } -// Define URL routes -// See https://github.com/flatiron/director -var routes = { - '/': () => render(require('./components/pages/Index')), - '/privacy': () => render(require('./components/pages/Privacy')) -}; - -// Initialize a router -router = new Router(routes).configure({html5history: true}).init(); +// Load page content +AppActions.loadPage(props.path, () => { + if (window.addEventListener) { + window.addEventListener('DOMContentLoaded', startup, false); + } else { + window.attachEvent('onload', startup); + } +}); diff --git a/src/components/App/App.js b/src/components/App/App.js new file mode 100644 index 000000000..4ddfee4b6 --- /dev/null +++ b/src/components/App/App.js @@ -0,0 +1,93 @@ +/* + * 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'; + +require('./App.less'); + +var React = require('react'); +var ExecutionEnvironment = require('react/lib/ExecutionEnvironment'); +var AppActions = require('../../actions/AppActions'); +var NavigationMixin = require('./NavigationMixin'); +var AppStore = require('../../stores/AppStore'); +var Navbar = require('../Navbar'); +var ContentPage = require('../ContentPage'); +var NotFoundPage = require('../NotFoundPage'); + +var Application = React.createClass({ + + mixins: [NavigationMixin], + + propTypes: { + path: React.PropTypes.string.isRequired, + onSetTitle: React.PropTypes.func.isRequired, + onSetMeta: React.PropTypes.func.isRequired, + onPageNotFound: React.PropTypes.func.isRequired + }, + + getInitialState() { + return {loading: false}; + }, + + componentWillMount() { + if (ExecutionEnvironment.canUseDOM) { + this.setState({loading: true}); + AppActions.loadPage(this.props.path, () => { + this.setState({loading: false}); + }); + } + }, + + render() { + var page = AppStore.getPage(this.props.path); + + if (page === undefined) { + return false; + } + + this.props.onSetTitle(page.title); + + if (page.type === 'notfound') { + this.props.onPageNotFound(); + return React.createElement(NotFoundPage, page); + } + + return ( + /* jshint ignore:start */ +
+ + { + this.props.path === '/' ? +
+
+

React

+

Complex web apps made easy

+
+
: +
+

{page.title}

+
+ } + +
+
+

+ © KriaSoft + Home + Privacy +

+
+
+
+ /* jshint ignore:end */ + ); + } + +}); + +module.exports = Application; diff --git a/src/components/App/App.less b/src/components/App/App.less new file mode 100644 index 000000000..f865e5889 --- /dev/null +++ b/src/components/App/App.less @@ -0,0 +1,9 @@ +/* + * 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. + */ + +.App {} diff --git a/src/components/App/NavigationMixin.js b/src/components/App/NavigationMixin.js new file mode 100644 index 000000000..f52f1b816 --- /dev/null +++ b/src/components/App/NavigationMixin.js @@ -0,0 +1,93 @@ +/* + * 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 React = require('react'); +var ExecutionEnvironment = require('react/lib/ExecutionEnvironment'); +var AppActions = require('../../actions/AppActions'); + +var NavigationMixin = { + + componentDidMount() { + if (ExecutionEnvironment.canUseDOM) { + window.addEventListener('popstate', this.handlePopState); + window.addEventListener('click', this.handleClick); + } + }, + + componentWillUnmount() { + window.removeEventListener('popstate', this.handlePopState); + window.removeEventListener('click', this.handleClick); + }, + + handlePopState(event) { + console.log('Application.handlePopState(' + (event.state ? event.state.path : '') + ')'); + if (event.state) { + var path = event.state.path; + // TODO: Replace current location + // replace(path, event.state); + } else { + AppActions.navigateTo(window.location.pathname); + } + }, + + handleClick(event) { + if (event.button === 1 || event.metaKey || event.ctrlKey || event.shiftKey || event.defaultPrevented) { + return; + } + + // Ensure link + var el = event.target; + while (el && el.nodeName !== 'A') { + el = el.parentNode; + } + if (!el || el.nodeName !== 'A') { + return; + } + + // Ignore if tag has + // 1. "download" attribute + // 2. rel="external" attribute + if (el.getAttribute('download') || el.getAttribute('rel') === 'external') { + return; + } + + // Ensure non-hash for the same path + var link = el.getAttribute('href'); + if (el.pathname === location.pathname && (el.hash || '#' === link)) { + return; + } + + // Check for mailto: in the href + if (link && link.indexOf('mailto:') > -1) { + return; + } + + // Check target + if (el.target) { + return; + } + + // X-origin + var origin = window.location.protocol + '//' + window.location.hostname + + (window.location.port ? ':' + window.location.port : ''); + if (!(el.href && el.href.indexOf(origin) === 0)) { + return; + } + + // Rebuild path + var path = el.pathname + el.search + (el.hash || ''); + + event.preventDefault(); + AppActions.navigateTo(path); + } + +}; + +module.exports = NavigationMixin; diff --git a/src/components/App/package.json b/src/components/App/package.json new file mode 100644 index 000000000..ebd8da0f9 --- /dev/null +++ b/src/components/App/package.json @@ -0,0 +1,6 @@ +{ + "name": "App", + "version": "0.0.0", + "private": true, + "main": "./App.js" +} diff --git a/src/components/Application/Application.js b/src/components/Application/Application.js deleted file mode 100644 index 2bc3097db..000000000 --- a/src/components/Application/Application.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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'; - -require('./Application.less'); - -var React = require('react'); -var PageStore = require('../../stores/PageStore'); -var Link = require('../Link'); -var Navbar = require('../Navbar'); - -/** - * Retrieves the current page metadata from the PageStore. - * @returns {{title: string}} - */ -function getState() { - return { - title: PageStore.get().title - }; -} - -var DefaultLayout = React.createClass({ - - mixins: [PageStore.Mixin], - - getInitialState() { - return getState(); - }, - - componentDidMount() { - PageStore.emitChange(); - }, - - render() { - /* jshint ignore:start */ - var header = this.props.children.type.breadcrumb ? ( -
-

{this.state.title}

- {this.props.children.type.breadcrumb} -
- ) : ( -
-
-

React

-

Complex web apps made easy

-
-
- ); - /* jshint ignore:end */ - - return ( - /* jshint ignore:start */ -
- - {header} - {this.props.children} -
-
-

- © KriaSoft - Home - Privacy -

-
-
-
- /* jshint ignore:end */ - ); - }, - - /** - * Event handler for 'change' events coming from the PageStore. - */ - onChange() { - this.setState(getState()); - } -}); - -module.exports = DefaultLayout; diff --git a/src/components/Application/Application.less b/src/components/Application/Application.less deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/components/Application/package.json b/src/components/Application/package.json deleted file mode 100644 index 7bd1b09ea..000000000 --- a/src/components/Application/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Application", - "version": "0.0.0", - "private": true, - "main": "./Application.js" -} diff --git a/src/components/ContentPage/ContentPage.js b/src/components/ContentPage/ContentPage.js new file mode 100644 index 000000000..795c00145 --- /dev/null +++ b/src/components/ContentPage/ContentPage.js @@ -0,0 +1,30 @@ +/* + * 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 React = require('react'); + +var ContentPage = React.createClass({ + + propTypes: { + body: React.PropTypes.string.isRequired + }, + + render() { + var { className, title, body, other } = this.props; + + /* jshint ignore:start */ + return
; + /* jshint ignore:end */ + } + +}); + +module.exports = ContentPage; diff --git a/src/components/ContentPage/ContentPage.less b/src/components/ContentPage/ContentPage.less new file mode 100644 index 000000000..900276594 --- /dev/null +++ b/src/components/ContentPage/ContentPage.less @@ -0,0 +1,9 @@ +/* + * 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. + */ + +.ContentPage {} diff --git a/src/components/ContentPage/package.json b/src/components/ContentPage/package.json new file mode 100644 index 000000000..26aa4558a --- /dev/null +++ b/src/components/ContentPage/package.json @@ -0,0 +1,6 @@ +{ + "name": "ContentPage", + "version": "0.0.0", + "private": true, + "main": "./ContentPage.js" +} diff --git a/src/components/HomePage/HomePage.js b/src/components/HomePage/HomePage.js new file mode 100644 index 000000000..4373534db --- /dev/null +++ b/src/components/HomePage/HomePage.js @@ -0,0 +1,28 @@ +/* + * 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 React = require('react'); + +var HomePage = React.createClass({ + + propTypes: { + body: React.PropTypes.string.isRequired + }, + + render() { + /* jshint ignore:start */ + return
; + /* jshint ignore:end */ + } + +}); + +module.exports = HomePage; diff --git a/src/components/HomePage/HomePage.less b/src/components/HomePage/HomePage.less new file mode 100644 index 000000000..24565cb97 --- /dev/null +++ b/src/components/HomePage/HomePage.less @@ -0,0 +1,9 @@ +/* + * 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. + */ + +.HomePage {} diff --git a/src/components/HomePage/package.json b/src/components/HomePage/package.json new file mode 100644 index 000000000..1528b69ef --- /dev/null +++ b/src/components/HomePage/package.json @@ -0,0 +1,6 @@ +{ + "name": "HomePage", + "version": "0.0.0", + "private": true, + "main": "./HomePage.js" +} diff --git a/src/components/Link/Link.js b/src/components/Link/Link.js index a4b4ece3f..ecaac7272 100644 --- a/src/components/Link/Link.js +++ b/src/components/Link/Link.js @@ -9,7 +9,7 @@ 'use strict'; var React = require('react'); -var RouteActions = require('../../actions/RouteActions'); +var AppActions = require('../../actions/AppActions'); var Link = React.createClass({ @@ -31,7 +31,7 @@ var Link = React.createClass({ handleClick(e) { e.preventDefault(); - RouteActions.setRoute(this.props.to); + AppActions.navigateTo(this.props.to); } }); diff --git a/src/components/Navbar/Navbar.js b/src/components/Navbar/Navbar.js index 707b94bbd..40866a937 100644 --- a/src/components/Navbar/Navbar.js +++ b/src/components/Navbar/Navbar.js @@ -9,7 +9,6 @@ 'use strict'; var React = require('react'); -var Link = require('../Link'); var Navbar = React.createClass({ @@ -18,10 +17,10 @@ var Navbar = React.createClass({ /* jshint ignore:start */
- + React React.js Starter Kit - +
/* jshint ignore:end */ diff --git a/src/components/NotFoundPage/NotFoundPage.js b/src/components/NotFoundPage/NotFoundPage.js new file mode 100644 index 000000000..1d5554629 --- /dev/null +++ b/src/components/NotFoundPage/NotFoundPage.js @@ -0,0 +1,30 @@ +/* + * 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'; + +//require('./NotFoundPage.less'); + +var React = require('react'); + +var NotFoundPage = React.createClass({ + + render() { + /* jshint ignore:start */ + return ( +
+

Page Not Found

+

Sorry, but the page you were trying to view does not exist.

+
+ ); + /* jshint ignore:end */ + } + +}); + +module.exports = NotFoundPage; diff --git a/src/components/NotFoundPage/NotFoundPage.less b/src/components/NotFoundPage/NotFoundPage.less new file mode 100644 index 000000000..d2a660c5c --- /dev/null +++ b/src/components/NotFoundPage/NotFoundPage.less @@ -0,0 +1,48 @@ +/* + * 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. + */ + +* { + line-height: 1.2; + margin: 0; +} + +html { + color: #888; + display: table; + font-family: sans-serif; + height: 100%; + text-align: center; + width: 100%; +} + +body { + display: table-cell; + vertical-align: middle; + margin: 2em auto; +} + +h1 { + color: #555; + font-size: 2em; + font-weight: 400; +} + +p { + margin: 0 auto; + width: 280px; +} + +@media only screen and (max-width: 280px) { + body, p { + width: 95%; + } + h1 { + font-size: 1.5em; + margin: 0 0 0.3em; + } +} diff --git a/src/components/NotFoundPage/package.json b/src/components/NotFoundPage/package.json new file mode 100644 index 000000000..f3adaaf4c --- /dev/null +++ b/src/components/NotFoundPage/package.json @@ -0,0 +1,6 @@ +{ + "name": "NotFoundPage", + "version": "0.0.0", + "private": true, + "main": "./NotFoundPage.js" +} diff --git a/src/components/pages/404.html b/src/components/pages/404.html deleted file mode 100644 index 9ee4a7b2e..000000000 --- a/src/components/pages/404.html +++ /dev/null @@ -1,59 +0,0 @@ - - - - - Page Not Found - - - - -

Page Not Found

-

Sorry, but the page you were trying to view does not exist.

- - - diff --git a/src/components/pages/Index.js b/src/components/pages/Index.js deleted file mode 100644 index debe78363..000000000 --- a/src/components/pages/Index.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 React = require('react'); -var PageActions = require('../../actions/PageActions'); -var App = require('../Application'); - -var HomePage = React.createClass({ - - statics: { - layout: App - }, - - componentWillMount() { - PageActions.set({title: 'React.js Starter Kit'}); - }, - - render() { - return ( - /* jshint ignore:start */ -
-
-
-

Runtime Components

-
-
React
-
A JavaScript library for building user interfaces, developed by Facebook
-
Director
-
A tiny and isomorphic URL router for JavaScript
-
Bootstrap
-
CSS framework for developing responsive, mobile first interfaces
-
-
-
-

Development Tools

-
-
Gulp
-
JavaScript streaming build system and task automation
-
Webpack
-
Compiles front-end source code into modules / bundles
-
BrowserSync
-
A lightweight HTTP server for development
-
-
-
-

Fork me on GitHub

-

github.com/kriasoft/react-starter-kit

-
-
-
- /* jshint ignore:end */ - ); - } - -}); - -module.exports = HomePage; diff --git a/src/components/pages/Privacy.js b/src/components/pages/Privacy.js deleted file mode 100644 index 2a9e92fc7..000000000 --- a/src/components/pages/Privacy.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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 React = require('react'); -var PageActions = require('../../actions/PageActions'); -var App = require('../Application'); -var Link = require('../Link'); - -var PrivacyPage = React.createClass({ - - statics: { - layout: App, - breadcrumb: ( - /* jshint ignore:start */ -
    -
  1. Home
  2. -
  3. Privacy
  4. -
- /* jshint ignore:end */ - ) - }, - - componentWillMount() { - PageActions.set({title: 'Privacy Policy'}); - }, - - render() { - return ( - /* jshint ignore:start */ -
-

- Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean consequat tortor fermentum mi - fermentum dignissim. Nullam vel ipsum ut ligula elementum lobortis. Maecenas aliquam, massa laoreet - lacinia pretium, nisi urna venenatis tortor, nec imperdiet tellus libero efficitur metus. Fusce - semper posuere ligula, et facilisis metus bibendum interdum. Mauris at mauris sit amet sem pharetra - commodo a eu leo. Nam at est non risus cursus maximus. Nam feugiat augue libero, id consectetur - tortor bibendum non. Quisque nec fringilla lorem. Nullam efficitur vulputate mauris, nec maximus leo - dignissim id. -

-

- In hac habitasse platea dictumst. Duis sagittis dui ac ex suscipit maximus. Morbi pellentesque - venenatis felis sed convallis. Nulla varius, nibh vitae placerat tempus, mauris sem elementum ipsum, - eget sollicitudin nisl est vel purus. Fusce malesuada odio velit, non cursus leo fermentum id. Cras - pharetra sodales fringilla. Etiam quis est a dolor egestas pellentesque. Maecenas non scelerisque - purus, congue cursus arcu. Donec vel dapibus mi. Mauris maximus posuere placerat. Sed et libero eu - nibh tristique mollis a eget lectus. Donec interdum augue sollicitudin vehicula hendrerit. Vivamus - justo orci, molestie ac sollicitudin ac, lobortis at tellus. Etiam rhoncus ullamcorper risus eu - tempor. Sed porttitor, neque ac efficitur gravida, arcu lacus pharetra dui, in consequat elit tellus - auctor nulla. Donec placerat elementum diam, vitae imperdiet lectus luctus at. -

-

- Nullam eu feugiat mi. Quisque nec tristique nisl, dignissim dictum leo. Nam non quam nisi. Donec - rutrum turpis ac diam blandit, id pulvinar mauris suscipit. Pellentesque tincidunt libero ultricies - risus iaculis, sit amet consequat velit blandit. Fusce quis varius nulla. Nullam nisi nisi, suscipit - ut magna quis, feugiat porta nibh. Sed id enim lectus. Suspendisse elementum justo sapien, sit amet - consequat orci accumsan et. Aliquam ornare ullamcorper sem sed finibus. Nullam ac lacus pulvinar, - egestas felis ut, accumsan est. -

-

- Pellentesque sagittis vehicula sem quis luctus. Proin sodales magna in lorem hendrerit aliquam. - Integer eu varius orci. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere - cubilia Curae; Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia - Curae; Ut at mauris nibh. Suspendisse maximus ac eros at vestibulum. -

-

- Interdum et malesuada fames ac ante ipsum primis in faucibus. Quisque egestas tortor et dui - consequat faucibus. Nunc vitae odio ornare, venenatis ligula a, vulputate nisl. Aenean congue varius - ex, sit amet bibendum odio posuere at. Nulla facilisi. In finibus, nulla vitae tincidunt ornare, - sapien nulla fermentum mauris, sed consectetur tortor arcu eget arcu. Vestibulum vel quam enim. -

-
- /* jshint ignore:end */ - ); - } - -}); - -module.exports = PrivacyPage; diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js index c58df2c3c..ea4542ffe 100644 --- a/src/constants/ActionTypes.js +++ b/src/constants/ActionTypes.js @@ -12,11 +12,10 @@ var keyMirror = require('react/lib/keyMirror'); var ActionTypes = keyMirror({ - // Route action types - SET_CURRENT_ROUTE: null, - - // Page action types - SET_CURRENT_PAGE: null + LOAD_PAGE: null, + LOAD_PAGE_SUCCESS: null, + LOAD_PAGE_ERROR: null, + CHANGE_LOCATION: null }); diff --git a/src/core/Store.js b/src/core/Store.js deleted file mode 100644 index b8450afec..000000000 --- a/src/core/Store.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 EventEmitter = require('events').EventEmitter; -var assign = require('react/lib/Object.assign'); -var invariant = require('react/lib/invariant'); - -var CHANGE_EVENT = 'change'; - -/** - * The Flux store base class. - */ -class Store { - - /** - * Constructs a Store object, extends it with EventEmitter and supplied - * methods parameter, and creates a mixin property for use in components. - * - * @param {object} methods Public methods for Store instance. - * @constructor - */ - constructor(methods) { - - var self = this; - - invariant(!methods.dispatcherToken,'"dispatcherToken" is a reserved name and cannot be used as a method name.'); - invariant(!methods.Mixin,'"Mixin" is a reserved name and cannot be used as a method name.'); - - assign(this, EventEmitter.prototype, methods); - - this.dispatcherToken = null; - - /** - * Base functionality for every Store constructor. Mixed into the - * `Store` prototype, but exposed statically for easy access. - */ - this.Mixin = { - - componentDidMount: function() { - self.addChangeListener(this.onChange); - }, - - componentWillUnmount: function() { - self.removeChangeListener(this.onChange); - } - - }; - } - - /** - * Emits change event. - */ - emitChange() { - this.emit(CHANGE_EVENT); - } - - /** - * Adds a change listener. - * - * @param {function} callback Callback function. - */ - addChangeListener(callback) { - this.on(CHANGE_EVENT, callback); - } - - /** - * Removes a change listener. - * - * @param {function} callback Callback function. - */ - removeChangeListener(callback) { - this.removeListener(CHANGE_EVENT, callback); - } - -} - -module.exports = Store; diff --git a/src/components/pages/index.html b/src/index.html similarity index 91% rename from src/components/pages/index.html rename to src/index.html index a26506e68..1c0f08151 100644 --- a/src/components/pages/index.html +++ b/src/index.html @@ -3,17 +3,17 @@ - <%= title %> - + <%- title %> + + <%= body %> -