├── .gitignore
├── less
└── styles.less
├── src
├── bootstrap.js
├── app.js
└── components
│ └── app-container.jsx
├── templates
└── index.html
├── readme.md
├── test
└── components
│ └── app-container.spec.js
├── package.json
├── .jshintrc
├── karma.conf.js
├── .jscsrc
└── gulpfile.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | build
3 | node_modules
--------------------------------------------------------------------------------
/less/styles.less:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 10px;
4 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 | });
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------