├── .gitignore ├── client └── src │ ├── js │ ├── constants │ │ ├── MessageTypes.js │ │ └── ActionTypes.js │ ├── reducers │ │ ├── index.js │ │ └── MessageReducer.js │ ├── pages │ │ └── index.js │ ├── containers │ │ ├── SignInContainer.js │ │ └── ChatContainer.js │ ├── socket.js │ ├── routes │ │ └── index.js │ ├── actions │ │ └── actions.js │ └── components │ │ ├── chat │ │ ├── MessageInput.js │ │ ├── MessageItem.js │ │ └── Chat.js │ │ └── signIn │ │ └── SignIn.js │ ├── html │ └── index.html │ └── less │ ├── main.less │ ├── signIn.less │ └── chat.less ├── README.md ├── package.json ├── server └── server.js ├── webpack.config.js └── webpack.plugins.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | *.iml 3 | .idea/* 4 | client/build/* -------------------------------------------------------------------------------- /client/src/js/constants/MessageTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/29/16. 3 | */ 4 | 5 | export const USER_MESSAGE = 'USER_MESSAGE'; 6 | export const SYSTEM_MESSAGE = 'SYSTEM_MESSAGE'; -------------------------------------------------------------------------------- /client/src/js/constants/ActionTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | // message 6 | export const SEND_MESSAGE = 'SEND_MESSAGE'; 7 | export const RECEIVE_MESSAGE = 'RECEIVE_MESSAGE'; 8 | 9 | // user 10 | export const USER_JOINED = 'USER_JOINED'; 11 | export const USER_LEFT = 'USER_LEFT'; -------------------------------------------------------------------------------- /client/src/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | import { combineReducers } from 'redux' 6 | import messages from 'js/reducers/MessageReducer' 7 | 8 | // const reducer = combineReducers({ 9 | // messages 10 | // }) 11 | 12 | const reducer = messages 13 | 14 | export default reducer 15 | -------------------------------------------------------------------------------- /client/src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Chatting Room 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /client/src/js/pages/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/20/16. 3 | */ 4 | 5 | import $ from "jquery" 6 | import React from 'react' 7 | import { render } from 'react-dom' 8 | import { createStore } from 'redux' 9 | import { Provider } from 'react-redux' 10 | import reducer from 'js/reducers' 11 | import Routes from 'js/routes' 12 | 13 | let store = createStore(reducer) 14 | 15 | import 'css/main.less' 16 | 17 | render( 18 | 19 | {Routes} 20 | , 21 | $('.main')[0] 22 | ); -------------------------------------------------------------------------------- /client/src/less/main.less: -------------------------------------------------------------------------------- 1 | /* Fix user-agent */ 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | html { 8 | font-weight: 300; 9 | -webkit-font-smoothing: antialiased; 10 | } 11 | 12 | html, input { 13 | font-family: 14 | "HelveticaNeue-Light", 15 | "Helvetica Neue Light", 16 | "Helvetica Neue", 17 | Helvetica, 18 | Arial, 19 | "Lucida Grande", 20 | sans-serif; 21 | } 22 | 23 | html, body { 24 | height: 100%; 25 | margin: 0; 26 | padding: 0; 27 | } -------------------------------------------------------------------------------- /client/src/js/containers/SignInContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react'; 6 | import { connect } from 'react-redux'; 7 | import {bindActionCreators} from 'redux'; 8 | 9 | import SignIn from 'js/components/signIn/SignIn'; 10 | import * as actions from 'js/actions/actions'; 11 | 12 | class SignInContainer extends Component { 13 | render() { 14 | return ( 15 | 16 | ); 17 | } 18 | } 19 | 20 | export default connect()(SignInContainer) 21 | -------------------------------------------------------------------------------- /client/src/js/socket.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/29/16. 3 | */ 4 | 5 | const Singleton = (function () { 6 | let instance; 7 | 8 | function createInstance() { 9 | const io = require('socket.io-client') 10 | const socket = io.connect(); 11 | return socket; 12 | } 13 | 14 | return { 15 | getInstance: function () { 16 | if (!instance) { 17 | instance = createInstance(); 18 | } 19 | return instance; 20 | } 21 | }; 22 | })(); 23 | 24 | export default Singleton; -------------------------------------------------------------------------------- /client/src/js/routes/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | import React from 'react' 6 | import ChatContainer from 'js/containers/ChatContainer'; 7 | import SignInContainer from 'js/containers/SignInContainer'; 8 | 9 | import { Router, Route, IndexRoute, browserHistory } from 'react-router'; 10 | 11 | const Routes = ( 12 | 13 | 14 | 15 | 16 | ); 17 | 18 | export default Routes; 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChattingRoom 2 | 3 | ## Online demo: 4 | 5 | [view it on my personal server](http://hshen.pw:3000) 6 | 7 | or the link below if my server doesn't work (๑•́₃ •̀๑) : 8 | 9 | [view it on heroku](https://murmuring-brook-22426.herokuapp.com) 10 | 11 | 12 | ## Run it locally: 13 | 14 | ###For production: 15 | ``` 16 | npm install 17 | npm run build 18 | node server/server.js 19 | ``` 20 | And then point your browser to `localhost:3000` 21 | 22 | 23 | ###For debug: 24 | ``` 25 | npm install 26 | npm run dev 27 | node server/server.js 28 | ``` 29 | And then point your browser to `localhost:3000` 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /client/src/js/actions/actions.js: -------------------------------------------------------------------------------- 1 | import * as types from 'js/constants/ActionTypes'; 2 | 3 | /* 4 | * action creator 5 | */ 6 | export function sendMessage(message) { 7 | return { 8 | type: types.SEND_MESSAGE, 9 | message 10 | } 11 | } 12 | 13 | export function receiveMessage(message) { 14 | return { 15 | type: types.RECEIVE_MESSAGE, 16 | message 17 | } 18 | } 19 | 20 | export function userJoined(data) { 21 | return { 22 | type: types.USER_JOINED, 23 | data 24 | } 25 | } 26 | 27 | export function userLeft(data) { 28 | return { 29 | type: types.USER_LEFT, 30 | data 31 | } 32 | } -------------------------------------------------------------------------------- /client/src/less/signIn.less: -------------------------------------------------------------------------------- 1 | .sign-in { 2 | background-color: #000; 3 | height: 100%; 4 | position: absolute; 5 | width: 100%; 6 | } 7 | 8 | .sign-in .form { 9 | height: 100px; 10 | margin-top: -100px; 11 | position: absolute; 12 | text-align: center; 13 | top: 50%; 14 | width: 100%; 15 | } 16 | 17 | .sign-in .form .username-input { 18 | background-color: transparent; 19 | border: none; 20 | border-bottom: 2px solid #fff; 21 | outline: none; 22 | padding-bottom: 15px; 23 | text-align: center; 24 | width: 400px; 25 | font-size: 200%; 26 | letter-spacing: 3px; 27 | } 28 | 29 | .sign-in .title { 30 | font-size: 200%; 31 | } 32 | 33 | .sign-in .title, .sign-in .username-input { 34 | color: #fff; 35 | font-weight: 100; 36 | } 37 | -------------------------------------------------------------------------------- /client/src/js/components/chat/MessageInput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react'; 6 | import { Input } from 'react-bootstrap'; 7 | 8 | export default class MessageComposer extends Component { 9 | constructor(props, context) { 10 | super(props, context); 11 | } 12 | 13 | handleSubmit(event) { 14 | if (event.which === 13) { 15 | event.preventDefault(); 16 | const text = this.refs.input.value.trim(); 17 | if (text.length > 0) { 18 | this.props.sendMessage(text); 19 | this.refs.input.value = ''; 20 | } 21 | } 22 | } 23 | 24 | render() { 25 | return ( 26 | 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /client/src/js/containers/ChatContainer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react'; 6 | import { connect } from 'react-redux'; 7 | import {bindActionCreators} from 'redux'; 8 | 9 | import Chat from 'js/components/chat/Chat'; 10 | import * as actions from 'js/actions/actions'; 11 | 12 | 13 | class ChatContainer extends Component { 14 | render() { 15 | return ( 16 | 17 | ); 18 | } 19 | } 20 | 21 | const mapStateToProps = (state, ownProps) => { 22 | return { 23 | messages: state.messages, 24 | // userName: state.user.userName 25 | } 26 | } 27 | 28 | const mapDispatchToProps = (dispatch, ownProps) => { 29 | return bindActionCreators({ 30 | receiveMessage: actions.receiveMessage, 31 | sendMessage: actions.sendMessage, 32 | userJoined: actions.userJoined, 33 | userLeft: actions.userLeft 34 | },dispatch); 35 | } 36 | 37 | export default connect(mapStateToProps, mapDispatchToProps)(ChatContainer) 38 | -------------------------------------------------------------------------------- /client/src/less/chat.less: -------------------------------------------------------------------------------- 1 | .chat { 2 | height: 100%; 3 | position: absolute; 4 | width: 100%; 5 | } 6 | 7 | /* Font */ 8 | 9 | .messages { 10 | font-size: 150%; 11 | } 12 | 13 | .input-message { 14 | font-size: 200%; 15 | } 16 | 17 | .log { 18 | color: gray; 19 | font-size: 70%; 20 | margin: 5px; 21 | text-align: center; 22 | } 23 | 24 | /* Messages */ 25 | 26 | ul { 27 | list-style: none; 28 | word-wrap: break-word; 29 | } 30 | 31 | .chat-area { 32 | height: 100%; 33 | padding-bottom: 60px; 34 | } 35 | 36 | .messages { 37 | height: 100%; 38 | margin: 0; 39 | overflow-y: scroll; 40 | padding: 10px 20px 10px 20px; 41 | } 42 | 43 | .system-message { 44 | color: gray; 45 | text-align: center; 46 | 47 | } 48 | 49 | .user-name { 50 | font-weight: 700; 51 | overflow: hidden; 52 | padding-right: 15px; 53 | text-align: right; 54 | } 55 | 56 | /* Input */ 57 | 58 | .input-message { 59 | border: 13px solid #66d1f1; 60 | bottom: 0; 61 | height: 100px; 62 | left: 0; 63 | outline: none; 64 | padding-left: 10px; 65 | position: absolute; 66 | right: 0; 67 | width: 100%; 68 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ChattingRoom", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "webpack", 7 | "dev": "webpack" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "ejs": "^2.5.2", 14 | "express": "^4.14.0", 15 | "jquery": "^3.1.0", 16 | "material-ui": "^0.15.4", 17 | "moment": "^2.15.1", 18 | "node-uuid": "^1.4.7", 19 | "react": "^15.3.2", 20 | "react-bootstrap": "^0.30.3", 21 | "react-dom": "^15.3.2", 22 | "react-redux": "^4.4.5", 23 | "react-router": "^2.8.1", 24 | "redux": "^3.6.0", 25 | "redux-auth": "0.0.5-beta5", 26 | "redux-thunk": "^2.1.0", 27 | "socket.io": "^1.4.8", 28 | "socket.io-client": "^1.4.8" 29 | }, 30 | "devDependencies": { 31 | "babel-core": "^6.9.1", 32 | "babel-eslint": "^6.1.2", 33 | "babel-loader": "^6.2.4", 34 | "babel-preset-es2015": "^6.9.0", 35 | "babel-preset-react": "^6.11.1", 36 | "bootstrap": "^3.3.6", 37 | "clean-webpack-plugin": "^0.1.10", 38 | "copy-webpack-plugin": "^3.0.1", 39 | "css-loader": "^0.23.1", 40 | "extract-text-webpack-plugin": "^1.0.1", 41 | "less": "^2.7.1", 42 | "less-loader": "^2.2.3", 43 | "redux-devtools": "^3.3.1", 44 | "style-loader": "^0.13.1", 45 | "webpack": "^1.13.1", 46 | "webpack-merge": "^0.14.1" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/src/js/components/signIn/SignIn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/27/16. 3 | */ 4 | /** 5 | * Created by hshen on 9/24/16. 6 | */ 7 | 8 | import React, { Component, PropTypes } from 'react'; 9 | import { browserHistory } from 'react-router'; 10 | import Singleton from 'js/socket' 11 | 12 | import 'css/signIn.less' 13 | 14 | export default class SignIn extends Component { 15 | 16 | constructor(props, context) { 17 | super(props, context); 18 | this.socket = Singleton.getInstance(); 19 | } 20 | 21 | handleSignIn(event) { 22 | if (event.which === 13) { 23 | event.preventDefault(); 24 | const userName = event.target.value.trim(); 25 | this.socket.emit('signIn', userName); 26 | browserHistory.push('/chat'); 27 | } 28 | } 29 | 30 | render() { 31 | const { messages} = this.props; 32 | return ( 33 |
34 |
35 |

