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