Initial Commit

This commit is contained in:
Ivan Diaz 2015-08-15 20:47:51 -03:00
commit d5cdf14e80
51 changed files with 1120 additions and 0 deletions

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
.DS_Store
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
bower_components
# Build files
build
.idea
.jshintrc

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014 Jake Marsh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

150
README.md Normal file
View File

@ -0,0 +1,150 @@
react-rocket
============
A boilerplate using ReactJS (along with React Router and RefluxJS), SASS, Gulp, and Browserify that also utilizes Gulp best practices from [this resource](https://github.com/greypants/gulp-starter).
---
### Getting up and running
1. Clone this repo from `https://github.com/jakemmarsh/react-rocket-boilerplate.git`
2. Run `npm install` from the root directory
3. Run `gulp dev` (may require installing Gulp globally `npm install gulp -g`)
4. Your browser will automatically be opened and directed to the browser-sync proxy address
5. To prepare assets for production, run the `gulp prod` task (Note: the production task does not fire up the express server, and won't provide you with browser-sync's live reloading. Simply use `gulp dev` during development. More information below)
Now that `gulp dev` is running, the server is up as well and serving files from the `/build` directory. Any changes in the `/app` directory will be automatically processed by Gulp and the changes will be injected to any open browsers pointed at the proxy address.
---
This boilerplate uses the latest versions of the following libraries:
- [ReactJS](https://github.com/facebook/react)
- [React Router](https://github.com/rackt/react-router)
- [RefluxJS](https://github.com/spoike/refluxjs)
- [SASS](http://sass-lang.com/)
- [Gulp](http://gulpjs.com/)
- [Browserify](http://browserify.org/)
Along with many Gulp libraries (these can be seen in either `package.json`, or at the top of each task in `/gulp/tasks/`).
---
### ReactJS
ReactJS is a "declarative, efficient, and flexible JavaScript library for building user interfaces."
- "**Just the UI**: Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project."
- "**Virtual DOM**: React uses a virtual DOM diff implementation for ultra-high performance. It can also render on the server using Node.js — no heavy browser DOM required."
- "**Data flow**: React implements one-way reactive data flow which reduces boilerplate and is easier to reason about than traditional data binding."
The ReactJS files are all located within `/app/js`, structured in the following manner:
```
/components
- Footer.js (Simple, static footer component rendered on all pages.)
- Header.js (Simple, static header component rendered on all pages.)
/mixins
- AuthenticatedRouteMixin.js (Example mixin that can be used to prevent users from accessing certain pages when not signed in.)
/pages
- HomePage.js (Example home page, serving as the default route.)
- NotFoundPage.js (Displayed any time the user requests a non-existent route.)
- SearchPage.js (Example search page to demonstrate navigation and individual pages.)
/utils
- APIUtils.js (General wrappers for API interaction via Superagent.)
- AuthAPI.js (Example functions for user authorization via a remote API.)
App.js (The main container component, rendered to the DOM and then responsible for rendering all pages.)
index.js (The main javascript file watched by Browserify, responsible for requiring the app and running the router.)
Routes.js (Defines the routing structure, along with each individual route path and handler.)
```
Each module you add to your project should be placed in the appropriate directory, and required in the necessary files. Once required, they will be automatically detected and compiled by Browserify (discussed later).
---
### RefluxJS
RefluxJS is a "simple library for unidirectional dataflow architecture inspired by ReactJS Flux."
"The pattern is composed of actions and data stores, where actions initiate new data to pass through data stores before coming back to the view components again. If a view component has an event that needs to make a change in the application's data stores, they need to do so by signalling to the stores through the actions available."
The RefluxJS files are also all locationed within `/app/js`, structured in the following manner:
```
/actions
- CurrentUserActions.js (Possible actions relevant to the current user. i.e. `checkAuth`, `login`, and `logout`.)
/stores
- CurrentUserStore.js (Responsible for storing the current user data, while listening to any `CurrentUserActions`.)
```
Each action or store you add to your project should be placed in the appropriate directory, and required in the necessary files. The necessary logic to trigger actions and listen to stores should also be added.
---
### React Router
React Router is a "complete routing library for React." It uses the JSX syntax to easily define route URLs and handlers, providing an easy-to-understand architecture and thus makes it easy to add new pages and routes.
The relevant files are all located within `/app/js`, structured in the following manner:
```
/pages (Each individual page to handle the defined routes and be rendered inside the app.)
App.js (The main component which is rendered to the DOM and responsible for rendering the current page.)
index.js (The main javascript file watched by Browserify, requiring the app and running the router.)
Routes.js (Defines the routing structure, along with each individual route path and handler.)
```
Any pages added to your project should be placed within the `app/js/pages` directory, and be required and assigned to a route inside `Routes.js`. If more complex nesting is required, any page can have a new `RouteHandler` as a child component.
---
### SASS
SASS, standing for 'Syntactically Awesome Style Sheets', is a CSS extension language adding things like extending, variables, and mixins to the language. This boilerplate provides a barebones file structure for your styles, with explicit imports into `app/styles/main.scss`. A Gulp task (discussed later) is provided for compilation and minification of the stylesheets based on this file.
---
### Browserify
Browserify is a Javascript file and module loader, allowing you to `require('modules')` in all of your files in the same manner as you would on the backend in a node.js environment. The bundling and compilation is then taken care of by Gulp, discussed below.
---
### Gulp
Gulp is a "streaming build system", providing a very fast and efficient method for running your build tasks.
##### Web Server
Gulp is used here to provide a very basic node/Express web server for viewing your application as you build. It serves static files from the `build/` directory, leaving routing up to React Router. All Gulp tasks are configured to automatically reload the server upon file changes. The application is served to `localhost:3000` once you run the `gulp` task. To take advantage of the fast live reload injection provided by browser-sync, you must load the site at the proxy address (which usually defaults to `server port + 1`, and within this boilerplate will by default be `localhost:3001`.)
##### Scripts
A number of build processes are automatically run on all of our Javascript files, run in the following order:
- **Browserify:** The main build process run on any Javascript files. This processes any of the `require('module')` statements, compiling the files as necessary.
- **Babelify:** This uses [babelJS](https://babeljs.io/) to provide support for ES6+ features, while simultaneously parsing and converting any existing JSX.
- **Debowerify:** Parses `require()` statements in your code, mapping them to `bower_components` when necessary. This allows you to use and include bower components just as you would npm modules.
- **Uglifyify:** This will minify the file created by Browserify and ngAnnotate.
The resulting file (`main.js`) is placed inside the directory `/build/js/`.
##### Styles
Just one task is necessary for processing our SASS files, and that is `gulp-sass`. This will read the `main.scss` file, processing and importing any dependencies and then minifying the result. This file (`main.css`) is placed inside the directory `/build/css/`.
- **gulp-autoprefixer:** Gulp is currently configured to run autoprefixer after compiling the scss. Autoprefixer will use the data based on current browser popularity and property support to apply prefixes for you. Autoprefixer is recommended by Google and used in Twitter, WordPress, Bootstrap and CodePen.
##### Images
Any images placed within `/app/images` will be automatically copied to the `build/images` directory. If running `gulp prod`, they will also be compressed via imagemin.
##### Watching files
All of the Gulp processes mentioned above are run automatically when any of the corresponding files in the `/app` directory are changed, and this is thanks to our Gulp watch tasks. Running `gulp dev` will begin watching all of these files, while also serving to `localhost:3000`, and with browser-sync proxy running at `localhost:3001` (by default).
##### Production Task
Just as there is the `gulp dev` task for development, there is also a `gulp prod` task for putting your project into a production-ready state. This will run each of the tasks, while also adding the image minification task discussed above. There is also an empty `gulp deploy` task that is included when running the production task. This deploy task can be fleshed out to automatically push your production-ready site to your hosting setup.
**Reminder:** When running the production task, gulp will not fire up the express server and serve your index.html. This task is designed to be run before the `deploy` step that may copy the files from `/build` to a production web server.

0
app/fonts/.gitkeep Normal file
View File

0
app/images/.gitkeep Normal file
View File

20
app/index.html Normal file
View File

@ -0,0 +1,20 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="description" content="">
<meta name="viewport" content="width=device-width">
<title>App Name</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<div id="app"></div>
<script src="/js/main.js"></script>
</body>
</html>

57
app/js/App.js Normal file
View File

@ -0,0 +1,57 @@
'use strict';
import React from 'react/addons';
import {ListenerMixin} from 'reflux';
import {RouteHandler} from 'react-router';
import CurrentUserActions from './actions/CurrentUserActions';
import CurrentUserStore from './stores/CurrentUserStore';
import Header from './components/Header';
import Footer from './components/Footer';
var App = React.createClass({
mixins: [ListenerMixin],
getInitialState() {
return {
currentUser: {}
};
},
_onUserChange(err, user) {
if ( err ) {
this.setState({ error: err });
} else {
this.setState({ currentUser: user || {}, error: null });
}
},
componentWillMount() {
console.log('About to mount App');
},
componentDidMount() {
this.listenTo(CurrentUserStore, this._onUserChange);
CurrentUserActions.checkLoginStatus();
},
render() {
return (
<div>
<Header />
<RouteHandler params={this.props.params}
query={this.props.query}
currentUser={this.state.currentUser} />
<Footer />
</div>
);
}
});
export default App;

22
app/js/Routes.js Normal file
View File

@ -0,0 +1,22 @@
'use strict';
import React from 'react/addons';
import {Route, NotFoundRoute, DefaultRoute} from 'react-router';
import App from './App';
import HomePage from './pages/HomePage';
import SearchPage from './pages/SearchPage';
import NotFoundPage from './pages/NotFoundPage';
export default (
<Route handler={App} path='/'>
<DefaultRoute handler={HomePage} />
<Route name='Home' path='/' handler={HomePage} />
<Route name='Search' path='/search' handler={SearchPage} />
<NotFoundRoute handler={NotFoundPage} />
</Route>
);

0
app/js/actions/.gitkeep Normal file
View File

View File

@ -0,0 +1,13 @@
'use strict';
import Reflux from 'reflux';
var CurrentUserActions = Reflux.createActions([
'checkLoginStatus',
'login',
'logout'
]);
export default CurrentUserActions;

View File

View File

@ -0,0 +1,19 @@
'use strict';
import React from 'react/addons';
var Footer = React.createClass({
render() {
return (
<footer>
Footer
</footer>
);
}
});
export default Footer;

View File

@ -0,0 +1,19 @@
'use strict';
import React from 'react/addons';
var Header = React.createClass({
render() {
return (
<header>
Header
</header>
);
}
});
export default Header;

15
app/js/index.js Normal file
View File

@ -0,0 +1,15 @@
'use strict';
import React from 'react/addons';
import Router from 'react-router';
import routes from './Routes';
if ( process.env.NODE_ENV !== 'production' ) {
// Enable React devtools
window.React = React;
}
Router.run(routes, Router.HistoryLocation, (Handler, state) => {
React.render(<Handler params={state.params} query={state.query} />, document.getElementById('app'));
});

0
app/js/mixins/.gitkeep Normal file
View File

View File

@ -0,0 +1,28 @@
'use strict';
import _ from 'lodash';
import {Navigation} from 'react-router';
import CurrentUserStore from '../stores/CurrentUserStore';
var AuthenticatedRouteMixin = {
mixins: [Navigation],
_checkIfRedirect() {
if ( _.isEmpty(CurrentUserStore.user) && CurrentUserStore.hasBeenChecked && this.isMounted() ) {
this.replaceWith('Home');
}
},
componentDidMount() {
this._checkIfRedirect();
},
componentDidUpdate() {
this._checkIfRedirect();
}
};
export default AuthenticatedRouteMixin;

33
app/js/pages/HomePage.js Normal file
View File

@ -0,0 +1,33 @@
'use strict';
import React from 'react/addons';
import {Link} from 'react-router';
import DocumentTitle from 'react-document-title';
var HomePage = React.createClass({
propTypes: {
currentUser: React.PropTypes.object.isRequired
},
render() {
return (
<DocumentTitle title="Home">
<section className="home-page">
<div>
Home
</div>
<div>
<Link to="Search">Search</Link>
</div>
</section>
</DocumentTitle>
);
}
});
export default HomePage;

View File

@ -0,0 +1,26 @@
'use strict';
import React from 'react/addons';
import DocumentTitle from 'react-document-title';
var NotFoundPage = React.createClass({
propTypes: {
currentUser: React.PropTypes.object.isRequired
},
render() {
return (
<DocumentTitle title="404: Not Found">
<section className="not-found-page">
Page Not Found
</section>
</DocumentTitle>
);
}
});
export default NotFoundPage;

View File

@ -0,0 +1,33 @@
'use strict';
import React from 'react/addons';
import {Link} from 'react-router';
import DocumentTitle from 'react-document-title';
var SearchPage = React.createClass({
propTypes: {
currentUser: React.PropTypes.object.isRequired
},
render() {
return (
<DocumentTitle title="Search">
<section className="search-page">
<div>
Search
</div>
<div>
<Link to="Home">Back to Home</Link>
</div>
</section>
</DocumentTitle>
);
}
});
export default SearchPage;

0
app/js/stores/.gitkeep Normal file
View File

View File

@ -0,0 +1,60 @@
'use strict';
import Reflux from 'reflux';
import CurrentUserActions from '../actions/CurrentUserActions';
import AuthAPI from '../utils/AuthAPI';
var CurrentUserStore = Reflux.createStore({
init() {
this.user = null;
this.hasBeenChecked = false;
this.listenTo(CurrentUserActions.checkLoginStatus, this.checkLoginStatus);
this.listenTo(CurrentUserActions.login, this.loginUser);
this.listenTo(CurrentUserActions.logout, this.logoutUser);
},
setUser(user, cb = function(){}) {
this.user = user;
cb(null, this.user);
this.trigger(null, this.user);
},
throwError(err, cb) {
cb(err);
this.trigger(err);
},
checkLoginStatus(cb = function(){}) {
if ( this.user ) {
this.setUser(this.user, cb);
} else {
AuthAPI.checkLoginStatus().then(user => {
this.hasBeenChecked = true;
this.setUser(user, cb);
}).catch(err => {
this.hasBeenChecked = true;
this.throwError(err, cb);
});
}
},
loginUser(user, cb = function(){}) {
AuthAPI.login(user).then(user => {
this.setUser(user, cb);
}).catch(err => {
this.throwError(err, cb);
});
},
logoutUser(cb = function(){}) {
AuthAPI.logout(this.user).then(() => {
this.setUser(null, cb);
});
}
});
export default CurrentUserStore;

0
app/js/utils/.gitkeep Normal file
View File

87
app/js/utils/APIUtils.js Normal file
View File

@ -0,0 +1,87 @@
'use strict';
import {camelizeKeys} from 'humps';
import request from 'superagent';
var APIUtils = {
root: '//localhost:3000/api/',
normalizeResponse(response) {
return camelizeKeys(response.body);
},
get(path) {
return new Promise((resolve, reject) => {
request.get(this.root + path)
.withCredentials()
.end((err, res) => {
if ( err || !res.ok ) {
reject(this.normalizeResponse(err || res));
} else {
resolve(this.normalizeResponse(res));
}
});
});
},
post(path, body) {
return new Promise((resolve, reject) => {
request.post(this.root + path, body)
.withCredentials()
.end((err, res) => {
console.log(err, res);
if ( err || !res.ok ) {
reject(this.normalizeResponse(err || res));
} else {
resolve(this.normalizeResponse(res));
}
});
});
},
patch(path, body) {
return new Promise((resolve, reject) => {
request.patch(this.root + path, body)
.withCredentials()
.end((err, res) => {
if ( err || !res.ok ) {
reject(this.normalizeResponse(err || res));
} else {
resolve(this.normalizeResponse(res));
}
});
});
},
put(path, body) {
return new Promise((resolve, reject) => {
request.put(this.root + path, body)
.withCredentials()
.end((err, res) => {
if ( err || !res.ok ) {
reject(this.normalizeResponse(err || res));
} else {
resolve(this.normalizeResponse(res));
}
});
});
},
del(path) {
return new Promise((resolve, reject) => {
request.del(this.root + path)
.withCredentials()
.end((err, res) => {
if ( err || !res.ok ) {
reject(this.normalizeResponse(err || res));
} else {
resolve(this.normalizeResponse(res));
}
});
});
}
};
export default APIUtils;

21
app/js/utils/AuthAPI.js Normal file
View File

@ -0,0 +1,21 @@
'use strict';
import APIUtils from './APIUtils';
var AuthAPI = {
checkLoginStatus() {
return APIUtils.get('auth/check');
},
login(user) {
return APIUtils.post('auth/login', user);
},
logout() {
return APIUtils.post('auth/logout');
}
};
export default AuthAPI;

6
app/styles/_base.scss Normal file
View File

@ -0,0 +1,6 @@
body {
font-family: Helvetica, sans-serif;
color: $font-color--dark;
background-color: $background--light;
padding: $half-space;
}

View File

@ -0,0 +1,42 @@
p {
margin-bottom: 1em;
}
.heading {
margin-bottom: 0.618em;
&.-large {
font-size: $font-size--lg;
font-weight: bold;
line-height: $half-space * 3 / 2;
}
&.-medium {
font-size: $font-size--md;
font-weight: normal;
line-height: $half-space;
}
&.-small {
font-size: $font-size--sm;
font-weight: bold;
line-height: $half-space * 2 / 3;
}
&.-smallest {
font-size: $font-size--xs;
font-weight: bold;
}
}
h1 {
@extend .heading.-large;
}
h2 {
@extend .heading.-medium;
}
h3 {
@extend .heading.-small;
}

19
app/styles/_vars.scss Normal file
View File

@ -0,0 +1,19 @@
// colors
$font-color--dark: #333;
$font-color--light: #fff;
$background--light: #eee;
$background--dark: #222;
$blue: #1f8de2;
$green: #1fe27b;
$red: #e21f3f;
// spacing
$full-space: 40px;
$half-space: 20px;
// font sizing
$font-size--xs: 10px;
$font-size--sm: 12px;
$font-size--md: 16px;
$font-size--lg: 24px;
$font-size--xl: 32px;

View File

View File

@ -0,0 +1,4 @@
footer {
height: 50px;
background: #eeeeee;
}

View File

@ -0,0 +1,4 @@
header {
height: 50px;
background: #eeeeee;
}

6
app/styles/main.scss Normal file
View File

@ -0,0 +1,6 @@
@import 'vars';
@import 'typography';
@import 'base';
@import 'elements/header';
@import 'elements/footer';

26
gulp/config.js Normal file
View File

@ -0,0 +1,26 @@
'use strict';
module.exports = {
'serverport': 3000,
'scripts': {
'src': './app/js/**/*.js',
'dest': './build/js/'
},
'images': {
'src': './app/images/**/*.{jpeg,jpg,png}',
'dest': './build/images/'
},
'styles': {
'src': './app/styles/**/*.scss',
'dest': './build/css/'
},
'sourceDir': './app/',
'buildDir': './build/'
};

9
gulp/index.js Normal file
View File

@ -0,0 +1,9 @@
'use strict';
var fs = require('fs');
var onlyScripts = require('./util/script-filter');
var tasks = fs.readdirSync('./gulp/tasks/').filter(onlyScripts);
tasks.forEach(function(task) {
require('./tasks/' + task);
});

13
gulp/tasks/browserSync.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
var config = require('../config');
var browserSync = require('browser-sync');
var gulp = require('gulp');
gulp.task('browserSync', function() {
browserSync({
proxy: 'localhost:' + config.serverport
});
});

63
gulp/tasks/browserify.js Normal file
View File

@ -0,0 +1,63 @@
'use strict';
var gulp = require('gulp');
var gulpif = require('gulp-if');
var gutil = require('gulp-util');
var source = require('vinyl-source-stream');
var streamify = require('gulp-streamify');
var sourcemaps = require('gulp-sourcemaps');
var rename = require('gulp-rename');
var watchify = require('watchify');
var browserify = require('browserify');
var babelify = require('babelify');
var uglify = require('gulp-uglify');
var browserSync = require('browser-sync');
var debowerify = require('debowerify');
var handleErrors = require('../util/handle-errors');
var config = require('../config');
// Based on: http://blog.avisi.nl/2014/04/25/how-to-keep-a-fast-build-with-browserify-and-reactjs/
function buildScript(file, watch) {
var bundler = browserify({
entries: [config.sourceDir + 'js/' + file],
debug: !global.isProd,
cache: {},
packageCache: {},
fullPaths: true
});
if ( watch ) {
bundler = watchify(bundler);
bundler.on('update', rebundle);
}
bundler.transform(babelify);
bundler.transform(debowerify);
function rebundle() {
var stream = bundler.bundle();
gutil.log('Rebundle...');
return stream.on('error', handleErrors)
.pipe(source(file))
.pipe(gulpif(global.isProd, streamify(uglify())))
.pipe(streamify(rename({
basename: 'main'
})))
.pipe(gulpif(!global.isProd, sourcemaps.write('./')))
.pipe(gulp.dest(config.scripts.dest))
.pipe(gulpif(browserSync.active, browserSync.reload({ stream: true, once: true })));
}
return rebundle();
}
gulp.task('browserify', function() {
// Only run watchify if NOT production
return buildScript('index.js', !global.isProd);
});

11
gulp/tasks/clean.js Normal file
View File

@ -0,0 +1,11 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var del = require('del');
gulp.task('clean', function(cb) {
del([config.buildDir], cb);
});

10
gulp/tasks/copyFonts.js Normal file
View File

@ -0,0 +1,10 @@
'use strict';
var gulp = require('gulp');
var config = require('../config');
gulp.task('copyFonts', function() {
gulp.src(config.sourceDir + 'fonts/**/*').pipe(gulp.dest(config.buildDir + 'fonts/'));
});

11
gulp/tasks/copyIcons.js Normal file
View File

@ -0,0 +1,11 @@
'use strict';
var gulp = require('gulp');
gulp.task('copyIcons', function() {
// Copy icons from root directory to build/
return gulp.src(['./*.png', './favicon.ico'])
.pipe(gulp.dest('build/'));
});

10
gulp/tasks/copyIndex.js Normal file
View File

@ -0,0 +1,10 @@
'use strict';
var gulp = require('gulp');
var config = require('../config');
gulp.task('copyIndex', function() {
gulp.src(config.sourceDir + 'index.html').pipe(gulp.dest(config.buildDir));
});

10
gulp/tasks/deploy.js Normal file
View File

@ -0,0 +1,10 @@
'use strict';
var gulp = require('gulp');
//var config = require('../config');
gulp.task('deploy', ['prod'], function() {
// Deploy to hosting environment
});

15
gulp/tasks/development.js Normal file
View File

@ -0,0 +1,15 @@
'use strict';
var gulp = require('gulp');
var runSequence = require('run-sequence');
gulp.task('dev', ['clean'], function(callback) {
callback = callback || function() {};
global.isProd = false;
// Run all tasks once
return runSequence(['sass', 'imagemin', 'browserify', 'copyFonts', 'copyIndex', 'copyIcons'], 'watch', callback);
});

17
gulp/tasks/imagemin.js Normal file
View File

@ -0,0 +1,17 @@
'use strict';
var gulp = require('gulp');
var gulpif = require('gulp-if');
var imagemin = require('gulp-imagemin');
var browserSync = require('browser-sync');
var config = require('../config');
gulp.task('imagemin', function() {
// Run imagemin task on all images
return gulp.src(config.images.src)
.pipe(gulpif(global.isProd, imagemin()))
.pipe(gulp.dest(config.images.dest))
.pipe(gulpif(browserSync.active, browserSync.reload({ stream: true, once: true })));
});

14
gulp/tasks/production.js Normal file
View File

@ -0,0 +1,14 @@
'use strict';
var gulp = require('gulp');
var runSequence = require('run-sequence');
gulp.task('prod', ['clean'], function(callback) {
callback = callback || function() {};
global.isProd = true;
runSequence(['sass', 'imagemin', 'browserify', 'copyFonts', 'copyIndex', 'copyIcons'], callback);
});

24
gulp/tasks/sass.js Normal file
View File

@ -0,0 +1,24 @@
'use strict';
var gulp = require('gulp');
var sass = require('gulp-sass');
var gulpif = require('gulp-if');
var browserSync = require('browser-sync');
var autoprefixer = require('gulp-autoprefixer');
var handleErrors = require('../util/handle-errors');
var config = require('../config');
gulp.task('sass', function () {
return gulp.src(config.styles.src)
.pipe(sass({
sourceComments: global.isProd ? 'none' : 'map',
sourceMap: 'sass',
outputStyle: global.isProd ? 'compressed' : 'nested'
}))
.pipe(autoprefixer("last 2 versions", "> 1%", "ie 8"))
.on('error', handleErrors)
.pipe(gulp.dest(config.styles.dest))
.pipe(gulpif(browserSync.active, browserSync.reload({ stream: true })));
});

36
gulp/tasks/server.js Normal file
View File

@ -0,0 +1,36 @@
'use strict';
var config = require('../config');
var http = require('http');
var express = require('express');
var gulp = require('gulp');
var gutil = require('gulp-util');
var morgan = require('morgan');
gulp.task('server', function() {
var server = express();
// log all requests to the console
server.use(morgan('dev'));
server.use(express.static(config.buildDir));
// Serve index.html for all routes to leave routing up to react-router
server.all('/*', function(req, res) {
res.sendFile('index.html', { root: 'build' });
});
// Start webserver if not already running
var s = http.createServer(server);
s.on('error', function(err){
if(err.code === 'EADDRINUSE'){
gutil.log('Development server is already started at port ' + config.serverport);
}
else {
throw err;
}
});
s.listen(config.serverport);
});

10
gulp/tasks/tests.js Normal file
View File

@ -0,0 +1,10 @@
'use strict';
var gulp = require('gulp');
//var config = require('../config');
gulp.task('test', function() {
// Run all tests
});

13
gulp/tasks/watch.js Normal file
View File

@ -0,0 +1,13 @@
'use strict';
var gulp = require('gulp');
var config = require('../config');
gulp.task('watch', ['browserSync', 'server'], function() {
// Scripts are automatically watched by Watchify inside Browserify task
gulp.watch(config.styles.src, ['sass']);
gulp.watch(config.images.src, ['imagemin']);
gulp.watch(config.sourceDir + 'index.html', ['copyIndex']);
});

View File

@ -0,0 +1,27 @@
'use strict';
var notify = require('gulp-notify');
module.exports = function(error) {
if( !global.isProd ) {
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);
// Keep gulp from hanging on this task
this.emit('end');
} else {
// Log the error and stop the process
// to prevent broken code from building
console.log(error);
process.exit(1);
}
};

View File

@ -0,0 +1,9 @@
'use strict';
var path = require('path');
// Filters out non .coffee and .js files. Prevents
// accidental inclusion of possible hidden files
module.exports = function(name) {
return /(\.(js|coffee)$)/i.test(path.extname(name));
};

5
gulpfile.js Normal file
View File

@ -0,0 +1,5 @@
'use strict';
global.isProd = false;
require('./gulp');

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "react-rocket-boilerplate",
"version": "0.2.0",
"author": "Jake Marsh <jakemmarsh@gmail.com>",
"description": "Boilerplate using React, Browserify, SASS, and Gulp.",
"repository": {
"type": "git",
"url": "https://github.com/jakemmarsh/react-rocket-boilerplate.git"
},
"keywords": [
"gulp",
"browserify",
"react",
"sass",
"boilerplate"
],
"private": true,
"engines": {
"node": "^0.12.x",
"npm": "^2.1.x"
},
"devDependencies": {
"babelify": "^6.1.x",
"browser-sync": "^2.7.13",
"browserify": "^10.2.6",
"debowerify": "^1.3.1",
"del": "^1.2.0",
"express": "^4.13.1",
"gulp": "^3.9.0",
"gulp-autoprefixer": "^2.3.1",
"gulp-if": "^1.2.5",
"gulp-imagemin": "^2.3.0",
"gulp-notify": "^2.2.0",
"gulp-rename": "^1.2.2",
"gulp-sass": "^2.0.x",
"gulp-sourcemaps": "^1.5.2",
"gulp-streamify": "0.0.5",
"gulp-uglify": "^1.2.0",
"gulp-util": "^3.0.6",
"humps": "^0.6.0",
"lodash": "^3.10.0",
"morgan": "^1.6.1",
"react": "^0.13.x",
"react-document-title": "^1.0.2",
"react-router": "^0.13.x",
"reflux": "^0.2.9",
"run-sequence": "^1.1.1",
"superagent": "^1.2.0",
"vinyl-source-stream": "^1.1.0",
"watchify": "^3.2.x"
}
}