├── public
└── .gitkeep
├── Procfile
├── index.js
├── client
├── styles.scss
├── modules
│ └── foo
│ │ ├── actions.js
│ │ ├── actions
│ │ ├── sample.js
│ │ └── sampleAsync.js
│ │ ├── components
│ │ └── sampleComponent.js
│ │ └── reducers.js
├── config.dev.js
├── config.prod.js
├── reducers.js
├── test
│ └── tautological.spec.js
├── components
│ ├── layouts
│ │ └── mainLayout.js
│ ├── widgets
│ │ └── header.js
│ └── pages
│ │ ├── info.js
│ │ ├── home.js
│ │ └── credits.js
├── routes.js
├── app.js
└── store.js
├── tests
└── helpers
│ └── plugins.js
├── server
├── views
│ ├── error.ejs
│ └── index.ejs
├── test
│ └── tautological.spec.js
├── routes
│ └── index.js
├── config.js
├── app.js
├── routes.js
└── bin
│ └── www
├── conf
└── platforms
│ ├── targets
│ ├── development.js
│ ├── production.js
│ └── default.js
│ └── index.js
├── app.json
├── tasks
├── watch.js
├── copyHtml.js
├── build.js
├── eslint.js
├── watchPackageJSON.js
├── watchClient.js
├── mocha.js
├── css.js
├── buildSrc.js
└── watchBrowserify.js
├── .editorconfig
├── .gitignore
├── Gulpfile.js
├── .eslintrc
├── .babelrc
├── package.json
└── README.md
/public/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: node index.js
2 |
3 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | require('./server/bin/www');
--------------------------------------------------------------------------------
/client/styles.scss:
--------------------------------------------------------------------------------
1 | body {
2 | //background: darkcyan;
3 | }
--------------------------------------------------------------------------------
/tests/helpers/plugins.js:
--------------------------------------------------------------------------------
1 | var should = require('chai').should();
--------------------------------------------------------------------------------
/server/views/error.ejs:
--------------------------------------------------------------------------------
1 |
<%= message %>
2 | <%= error.status %>
3 | <%= error.stack %>
4 |
--------------------------------------------------------------------------------
/client/modules/foo/actions.js:
--------------------------------------------------------------------------------
1 | export { sampleAction } from './actions/sample';
2 | export { sampleAsyncAction } from './actions/sampleAsync';
3 |
--------------------------------------------------------------------------------
/client/config.dev.js:
--------------------------------------------------------------------------------
1 | export default {
2 | server: {
3 | url: '//localhost:5000'
4 | },
5 | dev: {
6 | logActions: true
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/client/config.prod.js:
--------------------------------------------------------------------------------
1 | export default {
2 | server: {
3 | url: '//localhost:5000'
4 | },
5 | dev: {
6 | logActions: false
7 | }
8 | };
9 |
--------------------------------------------------------------------------------
/client/reducers.js:
--------------------------------------------------------------------------------
1 | // import fooReducer from 'modules/foo/reducers';
2 | // Add here the reducers
3 | export default {
4 | // foo: fooReducer
5 | };
6 |
--------------------------------------------------------------------------------
/client/modules/foo/actions/sample.js:
--------------------------------------------------------------------------------
1 | export function sampleAction() {
2 | return (dispatch) => {
3 | dispatch({
4 | type: 'FOO:ACTION_SAMPLE'
5 | });
6 | };
7 | }
8 |
--------------------------------------------------------------------------------
/client/test/tautological.spec.js:
--------------------------------------------------------------------------------
1 | describe('Demo test suite for the client', () => {
2 | it('Should pass the tautological test', () => {
3 | (true).should.be.equal(true);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/server/test/tautological.spec.js:
--------------------------------------------------------------------------------
1 | describe('Demo test suite for the server', () => {
2 | it('Should pass the tautological test', () => {
3 | (true).should.be.equal(true);
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/conf/platforms/targets/development.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 | const defaultsDeep = require('lodash.defaultsdeep');
4 | const defaultValues = require('./default');
5 |
6 | module.exports = () => defaultsDeep({}, defaultValues);
7 |
--------------------------------------------------------------------------------
/client/modules/foo/components/sampleComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class SampleComponent extends React.Component {
4 | render() {
5 | return (
6 | This is a sample component
7 | );
8 | }
9 | }
10 |
11 | export default SampleComponent;
12 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Node.js Getting Started",
3 | "description": "A barebones Node.js app using Express 4",
4 | "repository": "https://github.com/heroku/node-js-getting-started",
5 | "logo": "http://node-js-sample.herokuapp.com/node.svg",
6 | "keywords": ["node", "express", "static"],
7 | "image": "heroku/nodejs"
8 | }
9 |
--------------------------------------------------------------------------------
/tasks/watch.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = () => ({
4 | watch: {
5 | seq: [
6 | 'build',
7 | [
8 | 'watchClient',
9 | 'watchPackageJSON',
10 | 'watchBrowserify'
11 | ]
12 | ],
13 | description: 'Build the sources and run the watcher with hot reload features.'
14 | }
15 | });
16 |
--------------------------------------------------------------------------------
/client/components/layouts/mainLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Header from '../widgets/header';
3 |
4 | class MainLayout extends React.Component {
5 | render() {
6 | return (
7 |
8 |
9 | {this.props.children}
10 |
11 | );
12 | }
13 | }
14 |
15 | export default MainLayout;
16 |
--------------------------------------------------------------------------------
/tasks/copyHtml.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp, config) => {
4 | const tasks = {
5 | 'copy-html': {
6 | fn: copyHtmlTask,
7 | help: 'Copy the html files'
8 | }
9 | };
10 | return tasks;
11 |
12 | function copyHtmlTask() {
13 | gulp.src('client/*.html')
14 | .pipe(gulp.dest(config.folders.build));
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/client/modules/foo/reducers.js:
--------------------------------------------------------------------------------
1 | import Immutable from 'immutable';
2 | const defaultState = Immutable.Map(
3 | {
4 | bar: 10,
5 | baz: 20
6 | }
7 | );
8 |
9 | export default (state = defaultState, action) => {
10 | switch (action.type) {
11 | case 'FOO:ACTION_SAMPLE':
12 | return state.set('pop', 30);
13 | default :
14 | return state;
15 | }
16 | };
17 |
--------------------------------------------------------------------------------
/conf/platforms/targets/production.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const defaultsDeep = require('lodash.defaultsdeep');
3 | const defaultValues = require('./default');
4 |
5 | module.exports = () => defaultsDeep({}, defaultValues, {
6 | build: {
7 | uglify: {
8 | global: true,
9 | sourcemap: false,
10 | mangle: true,
11 | compress: true
12 | }
13 | }
14 | });
15 |
--------------------------------------------------------------------------------
/tasks/build.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = () => {
4 | const tasks = {
5 | build: {
6 | seq: [
7 | 'css',
8 | 'build-src',
9 | 'copy-html'
10 | ],
11 | description: 'Bundle the client package and copy it in the public folder. ' +
12 | 'Run \'npm run build\' to prepare a production environment.'
13 | }
14 | };
15 | return tasks;
16 | };
17 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | end_of_line = lf
9 | insert_final_newline = true
10 |
11 | # Matches multiple files with brace expansion notation
12 | # Set default charset
13 | [*.{js,html,css,scss}]
14 | charset = utf-8
15 | indent_style = space
16 | indent_size = 2
17 |
--------------------------------------------------------------------------------
/server/routes/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const express = require('express');
4 | const router = express.Router();
5 | const config = require('simple-registry').get('config');
6 |
7 | /* GET home page. */
8 | router.get('/', (req, res) => {
9 | res.render('index', {
10 | title: config.app.title,
11 | links: config.services.html.links,
12 | scripts: config.services.html.scripts
13 | });
14 | });
15 |
16 | module.exports = router;
17 |
--------------------------------------------------------------------------------
/tasks/eslint.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp) => {
4 | const tasks = {
5 | eslint: {
6 | fn: eslintTask,
7 | description: 'Run linting on the project sources.'
8 | }
9 | };
10 |
11 | return tasks;
12 |
13 | function eslintTask() {
14 | const eslint = require('gulp-eslint');
15 | return gulp.src(['client/**/*.js', 'tasks/**/*.js', 'server/**/*.js'])
16 | .pipe(eslint())
17 | .pipe(eslint.format());
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # npm ignore
2 | npm-debug.log
3 | node_modules
4 | mocha.json
5 | coverage
6 |
7 | .tmp
8 |
9 | # Sublime Text
10 | *.sublime-workspace
11 |
12 | # vim
13 | *.swp
14 |
15 | # kdiff3
16 | *.orig
17 |
18 | # OSX
19 | Thumbs.db
20 | .DS_Store
21 |
22 | # Packages
23 | *.jar
24 | *.zip
25 |
26 | # Eclipse files
27 | *.classpath
28 | *.project
29 | *.settings
30 | .checkstyle
31 |
32 | # IntelliJ IDEA files
33 | .idea/
34 | *.iml
35 | /.metadata
36 |
37 | #floobits
38 | .floo
39 | .flooignore
40 |
41 |
--------------------------------------------------------------------------------
/tasks/watchPackageJSON.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp) => {
4 | return {
5 | watchPackageJSON: {
6 | fn: watchPackageJSON,
7 | help: 'Watches the package.json for changes.'
8 | }
9 | };
10 |
11 | function watchPackageJSON() {
12 | gulp
13 | .watch('./package.json', event => {
14 | console.log(`File ${event.path} was ${event.type}, running tasks...`);
15 | })
16 | .on('error', error => {
17 | console.error(error);
18 | });
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/tasks/watchClient.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp) => {
4 | return {
5 | watchClient: {
6 | fn: watchClient,
7 | help: 'Watches the client files for changes.'
8 | }
9 | };
10 |
11 | function watchClient() {
12 | gulp
13 | .watch('client/**/*.html', ['copy-html'])
14 | .on('error', error => {
15 | console.error(error);
16 | });
17 |
18 | gulp
19 | .watch('client/**/*.css', ['css'])
20 | .on('error', error => {
21 | console.error(error);
22 | });
23 | }
24 | };
25 |
--------------------------------------------------------------------------------
/server/views/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= title %>
5 | <% for(var i = 0; i < links.length; i++) { %>
6 | <%= key %>="<%= links[i][key] %>" <% }) %> /><% } %>
7 |
8 |
9 |
10 |
The app is working
11 |
12 |
13 | <% for(var i = 0; i < scripts.length; i++) { %>
14 | <% } %>
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const gulp = require('gulp');
4 | const taskLoader = require('gulp-commonjs-tasks/task-loader');
5 |
6 | const consolePrefix = require('console-prefix');
7 | const platforms = require('./conf/platforms')({
8 | console: require('console-prefix')('[platforms-bootstrap]')
9 | });
10 | const options = {
11 | console: consolePrefix('[platforms]')
12 | };
13 | const config = platforms.current()(options);
14 |
15 | // load tasks
16 | const tasksContext = taskLoader.load('./tasks', gulp, config);
17 |
18 | // Add the gulp help task
19 | tasksContext.addHelpTask();
20 |
--------------------------------------------------------------------------------
/client/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route } from 'react-router';
3 |
4 | import MainLayout from './components/layouts/mainLayout';
5 | import HomePage from './components/pages/home';
6 | import InfoPage from './components/pages/info';
7 | import CreditsPage from './components/pages/credits';
8 |
9 | export default (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | );
18 |
--------------------------------------------------------------------------------
/client/app.js:
--------------------------------------------------------------------------------
1 | import 'babel-polyfill';
2 |
3 | import React from 'react';
4 | import ReactDOM from 'react-dom';
5 |
6 | import injectTapEventPlugin from 'react-tap-event-plugin';
7 |
8 | import { Provider } from 'react-redux';
9 | import { Router } from 'react-router';
10 |
11 | import { store, history } from './store';
12 | import routes from './routes';
13 |
14 | // Needed before react 1.0 release
15 | injectTapEventPlugin();
16 |
17 | ReactDOM.render(
18 |
19 |
20 | {routes}
21 |
22 |
23 | , document.getElementById('app')
24 | );
25 |
--------------------------------------------------------------------------------
/conf/platforms/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (options) => {
4 | const newOptions =
5 | require('lodash')
6 | .merge({
7 | default: 'development',
8 | targets: require('path').join(__dirname, 'targets'),
9 | transformResult: (config, platformTarget) =>
10 | () => {
11 | const args = Array.prototype.splice.call(arguments, 0);
12 | return require('lodash').merge({}, config.apply(null, args), {
13 | name: platformTarget
14 | });
15 | }
16 | }, options);
17 |
18 | return require('platform-config')(newOptions);
19 | };
20 |
--------------------------------------------------------------------------------
/server/config.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const consolePrefix = require('console-prefix');
3 |
4 | const platforms = require('../conf/platforms')({
5 | console: require('console-prefix')('[platforms-bootstrap]')
6 | });
7 |
8 | const options = {
9 | console: consolePrefix('[platforms]')
10 | };
11 |
12 | const config = platforms.current()(options);
13 |
14 | console.log('-------------------------------------------------------------');
15 | console.log('Great, as far as we can tell we have a valid config:', config.name);
16 | console.log('Let\'s move on');
17 | console.log('-------------------------------------------------------------');
18 | module.exports = config;
19 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "jasmine": true,
7 | "phantomjs": true,
8 | "es6": true
9 | },
10 | "rules": {
11 | "no-use-before-define": 0,
12 | "no-console": 0,
13 | "new-cap": 0,
14 | "consistent-return": 0,
15 | "comma-dangle": [
16 | 2,
17 | "never"
18 | ],
19 | "strict": 0,
20 | "max-len": [
21 | 1,
22 | 120
23 | ],
24 | "react/prop-types": 0,
25 | "react/jsx-boolean-value": [
26 | 1,
27 | "always"
28 | ],
29 | "react/no-multi-comp": 0,
30 | "react/jsx-no-bind": 0,
31 | "react/prefer-stateless-function": 0
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const express = require('express');
3 | const path = require('path');
4 | const logger = require('morgan');
5 | const cookieParser = require('cookie-parser');
6 | const bodyParser = require('body-parser');
7 | const app = express();
8 |
9 | // view engine setup
10 | app.set('views', path.join(__dirname, 'views'));
11 | app.set('view engine', 'ejs');
12 |
13 | // uncomment after placing your favicon in /public
14 | app.use(logger('dev'));
15 | app.use(bodyParser.json());
16 | app.use(bodyParser.urlencoded({ extended: false }));
17 | app.use(cookieParser());
18 | app.use(express.static(path.join(__dirname, '../public')));
19 |
20 | require('./routes')(app);
21 |
22 | module.exports = app;
23 |
--------------------------------------------------------------------------------
/tasks/mocha.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp) => {
4 | const tasks = {
5 | mocha: {
6 | fn: mochaTask,
7 | description: 'Run the unit test.'
8 | }
9 | };
10 | return tasks;
11 |
12 | function mochaTask(done) {
13 | const mocha = require('gulp-mocha-co');
14 | const babel = require('babel-register');
15 |
16 | gulp.src(['./tests/helpers/plugins.js',
17 | './client/**/test/*.spec.js',
18 | './server/**/test/*.spec.js',
19 | './tests/**/*.spec.js'
20 | ], { read: false })
21 | .pipe(
22 | mocha({
23 | reporter: 'spec',
24 | compilers: {
25 | js: babel
26 | }
27 | })
28 | )
29 | .on('end', done)
30 | .on('error', err => {
31 | console.log(err);
32 | done();
33 | });
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/server/routes.js:
--------------------------------------------------------------------------------
1 | const routes = require('./routes/index');
2 |
3 | module.exports = (app) => {
4 | app.use('/', routes);
5 |
6 | // catch 404 and forward to error handler
7 | app.use((req, res, next) => {
8 | const err = new Error('Not Found');
9 | err.status = 404;
10 | next(err);
11 | });
12 |
13 | // development error handler
14 | // will print stacktrace
15 | if (app.get('env') === 'development') {
16 | app.use((err, req, res) => {
17 | res.status(err.status || 500);
18 | res.render('error', {
19 | message: err.message,
20 | error: err
21 | });
22 | });
23 | }
24 |
25 | // production error handler
26 | // no stacktraces leaked to user
27 | app.use((err, req, res) => {
28 | res.status(err.status || 500);
29 | res.render('error', {
30 | message: err.message,
31 | error: {}
32 | });
33 | });
34 | };
35 |
--------------------------------------------------------------------------------
/conf/platforms/targets/default.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const path = require('path');
3 |
4 | module.exports = {
5 | app: {
6 | name: 'Some application name',
7 | title: 'Some application title'
8 | },
9 | server: {
10 | port: 5000
11 | },
12 | folders: {
13 | build: path.join(__dirname, '../../../public')
14 | },
15 | build: {},
16 | services: {
17 | html: {
18 | links: [
19 | {
20 | rel: 'stylesheet',
21 | crossorigin: 'anonymous',
22 | href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css',
23 | integrity: 'sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7'
24 | },
25 | { rel: 'stylesheet', type: 'text/css', href: '/styles.css' }
26 | ],
27 | scripts: [
28 | { type: 'text/javascript', src: '/main.js' }
29 | ]
30 | }
31 | }
32 | };
33 |
--------------------------------------------------------------------------------
/client/modules/foo/actions/sampleAsync.js:
--------------------------------------------------------------------------------
1 | import qwest from 'qwest';
2 | import config from 'config';
3 |
4 | export function sampleAsyncAction(value) {
5 | return (dispatch) => {
6 | dispatch({
7 | type: 'FOO:ASYNC_ACTION_START'
8 | });
9 | const url = `${config.server.url}/foo?value=${value}`;
10 | qwest
11 | .get(url)
12 | .then(
13 | (xhr, response) => {
14 | console.log(response);
15 | if (xhr.status !== 200) {
16 | return dispatch({
17 | type: 'FOO:ASYNC_ACTION_FAILED'
18 | });
19 | }
20 | // Manage the response
21 | if (response.success === true) {
22 | dispatch({
23 | type: 'FOO:ASYNC_ACTION_SUCCESS',
24 | val1: response.val1,
25 | val2: response.val2
26 | });
27 | }
28 | }
29 | );
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/client/components/widgets/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PageHeader, Grid, Row, Col, Nav, NavItem } from 'react-bootstrap';
3 | import { Link } from 'react-router';
4 |
5 | class Header extends React.Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 | Sample
13 | A sample app
14 |
15 |
16 |
17 | Home
18 | Info
19 | Credits
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | export default Header;
30 |
--------------------------------------------------------------------------------
/tasks/css.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp, config) => {
4 | const tasks = {
5 | css: {
6 | fn: cssTask,
7 | help: 'Create the css'
8 | }
9 | };
10 | return tasks;
11 |
12 | function cssTask() {
13 | const sass = require('gulp-sass');
14 | const cssGlobbing = require('gulp-css-globbing');
15 |
16 | return gulp.src(['client/styles.scss'])
17 | .pipe(cssGlobbing({
18 | extensions: ['.css', '.scss'],
19 | ignoreFolders: ['../styles'],
20 | autoReplaceBlock: {
21 | onOff: true,
22 | globBlockBegin: 'cssGlobbingBegin',
23 | globBlockEnd: 'cssGlobbingEnd',
24 | globBlockContents: '../**/*.scss'
25 | },
26 | scssImportPath: {
27 | leading_underscore: false,
28 | filename_extension: false
29 | }
30 | }))
31 | .pipe(sass().on('error', sass.logError))
32 | .pipe(gulp.dest(config.folders.build));
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/client/store.js:
--------------------------------------------------------------------------------
1 | import config from 'config';
2 | import thunkMiddleware from 'redux-thunk';
3 | import logMiddleware from 'redux-logger';
4 | import { createStore, combineReducers, applyMiddleware, compose } from 'redux';
5 | import { syncHistoryWithStore, routerReducer } from 'react-router-redux';
6 | import { useRouterHistory } from 'react-router';
7 | import { createHashHistory } from 'history';
8 |
9 | import reducers from './reducers';
10 |
11 | const reducer = combineReducers(Object.assign({}, reducers, {
12 | routing: routerReducer
13 | }));
14 |
15 | const history = useRouterHistory(createHashHistory)({ queryKey: false });
16 |
17 |
18 | const middlewares = [];
19 | // middlewares.push(reduxRouterMiddleware);
20 | middlewares.push(thunkMiddleware);
21 | if (config.dev.logActions) {
22 | middlewares.push(logMiddleware());
23 | }
24 | export const store = compose(
25 | applyMiddleware.apply(null, middlewares)
26 | )(createStore)(reducer);
27 |
28 | syncHistoryWithStore(history, store);
29 |
--------------------------------------------------------------------------------
/client/components/pages/info.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Jumbotron, Grid, Row, Col } from 'react-bootstrap';
3 |
4 | class InfoPage extends React.Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | Info
13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
14 | et
15 | dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
16 | aliquip ex
17 | ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
18 | fugiat
19 | nulla pariatur.
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | export default InfoPage;
30 |
--------------------------------------------------------------------------------
/tasks/buildSrc.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp, config) => {
4 | const tasks = {
5 | 'build-src': {
6 | fn: buildSrcTask,
7 | help: 'Build the src of the project'
8 | }
9 | };
10 |
11 | function buildSrcTask(done) {
12 | const browserify = require('browserify');
13 | const babelify = require('babelify');
14 | const uglifyify = require('uglifyify');
15 | const vinylSourceStream = require('vinyl-source-stream');
16 | const depCaseVerify = require('dep-case-verify');
17 |
18 | const browserifyBundle =
19 | browserify('client/app.js', { debug: false })
20 | .transform(babelify)
21 | .plugin(depCaseVerify);
22 |
23 | if (config.build.uglify) {
24 | browserifyBundle.transform(config.build.uglify, uglifyify);
25 | }
26 |
27 | browserifyBundle.bundle()
28 | .pipe(vinylSourceStream('main.js'))
29 | .pipe(gulp.dest(config.folders.build))
30 | .on('end', done);
31 | }
32 |
33 | return tasks;
34 | };
35 |
--------------------------------------------------------------------------------
/client/components/pages/home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Jumbotron, Grid, Row, Col } from 'react-bootstrap';
3 |
4 | class HomePage extends React.Component {
5 | render() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | Edit me!!
13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
14 | et
15 | dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
16 | aliquip ex
17 | ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
18 | fugiat
19 | nulla pariatur.
20 |
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | export default HomePage;
30 |
--------------------------------------------------------------------------------
/client/components/pages/credits.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { store } from '../../store';
3 | import { routeActions } from 'react-router-redux';
4 | import { Jumbotron, Grid, Row, Col, Button } from 'react-bootstrap';
5 |
6 | class CreditsPage extends React.Component {
7 |
8 | handleGoToInfo() {
9 | store.dispatch(routeActions.replace('/info'));
10 | }
11 |
12 | render() {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | Credits
20 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore
21 | et
22 | dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
23 | aliquip ex
24 | ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
25 | fugiat
26 | nulla pariatur.
27 |
28 | Info
29 |
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | export default CreditsPage;
38 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "es2015",
4 | "stage-0",
5 | "react"
6 | ],
7 | "env": {
8 | "development": {
9 | "plugins": [
10 | [
11 | "babel-plugin-module-alias",
12 | [
13 | {
14 | "src": "./client/modules",
15 | "expose": "modules"
16 | },
17 | {
18 | "src": "./client/config.dev.js",
19 | "expose": "config"
20 | }
21 | ]
22 | ],
23 | [
24 | "react-transform",
25 | {
26 | "transforms": [
27 | {
28 | "transform": "react-transform-hmr",
29 | "imports": [
30 | "react"
31 | ],
32 | "locals": [
33 | "module"
34 | ]
35 | },
36 | {
37 | "transform": "react-transform-catch-errors",
38 | "imports": [
39 | "react",
40 | "redbox-react"
41 | ]
42 | }
43 | ]
44 | }
45 | ]
46 | ]
47 | },
48 | "production": {
49 | "plugins": [
50 | [
51 | "babel-plugin-module-alias",
52 | [
53 | {
54 | "src": "./client/modules",
55 | "expose": "modules"
56 | },
57 | {
58 | "src": "./client/config.prod.js",
59 | "expose": "config"
60 | }
61 | ]
62 | ]
63 | ]
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/tasks/watchBrowserify.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = (gulp, config) => {
4 | return {
5 | watchBrowserify: {
6 | fn: watchBrowserify,
7 | help: 'Watches the browserify build.'
8 | }
9 | };
10 |
11 | function watchBrowserify() {
12 | const babelify = require('babelify');
13 | const browserify = require('browserify');
14 | const browserifyHMR = require('browserify-hmr');
15 | const uglifyify = require('uglifyify');
16 | const vinylSourceStream = require('vinyl-source-stream');
17 | const watchify = require('watchify');
18 | const gutil = require('gulp-util');
19 | const depCaseVerify = require('dep-case-verify');
20 |
21 | const browserifyBundle = browserify('client/app.js', {
22 | cache: {},
23 | packageCache: {},
24 | debug: true
25 | });
26 |
27 | // Transforms
28 | browserifyBundle.transform(babelify);
29 |
30 | if (config.uglify) {
31 | browserifyBundle.transform(config.uglify, uglifyify);
32 | }
33 |
34 | // Plugins
35 | browserifyBundle.plugin(depCaseVerify);
36 | browserifyBundle.plugin(browserifyHMR);
37 | browserifyBundle.plugin(watchify);
38 |
39 | function bundle() {
40 | browserifyBundle.bundle()
41 | .pipe(vinylSourceStream('main.js'))
42 | .pipe(gulp.dest(config.folders.build))
43 | .on('end', error => {
44 | if (error) {
45 | console.error(error);
46 | }
47 | });
48 | }
49 |
50 | browserifyBundle
51 | .on('log', (message) => {
52 | gutil.log('browserify', message);
53 | });
54 |
55 | browserifyBundle
56 | .on('update', bundle);
57 |
58 | bundle();
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/server/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | /**
5 | * Module dependencies.
6 | */
7 | const config = require('../config');
8 | const registry = require('simple-registry');
9 | registry.set('config', config);
10 | const app = require('../app');
11 | const debug = require('debug')(config.app.name + ':server');
12 | const http = require('http');
13 |
14 | /**
15 | * Get port from environment and store in Express.
16 | */
17 | const port = normalizePort(process.env.PORT || '5000');
18 | app.set('port', port);
19 |
20 | /**
21 | * Create HTTP server.
22 | */
23 |
24 | const server = http.createServer(app);
25 |
26 | /**
27 | * Listen on provided port, on all network interfaces.
28 | */
29 | app.listen(
30 | app.get('port'),
31 | () => console.log('Node app is running on port', app.get('port'))
32 | );
33 | server.on('error', onError);
34 | server.on('listening', onListening);
35 |
36 |
37 | /**
38 | * Normalize a port into a number, string, or false.
39 | */
40 |
41 | function normalizePort(val) {
42 | const tmpPort = parseInt(val, 10);
43 |
44 | if (isNaN(tmpPort)) {
45 | // named pipe
46 | return val;
47 | }
48 |
49 | if (tmpPort >= 0) {
50 | // port number
51 | return tmpPort;
52 | }
53 |
54 | return false;
55 | }
56 |
57 | /**
58 | * Event listener for HTTP server "error" event.
59 | */
60 |
61 | function onError(error) {
62 | if (error.syscall !== 'listen') {
63 | throw error;
64 | }
65 |
66 | const bind = typeof port === 'string'
67 | ? 'Pipe ' + port
68 | : 'Port ' + port;
69 |
70 | // handle specific listen errors with friendly messages
71 | switch (error.code) {
72 | case 'EACCES':
73 | console.error(bind + ' requires elevated privileges');
74 | process.exit(1);
75 | break;
76 | case 'EADDRINUSE':
77 | console.error(bind + ' is already in use');
78 | process.exit(1);
79 | break;
80 | default:
81 | throw error;
82 | }
83 | }
84 |
85 | /**
86 | * Event listener for HTTP server "listening" event.
87 | */
88 |
89 | function onListening() {
90 | const addr = server.address();
91 | const bind = typeof addr === 'string'
92 | ? 'pipe ' + addr
93 | : 'port ' + addr.port;
94 | debug('Listening on ' + bind);
95 | }
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-redux-scaffold",
3 | "version": "1.1.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node index.js",
8 | "test": "NODE_ENV=production ./node_modules/gulp/bin/gulp.js mocha",
9 | "build": "NODE_ENV=production ./node_modules/gulp/bin/gulp.js build"
10 | },
11 | "author": "Davide Fiorello ",
12 | "license": "ISC",
13 | "dependencies": {
14 | "babel-polyfill": "^6.5.0",
15 | "bluebird": "^3.2.2",
16 | "body-parser": "^1.15.0",
17 | "console-prefix": "^1.0.0",
18 | "cookie-parser": "^1.4.0",
19 | "ejs": "^2.4.1",
20 | "express": "^4.13.4",
21 | "history": "^2.0.0",
22 | "immutable": "^3.7.6",
23 | "lodash": "^4.3.0",
24 | "lodash.defaultsdeep": "^4.1.0",
25 | "morgan": "^1.6.1",
26 | "platform-config": "^0.1.5",
27 | "qwest": "^4.0.0",
28 | "react": "^0.14.7",
29 | "react-bootstrap": "^0.28.3",
30 | "react-cookie": "^0.4.3",
31 | "react-dom": "^0.14.7",
32 | "react-redux": "^4.4.0",
33 | "react-router": "^2.0.0",
34 | "react-router-redux": "^4.0.1",
35 | "react-tap-event-plugin": "^0.2.2",
36 | "react-transform-catch-errors": "^1.0.2",
37 | "react-transform-hmr": "^1.0.2",
38 | "redux": "^3.3.1",
39 | "redux-logger": "^2.5.2",
40 | "redux-thunk": "^2.0.1",
41 | "services-config": "^0.1.6",
42 | "simple-registry": "^0.2.2"
43 | },
44 | "devDependencies": {
45 | "babel": "^6.5.2",
46 | "babel-core": "^6.5.1",
47 | "babel-eslint": "^6.0.2",
48 | "babel-plugin-module-alias": "^1.2.0",
49 | "babel-plugin-react-transform": "^2.0.0",
50 | "babel-preset-es2015": "^6.5.0",
51 | "babel-preset-react": "^6.5.0",
52 | "babel-preset-stage-0": "^6.5.0",
53 | "babel-register": "^6.5.2",
54 | "babelify": "^7.2.0",
55 | "browserify": "^13.0.0",
56 | "browserify-hmr": "^0.3.1",
57 | "browserify-transform-tools": "^1.5.0",
58 | "chai": "^3.5.0",
59 | "chai-as-promised": "^5.2.0",
60 | "del": "^2.2.0",
61 | "dep-case-verify": "^2.0.0",
62 | "eslint-config-airbnb": "^6.2.0",
63 | "eslint-plugin-react": "^4.3.0",
64 | "gulp": "^3.9.1",
65 | "gulp-commonjs-tasks": "^1.2.0",
66 | "gulp-css-globbing": "^0.1.8",
67 | "gulp-eslint": "^2.0.0",
68 | "gulp-mocha-co": "^0.4.1-co.3",
69 | "gulp-sass": "^2.2.0",
70 | "redbox-react": "^1.2.2",
71 | "rewire": "^2.5.1",
72 | "sinon": "^1.17.3",
73 | "sinon-chai": "^2.8.0",
74 | "uglifyify": "^3.0.1",
75 | "vinyl-source-stream": "^1.1.0",
76 | "watchify": "^3.6.1"
77 | },
78 | "engines": {
79 | "node": "5.3.0"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-redux-scaffold
2 |
3 | ## TLTR
4 | A React/Redux scaffold project with a nodeJs server and a babel/browserify building system.
5 |
6 | ** requires node > 5.x **
7 |
8 | # Quick Start
9 |
10 | ### Just to check that everything is working
11 | ```
12 | # Install the node dependencies
13 | npm install
14 |
15 | # Bundle the client code
16 | npm run build
17 |
18 | # Run the node server
19 | npm start
20 |
21 | # Open the web browser at http://localhost:5000
22 | # You should then see a sample 'bootstrap' app
23 | ```
24 |
25 | ### Ok, is working but... I want to start to write my code ASAP
26 |
27 | Everything you need to start to do some experiment and start to write your react app is:
28 |
29 | ##### Run the server
30 | Open a terminal and in the app folder run:
31 |
32 | ```npm start```
33 |
34 | ##### Run the watcher
35 | Open another terminal and in the app folder
36 |
37 | ```gulp watch```
38 |
39 | ##### Open your browser at http://localhost:5000
40 | And you should see a sample boostrap app with a "Edit me!!" title in the center of the page.
41 |
42 | ##### Edit the code
43 | * Open your preferred IDE and start to open the ```client``` folder.
44 | * Open the `client/components/pages/home.js` file.
45 | * Change the `Edit me!! ` text in something different.
46 | * Check the browser (the content should automatically refresh)
47 |
48 | ##### Have a lot of fun!!
49 |
50 | # The project explained
51 |
52 | The project is built using many technologies.
53 |
54 | * Server . Is a simple nodeJs/express app that just serves the index file that load the client app. It could be used as well as a complete server app, adding all the features needed.
55 | * Client . Is a redux/react-router/react basic app. It uses ```qwest``` module as a Ajax library and `react-bootstrap` module as a front-end component framework.
56 | * Conf . The configuration is managed by the `platform-config` and the `service-config` modules.
57 | * Gulp tasks . The Gulp task are managed by the `gulp-commonjs-tasks` module, that allow keep the tasks clean and easy to mantain.
58 | * Test . The test environment is built using mocha.
59 | * Bundle . The bundle is made using a browserify/babel workflow that provide react hot reloading.
60 |
61 | ### File and folder structure
62 |
63 | ```
64 | .
65 | +-- client // React App
66 | | +-- ...
67 | +-- conf // System configration
68 | | +-- platforms // production/development/etc..
69 | | +-- ...
70 | | +-- services // Additional services configuration
71 | | +-- ...
72 | +-- public // Public folder, static files
73 | +-- server // NodeJs App
74 | | +-- ...
75 | +-- tasks // Gulp tasks
76 | | +-- ...
77 | +-- tests // Common test files
78 | | +-- helpers
79 | | +-- ...
80 | +-- index.js // NodeJS entry point
81 | +-- Gulpfile.js // Gulp entrypoint
82 | +-- .babelrc // Babel config file
83 | +-- .editornconfig // Editor config file
84 | +-- .eslintrc // Eslint config file
85 | +-- README.md // This file
86 |
87 | ```
88 |
89 | ## Build your bundle
90 | The development enviroment uses browserify to build the bundle. At the end of the process the client files are copied in the ```public``` folder.
91 |
92 | * main.js (Contains the app)
93 | * build.js (Contains all the external libs)
94 | * styles.css (Contains all the styles)
95 |
96 | #### Start the dev watch/build
97 |
98 | To start the watch task just run:
99 |
100 | ```
101 | gulp watch
102 | ```
103 | The task run a build process and enable the watcher. The task is run by default in `development` mode and add a `react-transform-hmr` transform to the babel bundle process for the hot reloading process.
104 | The build file doesn't work if the watcher is down.
105 | To build a no/watcher version run ```npm run build``` that simply uses the `production` environment and doesn't add the hot reload features to the code.
106 |
107 | #### Environments
108 |
109 | There are currently configured 2 environment:
110 |
111 | * production
112 | * development (default)
113 |
114 | To set the environment just add NODE_ENV=some_env before the commands.
115 |
116 | Eg. For the production building of the client
117 | ```
118 | NODE_ENV=production gulp build
119 | ```
120 | ##### Production
121 | * the client is uglified
122 | * the client doesn't require the watcher to run, hot reloader is disabled
123 |
124 | ##### Development
125 | * the client is not uglified
126 | * the client require the watcher to run, hot reloader is enabled
127 |
128 | The environments could be changed in the `config/platforms/targets` folder.
129 |
130 | * folders/build (The public folder location)
131 | * build/uglify (The uglify params, not uglified if missing)
132 |
133 | The babel parameters could be changed in the `.babelrc` file.
134 |
135 |
136 | ## Config
137 | The config is defined in th `config` folder.
138 |
139 | The app uses the node modules `platform-config` (https://github.com/sytac/platform-config)
140 |
141 | ```
142 | // The data related to the application
143 | app: {
144 | name: 'Some application name',
145 | title: 'Some application title'
146 | },
147 | // The server data used in node/express
148 | server: {
149 | port: 5000
150 | },
151 | // The folder containing the static files
152 | folders: {
153 | build: path.join(__dirname, '../../../public')
154 | },
155 | // The build/bundle parameter
156 | build: {
157 | uglify: {
158 | global: true,
159 | sourcemap: false,
160 | mangle: true,
161 | compress: true
162 | }
163 | },
164 | // Additional services
165 | services: {
166 | html: {
167 | // The list of -link- tags added to the index.html server file
168 | links: [
169 | {
170 | rel: 'stylesheet',
171 | crossorigin: 'anonymous',
172 | href: 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css',
173 | integrity: 'sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7'
174 | },
175 | { rel: 'stylesheet', type: 'text/css', href: '/styles.css' }
176 | ],
177 | // The list of -scripts- tags added to the index.html server file
178 | scripts: [
179 | { type: 'text/javascript', src: '/main.js' }
180 | ]
181 | }
182 | }
183 | ```
184 |
185 | ## Gulp / Bundle
186 | All the automation are created using Gulp with the task manager module `gulp-commonjs-tasks` (https://github.com/sytac/gulp-commonjs-tasks)
187 |
188 | The tasks are placed in `tasks` folder.
189 |
190 | Run `gulp help` to see le list of the tasks.
191 |
192 | * build Bundle the client package and copy it in the public folder. Run 'npm run build' to prepare a production environment.
193 | * eslint Run linting on the project sources.
194 | * mocha Run the unit test.
195 | * watch Build the sources and run the watcher with hot reload features.
196 |
--------------------------------------------------------------------------------