├── .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 | --------------------------------------------------------------------------------