diff --git a/README.md b/README.md
index 5f90251..672a837 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# React seed [](https://travis-ci.org/badsyntax/react-seed)
+# React seed [](https://circleci.com/gh/continuata/react-seed)
A boilerplate for building React apps with ES6 and webpack.
@@ -12,6 +12,7 @@ A boilerplate for building React apps with ES6 and webpack.
* Basic flux architecture with app actions, stores and example web API usage
* React router ([feature/react-router](https://github.com/badsyntax/react-seed/tree/feature/react-router))
* Material UI ([feature/material-ui](https://github.com/badsyntax/react-seed/tree/feature/material-ui))
+* Standard JS for linting
## Getting started
@@ -40,32 +41,30 @@ git clone --depth=1 https://github.com/badsyntax/react-seed.git my-project
```js
// Filename: Menu.jsx
-'use strict';
+import styles from './_Menu.scss'
+import React from 'react'
+import MenuItem from './MenuItem'
-import styles from './_Menu.scss';
-import React from 'react';
-import MenuItem from './MenuItem';
-
-let { Component, PropTypes } = React;
+let { Component, PropTypes } = React
export default class Menu extends Component {
static defaultProps = {
items: []
- };
+ }
static propTypes = {
items: PropTypes.array.isRequired
- };
+ }
- render() {
+ render () {
return (
- {this.props.items.map((item) => {
- return ();
+ {this.props.items.map((item, key) => {
+ return ()
}, this)}
- );
+ )
}
}
```
@@ -74,70 +73,71 @@ export default class Menu extends Component {
```js
// Filename: __tests__/Menu-test.jsx
+import React from 'react/addons'
+import { expect } from 'chai'
-'use strict';
-
-import React from 'react/addons';
-import { expect } from 'chai';
-
-import Menu from '../Menu.jsx';
-import MenuItem from '../MenuItem.jsx';
-
-// Here we create a mocked MenuItem component.
-class MockedMenuItem extends MenuItem {
- render() {
- return (
- {this.props.item.label}
- );
- }
-}
-
-// Here we set the mocked MenuItem component.
-Menu.__Rewire__('MenuItem', MockedMenuItem);
+import Menu from '../Menu.jsx'
describe('Menu', () => {
-
- let { TestUtils } = React.addons;
+ let { TestUtils } = React.addons
let menuItems = [
{ id: 1, label: 'Option 1' },
{ id: 2, label: 'Option 2' }
- ];
-
- let menu = TestUtils.renderIntoDocument(
-
- );
- let menuElem = React.findDOMNode(menu);
- let items = menuElem.querySelectorAll('li');
-
- it('Should render the menu items', () => {
- expect(items.length).to.equal(2);
- });
-
- it('Should render the menu item labels', () => {
- Array.prototype.forEach.call(items, (item, i) => {
- expect(item.textContent.trim()).to.equal(menuItems[i].label);
- });
+ ]
+
+ describe('Rendering', () => {
+ // Here we create a mocked MenuItem component.
+ let menu = TestUtils.renderIntoDocument(
+
+ )
+ let menuElem = React.findDOMNode(menu)
+ let items = menuElem.querySelectorAll('li')
+
+ it('Should render the menu items', () => {
+ expect(items.length).to.equal(2)
+ })
+
+ it('Should render the menu item labels', () => {
+ Array.prototype.forEach.call(items, (item, i) => {
+ expect(item.textContent.trim()).to.equal(menuItems[i].label)
+ })
+ })
})
- it('Should render the mocked menu item', () => {
- expect(items[0].className).to.equal('mocked-menu-item');
- });
-});
-
+ describe('Events', (done) => {
+ // Example of simulating browser events.
+ it('Should handle click events', () => {
+ let menu = TestUtils.renderIntoDocument(
+
+ )
+ let menuElem = React.findDOMNode(menu)
+ let items = menuElem.querySelectorAll('li')
+ let node = items[0].querySelector('a')
+
+ window.onAlert = function (alertText) {
+ expect(alertText).to.equal("ALERT: 'You clicked Option 1'")
+ done()
+ }
+
+ TestUtils.Simulate.click(node)
+ })
+ })
+})
```
-## Sass, CSS & webpack
+## Sass, Stylus, CSS & webpack
-`import` Sass and CSS files from within your JavaScript component files:
+`import` Sass, Stylus and CSS files from within your JavaScript component files:
```js
// Filename: app.jsx
import 'normalize.css/normalize.css';
import styles from './scss/app.scss';
+import styles from './styl/app.styl';
```
-* **Note:** If you're importing component Sass files from within your JavaScript component files, then each sass file will be compiled as part of a different compile process, and thus you cannot share global references. See [this issue](https://github.com/jtangelder/sass-loader/issues/105) for more information.
+* **Note:** If you're importing component Sass, Styl files from within your JavaScript component files, then each sass file will be compiled as part of a different compile process, and thus you cannot share global references. See [this issue](https://github.com/jtangelder/sass-loader/issues/105) for more information.
* Sass include paths can be adjusted in the `webpack/loaders.js` file.
* All CSS (compiled or otherwise) is run through Autoprefixer and style-loader. Any images/fonts etc referenced in the CSS will be copied to the build dir.
* CSS files are combined in the order in which they are imported in JavaScript, thus
diff --git a/app/actions/AppActions.js b/app/actions/AppActions.js
index 04ae62c..b3b2ad4 100644
--- a/app/actions/AppActions.js
+++ b/app/actions/AppActions.js
@@ -1,24 +1,9 @@
-import AppDispatcher from '../dispatcher/AppDispatcher';
-import WebAPI from '../util/WebAPI';
+import alt from '../alt'
-import {
- ITEMS_GET_SUCCESS,
- ITEMS_GET_ERROR
-} from '../constants/AppConstants';
-
-export default {
- getItems() {
- WebAPI.getItems()
- .then((items) => {
- AppDispatcher.dispatch({
- actionType: ITEMS_GET_SUCCESS,
- items: items
- });
- })
- .catch(() => {
- AppDispatcher.dispatch({
- actionType: ITEMS_GET_ERROR
- });
- });
+class AppActions {
+ constructor () {
+ this.generateActions('getItems')
}
-};
+}
+
+export default alt.createActions(AppActions)
diff --git a/app/alt.js b/app/alt.js
new file mode 100644
index 0000000..e54dc88
--- /dev/null
+++ b/app/alt.js
@@ -0,0 +1,2 @@
+import Alt from 'alt'
+export default new Alt()
diff --git a/app/app.jsx b/app/app.jsx
index b637681..65958f3 100644
--- a/app/app.jsx
+++ b/app/app.jsx
@@ -1,13 +1,14 @@
-import './favicon.ico';
-import './index.html';
-import 'babel-core/polyfill';
-import 'normalize.css/normalize.css';
-import './scss/app.scss';
+import './favicon.ico'
+import './index.html'
+import 'babel-core/polyfill'
+import 'normalize.css/normalize.css'
+import './scss/app.scss'
-import React from 'react';
-import App from './components/App/App';
+import React from 'react'
+import Router from 'react-router'
-React.render(
- ,
- document.getElementById('app')
-);
+import routes from './routes'
+
+Router.run(routes, function (Handler) {
+ React.render(React.createElement(Handler), document.body)
+})
diff --git a/app/app.tests.js b/app/app.tests.js
index 4eb1763..63395c9 100644
--- a/app/app.tests.js
+++ b/app/app.tests.js
@@ -1,4 +1,4 @@
-import 'babel-core/polyfill';
+import 'babel-core/polyfill'
-let context = require.context('.', true, /-test\.jsx?$/);
-context.keys().forEach(context);
+let context = require.context('.', true, /-test\.jsx?$/)
+context.keys().forEach(context)
diff --git a/app/components/App/App.jsx b/app/components/App/App.jsx
index 9435cdf..e5df830 100644
--- a/app/components/App/App.jsx
+++ b/app/components/App/App.jsx
@@ -1,40 +1,27 @@
-import styles from './_App.scss';
+import styles from './_App.scss'
-import React from 'react';
-import AppActions from '../../actions/AppActions';
-import ItemsStore from '../../stores/ItemsStore';
-import Body from '../Body/Body';
-import Footer from '../Footer/Footer';
-
-function getAppState() {
- return {
- items: ItemsStore.getAll()
- };
-}
+import React from 'react'
+import { RouteHandler } from 'react-router'
+import AppActions from '../../actions/AppActions'
+import ItemsStore from '../../stores/ItemsStore'
+import Footer from '../Footer/Footer'
export default class App extends React.Component {
-
- state = getAppState()
-
- componentDidMount() {
- ItemsStore.addChangeListener(this.onChange);
- AppActions.getItems();
- }
-
- componentWillUnmount() {
- ItemsStore.removeChangeListener(this.onChange);
- }
-
- onChange = () => {
- this.setState(getAppState());
+ constructor () {
+ super()
+ this.state = {items: []}
+ AppActions.getItems()
+ ItemsStore.listen((data) => {
+ this.setState({items: data.items})
+ })
}
- render() {
+ render () {
return (
-
+
- );
+ )
}
}
diff --git a/app/components/Body/Body.jsx b/app/components/Body/Body.jsx
index c2a3781..e4dfb0a 100644
--- a/app/components/Body/Body.jsx
+++ b/app/components/Body/Body.jsx
@@ -1,24 +1,25 @@
-import styles from './_Body.scss';
-import React from 'react';
-import Menu from '../Menu/Menu';
+import styles from './_Body.scss'
-let { PropTypes } = React;
+import React from 'react'
+import Menu from '../Menu/Menu'
+
+let { PropTypes } = React
export default class Body extends React.Component {
static defaultProps = {
items: []
- };
+ }
static propTypes = {
items: PropTypes.array.isRequired
- };
+ }
- render() {
+ render () {
return (
React Seed
-
This is an example seed app, powered by React, ES6 & webpack.
+
This is an example seed app, powered by React, ES6, Alt & webpack.
Here is some example data:
Getting started
@@ -29,6 +30,6 @@ export default class Body extends React.Component {
Change the data rendered above. Look in: ./app/components/App/App.jsx
Understand how data flows from the actions into the stores and then into the Body component.
- );
+ )
}
}
diff --git a/app/components/Footer/Footer.jsx b/app/components/Footer/Footer.jsx
index b470486..84f92fe 100644
--- a/app/components/Footer/Footer.jsx
+++ b/app/components/Footer/Footer.jsx
@@ -1,13 +1,13 @@
-import styles from './_Footer.scss';
-import React from 'react';
+import styles from './_Footer.scss'
+import React from 'react'
export default class Footer extends React.Component {
- render() {
- var year = (new Date()).getFullYear();
+ render () {
+ var year = (new Date()).getFullYear()
return (
- );
+ )
}
}
diff --git a/app/components/Footer/__tests__/Footer-test.jsx b/app/components/Footer/__tests__/Footer-test.jsx
index 8ab4f1b..cfbd313 100644
--- a/app/components/Footer/__tests__/Footer-test.jsx
+++ b/app/components/Footer/__tests__/Footer-test.jsx
@@ -1,15 +1,15 @@
-import React from 'react/addons';
-import Footer from '../Footer.jsx';
-import { expect } from 'chai';
+import React from 'react/addons'
+import Footer from '../Footer.jsx'
+import { expect } from 'chai'
-let { TestUtils } = React.addons;
+let { TestUtils } = React.addons
describe('Footer', () => {
it('Should have the correct footer element', () => {
let footer = TestUtils.renderIntoDocument(
- );
- let footerElem = React.findDOMNode(footer);
- expect(footerElem.tagName.toLowerCase()).to.equal('footer');
- });
-});
+ )
+ let footerElem = React.findDOMNode(footer)
+ expect(footerElem.tagName.toLowerCase()).to.equal('footer')
+ })
+})
diff --git a/app/components/Menu/Menu.jsx b/app/components/Menu/Menu.jsx
index 09eaa84..e6e37d0 100644
--- a/app/components/Menu/Menu.jsx
+++ b/app/components/Menu/Menu.jsx
@@ -1,26 +1,26 @@
-import styles from './_Menu.scss';
-import React from 'react';
-import MenuItem from './MenuItem';
+import styles from './_Menu.scss'
+import React from 'react'
+import MenuItem from './MenuItem'
-let { Component, PropTypes } = React;
+let { Component, PropTypes } = React
export default class Menu extends Component {
static defaultProps = {
items: []
- };
+ }
static propTypes = {
items: PropTypes.array.isRequired
- };
+ }
- render() {
+ render () {
return (
- {this.props.items.map((item) => {
- return ();
+ {this.props.items.map((item, key) => {
+ return ()
}, this)}
- );
+ )
}
}
diff --git a/app/components/Menu/MenuItem.jsx b/app/components/Menu/MenuItem.jsx
index 07d58d2..75e972f 100644
--- a/app/components/Menu/MenuItem.jsx
+++ b/app/components/Menu/MenuItem.jsx
@@ -1,25 +1,25 @@
-import React from 'react';
+import React from 'react'
-let { Component, PropTypes } = React;
+let { Component, PropTypes } = React
export default class MenuItem extends Component {
static propTypes = {
item: PropTypes.object.isRequired
- };
+ }
onItemClick = (e) => {
- e.preventDefault();
- window.alert('You clicked ' + this.props.item.label);
+ e.preventDefault()
+ window.alert('You clicked ' + this.props.item.label)
}
- render() {
+ render () {
return (
-
+
{this.props.item.label}
- );
+ )
}
}
diff --git a/app/components/Menu/__tests__/Menu-test.jsx b/app/components/Menu/__tests__/Menu-test.jsx
index 27505b1..e0251c3 100644
--- a/app/components/Menu/__tests__/Menu-test.jsx
+++ b/app/components/Menu/__tests__/Menu-test.jsx
@@ -1,79 +1,51 @@
-import React from 'react/addons';
-import { expect } from 'chai';
+import React from 'react/addons'
+import { expect } from 'chai'
-import Menu from '../Menu.jsx';
-import MenuItem from '../MenuItem.jsx';
+import Menu from '../Menu.jsx'
describe('Menu', () => {
-
- let { TestUtils } = React.addons;
+ let { TestUtils } = React.addons
let menuItems = [
{ id: 1, label: 'Option 1' },
{ id: 2, label: 'Option 2' }
- ];
+ ]
describe('Rendering', () => {
-
// Here we create a mocked MenuItem component.
- class MockedMenuItem extends MenuItem {
- render() {
- return (
- {this.props.item.label}
- );
- }
- }
-
- // Here we set the mocked MenuItem component.
- Menu.__Rewire__('MenuItem', MockedMenuItem);
-
let menu = TestUtils.renderIntoDocument(
- );
- let menuElem = React.findDOMNode(menu);
- let items = menuElem.querySelectorAll('li');
+ )
+ let menuElem = React.findDOMNode(menu)
+ let items = menuElem.querySelectorAll('li')
it('Should render the menu items', () => {
- expect(items.length).to.equal(2);
- });
+ expect(items.length).to.equal(2)
+ })
it('Should render the menu item labels', () => {
Array.prototype.forEach.call(items, (item, i) => {
- expect(item.textContent.trim()).to.equal(menuItems[i].label);
- });
- });
-
- it('Should render the mocked menu item', () => {
- expect(menuElem.querySelectorAll('li')[0].className).to.equal('mocked-menu-item');
- });
- });
-
- describe('Events', () => {
+ expect(item.textContent.trim()).to.equal(menuItems[i].label)
+ })
+ })
+ })
+ describe('Events', (done) => {
// Example of simulating browser events.
it('Should handle click events', () => {
-
- var clicked = 0;
-
- class MockedMenuItemWithClickHandler extends MenuItem {
- onItemClick = () => {
- clicked++;
- }
- }
-
- Menu.__Rewire__('MenuItem', MockedMenuItemWithClickHandler);
-
let menu = TestUtils.renderIntoDocument(
- );
- let menuElem = React.findDOMNode(menu);
- let items = menuElem.querySelectorAll('li');
- let node = items[0].querySelector('a');
-
- TestUtils.Simulate.click(node);
- TestUtils.Simulate.click(node);
+ )
+ let menuElem = React.findDOMNode(menu)
+ let items = menuElem.querySelectorAll('li')
+ let node = items[0].querySelector('a')
+
+ window.onAlert = function (alertText) {
+ expect(alertText).to.equal("ALERT: 'You clicked Option 1'")
+ done()
+ }
- expect(clicked).to.equal(2);
- });
- });
-});
+ TestUtils.Simulate.click(node)
+ })
+ })
+})
diff --git a/app/constants/AppConstants.js b/app/constants/AppConstants.js
deleted file mode 100644
index 1c27e06..0000000
--- a/app/constants/AppConstants.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import pkg from '../../package';
-
-export const DEBUG = (process.env.NODE_ENV !== 'production');
-export const APP_TITLE = pkg.name;
-export const ITEMS_GET_SUCCESS = 'ITEMS_GET_SUCCESS';
-export const ITEMS_GET_ERROR = 'ITEMS_GET_ERROR';
-export const ITEMS_UPDATED = 'ITEMS_UPDATED';
diff --git a/app/dispatcher/AppDispatcher.js b/app/dispatcher/AppDispatcher.js
index 9178b97..e54dc88 100644
--- a/app/dispatcher/AppDispatcher.js
+++ b/app/dispatcher/AppDispatcher.js
@@ -1,3 +1,2 @@
-import Flux from 'flux';
-
-export default new Flux.Dispatcher();
+import Alt from 'alt'
+export default new Alt()
diff --git a/app/routes.jsx b/app/routes.jsx
new file mode 100644
index 0000000..d72c72c
--- /dev/null
+++ b/app/routes.jsx
@@ -0,0 +1,10 @@
+import React from 'react'
+import { Route, DefaultRoute } from 'react-router'
+import App from './components/App/App'
+import Body from './components/Body/Body'
+
+export default (
+
+
+
+)
diff --git a/app/stores/BaseStore.js b/app/stores/BaseStore.js
deleted file mode 100644
index 6102ae2..0000000
--- a/app/stores/BaseStore.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import { EventEmitter } from 'events';
-
-export default class BaseStore extends EventEmitter {
-
- constructor(...args) {
- super(...args);
- this.data = new Set([]);
- }
-
- setAll(items) {
- this.data = new Set(items);
- this.emitChange();
- }
-
- getAll() {
- return Array.from(this.data);
- }
-
- set(item) {
- if (!this.data.has(item)) {
- this.data.add(item);
- this.emitChange();
- }
- }
-
- remove(item) {
- this.data.delete(item);
- this.emitChange();
- }
-}
diff --git a/app/stores/ItemsStore.js b/app/stores/ItemsStore.js
index 7a601c5..49373d2 100644
--- a/app/stores/ItemsStore.js
+++ b/app/stores/ItemsStore.js
@@ -1,35 +1,22 @@
-import BaseStore from './BaseStore';
-import AppDispatcher from '../dispatcher/AppDispatcher';
-
-import {
- ITEMS_UPDATED,
- ITEMS_GET_SUCCESS
-} from '../constants/AppConstants';
-
-class ItemsStore extends BaseStore {
-
- emitChange() {
- this.emit(ITEMS_UPDATED);
- }
-
- addChangeListener(callback) {
- this.on(ITEMS_UPDATED, callback);
+import alt from '../alt'
+import WebAPI from '../util/WebAPI'
+import AppActions from '../actions/AppActions'
+
+class ItemsStore {
+ constructor () {
+ this.bindAction(AppActions.getItems, this.getItems)
+ this.state = {items: [], error: null}
}
- removeChangeListener(callback) {
- this.removeListener(ITEMS_UPDATED, callback);
+ getItems () {
+ WebAPI.getItems()
+ .then((items) => {
+ this.setState({items: items})
+ })
+ .catch(() => {
+ this.state.error = 'error'
+ })
}
}
-let store = new ItemsStore();
-
-AppDispatcher.register((action) => {
- switch(action.actionType) {
- case ITEMS_GET_SUCCESS:
- store.setAll(action.items);
- break;
- default:
- }
-});
-
-export default store;
+export default alt.createStore(ItemsStore, 'ItemsStore')
diff --git a/app/stores/__tests__/BaseStore-test.js b/app/stores/__tests__/BaseStore-test.js
deleted file mode 100644
index 2fe2ac2..0000000
--- a/app/stores/__tests__/BaseStore-test.js
+++ /dev/null
@@ -1,78 +0,0 @@
-import 'babel-core/polyfill';
-import BaseStore from '../BaseStore.js';
-import { expect } from 'chai';
-
-const ITEMS_UPDATED = 'ITEMS_UPDATED';
-
-class TestStore extends BaseStore {
- emitChange() {
- this.emit(ITEMS_UPDATED);
- }
- addChangeListener(callback) {
- this.on(ITEMS_UPDATED, callback);
- }
- removeChangeListener(callback) {
- this.removeListener(ITEMS_UPDATED, callback);
- }
-}
-
-describe('BaseStore', () => {
-
- it('Should set, get and remove data', function() {
-
- let store = new TestStore();
-
- expect(store.getAll()).to.eql([]);
-
- let item = {
- foo: 'bar'
- };
-
- store.setAll([item]);
- expect(store.getAll()).to.eql([item]);
-
- let item2 = {
- foobaz: 'bar'
- };
-
- store.set(item2);
- store.set(item2); // intentional check for unique items
- expect(store.getAll()).to.eql([item, item2]);
-
- store.remove(item);
- expect(store.getAll()).to.eql([item2]);
- });
-
- it('Should call the change listener when data changes', () => {
-
- let store = new TestStore();
- let onChange = sinon.spy();
- store.addChangeListener(onChange);
-
- store.setAll([{
- foo: 'bar'
- }]);
- store.set([{
- foobaz: 'bar'
- }]);
- store.remove({
- foo: 'bar'
- });
- expect(onChange.callCount).to.equal(3);
- });
-
- it('Should remove the change listener', () => {
-
- let store = new TestStore();
- let onChange = sinon.spy();
- store.addChangeListener(onChange);
- store.setAll([{
- foo: 'bar'
- }]);
- store.removeChangeListener(onChange);
- store.setAll([{
- foo: 'bar'
- }]);
- expect(onChange.callCount).to.equal(1);
- });
-});
diff --git a/app/util/WebAPI.js b/app/util/WebAPI.js
index 56709a9..9b16997 100644
--- a/app/util/WebAPI.js
+++ b/app/util/WebAPI.js
@@ -1,14 +1,14 @@
export default {
- getItems() {
+ getItems () {
return new Promise((resolve) => {
setTimeout(() => {
resolve(['Item 1', 'Item 2', 'Item 3'].map((item, i) => {
return {
id: i,
label: item
- };
- }));
- }, 500);
- });
+ }
+ }))
+ }, 500)
+ })
}
-};
+}
diff --git a/circle.yml b/circle.yml
new file mode 100644
index 0000000..d05c9d6
--- /dev/null
+++ b/circle.yml
@@ -0,0 +1,5 @@
+test:
+ override:
+ - npm run test-circleci
+ pre:
+ - npm run pretest-circleci
diff --git a/dev-server.js b/dev-server.js
index 4dc87ff..44b8a2b 100644
--- a/dev-server.js
+++ b/dev-server.js
@@ -1,23 +1,23 @@
-var util = require('util');
-var webpack = require('webpack');
-var WebpackDevServer = require('webpack-dev-server');
-var opn = require('opn');
-var pkg = require('./package.json');
+var util = require('util')
+var webpack = require('webpack')
+var WebpackDevServer = require('webpack-dev-server')
+var opn = require('opn')
+var pkg = require('./package.json')
-var port = pkg.config.devPort;
-var host = pkg.config.devHost;
+var port = pkg.config.devPort
+var host = pkg.config.devHost
-var configPath = process.argv[2] || './webpack/config';
-var config = require(configPath);
+var configPath = process.argv[2] || './webpack/config'
+var config = require(configPath)
var server = new WebpackDevServer(
webpack(config),
config.devServer
-);
+)
server.listen(port, host, function (err) {
- if (err) { console.log(err); }
- var url = util.format('http://%s:%d', host, port);
- console.log('Listening at %s', url);
- opn(url);
-});
+ if (err) { console.log(err) }
+ var url = util.format('http://%s:%d', host, port)
+ console.log('Listening at %s', url)
+ opn(url)
+})
diff --git a/karma.conf.js b/karma.conf.js
index 1d2d4cc..2a214b2 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -1,4 +1,4 @@
-module.exports = function(config) {
+module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['source-map-support', 'mocha', 'sinon'],
@@ -7,18 +7,18 @@ module.exports = function(config) {
],
exclude: [],
preprocessors: {
- 'app/app.tests.js': ['webpack', 'sourcemap'],
+ 'app/app.tests.js': ['webpack', 'sourcemap']
},
reporters: ['mocha'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
- browsers: [/*'Chrome', */'PhantomJS'],
+ browsers: ['PhantomJS'],
singleRun: false,
webpack: require('./webpack/config.test'),
webpackMiddleware: {
noInfo: true
}
- });
-};
+ })
+}
diff --git a/package.json b/package.json
index 74e8332..c0552cf 100644
--- a/package.json
+++ b/package.json
@@ -14,31 +14,33 @@
"build": "NODE_ENV=production npm run webpack",
"clean": "rimraf $npm_package_config_buildDir && mkdir $npm_package_config_buildDir",
"env": "env",
- "lint": "eslint --ext .js --ext .jsx ./app ./webpack && echo No linting errors.",
+ "lint": "standard",
"prebuild": "npm run clean",
"prestart": "npm install",
"pretest": "npm install && npm run lint",
- "pretest-travis": "npm install && npm run lint",
+ "pretest-circleci": "npm run lint",
"start": "NODE_ENV=development node dev-server ./webpack/config",
"test": "NODE_ENV=test karma start",
- "test-travis": "NODE_ENV=test karma start --single-run",
+ "test-circleci": "NODE_ENV=test karma start --single-run",
"webpack": "webpack --colors --progress --config ./webpack/config"
},
"dependencies": {
+ "alt": "^0.17.3",
"classnames": "^2.1.3",
"flux": "^2.0.3",
"normalize.css": "^3.0.3",
- "react": "^0.13.3"
+ "react": "^0.13.3",
+ "react-router": "^0.13.3"
},
"devDependencies": {
"autoprefixer-core": "^5.2.1",
- "babel-core": "^5.8.3",
- "babel-eslint": "^3.1.23",
- "babel-loader": "^5.3.1",
- "babel-plugin-rewire": "^0.1.8",
+ "babel-core": "^5.8.x",
+ "babel-eslint": "^4.1.x",
+ "babel-loader": "^5.4.x",
+ "babel-plugin-rewire": "^0.1.22",
"babel-runtime": "^5.6.15",
"chai": "^3.0.0",
- "css-loader": "^0.15.2",
+ "css-loader": "^0.23.0",
"eslint": "^0.24.0",
"eslint-plugin-react": "^2.6.4",
"extract-text-webpack-plugin": "^0.8.2",
@@ -68,8 +70,11 @@
"rimraf": "^2.4.1",
"sass-loader": "^1.0.2",
"sinon": "^1.15.4",
+ "snazzy": "^2.0.1",
"source-map-support": "^0.3.2",
+ "standard": "^5.4.1",
"style-loader": "^0.12.3",
+ "stylus-loader": "^1.4.2",
"template-html-loader": "0.0.3",
"webpack": "^1.10.1",
"webpack-dev-server": "^1.10.1"
@@ -77,43 +82,25 @@
"engines": {
"node": ">=0.12.0"
},
- "eslintConfig": {
- "env": {
- "browser": true,
- "node": true,
- "es6": true
- },
+ "standard": {
+ "parser": "babel-eslint",
"ecmaFeatures": {
+ "classes": true,
"modules": true,
"jsx": true
},
- "globals": {
- "describe": true,
- "it": true,
- "sinon": true
- },
- "parser": "babel-eslint",
+ "globals": [
+ "describe",
+ "it"
+ ],
"plugins": [
"react"
],
"rules": {
- "strict": [
- 2,
- "global"
- ],
- "indent": [
- 2,
- 2
- ],
- "quotes": [
- 2,
- "single"
- ],
- "no-alert": 0,
- "no-underscore-dangle": 0,
- "react/display-name": 0,
- "react/jsx-quotes": 1,
+ "react/jsx-boolean-value": 1,
"react/jsx-no-undef": 1,
+ "react/jsx-quotes": 1,
+ "react/jsx-sort-prop-types": 1,
"react/jsx-sort-props": 1,
"react/jsx-uses-react": 1,
"react/jsx-uses-vars": 1,
@@ -121,10 +108,20 @@
"react/no-did-update-set-state": 1,
"react/no-multi-comp": 1,
"react/no-unknown-property": 1,
- "react/prop-types": 0,
+ "react/prop-types": 1,
"react/react-in-jsx-scope": 1,
"react/self-closing-comp": 1,
- "react/wrap-multilines": 1
+ "react/sort-comp": 1,
+ "react/wrap-multilines": 1,
+ "strict": 0,
+ "indent": [
+ 2,
+ 4
+ ],
+ "no-unused-vars": 0
+ },
+ "env": {
+ "node": true
}
}
}
diff --git a/webpack/config.js b/webpack/config.js
index ebda29b..9833b69 100644
--- a/webpack/config.js
+++ b/webpack/config.js
@@ -1,19 +1,19 @@
-var path = require('path');
-var util = require('util');
-var autoprefixer = require('autoprefixer-core');
-var pkg = require('../package.json');
+var path = require('path')
+var util = require('util')
+var autoprefixer = require('autoprefixer-core')
+var pkg = require('../package.json')
-var loaders = require('./loaders');
-var plugins = require('./plugins');
+var loaders = require('./loaders')
+var plugins = require('./plugins')
-var DEBUG = process.env.NODE_ENV === 'development';
-var TEST = process.env.NODE_ENV === 'test';
+var DEBUG = process.env.NODE_ENV === 'development'
+var TEST = process.env.NODE_ENV === 'test'
-var jsBundle = path.join('js', util.format('[name].%s.js', pkg.version));
+var jsBundle = path.join('js', util.format('[name].%s.js', pkg.version))
var entry = {
app: ['./app.jsx']
-};
+}
if (DEBUG) {
entry.app.push(
@@ -22,8 +22,8 @@ if (DEBUG) {
pkg.config.devHost,
pkg.config.devPort
)
- );
- entry.app.push('webpack/hot/dev-server');
+ )
+ entry.app.push('webpack/hot/dev-server')
}
var config = {
@@ -56,6 +56,6 @@ var config = {
inline: true,
stats: { colors: true }
}
-};
+}
-module.exports = config;
+module.exports = config
diff --git a/webpack/config.test.js b/webpack/config.test.js
index a4e49fb..f02a645 100644
--- a/webpack/config.test.js
+++ b/webpack/config.test.js
@@ -1,8 +1,8 @@
-var config = require('./config');
+var config = require('./config')
-delete config.context;
-delete config.entry;
-delete config.output;
-delete config.devServer;
+delete config.context
+delete config.entry
+delete config.output
+delete config.devServer
-module.exports = config;
+module.exports = config
diff --git a/webpack/loaders.js b/webpack/loaders.js
index 840974a..1de5ba9 100644
--- a/webpack/loaders.js
+++ b/webpack/loaders.js
@@ -1,14 +1,14 @@
-var path = require('path');
-var pkg = require('../package.json');
-var ExtractTextPlugin = require('extract-text-webpack-plugin');
+var path = require('path')
+var pkg = require('../package.json')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
-var DEBUG = process.env.NODE_ENV === 'development';
-var TEST = process.env.NODE_ENV === 'test';
+var DEBUG = process.env.NODE_ENV === 'development'
+var TEST = process.env.NODE_ENV === 'test'
-var jsxLoader;
-var sassLoader;
-var cssLoader;
-var fileLoader = 'file-loader?name=[path][name].[ext]';
+var jsxLoader
+var sassLoader
+var cssLoader
+var fileLoader = 'file-loader?name=[path][name].[ext]'
var htmlLoader = [
'file-loader?name=[path][name].[ext]',
'template-html-loader?' + [
@@ -18,44 +18,44 @@ var htmlLoader = [
'title=' + pkg.name,
'debug=' + DEBUG
].join('&')
-].join('!');
-var jsonLoader = ['json-loader'];
+].join('!')
+var jsonLoader = ['json-loader']
var sassParams = [
'outputStyle=expanded',
'includePaths[]=' + path.resolve(__dirname, '../app/scss'),
'includePaths[]=' + path.resolve(__dirname, '../node_modules')
-];
+]
if (DEBUG || TEST) {
- jsxLoader = [];
+ jsxLoader = []
if (!TEST) {
- jsxLoader.push('react-hot');
+ jsxLoader.push('react-hot')
}
- jsxLoader.push('babel-loader?optional[]=runtime&stage=0&plugins=rewire');
- sassParams.push('sourceMap', 'sourceMapContents=true');
+ jsxLoader.push('babel-loader?optional[]=runtime&stage=0')
+ sassParams.push('sourceMap', 'sourceMapContents=true')
sassLoader = [
'style-loader',
'css-loader?sourceMap&modules&localIdentName=[name]__[local]___[hash:base64:5]',
'postcss-loader',
'sass-loader?' + sassParams.join('&')
- ].join('!');
+ ].join('!')
cssLoader = [
'style-loader',
'css-loader?sourceMap&modules&localIdentName=[name]__[local]___[hash:base64:5]',
'postcss-loader'
- ].join('!');
+ ].join('!')
} else {
- jsxLoader = ['babel-loader?optional[]=runtime&stage=0&plugins=rewire'];
+ jsxLoader = ['babel-loader?optional[]=runtime&stage=0&plugins=rewire']
sassLoader = ExtractTextPlugin.extract('style-loader', [
'css-loader?modules&localIdentName=[hash:base64:5]',
'postcss-loader',
'sass-loader?' + sassParams.join('&')
- ].join('!'));
+ ].join('!'))
cssLoader = ExtractTextPlugin.extract('style-loader', [
'css-loader?modules&localIdentName=[hash:base64:5]',
'postcss-loader'
- ].join('!'));
+ ].join('!'))
}
var loaders = [
@@ -84,7 +84,11 @@ var loaders = [
{
test: /\.scss$/,
loader: sassLoader
+ },
+ {
+ test: /\.styl$/,
+ loader: 'css-loader!stylus-loader?paths=node_modules/bootstrap-stylus/stylus/'
}
-];
+]
-module.exports = loaders;
+module.exports = loaders
diff --git a/webpack/plugins.js b/webpack/plugins.js
index 1df9b3e..4133db3 100644
--- a/webpack/plugins.js
+++ b/webpack/plugins.js
@@ -1,21 +1,21 @@
-var path = require('path');
-var util = require('util');
-var ExtractTextPlugin = require('extract-text-webpack-plugin');
-var webpack = require('webpack');
-var pkg = require('../package.json');
+var path = require('path')
+var util = require('util')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var webpack = require('webpack')
+var pkg = require('../package.json')
-var DEBUG = process.env.NODE_ENV === 'development';
-var TEST = process.env.NODE_ENV === 'test';
+var DEBUG = process.env.NODE_ENV === 'development'
+var TEST = process.env.NODE_ENV === 'test'
-var cssBundle = path.join('css', util.format('[name].%s.css', pkg.version));
+var cssBundle = path.join('css', util.format('[name].%s.css', pkg.version))
var plugins = [
new webpack.optimize.OccurenceOrderPlugin()
-];
+]
if (DEBUG) {
plugins.push(
new webpack.HotModuleReplacementPlugin()
- );
+ )
} else if (!TEST) {
plugins.push(
new ExtractTextPlugin(cssBundle, {
@@ -29,7 +29,7 @@ if (DEBUG) {
}
}),
new webpack.NoErrorsPlugin()
- );
+ )
}
-module.exports = plugins;
+module.exports = plugins