├── .eslintignore
├── mocha.opts
├── .babelrc
├── test-setup.js
├── .gitignore
├── src
├── app
│ ├── app.scss
│ ├── store.js
│ ├── app.jsx
│ ├── app.spec.js
│ └── store.spec.js
├── index.ejs
└── index.jsx
├── .editorconfig
├── README.md
├── webpack.config.dev.js
├── .eslintrc
├── webpack.config.prod.js
├── webpack.config.js
└── package.json
/.eslintignore:
--------------------------------------------------------------------------------
1 | src/**/*.spec.*
2 | webpack.*.js
3 |
--------------------------------------------------------------------------------
/mocha.opts:
--------------------------------------------------------------------------------
1 | --compilers js:babel-core/register
2 | --require ignore-styles
3 | --require babel-polyfill
4 | test-setup.js
5 | src/**/*.spec.js
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "latest",
4 | "react"
5 | ],
6 | "plugins": [
7 | "transform-decorators-legacy",
8 | "transform-class-properties",
9 | "transform-object-rest-spread"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/test-setup.js:
--------------------------------------------------------------------------------
1 | const jsdom = require('jsdom');
2 |
3 | if (typeof document === 'undefined') {
4 | global.document = jsdom.jsdom('
');
5 | global.window = document.defaultView;
6 | global.navigator = global.window.navigator;
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | logs
2 | *.log
3 | npm-debug.log*
4 | pids
5 | *.pid
6 | *.seed
7 | lib-cov
8 | coverage
9 | .nyc_output
10 | .grunt
11 | .lock-wscript
12 | build/Release
13 | node_modules
14 | .idea
15 | jspm_packages
16 | .npm
17 | .node_repl_history
18 | dist
19 | /coverage/
20 |
--------------------------------------------------------------------------------
/src/app/app.scss:
--------------------------------------------------------------------------------
1 | button {
2 | padding: 20px;
3 | background: darken(#eaeaea, 10%);
4 | color: #333;
5 | border: 0;
6 | border-radius: 20px;
7 | transition: all 500ms;
8 | font-size: responsive;
9 |
10 | &:hover {
11 | background: #cacaca;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | <%= htmlWebpackPlugin.options.title %>
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/store.js:
--------------------------------------------------------------------------------
1 | import { observable, action } from 'mobx';
2 |
3 | class Store {
4 | @observable timer = 0;
5 |
6 | constructor() {
7 | setInterval(() => {
8 | this.timer += 1;
9 | }, 1000);
10 | }
11 |
12 | @action resetTimer = () => { this.timer = 0; };
13 | }
14 |
15 | export default new Store();
16 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 2
10 | end_of_line = lf
11 | charset = utf-8
12 | trim_trailing_whitespace = true
13 | insert_final_newline = true
14 |
--------------------------------------------------------------------------------
/src/app/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { observer } from 'mobx-react';
3 | import './app.scss';
4 |
5 | function App({ store }) {
6 | return (
7 |
8 |
11 |
12 | );
13 | }
14 |
15 | export default observer(App);
16 |
--------------------------------------------------------------------------------
/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import 'bootstrap/dist/css/bootstrap.css';
4 |
5 | import store from './app/store';
6 | import App from './app/app';
7 |
8 | const el = document.createElement('div');
9 | document.body.appendChild(el);
10 |
11 | render(
12 | ,
13 | el,
14 | );
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | React-MobX-Webpack Boiler Plate
2 | ===
3 |
4 | > a boiler plate for React, MobX and Webpack application with tests and coverage.
5 |
6 | Install
7 | ---
8 |
9 | just `clone` this repo then run `npm i && npm t`
10 |
11 | ### Tasks
12 | ---
13 |
14 | - `npm start` - start the dev server at http://localhost:8100
15 | - `npm test` - run test suits
16 | - `npm run cover` - run coverage
17 | - `npm run cover-view` - view html report at http://localhost:3000
18 | - `npm run build` - build to dist dir
19 | - `npm run lint-fix` - lint and auto-fix errors
20 | - `npm run changelog` - create changelog (using angular commit guidelines)
21 |
22 |
--------------------------------------------------------------------------------
/src/app/app.spec.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {mount} from 'enzyme';
3 | import {expect} from 'chai';
4 | import store from './store';
5 | import App from './app';
6 |
7 | describe('App', (component) => {
8 |
9 | beforeEach (() => {
10 | component = mount();
11 | });
12 |
13 | describe('Unit tests', () => {
14 | it('should have 1 button', () => {
15 | expect(component.find('button').length).to.equal(1);
16 | });
17 |
18 | it('should have appState prop', () => {
19 | expect(component.props.store).to.be.defined;
20 | });
21 | });
22 |
23 | describe('Integration test', () => {
24 | it('should reset timer on button click', () => {
25 | component.find('button').simulate('click');
26 | expect(store.timer).to.eq(0);
27 | });
28 | });
29 | });
30 |
--------------------------------------------------------------------------------
/src/app/store.spec.js:
--------------------------------------------------------------------------------
1 | import store from './store';
2 | import {expect} from 'chai';
3 |
4 | describe('Store', () => {
5 | it('is a class', () => {
6 | expect(store).to.be.a('object')
7 | });
8 |
9 | it('sets timer', () => {
10 | store.timer = 1;
11 |
12 | expect(store.timer).to.equals(1);
13 | });
14 |
15 | describe('constructor', () => {
16 | it('checks setTimeout', (done) => {
17 | store.timer = 0;
18 |
19 | setTimeout(()=> {
20 | expect(store.timer).to.equals(2);
21 | done();
22 | }, 1000)
23 | });
24 | });
25 |
26 | describe('resetTimer()', () => {
27 | it('is a function', () => {
28 | expect(store.resetTimer).to.be.a('function');
29 | });
30 |
31 | it('resets timer', () => {
32 | store.timer = 1;
33 | store.resetTimer();
34 |
35 | expect(store.timer).to.equals(0);
36 | })
37 |
38 | });
39 |
40 |
41 | });
42 |
--------------------------------------------------------------------------------
/webpack.config.dev.js:
--------------------------------------------------------------------------------
1 | const config = require('./webpack.config');
2 | const webpack = require('webpack');
3 |
4 | config.output.filename = 'js/[name].js';
5 |
6 | config.module.loaders.push({
7 | test: /\.s?css$/,
8 | loaders: ['style', 'css?sourceMap&importLoaders=1', 'postcss']
9 | });
10 |
11 |
12 | config.module.loaders.push({
13 | test: /\.(woff2?|ttf|svg|eot)?(\?.+)?$/,
14 | loader: 'url'
15 | });
16 |
17 | config.module.loaders.push({
18 | test: /\.(jpg|png|gif)?(\?.+)?$/,
19 | loader: 'url'
20 | });
21 |
22 | config.module.loaders.push({
23 | test: /\.jsx?$/,
24 | exclude: /node_modules/,
25 | loader: 'eslint-loader',
26 | });
27 |
28 | const babelLoader = config.module.loaders.find(o => o.loader === 'babel');
29 | babelLoader.query = {presets: ['react-hmre']};
30 |
31 | config.eslint = {
32 | configFile: './.eslintrc',
33 | failOnError: true,
34 | cache: true
35 | };
36 |
37 | if (process.env.autofix) {
38 | config.eslint.fix = true;
39 | }
40 |
41 | module.exports = config;
42 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "ecmaFeatures": {
3 | "jsx": true,
4 | "modules": true
5 | },
6 | "env": {
7 | "browser": true,
8 | "node": true
9 | },
10 | "extends": "airbnb",
11 | "globals": {
12 | "before": false,
13 | "beforeEach": false,
14 | "describe": false,
15 | "it": false
16 | },
17 | "parser": "babel-eslint",
18 | "plugins": [
19 | "react"
20 | ],
21 | "rules": {
22 | "quotes": [
23 | "error",
24 | "single"
25 | ],
26 | "indent": [
27 | "error",
28 | 2
29 | ],
30 | "class-methods-use-this": 0,
31 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
32 | "jsx-a11y/label-has-for": 1,
33 | "jsx-a11y/no-static-element-interactions" : 0,
34 | "max-len": [2, 120, 2, {"ignoreComments": true}],
35 | "react/prop-types": 0,
36 | "react/jsx-uses-react": 2,
37 | "react/jsx-uses-vars": 2,
38 | "react/jsx-no-undef": 2,
39 | "react/react-in-jsx-scope": 2,
40 | "react/prefer-stateless-function": 0,
41 | "strict": 0
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/webpack.config.prod.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
3 | const pkg = require('./package.json');
4 | const path = require('path');
5 |
6 | const config = require('./webpack.config');
7 |
8 | config.output = {
9 | path: path.join('dist', pkg.version),
10 | filename: 'js/[name].[hash].js',
11 | publicPath: `/${pkg.name}/${pkg.version}/`
12 | };
13 |
14 | config.output.filename = `js/[name].[hash].js`;
15 | config.devtool = 'source-map';
16 |
17 | config.module.loaders.push({
18 | test: /\.s?css$/,
19 | loader: ExtractTextPlugin.extract('style', 'css?sourceMap&importLoaders=1!postcss')
20 | });
21 |
22 | config.module.loaders.push({
23 | test: /\.(woff2?|ttf|svg|eot)?(\?.+)?$/,
24 | loader: 'file?name=assets/[name].[ext]'
25 | });
26 |
27 | config.module.loaders.push({
28 | test: /\.(jpg|png|gif)?(\?.+)?$/,
29 | loader: 'file?name=img/[name].[hash].[ext]'
30 | });
31 |
32 | config.plugins.push(new ExtractTextPlugin('styles.[hash].css'));
33 | config.plugins.push(new webpack.DefinePlugin({
34 | 'process.env': {
35 | NODE_ENV: JSON.stringify('production'),
36 | },
37 | }));
38 |
39 | module.exports = config;
40 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const webpack = require('webpack');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 | const rucksack = require('rucksack-css');
5 | const _ = require('lodash');
6 | const pkg = require('./package.json');
7 |
8 | module.exports = {
9 | entry: {
10 | app: [
11 | './'
12 | ],
13 | vendors: [
14 | 'babel-polyfill',
15 | ...Object.keys(pkg.dependencies).filter(x => x !== 'bootstrap')
16 | ]
17 | },
18 |
19 | output: {
20 | path: path.resolve('./dist'),
21 | //publicPath: `/`
22 | },
23 |
24 | context: path.resolve(__dirname, 'src'),
25 |
26 | module: {
27 | loaders: [
28 | { test: /\.jsx?$/, exclude: /node_modules/, loader: 'babel' },
29 | { test: /\.html$/, loader: 'html' },
30 | { test: /\.json$/, loader: 'json' }
31 | ],
32 |
33 | noParse: []
34 | },
35 |
36 | resolve: {
37 | extensions: ['', '.js', '.jsx'],
38 | alias: {}
39 | },
40 |
41 | postcss: [
42 | require('postcss-import')(),
43 | require('postcss-mixins')(),
44 | require('postcss-simple-vars')(),
45 | require('postcss-nested')(),
46 |
47 | rucksack({
48 | autoprefixer: true
49 | })
50 | ],
51 |
52 | plugins: [
53 | new webpack.optimize.CommonsChunkPlugin({
54 | name: 'vendors',
55 | minChunks: Infinity
56 | }),
57 |
58 | new HtmlWebpackPlugin({
59 | title: _.startCase(pkg.name),
60 | chunks: ['vendors', 'app'],
61 | filename: 'index.html',
62 | template: 'index.ejs'
63 | })
64 | ],
65 | };
66 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-mobx-webpack-seed",
3 | "version": "1.1.10",
4 | "description": "a boiler plate for React, MobX and Webpack application with tests and coverage.",
5 | "main": "./index.jsx",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/alonronin/react-mobx-webpack-seed.git"
9 | },
10 | "scripts": {
11 | "build": "webpack --config webpack.config.prod.js -p --optimize-dedupe",
12 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
13 | "clean": "trash dist",
14 | "cover": "nyc npm t",
15 | "cover-view": "serve coverage/lcov-report",
16 | "dev": "webpack --config webpack.config.dev.js -d --progress --colors",
17 | "lint": "eslint src --ext .jsx,.js",
18 | "lint-fix": "npm run lint -- --fix",
19 | "prebuild": "npm run clean && npm run lint",
20 | "recommend-bump": "conventional-recommended-bump -p angular",
21 | "start": "webpack-dev-server --config webpack.config.dev.js -d --open --history-api-fallback --hot --inline --progress --colors --content-base ./dist --port 8100",
22 | "start-autofix": "cross-env autofix='true' npm run start",
23 | "test": "mocha --opts mocha.opts"
24 | },
25 | "author": "",
26 | "license": "ISC",
27 | "devDependencies": {
28 | "babel-core": "^6.18.2",
29 | "babel-eslint": "^7.1.0",
30 | "babel-loader": "^6.2.7",
31 | "babel-plugin-transform-class-properties": "^6.18.0",
32 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
33 | "babel-plugin-transform-object-rest-spread": "^6.20.2",
34 | "babel-polyfill": "^6.16.0",
35 | "babel-preset-latest": "^6.16.0",
36 | "babel-preset-react": "^6.16.0",
37 | "babel-preset-react-hmre": "^1.1.1",
38 | "chai": "^3.5.0",
39 | "conventional-changelog-cli": "^1.2.0",
40 | "conventional-recommended-bump": "^0.3.0",
41 | "cross-env": "^3.1.3",
42 | "css-loader": "^0.26.1",
43 | "enzyme": "^2.5.1",
44 | "eslint": "^3.9.1",
45 | "eslint-config-airbnb": "^14.1.0",
46 | "eslint-loader": "^1.6.1",
47 | "eslint-plugin-import": "^2.2.0",
48 | "eslint-plugin-jsx-a11y": "^4.0.0",
49 | "eslint-plugin-react": "^6.7.1",
50 | "extract-text-webpack-plugin": "^1.0.1",
51 | "file-loader": "^0.10.0",
52 | "html-loader": "^0.4.4",
53 | "html-webpack-plugin": "^2.24.1",
54 | "ignore-styles": "^5.0.1",
55 | "jsdom": "^9.8.3",
56 | "json-loader": "^0.5.4",
57 | "mobx-react-devtools": "^4.2.9",
58 | "mocha": "^3.1.2",
59 | "nyc": "^10.0.0",
60 | "postcss-import": "^9.0.0",
61 | "postcss-loader": "^1.1.0",
62 | "postcss-mixins": "^5.4.0",
63 | "postcss-nested": "^1.0.0",
64 | "postcss-simple-vars": "^3.0.0",
65 | "react-addons-test-utils": "^15.4.1",
66 | "rucksack-css": "^0.9.1",
67 | "serve": "^3.3.1",
68 | "sinon": "^1.17.6",
69 | "style-loader": "^0.13.1",
70 | "trash-cli": "^1.4.0",
71 | "url-loader": "^0.5.7",
72 | "webpack": "^1.14.0",
73 | "webpack-dev-server": "^1.16.3"
74 | },
75 | "dependencies": {
76 | "bluebird": "^3.4.6",
77 | "bootstrap": "^4.0.0-alpha.6",
78 | "classnames": "^2.2.5",
79 | "lodash": "^4.17.0",
80 | "mobx": "^3.1.0",
81 | "mobx-react": "^4.0.3",
82 | "react": "^15.3.2",
83 | "react-addons-css-transition-group": "^15.3.2",
84 | "react-addons-transition-group": "^15.3.2",
85 | "react-dom": "^15.3.2",
86 | "react-router": "^3.0.0",
87 | "react-select": "1.0.0-rc.3",
88 | "reactstrap": "^4.2.0"
89 | },
90 | "nyc": {
91 | "lines": 95,
92 | "exclude": [
93 | "**/*.spec.js"
94 | ],
95 | "include": [
96 | "src/app/**/*.js",
97 | "src/app/**/*.jsx"
98 | ],
99 | "extension": [
100 | ".jsx"
101 | ],
102 | "reporter": [
103 | "lcov",
104 | "text",
105 | "text-summary"
106 | ],
107 | "check-coverage": true,
108 | "report-dir": "./coverage"
109 | }
110 | }
111 |
--------------------------------------------------------------------------------