├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── index.template.html ├── package.json ├── server.js ├── src ├── App.js ├── actions │ ├── guestbookActions.js │ └── types.js ├── components │ ├── Application.js │ ├── Application.scss │ ├── Comment.js │ ├── Comment.scss │ ├── Form.js │ ├── List.js │ └── List.scss ├── index.js ├── pages │ ├── About.js │ ├── Guestbook.js │ └── index.js ├── reducers │ ├── guestbook.js │ └── index.js └── routes.js ├── webpack.config.js └── webpack.config.production.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/* 2 | example/* 3 | test/* 4 | node_modules/* 5 | Gruntfile.js 6 | package.json 7 | webpack.*.js 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "stage": 0, 4 | "ecmaFeatures": { 5 | "modules": true, 6 | "jsx": true 7 | }, 8 | "plugins": [ 9 | "react" 10 | ], 11 | "env": { 12 | "es6": true, 13 | "jasmine": true, 14 | "node": true, 15 | "mocha": true, 16 | "browser": true, 17 | "builtin": true 18 | }, 19 | globals: { 20 | "jest": true, 21 | "describe": true, 22 | "it": true, 23 | "expect": true, 24 | "beforeEach": true, 25 | "Parse": true 26 | }, 27 | "rules": { 28 | "block-scoped-var": 2, 29 | "camelcase": 0, 30 | "comma-style": 2, 31 | "curly": [2, "all"], 32 | "dot-notation": 0, 33 | "eqeqeq": [2, "allow-null"], 34 | "global-strict": [0], 35 | "guard-for-in": 2, 36 | "key-spacing": 0, 37 | "new-cap": 2, 38 | "no-bitwise": 2, 39 | "no-caller": 2, 40 | "no-cond-assign": [2, "except-parens"], 41 | "no-debugger": 2, 42 | "no-empty": 2, 43 | "no-eval": 2, 44 | "no-extend-native": 2, 45 | "no-extra-parens": 0, 46 | "no-irregular-whitespace": 2, 47 | "no-iterator": 2, 48 | "no-loop-func": 2, 49 | "no-multi-spaces": 0, 50 | "no-multi-str": 0, 51 | "no-mixed-spaces-and-tabs": 0, 52 | "no-new": 2, 53 | "no-plusplus": 0, 54 | "no-proto": 2, 55 | "no-script-url": 2, 56 | "no-sequences": 2, 57 | "no-shadow": 2, 58 | "no-undef": 2, 59 | "no-underscore-dangle": 0, 60 | "no-unused-vars": 2, 61 | "no-use-before-define": 2, 62 | "no-with": 2, 63 | "quotes": [2, "single"], 64 | "react/display-name": 0, 65 | "react/jsx-boolean-value": 0, 66 | "react/jsx-quotes": 2, 67 | "react/jsx-no-undef": 1, 68 | "react/jsx-sort-props": 0, 69 | "react/jsx-sort-prop-types": 0, 70 | "react/jsx-uses-react": 2, 71 | "react/jsx-uses-vars": 2, 72 | "react/no-did-mount-set-state": 2, 73 | "react/no-did-update-set-state": 2, 74 | "react/no-multi-comp": 2, 75 | "react/no-unknown-property": 2, 76 | "react/prop-types": [2, { 77 | ignore: 'children' 78 | }], 79 | "react/react-in-jsx-scope": 2, 80 | "react/self-closing-comp": 2, 81 | "react/sort-comp": [2, { 82 | order: [ 83 | '/^constructor$/', 84 | 'lifecycle', 85 | 'everything-else', 86 | 'render' 87 | ] 88 | }], 89 | "react/wrap-multilines": 2, 90 | "semi": [2, "always"], 91 | "strict": [2, "global"], 92 | "valid-typeof": 2, 93 | "wrap-iife": [2, "inside"] 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### SublimeText ### 2 | *.sublime-workspace 3 | 4 | ### OSX ### 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear on external disk 14 | .Spotlight-V100 15 | .Trashes 16 | 17 | ### Windows ### 18 | # Windows image file caches 19 | Thumbs.db 20 | ehthumbs.db 21 | 22 | # Folder config file 23 | Desktop.ini 24 | 25 | # Recycle Bin used on file shares 26 | $RECYCLE.BIN/ 27 | 28 | # App specific 29 | 30 | node_modules/ 31 | .tmp 32 | /src/main.js 33 | .grunt/ 34 | npm-debug.log 35 | dist/ 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | react-redux-guestbook-practice 2 | --- 3 | 4 | Just for practice. :v: :v: :v: 5 | 6 | 7 | ## Screenshot 8 | 9 | ![guestbook](http://i.imgur.com/4KGniUP.png) 10 | 11 | 12 | ## Thanks 13 | 14 | * [React](https://github.com/facebook/react) v15.1.0 15 | * [Redux](https://github.com/gaearon/redux) v3.5.2 16 | * [React-Redux](https://github.com/rackt/react-redux) v4.4.5 17 | * [React-Router](https://github.com/rackt/react-router) v2.4.1 18 | * [Redux-Form](https://github.com/erikras/redux-form) v5.2.5 19 | * [Webpack](https://github.com/webpack/webpack) 20 | * [Firebase](https://firebase.google.com/) 21 | 22 | 23 | ## License 24 | 25 | MIT 26 | 27 | 28 | ## Author 29 | 30 | [Patrick Wang](http://patw.me) 31 | -------------------------------------------------------------------------------- /index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React + React-Router + Redux Demo 6 | 7 | 8 | 9 | 10 | 11 | 17 | 18 | 19 | Fork me on GitHub 20 | 21 |
22 |
23 |
24 | 27 | 28 |
29 |

If you can see this, something is broken (or JS is not enabled)!!.

30 |
31 | 32 |
33 | 34 |

Lincense

35 | 36 |

MIT License

37 |
38 |
39 | 47 |
48 | 49 | 50 | 53 | {% for (var chunk in o.htmlWebpackPlugin.files.chunks) { %} 54 | 55 | {% } %} 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-redux-guestbook-practice", 3 | "version": "1.0.0", 4 | "description": "React + Redux Guestbook Practice project", 5 | "scripts": { 6 | "build": "npm run clean && npm run lint && webpack", 7 | "clean": "rm -rf dist", 8 | "deploy": "npm run dist && gh-pages -d dist", 9 | "dist": "npm run build -- --config webpack.config.production.js", 10 | "lint": "eslint src", 11 | "start": "npm run build && node server.js", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "react.js", 17 | "redux", 18 | "react-router", 19 | "guestbook", 20 | "practice" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/patw0929/react-redux-guestbook-practice.git" 25 | }, 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/patw0929/react-redux-guestbook-practice/issues" 29 | }, 30 | "author": "patw (http://patw.me/)", 31 | "dependencies": { 32 | "firebase": "^3.0.5", 33 | "react": "^15.1.0", 34 | "react-dom": "^15.1.0", 35 | "react-redux": "^4.4.5", 36 | "react-router": "^2.4.1", 37 | "redux": "^3.5.2", 38 | "redux-form": "^5.2.5", 39 | "redux-thunk": "^0.1.0" 40 | }, 41 | "devDependencies": { 42 | "babel-core": "^5.8.23", 43 | "babel-eslint": "^4.1.1", 44 | "babel-loader": "^5.3.2", 45 | "css-loader": "^0.17.0", 46 | "file-loader": "^0.8.4", 47 | "eslint": "^1.3.1", 48 | "eslint-plugin-react": "^3.3.1", 49 | "express": "^4.13.3", 50 | "html-webpack-plugin": "^1.6.1", 51 | "gh-pages": "^0.4.0", 52 | "load-grunt-tasks": "~0.6.0", 53 | "node-sass": "^3.3.2", 54 | "react-hot-loader": "^1.3.0", 55 | "sass-loader": "^2.0.1", 56 | "style-loader": "^0.12.3", 57 | "superagent": "^1.3.0", 58 | "url-loader": "^0.5.6", 59 | "webpack": "^1.12.1", 60 | "webpack-dev-middleware": "^1.2.0", 61 | "webpack-hot-middleware": "^2.0.0", 62 | "webpack-dev-server": "^1.6.5" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var config = require('./webpack.config'); 4 | 5 | new WebpackDevServer(webpack(config), { 6 | contentBase: './', 7 | publicPath: config.output.publicPath, 8 | hot: true, 9 | historyApiFallback: true, 10 | stats: { 11 | colors: true 12 | } 13 | }).listen(3000, 'localhost', function (err) { 14 | if (err) { 15 | console.log(err); 16 | } 17 | 18 | console.log('Listening at localhost:3000'); 19 | }); 20 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { createStore, applyMiddleware } from 'redux'; 3 | import thunkMiddleware from 'redux-thunk'; 4 | import { Router } from 'react-router'; 5 | import { Provider } from 'react-redux'; 6 | import routes from './routes'; 7 | import reducers from './reducers/index'; 8 | 9 | const createStoreWithMiddleware = applyMiddleware( 10 | thunkMiddleware, // lets us dispatch() functions 11 | )(createStore); 12 | 13 | export default class App extends Component { 14 | static propTypes = { 15 | history: PropTypes.object 16 | }; 17 | 18 | render() { 19 | const { history } = this.props; 20 | return ( 21 | 22 | 23 | 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/actions/guestbookActions.js: -------------------------------------------------------------------------------- 1 | import firebase from 'firebase'; 2 | import * as types from './types'; 3 | 4 | var config = { 5 | apiKey: 'AIzaSyA8L9c-7U--zFqqpLNjl0QHGe7LrMD7d9Q', 6 | authDomain: 'react-redux-guestbook-practice.firebaseapp.com', 7 | databaseURL: 'https://react-redux-guestbook-practice.firebaseio.com', 8 | storageBucket: 'react-redux-guestbook-practice.appspot.com', 9 | }; 10 | firebase.initializeApp(config); 11 | const database = firebase.database(); 12 | 13 | export function retrieveCommentData(comments) { 14 | return { 15 | type: types.RETRIEVE_COMMENT_DATA, 16 | payload: comments, 17 | }; 18 | } 19 | 20 | export function fetchComments() { 21 | return dispatch => { 22 | const comments = []; 23 | database.ref('comments').orderByChild('created_at').once('value', snapshot => { 24 | const data = snapshot.val(); 25 | for (const key in data) { 26 | if ({}.hasOwnProperty.call(data, key)) { 27 | comments.push(data[key]); 28 | } 29 | } 30 | comments.sort((a, b) => { 31 | const createdA = a.created_at; 32 | const createdB = b.created_at; 33 | return createdB - createdA; 34 | }); 35 | dispatch(retrieveCommentData(comments)); 36 | }); 37 | }; 38 | } 39 | 40 | export function postComment(name, email, comment) { 41 | return dispatch => { 42 | const newCommentKey = database.ref().child('comments').push().key; 43 | const created_at = +new Date(); 44 | const updates = {}; 45 | updates['/comments/' + newCommentKey] = { 46 | name, 47 | email, 48 | comment, 49 | created_at, 50 | }; 51 | database.ref().update(updates); 52 | dispatch(fetchComments()); 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const RETRIEVE_COMMENT_DATA = 'RETRIEVE_COMMENT_DATA'; 2 | export const SAVE_COMMENT_DATA = 'SAVE_COMMENT_DATA'; 3 | -------------------------------------------------------------------------------- /src/components/Application.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { Link } from 'react-router'; 3 | import './Application.scss'; 4 | 5 | export default class Application extends Component { 6 | constructor(props, context) { 7 | super(props, context); 8 | } 9 | 10 | static propTypes = { 11 | location: PropTypes.object 12 | }; 13 | 14 | render() { 15 | return ( 16 |
17 |
18 |
    19 |
  • 20 | About 21 |
  • 22 |
  • 23 | Guestbook 24 |
  • 25 |
26 |
27 | 28 |
29 | {this.props.children} 30 |
31 |
32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/Application.scss: -------------------------------------------------------------------------------- 1 | #main { 2 | margin: 20px 0; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Comment.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import './Comment.scss'; 3 | 4 | const Comment = (props) => { 5 | return ( 6 |
7 |
8 |
9 | Name: {props.name} 10 |
11 | 12 |
13 | Email: {props.email} 14 |
15 | 16 |
17 | Comment: {props.comment} 18 |
19 | 20 |
21 | at {new Date(props.created_at).toLocaleString()} 22 |
23 |
24 |
25 | ); 26 | }; 27 | 28 | Comment.propTypes = { 29 | name: PropTypes.string, 30 | email: PropTypes.string, 31 | comment: PropTypes.string, 32 | created_at: PropTypes.number, 33 | }; 34 | 35 | export default Comment; 36 | -------------------------------------------------------------------------------- /src/components/Comment.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | margin: 10px 0; 3 | } 4 | 5 | .inner { 6 | padding: 10px; 7 | border: 1px solid #eee; 8 | border-radius: 3px; 9 | } 10 | -------------------------------------------------------------------------------- /src/components/Form.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { reduxForm } from 'redux-form'; 3 | import { postComment } from '../actions/guestbookActions'; 4 | 5 | class Form extends Component { 6 | static propTypes = { 7 | postComment: PropTypes.func, 8 | resetForm: PropTypes.func, 9 | handleSubmit: PropTypes.func, 10 | fields: PropTypes.object, 11 | }; 12 | 13 | onSubmit({ name, email, comment }) { 14 | this.props.postComment.call(this, name, email, comment); 15 | this.props.resetForm(); 16 | } 17 | 18 | render() { 19 | const { handleSubmit, fields: { name, email, comment } } = this.props; 20 | 21 | return ( 22 |
24 |
25 | 26 |
27 | 28 |
29 | {name.touched ? name.error : ''} 30 |
31 |
32 |
33 | 34 |
35 | 36 |
37 | 38 |
39 | {email.touched ? email.error : ''} 40 |
41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 |
49 | {comment.touched ? comment.error : ''} 50 |
51 |
52 |
53 | 54 |
55 |
56 | 57 |
58 |
59 |
60 | ); 61 | } 62 | } 63 | 64 | function isEmail(email){ 65 | return /^([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x22([^\x0d\x22\x5c\x80-\xff]|\x5c[\x00-\x7f])*\x22))*\x40([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d)(\x2e([^\x00-\x20\x22\x28\x29\x2c\x2e\x3a-\x3c\x3e\x40\x5b-\x5d\x7f-\xff]+|\x5b([^\x0d\x5b-\x5d\x80-\xff]|\x5c[\x00-\x7f])*\x5d))*$/.test( email ); 66 | } 67 | 68 | function validate(values) { 69 | const errors = {}; 70 | 71 | if (!values.name) { 72 | errors.name = 'Please fill the name'; 73 | } 74 | 75 | if (!values.email) { 76 | errors.email = 'Please fill the email'; 77 | } else { 78 | if (!isEmail(values.email)) { 79 | errors.email = 'E-mail format is incorrect'; 80 | } 81 | } 82 | 83 | if (!values.comment) { 84 | errors.comment = 'Please fill the comment'; 85 | } 86 | 87 | return errors; 88 | } 89 | 90 | export default reduxForm({ 91 | form: 'guestbook', 92 | fields: ['name', 'email', 'comment'], 93 | validate, 94 | }, null, { postComment })(Form); 95 | -------------------------------------------------------------------------------- /src/components/List.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { fetchComments } from '../actions/guestbookActions'; 4 | import Comment from './Comment'; 5 | import './List.scss'; 6 | 7 | class List extends Component { 8 | static propTypes = { 9 | comments: PropTypes.array, 10 | fetchComments: PropTypes.func, 11 | }; 12 | 13 | componentWillMount() { 14 | this.props.fetchComments(); 15 | } 16 | 17 | render() { 18 | let result = this.props.comments.map((item, index) => { 19 | return ( 20 | 25 | ); 26 | }); 27 | 28 | return ( 29 |
30 |

{'Total: ' + this.props.comments.length + ' Comments'}

31 | 32 |
33 | {result} 34 |
35 |
36 | ); 37 | } 38 | } 39 | 40 | function mapStateToProps(state) { 41 | return { 42 | comments: state.guestbook, 43 | }; 44 | } 45 | 46 | export default connect(mapStateToProps, { fetchComments })(List); 47 | -------------------------------------------------------------------------------- /src/components/List.scss: -------------------------------------------------------------------------------- 1 | .guestbook__list { 2 | padding: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { browserHistory, hashHistory } from 'react-router'; 4 | import App from './App'; 5 | 6 | // Use hash location for Github Pages 7 | // but switch to HTML5 history locally. 8 | const history = process.env.NODE_ENV === 'production' ? 9 | hashHistory : browserHistory; 10 | 11 | ReactDOM.render(, document.getElementById('app')); 12 | -------------------------------------------------------------------------------- /src/pages/About.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const About = () => { 4 | return ( 5 |
6 | Hi, this is a about page. 7 |
8 | ); 9 | }; 10 | 11 | export default About; 12 | -------------------------------------------------------------------------------- /src/pages/Guestbook.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Form from '../components/Form'; 3 | import List from '../components/List'; 4 | 5 | const Guestbook = () => { 6 | return ( 7 |
8 |
9 |
10 | 11 |
12 | ); 13 | }; 14 | 15 | export default Guestbook; 16 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as About } from './About'; 2 | export { default as Guestbook } from './Guestbook'; 3 | -------------------------------------------------------------------------------- /src/reducers/guestbook.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/types'; 2 | 3 | export default (state = [], action) => { 4 | switch (action.type) { 5 | case types.RETRIEVE_COMMENT_DATA: 6 | return action.payload; 7 | } 8 | 9 | return state; 10 | }; 11 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { reducer as formReducer } from 'redux-form'; 3 | import guestbookReducer from './guestbook'; 4 | 5 | const rootReducer = combineReducers({ 6 | guestbook: guestbookReducer, 7 | form: formReducer, 8 | }); 9 | 10 | export default rootReducer; 11 | -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, IndexRoute } from 'react-router'; 3 | import Application from './components/Application'; 4 | import * as pages from './pages/index'; 5 | 6 | const { 7 | About, 8 | Guestbook 9 | } = pages; 10 | 11 | export default ( 12 | 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack development server configuration 3 | * 4 | * This file is set up for serving the webpack-dev-server, which will watch for changes and recompile as required if 5 | * the subfolder /webpack-dev-server/ is visited. Visiting the root will not automatically reload. 6 | */ 7 | 8 | var webpack = require('webpack'), 9 | path = require('path'), 10 | HtmlWebpackPlugin = require('html-webpack-plugin'), 11 | eslintrcPath = path.resolve(__dirname, '.eslintrc'); 12 | 13 | module.exports = { 14 | devtool: 'eval', 15 | entry: [ 16 | 'webpack-dev-server/client?http://localhost:3000', 17 | 'webpack/hot/only-dev-server', 18 | './src/index', 19 | ], 20 | 21 | output: { 22 | filename: 'app.js', 23 | path: path.join(__dirname, 'dist'), 24 | publicPath: '/' 25 | }, 26 | 27 | stats: { 28 | colors: true, 29 | reasons: true 30 | }, 31 | 32 | resolve: { 33 | extensions: ['', '.js', '.jsx'], 34 | alias: { 35 | 'styles': __dirname + '/src/styles', 36 | 'mixins': __dirname + '/src/mixins', 37 | 'components': __dirname + '/src/components/' 38 | } 39 | }, 40 | module: { 41 | loaders: [{ 42 | test: /\.(js|jsx)$/, 43 | exclude: /(node_modules)/, 44 | loader: 'react-hot!babel-loader' 45 | }, { 46 | test: /\.scss/, 47 | loader: 'style-loader!css-loader!sass-loader?outputStyle=expanded' 48 | }, { 49 | test: /\.css$/, 50 | loader: 'style-loader!css-loader' 51 | }, { 52 | test: /\.(png|jpg|woff|woff2)$/, 53 | loader: 'url-loader?limit=8192' 54 | }] 55 | }, 56 | 57 | plugins: [ 58 | new webpack.HotModuleReplacementPlugin(), 59 | new webpack.NoErrorsPlugin(), 60 | new webpack.DefinePlugin({ 61 | 'process.env': { 62 | 'NODE_ENV': JSON.stringify('development') 63 | }, 64 | '__DEVTOOLS__': true 65 | }), 66 | new HtmlWebpackPlugin({ 67 | filename: './index.html', 68 | template: './index.template.html' 69 | }) 70 | ], 71 | 72 | eslint: { 73 | configFile: eslintrcPath 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Webpack distribution configuration 3 | * 4 | * This file is set up for serving the distribution version. It will be compiled to dist/ by default 5 | */ 6 | 7 | var webpack = require('webpack'), 8 | path = require('path'), 9 | HtmlWebpackPlugin = require('html-webpack-plugin'), 10 | eslintrcPath = path.resolve(__dirname, '.eslintrc'); 11 | 12 | module.exports = { 13 | devtool: 'eval', 14 | entry: { 15 | app: './src/index.js' 16 | }, 17 | 18 | output: { 19 | filename: '[name].min.js', 20 | path: path.join(__dirname, 'dist'), 21 | publicPath: '' 22 | }, 23 | 24 | stats: { 25 | colors: true, 26 | reasons: false 27 | }, 28 | 29 | plugins: [ 30 | new webpack.optimize.DedupePlugin(), 31 | new webpack.optimize.UglifyJsPlugin({ 32 | compress: { 33 | warnings: false 34 | } 35 | }), 36 | new webpack.DefinePlugin({ 37 | 'process.env': { 38 | 'NODE_ENV': JSON.stringify('production') 39 | }, 40 | '__DEVTOOLS__': false 41 | }), 42 | new webpack.optimize.OccurenceOrderPlugin(), 43 | new webpack.optimize.AggressiveMergingPlugin(), 44 | new HtmlWebpackPlugin({ 45 | filename: './index.html', 46 | template: './index.template.html' 47 | }) 48 | ], 49 | 50 | resolve: { 51 | extensions: ['', '.js'], 52 | alias: { 53 | 'styles': __dirname + '/src/styles', 54 | 'mixins': __dirname + '/src/mixins', 55 | 'components': __dirname + '/src/components/' 56 | } 57 | }, 58 | 59 | module: { 60 | loaders: [{ 61 | test: /\.(js|jsx)$/, 62 | exclude: /node_modules/, 63 | loader: 'babel-loader' 64 | }, { 65 | test: /\.css$/, 66 | loader: 'style-loader!css-loader' 67 | }, { 68 | test: /\.scss/, 69 | loader: 'style-loader!css-loader!sass-loader?outputStyle=expanded' 70 | }, { 71 | test: /\.(png|jpg|woff|woff2)$/, 72 | loader: 'url-loader?limit=8192' 73 | }] 74 | }, 75 | 76 | eslint: { 77 | configFile: eslintrcPath 78 | } 79 | }; 80 | --------------------------------------------------------------------------------