Who are you?

36 | 41 |
42 |
43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/src/js/components/chat/MessageItem.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import * as messageTypes from 'js/constants/MessageTypes' 3 | 4 | export default class MessageItem extends React.Component { 5 | 6 | 7 | render() { 8 | const { message } = this.props; 9 | switch (message.type) { 10 | case messageTypes.USER_MESSAGE: 11 | const userNameColor = this._getUserNameColor(message.userName); 12 | return ( 13 |
  • 14 | {message.userName} 15 | {message.text} 16 |
  • 17 | ); 18 | case messageTypes.SYSTEM_MESSAGE: 19 | return ( 20 |
  • 21 | {message.text} 22 |
  • 23 | ); 24 | } 25 | } 26 | 27 | _getUserNameColor(userName) { 28 | const COLORS = [ 29 | '#e21400', '#91580f', '#f8a700', '#f78b00', 30 | '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', 31 | '#3b88eb', '#3824aa', '#a700ff', '#d300e7' 32 | ]; 33 | // Compute hash code 34 | let hash = 7; 35 | for (let i = 0; i < userName.length; i++) { 36 | hash = userName.charCodeAt(i) + (hash << 5) - hash; 37 | } 38 | // Calculate color 39 | const index = Math.abs(hash % COLORS.length); 40 | return COLORS[index]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/src/js/reducers/MessageReducer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | import React from 'react' 6 | import * as actionTypes from 'js/constants/ActionTypes' 7 | import * as messageTypes from 'js/constants/MessageTypes' 8 | 9 | 10 | const initialState = { 11 | messages: [{type: messageTypes.SYSTEM_MESSAGE, 12 | text: 'Welcome to our chatting room!'}], 13 | }; 14 | 15 | export default function messages(state = initialState, action) { 16 | switch (action.type) { 17 | case actionTypes.RECEIVE_MESSAGE: 18 | const message = action.message; 19 | return {...state, 20 | messages: [...state.messages, { 21 | type: messageTypes.USER_MESSAGE, 22 | text: message.text, 23 | userName: message.userName 24 | }] 25 | } 26 | case actionTypes.USER_JOINED: 27 | return {...state, 28 | messages: [...state.messages, { 29 | type: messageTypes.SYSTEM_MESSAGE, 30 | text: `${action.data.userName} joined! Now ${action.data.userNumber} participants.` 31 | }] 32 | } 33 | case actionTypes.USER_LEFT: 34 | return {...state, 35 | messages: [...state.messages, { 36 | type: messageTypes.SYSTEM_MESSAGE, 37 | text: `${action.data.userName} left! Now ${action.data.userNumber} participants.` 38 | }] 39 | } 40 | case actionTypes.SEND_MESSAGE: 41 | return state; 42 | default: 43 | return state; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/23/16. 3 | */ 4 | 5 | // Import modules 6 | var express = require('express'); 7 | var path = require('path'); 8 | var ejs = require('ejs'); 9 | 10 | // Create server 11 | var app = express() 12 | , server = require('http').createServer(app) 13 | , io = require('socket.io').listen(server); 14 | var port = process.env.PORT || 3000; 15 | server.listen(port); 16 | 17 | // Return index.html for '/' 18 | app.get('/', function (req, res) { 19 | res.render('index'); 20 | }); 21 | 22 | // Set path for views and static resources 23 | app.set('views', './client/src/html'); 24 | app.set('view engine', 'html'); 25 | app.engine('html', ejs.renderFile); 26 | app.use('/static', express.static('./client/build')); 27 | 28 | var userNumber = 0; 29 | 30 | io.sockets.on('connection', function (socket) { 31 | var signedIn = false; 32 | 33 | socket.on('newMessage', function (text) { 34 | io.sockets.emit('newMessage',{ 35 | userName: socket.userName, 36 | text: text 37 | }) 38 | }); 39 | 40 | socket.on('signIn', function (userName) { 41 | if (signedIn) return; 42 | 43 | // we store the username in the socket session for this client 44 | socket.userName = userName; 45 | ++userNumber; 46 | signedIn = true; 47 | 48 | io.sockets.emit('userJoined', { 49 | userName: userName, 50 | userNumber: userNumber 51 | }); 52 | }); 53 | 54 | socket.on('disconnect', function () { 55 | if (signedIn) { 56 | --userNumber; 57 | 58 | io.sockets.emit('userLeft', { 59 | userName: socket.userName, 60 | userNumber: userNumber 61 | }); 62 | } 63 | }); 64 | 65 | }); -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/20/16. 3 | */ 4 | 5 | // Define paths 6 | const path = require('path'); 7 | const PATHS = { 8 | app: path.join(__dirname, 'client', 'src'), 9 | build: path.join(__dirname, 'client', 'build') 10 | }; 11 | 12 | // A tool to merge config object 13 | const merge = require('webpack-merge'); 14 | 15 | // Configuration for plugins 16 | const plugins = require('./webpack.plugins'); 17 | 18 | // Common configuration for production and dev 19 | const common = merge( 20 | { 21 | entry: { 22 | index: path.join(PATHS.app,'js','pages','index.js'), 23 | }, 24 | output: { 25 | path: path.join(PATHS.build,'js'), 26 | filename: '[name].js', 27 | chunkFilename: '[chunkhash].js' 28 | }, 29 | resolve: { 30 | root: [ 31 | PATHS.app 32 | ], 33 | alias: { 34 | js: 'js', 35 | css: 'less' 36 | }, 37 | extensions: ['', '.js', '.jsx'] 38 | } 39 | }, 40 | plugins.clean(PATHS.build), 41 | plugins.copy(), 42 | plugins.extractCommon('common.js'), 43 | plugins.less(), 44 | plugins.babel() 45 | ); 46 | 47 | var config = null; 48 | 49 | // Detect the branch where npm is running on 50 | switch(process.env.npm_lifecycle_event) { 51 | case 'build': 52 | config = merge( 53 | common, 54 | plugins.minify() 55 | ); 56 | break; 57 | 58 | case 'dev': 59 | default: 60 | config = merge( 61 | common, 62 | // Set source map for debug 63 | { 64 | devtool: 'source-map' 65 | } 66 | ); 67 | break; 68 | } 69 | 70 | module.exports = config; 71 | -------------------------------------------------------------------------------- /client/src/js/components/chat/Chat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/24/16. 3 | */ 4 | 5 | import React, { Component, PropTypes } from 'react'; 6 | import MessageInput from 'js/components/chat/MessageInput'; 7 | import MessageItem from 'js/components/chat/MessageItem'; 8 | import Singleton from 'js/socket' 9 | 10 | import 'css/chat.less' 11 | 12 | export default class Chat extends Component { 13 | 14 | constructor(props, context) { 15 | super(props, context); 16 | this.socket = Singleton.getInstance(); 17 | } 18 | 19 | componentWillMount() { 20 | const { receiveMessage, userJoined, userLeft } = this.props; 21 | this.socket.on('newMessage',function (msg) { 22 | receiveMessage(msg); 23 | }); 24 | this.socket.on('userJoined',function (data) { 25 | console.log(data); 26 | userJoined(data); 27 | }); 28 | this.socket.on('userLeft',function (data) { 29 | console.log(data); 30 | userLeft(data); 31 | }); 32 | } 33 | 34 | sendMessage(newMessage) { 35 | const { sendMessage, userName } = this.props; 36 | if (newMessage.length !== 0) { 37 | sendMessage(newMessage); 38 | this.socket.emit('newMessage', newMessage); 39 | } 40 | } 41 | 42 | render() { 43 | const { messages} = this.props; 44 | return ( 45 |
    46 |
    47 |
      48 | {messages.map( (message, index) => 49 | 50 | )} 51 |
    52 |
    53 | 54 |
    55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /webpack.plugins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by hshen on 9/20/16. 3 | */ 4 | const webpack = require('webpack'); 5 | 6 | exports.minify = function () { 7 | return { 8 | plugins: [ 9 | new webpack.optimize.UglifyJsPlugin({ 10 | // Don't beautify output (enable for neater output) 11 | beautify: false, 12 | // Eliminate comments 13 | comments: false, 14 | compress: { 15 | warnings: false, 16 | // Drop `console` statements 17 | drop_console: true 18 | } 19 | }) 20 | ] 21 | }; 22 | } 23 | 24 | // Clean a specific folder 25 | exports.clean = function (path) { 26 | const CleanWebpackPlugin = require('clean-webpack-plugin'); 27 | 28 | return { 29 | plugins: [ 30 | new CleanWebpackPlugin([path], { 31 | // Without `root` CleanWebpackPlugin won't point to our 32 | // project and will fail to work. 33 | root: process.cwd() 34 | }) 35 | ] 36 | }; 37 | } 38 | 39 | exports.extractCommon = function (name) { 40 | return { 41 | plugins: [ 42 | new webpack.optimize.CommonsChunkPlugin(name) 43 | ] 44 | }; 45 | } 46 | 47 | exports.copy = function () { 48 | const path = require('path'); 49 | const PATHS = { 50 | app: path.join(__dirname, 'src'), 51 | build: path.join(__dirname, 'build') 52 | }; 53 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 54 | 55 | return { 56 | plugins: [ 57 | new CopyWebpackPlugin([ 58 | { from: path.join(PATHS.app,'html'), to: path.join(PATHS.build,'html')}, 59 | ], { 60 | ignore: [ 61 | 62 | ], 63 | // By default, we only copy modified files during 64 | // a watch or webpack-dev-server build. Setting this 65 | // to `true` copies all files. 66 | copyUnmodified: true 67 | }) 68 | ] 69 | }; 70 | } 71 | 72 | exports.less = function () { 73 | return { 74 | module: { 75 | loaders: [ 76 | { 77 | test: /\.less$/, 78 | exclude: /node_modules/, 79 | loader: "style!css!less" 80 | } 81 | ] 82 | } 83 | }; 84 | }; 85 | 86 | exports.babel = function () { 87 | return { 88 | module: { 89 | loaders: [ 90 | { 91 | test: /\.jsx?$/, 92 | exclude: /(node_modules)/, 93 | loader: 'babel', 94 | query: { 95 | presets: ['es2015', 'react'], 96 | plugins: ['transform-object-rest-spread'] 97 | } 98 | } 99 | ] 100 | } 101 | }; 102 | }; 103 | --------------------------------------------------------------------------------