├── .babelrc ├── .eslintrc ├── .gitignore ├── README.md ├── index.html ├── package.json ├── src ├── actions │ ├── index.js │ └── itemsActions.js ├── app.js ├── components │ ├── colorizeWrapper.js │ ├── todoItem.e2e.spec.js │ ├── todoItem.js │ └── todoItem.spec.js ├── containers │ └── todoList.js ├── reducers │ ├── index.js │ └── itemsReducer.js ├── store │ └── configureStore.js └── testSetup.spec.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "modules": true, 4 | "experimentalObjectRestSpread": true, 5 | "jsx": true 6 | }, 7 | 8 | "env": { 9 | "browser": true, 10 | "es6": true, 11 | "node": true 12 | }, 13 | 14 | "plugins": [ 15 | "standard", 16 | "react" 17 | ], 18 | 19 | "globals": { 20 | "document": false, 21 | "navigator": false, 22 | "window": false, 23 | "expect": false, 24 | "sinon": false, 25 | "before": false, 26 | "after": false, 27 | "afterEach": false, 28 | "describe": false, 29 | "beforeEach": false, 30 | "inject": false, 31 | "it": false 32 | }, 33 | 34 | "rules": { 35 | "accessor-pairs": 2, 36 | "arrow-spacing": [2, { "before": true, "after": true }], 37 | "block-spacing": [2, "always"], 38 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 39 | "comma-dangle": [2, "never"], 40 | "comma-spacing": [2, { "before": false, "after": true }], 41 | "comma-style": [2, "last"], 42 | "constructor-super": 2, 43 | "curly": [2, "multi-line"], 44 | "dot-location": [2, "property"], 45 | "eol-last": 2, 46 | "eqeqeq": [2, "allow-null"], 47 | "generator-star-spacing": [2, { "before": true, "after": true }], 48 | "handle-callback-err": [2, "^(err|error)$" ], 49 | "indent": [2, 2, { "SwitchCase": 1 }], 50 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 51 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 52 | "new-parens": 2, 53 | "no-array-constructor": 2, 54 | "no-caller": 2, 55 | "no-class-assign": 2, 56 | "no-cond-assign": 2, 57 | "no-const-assign": 2, 58 | "no-control-regex": 2, 59 | "no-debugger": 2, 60 | "no-delete-var": 2, 61 | "no-dupe-args": 2, 62 | "no-dupe-class-members": 2, 63 | "no-dupe-keys": 2, 64 | "no-duplicate-case": 2, 65 | "no-empty-character-class": 2, 66 | "no-empty-label": 2, 67 | "no-eval": 2, 68 | "no-ex-assign": 2, 69 | "no-extend-native": 2, 70 | "no-extra-bind": 2, 71 | "no-extra-boolean-cast": 2, 72 | "no-extra-parens": [2, "functions"], 73 | "no-fallthrough": 2, 74 | "no-floating-decimal": 2, 75 | "no-func-assign": 2, 76 | "no-implied-eval": 2, 77 | "no-inner-declarations": [2, "functions"], 78 | "no-invalid-regexp": 2, 79 | "no-irregular-whitespace": 2, 80 | "no-iterator": 2, 81 | "no-label-var": 2, 82 | "no-labels": 2, 83 | "no-lone-blocks": 2, 84 | "no-mixed-spaces-and-tabs": 2, 85 | "no-multi-spaces": 2, 86 | "no-multi-str": 2, 87 | "no-multiple-empty-lines": [2, { "max": 1 }], 88 | "no-native-reassign": 2, 89 | "no-negated-in-lhs": 2, 90 | "no-new": 2, 91 | "no-new-func": 2, 92 | "no-new-object": 2, 93 | "no-new-require": 2, 94 | "no-new-wrappers": 2, 95 | "no-obj-calls": 2, 96 | "no-octal": 2, 97 | "no-octal-escape": 2, 98 | "no-proto": 2, 99 | "no-redeclare": 2, 100 | "no-regex-spaces": 2, 101 | "no-return-assign": 2, 102 | "no-self-compare": 2, 103 | "no-sequences": 2, 104 | "no-shadow-restricted-names": 2, 105 | "no-spaced-func": 2, 106 | "no-sparse-arrays": 2, 107 | "no-this-before-super": 2, 108 | "no-throw-literal": 2, 109 | "no-trailing-spaces": 2, 110 | "no-undef": 2, 111 | "no-undef-init": 2, 112 | "no-unexpected-multiline": 2, 113 | "no-unneeded-ternary": 2, 114 | "no-unreachable": 2, 115 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 116 | "no-useless-call": 2, 117 | "no-with": 2, 118 | "one-var": [2, { "initialized": "never" }], 119 | "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], 120 | "padded-blocks": [2, "never"], 121 | "quotes": [2, "single", "avoid-escape"], 122 | "radix": 2, 123 | "semi": [2, "never"], 124 | "space-after-keywords": [2, "always"], 125 | "space-before-blocks": [2, "always"], 126 | "space-before-function-paren": [2, "always"], 127 | "space-in-parens": [2, "never"], 128 | "space-infix-ops": 2, 129 | "space-return-throw-case": 2, 130 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 131 | "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","] }], 132 | "use-isnan": 2, 133 | "valid-typeof": 2, 134 | "wrap-iife": [2, "any"], 135 | "yoda": [2, "never"], 136 | 137 | "standard/object-curly-even-spacing": [2, "either"], 138 | "standard/array-bracket-even-spacing": [2, "either"], 139 | "standard/computed-property-even-spacing": [2, "even"], 140 | 141 | "react/display-name": 1, 142 | "react/forbid-prop-types": 1, 143 | "react/jsx-boolean-value": 1, 144 | "react/jsx-closing-bracket-location": 0, 145 | "react/jsx-curly-spacing": 1, 146 | "react/jsx-indent-props": 1, 147 | "react/jsx-max-props-per-line": 0, 148 | "react/jsx-no-bind": 1, 149 | "react/jsx-no-duplicate-props": 1, 150 | "react/jsx-no-literals": 1, 151 | "react/jsx-no-undef": 1, 152 | "react/jsx-quotes": 1, 153 | "react/jsx-sort-prop-types": 0, 154 | "react/jsx-sort-props": 0, 155 | "react/jsx-uses-react": 1, 156 | "react/jsx-uses-vars": 1, 157 | "react/no-danger": 1, 158 | "react/no-did-mount-set-state": 1, 159 | "react/no-did-update-set-state": 1, 160 | "react/no-direct-mutation-state": 1, 161 | "react/no-multi-comp": 1, 162 | "react/no-set-state": 0, 163 | "react/no-unknown-property": 1, 164 | "react/prefer-es6-class": 1, 165 | "react/prop-types": 1, 166 | "react/react-in-jsx-scope": 1, 167 | "react/require-extension": 1, 168 | "react/self-closing-comp": 1, 169 | "react/sort-comp": 1, 170 | "react/wrap-multilines": 1 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Distribution 30 | dist 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-baby-steps 2 | [ ![Codeship Status for RisingStack/react-baby-steps](https://codeship.com/projects/86ca0ee0-7f3e-0133-9947-3eaf58434e02/status?branch=master)](https://codeship.com/projects/120531) 3 | 4 | Introducing beginners to the world of React via coding. 5 | The goal of this repo is to understand the basic concepts before you start using magic ~Flux libraries. 6 | 7 | If you are looking for the ideas behind React, like VirtualDOM, you should start here: 8 | https://blog.risingstack.com/the-react-way-getting-started-tutorial/ 9 | 10 | Chapters as releases: 11 | https://github.com/RisingStack/react-baby-steps/releases 12 | 13 | ## chapters 14 | 15 | I recommend you to checkout each chapters step by step and see what's changed. 16 | If you are hungry for detailed explanation, ping me on Twitter: https://twitter.com/slashdotpeter 17 | 18 | 1. [Build system](https://github.com/RisingStack/react-baby-steps/releases/tag/01_Build_system) 19 | 2. [Components](https://github.com/RisingStack/react-baby-steps/releases/tag/02_Components) 20 | 3. [Component lifecycle](https://github.com/RisingStack/react-baby-steps/releases/tag/03_Component_Lifecycle) 21 | 4. [Immutable.js](https://github.com/RisingStack/react-baby-steps/releases/tag/04_ImmutableJS) 22 | 5. [Component's state](https://github.com/RisingStack/react-baby-steps/releases/tag/05_Component_state) 23 | 6. [Mixins and LinkState](https://github.com/RisingStack/react-baby-steps/releases/tag/06_Mixins_LinkState) 24 | 7. [Higher order components](https://github.com/RisingStack/react-baby-steps/releases/tag/07_Higher_order_components) 25 | 8. [Context](https://github.com/RisingStack/react-baby-steps/releases/tag/08_Context) 26 | 9. [Component testing](https://github.com/RisingStack/react-baby-steps/releases/tag/09_Component_testing) 27 | 10. [Flux basics with Rx](https://github.com/RisingStack/react-baby-steps/releases/tag/10_Flux_basics_with_Rx) 28 | 11. [Redux](https://github.com/RisingStack/react-baby-steps/releases/tag/11_Redux) 29 | 30 | ## next 31 | 32 | In the future I plan to add more chapters like data fetching, routing and complex data handling with Redux. 33 | 34 | ## run 35 | ``` 36 | npm i 37 | npm run dev 38 | ``` 39 | 40 | ## test 41 | ``` 42 | npm test 43 | ``` 44 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-split", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "test": "mocha --compilers js:babel-register 'src/**/*.spec.js'", 7 | "dev": "webpack-dev-server --port 8081 --config webpack.config.js" 8 | }, 9 | "author": "Peter Marton", 10 | "license": "ISC", 11 | "engines": { 12 | "node": "4.2.3" 13 | }, 14 | "devDependencies": { 15 | "babel-core": "^6.2.1", 16 | "babel-loader": "^6.2.0", 17 | "babel-preset-es2015": "^6.1.18", 18 | "babel-preset-react": "^6.1.18", 19 | "babel-register": "^6.3.13", 20 | "chai": "^3.4.1", 21 | "classnames": "^2.2.1", 22 | "enzyme": "^1.0.0", 23 | "eslint": "^1.10.2", 24 | "eslint-plugin-react": "^3.10.0", 25 | "eslint-plugin-standard": "^1.3.1", 26 | "immutable": "^3.7.5", 27 | "lodash": "^3.10.1", 28 | "mocha": "^2.3.4", 29 | "react": "^0.14.3", 30 | "react-addons-linked-state-mixin": "^0.14.3", 31 | "react-addons-test-utils": "^0.14.3", 32 | "react-dom": "^0.14.3", 33 | "react-immutable-proptypes": "^1.5.0", 34 | "react-mixin": "^3.0.3", 35 | "react-redux": "^4.0.0", 36 | "redux": "^3.0.4", 37 | "sinon": "^1.17.2", 38 | "sinon-chai": "^2.8.0", 39 | "webpack": "^1.12.9", 40 | "webpack-dev-server": "^1.14.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import * as items from './itemsActions' 4 | 5 | export default { 6 | items 7 | } 8 | -------------------------------------------------------------------------------- /src/actions/itemsActions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export const ITEM_TOGGLE_RESOLVED = 'ITEM_TOGGLE_RESOLVED' 4 | 5 | export function itemToggleResolved (id) { 6 | return { 7 | type: ITEM_TOGGLE_RESOLVED, 8 | id 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React from 'react' 4 | import { render } from 'react-dom' 5 | 6 | import { Provider } from 'react-redux' 7 | import configureStore from './store/configureStore' 8 | 9 | import TodoList from './containers/todoList' 10 | 11 | const store = configureStore() 12 | 13 | render( 14 | 15 | 16 | , 17 | document.getElementById('root') 18 | ) 19 | -------------------------------------------------------------------------------- /src/components/colorizeWrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { Component } from 'react' 4 | 5 | export default function connect (ComponentToWrap, colors) { 6 | class Colorize extends Component { 7 | render () { 8 | const color = colors[Math.floor(Math.random() * colors.length)] 9 | 10 | return 11 | } 12 | } 13 | 14 | Colorize.displayName = 'Colorize' 15 | 16 | return Colorize 17 | } 18 | -------------------------------------------------------------------------------- /src/components/todoItem.e2e.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { render } from 'enzyme' 3 | import { fromJS } from 'immutable' 4 | import { expect } from 'chai' 5 | 6 | import TodoItem from './todoItem' 7 | 8 | describe(' e2e', () => { 9 | it('to have color', () => { 10 | const props = { 11 | color: '#ccc', 12 | item: fromJS({ 13 | id: 1, 14 | name: 'My Item', 15 | isResolved: true 16 | }), 17 | toggleItemResolve: () => {} 18 | } 19 | const wrapper = render() 20 | expect(wrapper.html()).to.contain('#ccc') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /src/components/todoItem.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { Component, PropTypes } from 'react' 4 | import ImmutablePropTypes from 'react-immutable-proptypes' 5 | import classNames from 'classnames' 6 | 7 | /** 8 | * @class TodoItem 9 | */ 10 | class TodoItem extends Component { 11 | constructor () { 12 | super() 13 | 14 | this.toggleResolved = this.toggleResolved.bind(this) 15 | } 16 | 17 | // Lifecycle methods 18 | componentWillMount () {} 19 | componentDidMount () {} 20 | 21 | componentWillReceiveProps (nextProps) {} 22 | shouldComponentUpdate (nextProps, nextState) { 23 | // reference comparison is cheap 24 | const isDirty = nextProps.item !== this.props.item 25 | console.log(`shouldComponentUpdate id: ${nextProps.item.get('id')}, dirty: ${isDirty}`) 26 | 27 | return isDirty 28 | } 29 | 30 | componentWillUpdate (nextProps, nextState) {} 31 | componentDidUpdate (nextProps, nextState) {} 32 | 33 | componentWillUnmount () {} 34 | 35 | toggleResolved () { 36 | const { toggleItemResolve, item } = this.props 37 | 38 | toggleItemResolve(item) 39 | } 40 | 41 | /** 42 | * @method render 43 | * @return {JSX} 44 | */ 45 | render () { 46 | const { item, color } = this.props 47 | const { toggleResolved } = this 48 | const style = { 49 | color: color 50 | } 51 | 52 | const className = classNames({ 53 | 'resolved': item.get('isResolved') 54 | }) 55 | 56 | return ( 57 | {item.get('id')} 58 | {item.get('name')} 59 | 60 | 61 | 62 | ) 63 | } 64 | } 65 | 66 | TodoItem.displayName = 'TodoItem' 67 | 68 | TodoItem.propTypes = { 69 | item: ImmutablePropTypes.contains({ 70 | id: PropTypes.number.isRequired, 71 | name: PropTypes.string.isRequired, 72 | isResolved: PropTypes.bool.isRequired 73 | }).isRequired, 74 | color: PropTypes.string.isRequired, 75 | toggleItemResolve: PropTypes.func.isRequired 76 | } 77 | 78 | export default TodoItem 79 | -------------------------------------------------------------------------------- /src/components/todoItem.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { shallow } from 'enzyme' 3 | import { expect } from 'chai' 4 | import { fromJS } from 'immutable' 5 | 6 | import TodoItem from './todoItem' 7 | 8 | describe('', () => { 9 | let props 10 | 11 | beforeEach(function () { 12 | props = { 13 | color: '#ccc', 14 | item: fromJS({ 15 | id: 1, 16 | name: 'My Item', 17 | isResolved: true 18 | }), 19 | toggleItemResolve: this.sandbox.spy() 20 | } 21 | }) 22 | 23 | it('should have id and name', () => { 24 | const wrapper = shallow() 25 | expect(wrapper.prop('style')).to.be.eql({ color: '#ccc' }) 26 | 27 | expect(wrapper.find('td').length).to.be.equal(3) 28 | expect(wrapper.contains({1})).to.be.true 29 | expect(wrapper.contains({'My Item'})).to.be.true 30 | }) 31 | 32 | it('should add .resolved class', () => { 33 | const wrapper = shallow() 34 | expect(wrapper.hasClass('resolved')).to.be.true 35 | }) 36 | 37 | it('simulates click events', function () { 38 | const wrapper = shallow() 39 | wrapper.find('button').simulate('click') 40 | 41 | expect(props.toggleItemResolve).to.be.calledOnce 42 | expect(props.toggleItemResolve).to.be.calledWith(props.item) 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /src/containers/todoList.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import React, { Component, PropTypes } from 'react' 4 | import ImmutablePropTypes from 'react-immutable-proptypes' 5 | import LinkedStateMixin from 'react-addons-linked-state-mixin' 6 | import ReactMixin from 'react-mixin' 7 | import { connect } from 'react-redux' 8 | 9 | import actions from '../actions' 10 | import colorizeWrapper from '../components/colorizeWrapper' 11 | import TodoItem from '../components/todoItem' 12 | 13 | const ColoredTodoItem = colorizeWrapper(TodoItem, ['#d15f11', '#115bd1', '#d6d641']) 14 | 15 | /** 16 | * @class TodoList 17 | */ 18 | class TodoList extends Component { 19 | constructor () { 20 | super() 21 | 22 | this.state = { 23 | query: null 24 | } 25 | 26 | // autobinding only for lifecycle methods 27 | this.linkState = this.linkState.bind(this) 28 | } 29 | 30 | static isMatch (query, item) { 31 | if (!query) { 32 | return true 33 | } 34 | 35 | return item.get('name').match(new RegExp(query, 'i')) 36 | } 37 | 38 | /** 39 | * @method render 40 | * @return {JSX} 41 | */ 42 | render () { 43 | const style = { 44 | width: '20%', 45 | backgroundColor: '#ececec' 46 | } 47 | 48 | const { items, itemToggleResolved } = this.props 49 | const { query } = this.state 50 | 51 | const toggleItemResolve = item => itemToggleResolved(item) 52 | 53 | return ( 54 |
55 | 56 | 57 | 58 | {items 59 | .filter(item => TodoList.isMatch(query, item)) 60 | .map(item => )} 61 | 62 |
63 |
64 | ) 65 | } 66 | } 67 | 68 | TodoList.displayName = 'TodoList' 69 | 70 | TodoList.propTypes = { 71 | items: ImmutablePropTypes.listOf(ImmutablePropTypes.contains({ 72 | id: PropTypes.number.isRequired, 73 | name: PropTypes.string.isRequired, 74 | isResolved: PropTypes.bool.isRequired 75 | })).isRequired, 76 | itemToggleResolved: PropTypes.func.isRequired 77 | } 78 | 79 | ReactMixin.onClass(TodoList, LinkedStateMixin) 80 | 81 | function mapStateToProps (state) { 82 | return { 83 | items: state.items 84 | } 85 | } 86 | 87 | function mapDispatchToProps (dispatch) { 88 | const { itemToggleResolved } = actions.items 89 | 90 | return { 91 | itemToggleResolved: item => dispatch(itemToggleResolved(item.get('id'))) 92 | } 93 | } 94 | 95 | export default connect(mapStateToProps, mapDispatchToProps)(TodoList) 96 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { combineReducers } from 'redux' 4 | 5 | import items from './itemsReducer' 6 | 7 | export default combineReducers({ 8 | items 9 | }) 10 | -------------------------------------------------------------------------------- /src/reducers/itemsReducer.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { ITEM_TOGGLE_RESOLVED } from '../actions/itemsActions' 4 | import { fromJS } from 'immutable' 5 | 6 | const defaultState = fromJS([ 7 | { 8 | id: 1, 9 | name: 'Buy cat food', 10 | isResolved: false 11 | }, 12 | { 13 | id: 2, 14 | name: 'Learn React', 15 | isResolved: true 16 | }, 17 | { 18 | id: 3, 19 | name: 'Avoid semicolons', 20 | isResolved: false 21 | } 22 | ]) 23 | 24 | function onItemToggleResolved (state, action) { 25 | const idx = state.findIndex(item => item.get('id') === action.id) 26 | return state.updateIn([idx, 'isResolved'], isResolved => !isResolved) 27 | } 28 | 29 | export default function (state = defaultState, action) { 30 | switch (action.type) { 31 | 32 | case ITEM_TOGGLE_RESOLVED: 33 | return onItemToggleResolved(state, action) 34 | 35 | default: 36 | return state 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/store/configureStore.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import { createStore } from 'redux' 4 | 5 | import rootReducer from '../reducers' 6 | 7 | export default function configureStore () { 8 | return createStore(rootReducer) 9 | } 10 | -------------------------------------------------------------------------------- /src/testSetup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon') 2 | const chai = require('chai') 3 | const sinonChai = require('sinon-chai') 4 | 5 | before(function () { 6 | chai.use(sinonChai) 7 | }) 8 | 9 | beforeEach(function () { 10 | this.sandbox = sinon.sandbox.create() 11 | }) 12 | 13 | afterEach(function () { 14 | this.sandbox.restore() 15 | }) 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | 5 | const SRC_FOLDER = path.join(__dirname, 'src') 6 | const DIST_FOLDER = path.join(__dirname, 'dist') 7 | 8 | const webpackConfig = { 9 | entry: { 10 | app: [path.join(SRC_FOLDER, 'app.js')] 11 | }, 12 | output: { 13 | publicPath: '/', // This is used for generated urls 14 | path: DIST_FOLDER, 15 | filename: 'scripts/[name].js' 16 | }, 17 | module: { 18 | loaders: [ 19 | { 20 | test: /\.js$/, 21 | loaders: ['babel'], 22 | exclude: /node_modules/, 23 | include: [SRC_FOLDER] 24 | } 25 | ] 26 | } 27 | } 28 | 29 | module.exports = webpackConfig 30 | --------------------------------------------------------------------------------