├── .gitignore ├── .jscsrc ├── .jshintrc ├── gulpfile.js ├── karma.conf.js ├── less └── styles.less ├── package.json ├── readme.md ├── src ├── app.js ├── bootstrap.js └── components │ └── app-container.jsx ├── templates └── index.html └── test └── components └── app-container.spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | node_modules -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "disallowSpacesInNamedFunctionExpression": { 4 | "beforeOpeningRoundBrace": true 5 | }, 6 | "disallowSpacesInFunctionExpression": { 7 | "beforeOpeningRoundBrace": true 8 | }, 9 | "disallowSpacesInAnonymousFunctionExpression": { 10 | "beforeOpeningRoundBrace": true 11 | }, 12 | "disallowSpacesInFunctionDeclaration": { 13 | "beforeOpeningRoundBrace": true 14 | }, 15 | "disallowEmptyBlocks": true, 16 | "disallowSpacesInsideArrayBrackets": true, 17 | "disallowSpacesInsideParentheses": true, 18 | "disallowQuotedKeysInObjects": true, 19 | "disallowSpaceAfterObjectKeys": true, 20 | "disallowSpaceAfterPrefixUnaryOperators": true, 21 | "disallowSpaceBeforePostfixUnaryOperators": true, 22 | "disallowSpaceBeforeBinaryOperators": [ 23 | "," 24 | ], 25 | "disallowMixedSpacesAndTabs": true, 26 | "disallowTrailingWhitespace": true, 27 | "disallowTrailingComma": true, 28 | "disallowYodaConditions": true, 29 | "disallowKeywords": [ "with" ], 30 | "disallowMultipleLineBreaks": true, 31 | "disallowMultipleVarDecl": true, 32 | "requireSpaceBeforeBlockStatements": true, 33 | "requireParenthesesAroundIIFE": true, 34 | "requireSpacesInConditionalExpression": { 35 | "afterTest": true, 36 | "beforeConsequent": true, 37 | "afterConsequent": true, 38 | "beforeAlternate": true 39 | }, 40 | "requireBlocksOnNewline": 1, 41 | "requireCommaBeforeLineBreak": true, 42 | "requireSpaceBeforeBinaryOperators": true, 43 | "requireSpaceAfterBinaryOperators": true, 44 | "requireCamelCaseOrUpperCaseIdentifiers": true, 45 | "requireLineFeedAtFileEnd": true, 46 | "requireCapitalizedConstructors": true, 47 | "requireDotNotation": true, 48 | "requireCurlyBraces": [ 49 | "do" 50 | ], 51 | "requireSpaceAfterKeywords": [ 52 | "if", 53 | "else", 54 | "for", 55 | "while", 56 | "do", 57 | "switch", 58 | "case", 59 | "return", 60 | "try", 61 | "catch", 62 | "typeof" 63 | ], 64 | "safeContextKeyword": "self", 65 | "validateLineBreaks": "LF", 66 | "validateQuoteMarks": "'", 67 | "validateIndentation": 4, 68 | "maximumLineLength": 120 69 | } -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxerr": 50, 3 | "bitwise": true, 4 | "camelcase": false, 5 | "curly": true, 6 | "eqeqeq": true, 7 | "forin": true, 8 | "freeze": true, 9 | "immed": false, 10 | "indent": 4, 11 | "latedef": false, 12 | "newcap": false, 13 | "noarg": true, 14 | "noempty": true, 15 | "nonbsp": true, 16 | "nonew": false, 17 | "plusplus": false, 18 | "quotmark": "single", 19 | "undef": true, 20 | "unused": true, 21 | "strict": true, 22 | "maxparams": 5, 23 | "maxlen": 120, 24 | 25 | "asi": false, 26 | "boss": false, 27 | "debug": false, 28 | "eqnull": false, 29 | "esnext": true, 30 | "moz": false, 31 | "evil": false, 32 | "expr": false, 33 | "funcscope": false, 34 | "globalstrict": false, 35 | "iterator": false, 36 | "lastsemic": false, 37 | "laxbreak": false, 38 | "laxcomma": false, 39 | "loopfunc": false, 40 | "multistr": false, 41 | "noyield": false, 42 | "notypeof": false, 43 | "proto": false, 44 | "scripturl": false, 45 | "shadow": false, 46 | "sub": false, 47 | "supernew": false, 48 | "validthis": false, 49 | 50 | "browser": true, 51 | "browserify": true, 52 | "couch": false, 53 | "devel": true, 54 | "dojo": false, 55 | "jasmine": true, 56 | "jquery": true, 57 | "mocha": false, 58 | "mootools": false, 59 | "node": true, 60 | "nonstandard": false, 61 | "phantom": false, 62 | "prototypejs": false, 63 | "qunit": false, 64 | "rhino": false, 65 | "shelljs": false, 66 | "typed": false, 67 | "worker": false, 68 | "wsh": false, 69 | "yui": false, 70 | "globals": {} 71 | } -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var gutil = require('gulp-util'); 6 | var runSequence = require('run-sequence'); 7 | var gulpif = require('gulp-if'); 8 | var rename = require('gulp-rename'); 9 | var del = require('del'); 10 | 11 | var jshint = require('gulp-jshint'); 12 | 13 | var browserify = require('browserify'); 14 | var babelify = require('babelify'); 15 | 16 | var uglify = require('gulp-uglify'); 17 | var sourcemaps = require('gulp-sourcemaps'); 18 | 19 | var source = require('vinyl-source-stream'); 20 | var buffer = require('vinyl-buffer'); 21 | 22 | var less = require('gulp-less'); 23 | var minify = require('gulp-minify-css'); 24 | var autoprefixer = require('gulp-autoprefixer'); 25 | 26 | var htmlreplace = require('gulp-html-replace'); 27 | 28 | var karma = require('karma').server; 29 | 30 | var browserSync = require('browser-sync'); 31 | 32 | var env = process.env.NODE_ENV || 'development'; 33 | 34 | var filePaths = { 35 | src: { 36 | scripts: { 37 | entry: './src/bootstrap.js', 38 | all: ['./src/**/*.js', './src/**/*.jsx'], 39 | bundled: env === 'development' ? 'app.js' : 'app.min.js' 40 | }, 41 | styles: { 42 | entry: 'styles.less', 43 | all: './less/**/*.less', 44 | bundled: env === 'development' ? 'styles.css' : 'styles.min.css' 45 | }, 46 | teplates: { 47 | entry: './templates/index.html' 48 | } 49 | }, 50 | dest: './build' 51 | }; 52 | 53 | gulp.task('lint:scripts', function() { 54 | return gulp.src(filePaths.src.scripts.all) 55 | .pipe(jshint()) 56 | .pipe(jshint.reporter('default')); 57 | }); 58 | 59 | gulp.task('build:scripts', function() { 60 | return browserify(filePaths.src.scripts.entry, { 61 | paths: ['./node_modules', './src'], 62 | debug: env === 'development' 63 | }) 64 | .transform(babelify) 65 | .bundle() 66 | .on('error', function(err) { 67 | gutil.log(gutil.colors.red(err)); 68 | }) 69 | .pipe(source(filePaths.src.scripts.bundled)) 70 | .pipe(buffer()) 71 | .pipe(gulpif(env === 'development', sourcemaps.init({ 72 | loadMaps: true 73 | }))) 74 | .pipe(gulpif(env === 'production', uglify({ 75 | mangle: false 76 | }))) 77 | .pipe(gulpif(env === 'development', sourcemaps.write('./'))) 78 | .pipe(gulp.dest(filePaths.dest)); 79 | }); 80 | 81 | gulp.task('reload:scripts', ['build:scripts'], function() { 82 | browserSync.reload(); 83 | }); 84 | 85 | gulp.task('build:styles', function() { 86 | return gulp.src(filePaths.src.styles.all) 87 | .pipe(less({ 88 | paths: [path.join(__dirname, 'less', 'includes')] 89 | }).on('error', function(err) { 90 | gutil.log(gutil.colors.red(err)); 91 | })) 92 | .pipe(autoprefixer({ 93 | browsers: ['last 2 versions'] 94 | })) 95 | .pipe(gulpif(env === 'production', minify())) 96 | .pipe(rename(filePaths.src.styles.bundled)) 97 | .pipe(gulp.dest(filePaths.dest)); 98 | }); 99 | 100 | gulp.task('reload:styles', ['build:styles'], function() { 101 | browserSync.reload(); 102 | }); 103 | 104 | gulp.task('build:templates', function() { 105 | return gulp.src(filePaths.src.teplates.entry) 106 | .pipe(htmlreplace({ 107 | css: filePaths.src.styles.bundled, 108 | js: filePaths.src.scripts.bundled 109 | })) 110 | .pipe(gulp.dest(filePaths.dest)); 111 | }); 112 | 113 | gulp.task('reload:templates', ['build:templates'], function() { 114 | browserSync.reload(); 115 | }); 116 | 117 | gulp.task('test', function(done) { 118 | karma.start({ 119 | configFile: __dirname + '/karma.conf.js' 120 | }, function() { 121 | done(); 122 | }); 123 | }); 124 | 125 | gulp.task('clean', function(callback) { 126 | del(filePaths.dest, null, callback); 127 | }); 128 | 129 | gulp.task('browser-sync', function() { 130 | browserSync({ 131 | server: { 132 | baseDir: filePaths.dest 133 | }, 134 | port: process.env.PORT || 8080 135 | }); 136 | }); 137 | 138 | gulp.task('watch', function() { 139 | var callback = function() { 140 | gulp.watch(filePaths.src.scripts.all, ['reload:scripts']); 141 | gulp.watch(filePaths.src.styles.all, ['reload:styles']); 142 | gulp.watch(filePaths.src.teplates.entry, ['reload:templates']); 143 | 144 | gutil.log(gutil.colors.green('Watching for changes...')); 145 | }; 146 | 147 | runSequence( 148 | 'clean', 149 | 'lint:scripts', 150 | ['build:scripts', 'build:styles', 'build:templates'], 151 | 'browser-sync', 152 | callback 153 | ); 154 | }); 155 | 156 | gulp.task('build', function() { 157 | var callback = function() { 158 | gutil.log(gutil.colors.green('Build complete.')); 159 | }; 160 | 161 | runSequence( 162 | 'clean', 163 | 'lint:scripts', 164 | ['build:scripts', 'build:styles', 'build:templates'], 165 | callback 166 | ); 167 | }); 168 | 169 | gulp.task('default', ['build']); 170 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | 9 | // frameworks to use 10 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 11 | frameworks: ['browserify', 'jasmine', 'source-map-support'], 12 | 13 | // list of files / patterns to load in the browser 14 | files: [ 15 | 'test/**/*spec.js' 16 | ], 17 | 18 | browserify: { 19 | debug: true, 20 | transform: ['babelify'], 21 | paths: ['./node_modules', './src'] 22 | }, 23 | 24 | // preprocess matching files before serving them to the browser 25 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 26 | preprocessors: { 27 | 'src/**/*.js': ['browserify'], 28 | 'test/**/*spec.js': ['browserify'] 29 | }, 30 | 31 | // test results reporter to use 32 | // possible values: 'dots', 'progress' 33 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 34 | reporters: ['progress'], 35 | 36 | // web server port 37 | port: 9876, 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | // level of logging 43 | // possible values: config.LOG_DISABLE/config.LOG_ERROR/config.LOG_WARN/config.LOG_INFO/config.LOG_DEBUG 44 | logLevel: config.LOG_INFO, 45 | 46 | // enable / disable watching file and executing tests whenever any file changes 47 | autoWatch: true, 48 | browserNoActivityTimeout: 10000, 49 | 50 | // start these browsers 51 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 52 | browsers: ['Chrome'], 53 | 54 | // Continuous Integration mode 55 | // if true, Karma captures browsers, runs the tests and exits 56 | singleRun: false 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /less/styles.less: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 10px; 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactBoilerplate", 3 | "version": "1.0.0", 4 | "description": "Simple react boilerplate using babel, browserify, less, karma, jasmine and gulp", 5 | "author": "r.warpecha@gmail.com", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "babelify": "^6.0.2", 9 | "browser-sync": "^2.7.1", 10 | "browserify": "^10.1.3", 11 | "del": "^1.1.1", 12 | "gulp": "^3.8.11", 13 | "gulp-autoprefixer": "^2.2.0", 14 | "gulp-html-replace": "^1.4.5", 15 | "gulp-if": "^1.2.5", 16 | "gulp-jscs": "^1.6.0", 17 | "gulp-jshint": "^1.10.0", 18 | "gulp-less": "^3.0.3", 19 | "gulp-minify-css": "^1.1.1", 20 | "gulp-rename": "^1.2.2", 21 | "gulp-sourcemaps": "^1.5.2", 22 | "gulp-uglify": "^1.2.0", 23 | "gulp-util": "^3.0.4", 24 | "jasmine-core": "^2.3.4", 25 | "karma": "^0.12.31", 26 | "karma-browserify": "^4.1.2", 27 | "karma-chrome-launcher": "^0.1.12", 28 | "karma-jasmine": "^0.3.5", 29 | "karma-source-map-support": "^1.0.0", 30 | "run-sequence": "^1.1.0", 31 | "vinyl-buffer": "^1.0.0", 32 | "vinyl-source-stream": "^1.1.0" 33 | }, 34 | "dependencies": { 35 | "react": "^0.13.3" 36 | }, 37 | "scripts": { 38 | "test": "gulp test", 39 | "build": "gulp build", 40 | "watch": "gulp watch" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | #React Boilerplate 2 | Simple react boilerplate. 3 | ##What's inside? 4 | - Gulp for build process 5 | - Browserify with babel 6 | - Karma and Jasmine for unit testing 7 | - Less with autoprefixer for styles 8 | ##Usage 9 | Simple clone repository and run 'npm install' 10 | 11 | Scripts for automation: 12 | - `npm run build` to build app. 13 | - 'npm run watch' to build, start browser and watching changes 14 | - 'npm run test' to start karma 15 | 16 | If you want to build your app for production (minified without sourcemaps) use: 17 | - 'NODE_ENV=production npm run build' 18 | ##License 19 | MIT -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import AppContainer from './components/app-container.jsx'; 5 | 6 | export default class App { 7 | constructor(node) { 8 | this.node = node; 9 | } 10 | run() { 11 | React.render(React.createElement(AppContainer, null), this.node); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/bootstrap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import App from 'app'; 4 | 5 | let app = new App(document.querySelector('#appContainer')); 6 | 7 | app.run(); 8 | -------------------------------------------------------------------------------- /src/components/app-container.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | 5 | export default class AppContainer extends React.Component { 6 | render() { 7 | /* jshint ignore:start */ 8 | /* jscs: disable */ 9 | return ( 10 |
11 |

Hello world

12 |
13 | ); 14 | /* jshint ignore:end */ 15 | /* jscs: enable */ 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello World 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/components/app-container.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react/addons'; 4 | import AppContainer from 'components/app-container.jsx'; 5 | 6 | let TestUtils = React.addons.TestUtils; 7 | 8 | describe('AppContainer', () => { 9 | let component; 10 | 11 | beforeEach(() => { 12 | component = TestUtils.renderIntoDocument(React.createElement(AppContainer)); 13 | }); 14 | 15 | it('should display correct value', () => { 16 | let HelloWorldNode = TestUtils.findRenderedDOMComponentWithTag(component, 'h1'); 17 | 18 | expect(HelloWorldNode.getDOMNode().textContent).toBe('Hello world'); 19 | }); 20 | }); --------------------------------------------------------------------------------