Browse Source

Initial commit

master
Josh Habdas 4 years ago
commit
ba5d1241fe
39 changed files with 859 additions and 0 deletions
  1. +6
    -0
      .babelrc
  2. +16
    -0
      .eslintrc
  3. +5
    -0
      .gitignore
  4. +16
    -0
      dist/index.html
  5. +21
    -0
      dist/scripts/app.7dc18b575a73d3429682.js
  6. +1
    -0
      dist/scripts/app.7dc18b575a73d3429682.js.map
  7. +2
    -0
      dist/styles/app.7dc18b575a73d3429682.css
  8. +1
    -0
      dist/styles/app.7dc18b575a73d3429682.css.map
  9. +61
    -0
      package.json
  10. +61
    -0
      readme.md
  11. +15
    -0
      server.js
  12. +9
    -0
      src/app/actions/index.js
  13. +18
    -0
      src/app/components/App.jsx
  14. +12
    -0
      src/app/components/about/About.jsx
  15. +6
    -0
      src/app/components/about/about.scss
  16. +83
    -0
      src/app/components/board/Board.jsx
  17. +17
    -0
      src/app/components/board/board.scss
  18. +5
    -0
      src/app/components/bundle.scss
  19. +21
    -0
      src/app/components/common/Header.jsx
  20. +13
    -0
      src/app/components/common/base/global.scss
  21. +4
    -0
      src/app/components/common/base/variables.scss
  22. +28
    -0
      src/app/components/common/header.scss
  23. +46
    -0
      src/app/components/home/Home.jsx
  24. +6
    -0
      src/app/components/home/home.scss
  25. +64
    -0
      src/app/components/tile/Tile.jsx
  26. +13
    -0
      src/app/constants/ActionTypes.js
  27. +2
    -0
      src/app/constants/GameRules.js
  28. +25
    -0
      src/app/index.js
  29. +27
    -0
      src/app/reducers/board.js
  30. +47
    -0
      src/app/reducers/game.js
  31. +12
    -0
      src/app/reducers/index.js
  32. +23
    -0
      src/app/reducers/player.js
  33. +11
    -0
      src/app/store/configureStore.js
  34. +14
    -0
      src/index.html
  35. +15
    -0
      test/components/app_test.js
  36. +40
    -0
      test/test_helper.js
  37. +7
    -0
      webpack/webpack-dev.config.js
  38. +6
    -0
      webpack/webpack-prod.config.js
  39. +80
    -0
      webpack/webpack.config.js

+ 6
- 0
.babelrc View File

@ -0,0 +1,6 @@
{
"presets": ["react", "es2015", "stage-1"],
"plugins": [
"transform-object-rest-spread"
]
}

+ 16
- 0
.eslintrc View File

@ -0,0 +1,16 @@
{
"extends": "airbnb",
"parserOptions": {
"ecmaVersion": 6,
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"plugins": [
"prefer-object-spread"
],
"rules": {
"semi": ["error", "never"],
"prefer-object-spread/prefer-object-spread": "error"
},
}

+ 5
- 0
.gitignore View File

@ -0,0 +1,5 @@
/node_modules
/.idea
.DS_Store
bundle.js
npm-debug.log

+ 16
- 0
dist/index.html View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="robots" content="index,follow"/>
<meta name="googlebot" content="index,follow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>5x5 Tic-Tac-Toe</title>
<link href="/styles/app.7dc18b575a73d3429682.css" rel="stylesheet">
</head>
<body>
<div id="react-root"></div>
<script type="text/javascript" src="/scripts/app.7dc18b575a73d3429682.js"></script>
</body>
</html>

+ 21
- 0
dist/scripts/app.7dc18b575a73d3429682.js
File diff suppressed because it is too large
View File


+ 1
- 0
dist/scripts/app.7dc18b575a73d3429682.js.map
File diff suppressed because it is too large
View File


+ 2
- 0
dist/styles/app.7dc18b575a73d3429682.css View File

@ -0,0 +1,2 @@
@import url(https://fonts.googleapis.com/css?family=Nunito:400,300);body{margin:0;padding:0;font-family:Nunito,sans-serif;font-size:16px}.container{width:100%;text-align:center}header{width:100%;padding:0;border-bottom:1px solid #e0e2e2;margin-bottom:40px}header nav ul{text-align:center}header nav ul li{display:inline;margin-right:60px}header nav ul li a{color:#333;text-decoration:none}header nav ul li a:hover{color:#288feb;text-decoration:underline}.home h1{color:#288feb}.about h1{color:#28cb75}
/*# sourceMappingURL=app.7dc18b575a73d3429682.css.map*/

+ 1
- 0
dist/styles/app.7dc18b575a73d3429682.css.map View File

@ -0,0 +1 @@
{"version":3,"sources":[],"names":[],"mappings":"","file":"/styles/app.7dc18b575a73d3429682.css","sourceRoot":""}

+ 61
- 0
package.json View File

@ -0,0 +1,61 @@
{
"name": "tic-tac-toe",
"version": "1.0.0",
"description": "5x5 tic-tac-toe",
"scripts": {
"dev": "webpack-dev-server --config ./webpack/webpack-dev.config.js --watch --colors",
"build": "rm -rf dist && webpack --config ./webpack/webpack-prod.config.js --colors",
"start": "PORT=8080 pm2 start ./server.js",
"test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test",
"test:watch": "npm run test -- --watch",
"lint": "eslint src test webpack"
},
"repository": {
"type": "git",
"url": "https://github.com/jhabdas/tictactoe"
},
"author": "Josh Habdas",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.8.0",
"babel-loader": "^6.2.4",
"babel-plugin-transform-object-rest-spread": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babel-preset-react": "^6.5.0",
"babel-preset-stage-1": "^6.5.0",
"chai": "^3.5.0",
"chai-jquery": "^2.0.0",
"css-loader": "^0.23.1",
"eslint": "^2.13.1",
"eslint-config-airbnb": "^9.0.1",
"eslint-plugin-import": "^1.8.0",
"eslint-plugin-jsx-a11y": "^1.2.0",
"eslint-plugin-prefer-object-spread": "^1.1.0",
"eslint-plugin-react": "^5.1.1",
"extract-text-webpack-plugin": "^1.0.1",
"html-webpack-plugin": "^2.16.1",
"jquery": "^2.2.3",
"jsdom": "^9.0.0",
"lodash": "^4.13.1",
"mocha": "^2.4.5",
"node-sass": "^3.7.0",
"react-addons-test-utils": "^15.0.2",
"react-hot-loader": "^1.3.0",
"remote-redux-devtools": "^0.3.3",
"sass-loader": "^3.2.0",
"style-loader": "^0.13.1",
"url-loader": "^0.5.7",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1"
},
"dependencies": {
"express": "^4.13.4",
"material-ui": "^0.15.2",
"react": "^15.0.2",
"react-dom": "^15.0.2",
"react-redux": "^4.4.5",
"react-router": "^2.4.0",
"react-tap-event-plugin": "^1.0.0",
"redux": "^3.5.2"
}
}

+ 61
- 0
readme.md View File

@ -0,0 +1,61 @@
# Tic-Tac-Toe
## Installation
Clone Repo
````
git clone git@github.com:jhabdas/tictactoe.git
````
Install dependencies
````
cd tic-tac-toe
npm i
````
### Start development server with hot reloading
````
npm run dev
````
### Testing
Run test once
````
npm run test
````
Test watch
````
npm run test:watch
````
### Linting
For linting we're using ESLint with a modified Airbnb Eslint config
````
npm run lint
````
### Production
Build for production
````
npm run build
````
Start production server
````
npm start
````
Requires pm2 for production server. Install it on globally with 'npm i -g pm2'.

+ 15
- 0
server.js View File

@ -0,0 +1,15 @@
const express = require('express');
const app = express();
app.use(express.static('./'));
app.use(express.static('dist'));
app.get('*', (req, res) => {
res.sendFile(`${__dirname}/dist/index.html`);
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log('app listening on', port);
});

+ 9
- 0
src/app/actions/index.js View File

@ -0,0 +1,9 @@
import * as types from '../constants/ActionTypes'
export function chooseMove(player, tile) {
return {
type: types.CHOOSE_MOVE,
player,
tile,
}
}

+ 18
- 0
src/app/components/App.jsx View File

@ -0,0 +1,18 @@
import React, { PropTypes } from 'react'
import Header from './common/Header'
import injectTapEventPlugin from 'react-tap-event-plugin'
injectTapEventPlugin()
function App({ children }) {
return (
<div className="container">
<Header />
{children}
</div>
)
}
App.propTypes = { children: PropTypes.object }
export default App

+ 12
- 0
src/app/components/about/About.jsx View File

@ -0,0 +1,12 @@
import React from 'react';
function About() {
return (
<div className="container about">
<h1>About</h1>
<p>5x5 Tic-Tac-Toe</p>
</div>
)
}
export default About;

+ 6
- 0
src/app/components/about/about.scss View File

@ -0,0 +1,6 @@
.about {
h1 {
color: $light-green;
}
}

+ 83
- 0
src/app/components/board/Board.jsx View File

@ -0,0 +1,83 @@
import React, { PropTypes } from 'react'
import Tile from '../tile/Tile'
import { connect } from 'react-redux'
import { chooseMove } from '../../actions'
import './board.scss'
const tilesData = [
{ col: 0, row: 0 },
{ col: 1, row: 0 },
{ col: 2, row: 0 },
{ col: 3, row: 0 },
{ col: 4, row: 0 },
{ col: 0, row: 1 },
{ col: 1, row: 1 },
{ col: 2, row: 1 },
{ col: 3, row: 1 },
{ col: 4, row: 1 },
{ col: 0, row: 2 },
{ col: 1, row: 2 },
{ col: 2, row: 2 },
{ col: 3, row: 2 },
{ col: 4, row: 2 },
{ col: 0, row: 3 },
{ col: 1, row: 3 },
{ col: 2, row: 3 },
{ col: 3, row: 3 },
{ col: 4, row: 3 },
{ col: 0, row: 4 },
{ col: 1, row: 4 },
{ col: 2, row: 4 },
{ col: 3, row: 4 },
{ col: 4, row: 4 },
]
function Board({
onTileClick,
boardData,
}) {
return (
<div className="grid">
<div className="gridList">
{tilesData.map((tile) => (
<Tile
key={[tile.col, tile.row]}
onClick={onTileClick}
owner={(() => {
return boardData[tile.row][tile.col]
? boardData[tile.row][tile.col]
: null
})()}
{...tile}
/>
))}
</div>
</div>
)
}
Board.propTypes = {
onTileClick: PropTypes.func.isRequired,
boardData: PropTypes.array.isRequired,
}
function mapStateToProps(state) {
return {
boardData: state.board.boardData,
}
}
function mapDispatchToProps(dispatch) {
return {
onTileClick: (player, tile) => {
dispatch(chooseMove(player, tile))
},
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(Board)

+ 17
- 0
src/app/components/board/board.scss View File

@ -0,0 +1,17 @@
.grid {
display: flex;
flex-wrap: wrap;
justify-content: space-around;
margin-top: 50px;
// transform: rotate(-1deg);
.gridList {
width: 270px;
min-width: 270px;
height: 250px;
.gridTile {
width: 50px;
}
}
}

+ 5
- 0
src/app/components/bundle.scss View File

@ -0,0 +1,5 @@
@import './common/base/variables';
@import './common/base/global';
@import './common/header';
@import './home/home';
@import './about/about';

+ 21
- 0
src/app/components/common/Header.jsx View File

@ -0,0 +1,21 @@
import React from 'react';
import { Link } from 'react-router';
function Header() {
return (
<header>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
</ul>
</nav>
</header>
)
}
export default Header;

+ 13
- 0
src/app/components/common/base/global.scss View File

@ -0,0 +1,13 @@
@import url(https://fonts.googleapis.com/css?family=Roboto:400,300,500);
body {
margin: 0;
padding: 0;
font-family: 'Roboto', sans-serif;
font-size: 16px;
}
.container {
width: 100%;
text-align: center;
}

+ 4
- 0
src/app/components/common/base/variables.scss View File

@ -0,0 +1,4 @@
$gray: #5b5b5b;
$light-gray: #e0e2e2;
$light-blue: #288feb;
$light-green: #28cb75;

+ 28
- 0
src/app/components/common/header.scss View File

@ -0,0 +1,28 @@
header {
width: 100%;
padding: 0;
border-bottom: 1px solid $light-gray;
margin-bottom: 40px;
nav {
ul {
text-align: center;
li {
display: inline;
margin-right: 60px;
a {
color: #333;
text-decoration: none;
&:hover {
color: $light-blue;
text-decoration: underline;
}
}
}
}
}
}

+ 46
- 0
src/app/components/home/Home.jsx View File

@ -0,0 +1,46 @@
import React from 'react'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import Dialog from 'material-ui/Dialog'
import FlatButton from 'material-ui/FlatButton'
import TextField from 'material-ui/TextField'
import Board from '../board/Board'
const actions = [
<FlatButton
label="Cancel"
primary
onTouchTap={() => null}
/>,
<FlatButton
label="Submit"
primary
keyboardFocused
onTouchTap={() => null}
/>,
]
function Home() {
return (
<MuiThemeProvider>
<div className="container home">
<h1>5x5 Tic-Tac-Toe</h1>
<Board />
<Dialog
title="What's your name, hoss?"
actions={actions}
modal={false}
open={false}
onRequestClose={() => null}
>
<TextField
hintText="thedailyserver"
/>
</Dialog>
</div>
</MuiThemeProvider>
)
}
export default Home

+ 6
- 0
src/app/components/home/home.scss View File

@ -0,0 +1,6 @@
.home {
h1 {
color: $light-blue;
}
}

+ 64
- 0
src/app/components/tile/Tile.jsx View File

@ -0,0 +1,64 @@
import React, { PropTypes } from 'react'
import FlatButton from 'material-ui/FlatButton'
import SvgIcon from 'material-ui/SvgIcon'
/* eslint-disable max-len */
const CircleIcon = (props) => (
<SvgIcon {...props}>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M12 2C6.47 2 2 6.47 2 12s4.47 10 10 10 10-4.47 10-10S17.53 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z" />
</SvgIcon>
)
const CloseIcon = (props) => (
<SvgIcon {...props}>
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
<path d="M0 0h24v24H0z" fill="none" />
</SvgIcon>
)
const EmptyIcon = (props) => (
<SvgIcon {...props}>
<path d="M0 0h24v24H0z" fill="none" />
</SvgIcon>
)
/* eslint-enable max-len */
function plotIcon(owner) {
return owner === 'player'
? <CircleIcon />
: <CloseIcon />
}
function Tile({
onClick,
owner,
col,
row,
}) {
return (
<FlatButton
backgroundColor="#cee5fb"
className="gridTile"
icon={owner ? plotIcon(owner) : <EmptyIcon />}
onTouchTap={() => {
onClick('player', { row, col })
}}
rippleColor="#288feb"
style={{
height: '50px',
minWidth: '50px',
margin: '2px',
}}
/>
)
}
Tile.propTypes = {
onClick: PropTypes.func.isRequired,
col: PropTypes.number.isRequired,
row: PropTypes.number.isRequired,
owner: PropTypes.string,
}
export default Tile

+ 13
- 0
src/app/constants/ActionTypes.js View File

@ -0,0 +1,13 @@
// Common
export const CHOOSE_MOVE = 'CHOOSE_MOVE'
// Player
export const CHOOSE_NAME = 'CHOOSE_NAME'
// Game
export const ENABLE_UNDO_LAST_MOVE = 'ENABLE_UNDO_LAST_MOVE'
export const DISABLE_UNDO_LAST_MOVE = 'DISABLE_UNDO_LAST_MOVE'
export const NPC_START_PROCESSING = 'NPC_START_PROCESSING'
export const NPC_FINISH_PROCESSING = 'NPC_FINISH_PROCESSING'
export const END_GAME_WITH_WINNER = 'END_GAME_WITH_WINNER'
export const END_GAME_WITH_DRAW = 'END_GAME_WITH_DRAW'

+ 2
- 0
src/app/constants/GameRules.js View File

@ -0,0 +1,2 @@
export const maxNPCComputeTime = 2000 // 2s
export const maxPCUndoTime = 3000 // 3s

+ 25
- 0
src/app/index.js View File

@ -0,0 +1,25 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import configureStore from './store/configureStore'
import { Router, Route, IndexRoute, browserHistory } from 'react-router'
import App from './components/App'
import Home from './components/home/Home'
import About from './components/about/About'
import './components/bundle.scss'
const store = configureStore()
ReactDOM.render(
<Provider store={store}>
<Router onUpdate={() => window.scrollTo(0, 0)} history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="/about" component={About} />
</Route>
</Router>
</Provider>
, document.getElementById('react-root')
)

+ 27
- 0
src/app/reducers/board.js View File

@ -0,0 +1,27 @@
import * as types from '../constants/ActionTypes'
import _ from 'lodash'
const initialState = {
boardData: [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
],
}
export default function board(state = initialState, action) {
switch (action.type) {
case types.CHOOSE_MOVE: {
const boardData = _.cloneDeep(state.boardData)
boardData[action.tile.row][action.tile.col] = action.player
return {
...state,
boardData,
}
}
default:
return initialState
}
}

+ 47
- 0
src/app/reducers/game.js View File

@ -0,0 +1,47 @@
import * as types from '../constants/ActionTypes'
const initialState = {
canPlayerUndoLastMove: false,
isNpcProcessing: false,
isOver: false,
winner: null,
}
export default function game(state = initialState, action) {
switch (action.type) {
case types.ENABLE_UNDO_LAST_MOVE:
return {
...state,
canPlayerUndoLastMove: true,
}
case types.DISABLE_UNDO_LAST_MOVE:
return {
...state,
canPlayerUndoLastMove: false,
}
case types.NPC_START_PROCESSING:
return {
...state,
isNpcProcessing: true,
}
case types.NPC_FINISH_PROCESSING:
return {
...state,
isNpcProcessing: false,
}
case types.END_GAME_WITH_WINNER:
return {
...state,
isOver: true,
winner: action.winner,
}
case types.END_GAME_WITH_DRAW: {
return {
...state,
isOver: true,
}
}
default:
return initialState
}
}

+ 12
- 0
src/app/reducers/index.js View File

@ -0,0 +1,12 @@
import { combineReducers } from 'redux'
import board from './board'
import game from './game'
import player from './player'
const rootReducer = combineReducers({
board,
game,
player,
})
export default rootReducer

+ 23
- 0
src/app/reducers/player.js View File

@ -0,0 +1,23 @@
import * as types from '../constants/ActionTypes'
const initialState = {
chosenName: '',
lastMove: null,
}
export default function game(state = initialState, action) {
switch (action.type) {
case types.CHOOSE_NAME:
return {
...state,
chosenName: action.name,
}
case types.CHOOSE_MOVE:
return {
...state,
lastMove: action.tile,
}
default:
return initialState
}
}

+ 11
- 0
src/app/store/configureStore.js View File

@ -0,0 +1,11 @@
import { createStore, applyMiddleware, compose } from 'redux'
import devTools from 'remote-redux-devtools'
import reducer from '../reducers'
export default function configureStore(initialState) {
const enhancer = compose(
applyMiddleware(),
devTools()
)
return createStore(reducer, initialState, enhancer)
}

+ 14
- 0
src/index.html View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="robots" content="index,follow"/>
<meta name="googlebot" content="index,follow"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title>5x5 Tic-Tac-Toe</title>
</head>
<body>
<div id="react-root"></div>
</body>
</html>

+ 15
- 0
test/components/app_test.js View File

@ -0,0 +1,15 @@
/* eslint-disable no-undef, no-unused-expressions */
import { renderComponent, expect } from '../test_helper';
import App from '../../src/app/components/App';
describe('App', () => {
let component;
beforeEach(() => {
component = renderComponent(App);
});
it('renders something', () => {
expect(component).to.exist;
});
});

+ 40
- 0
test/test_helper.js View File

@ -0,0 +1,40 @@
/* eslint-disable func-names */
import _$ from 'jquery';
import React from 'react';
import ReactDOM from 'react-dom';
import TestUtils from 'react-addons-test-utils';
import jsdom from 'jsdom';
import chai, { expect } from 'chai';
import chaiJquery from 'chai-jquery';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducers from '../src/app/reducers';
global.document = jsdom.jsdom('<!doctype html><html><body></body></html>');
global.window = global.document.defaultView;
global.navigator = {
userAgent: 'node.js',
};
const $ = _$(window);
chaiJquery(chai, chai.util, $);
function renderComponent(ComponentClass, props = {}, state = {}) {
const componentInstance = TestUtils.renderIntoDocument(
<Provider store={createStore(reducers, state)}>
<ComponentClass {...props} />
</Provider>
);
return $(ReactDOM.findDOMNode(componentInstance));
}
$.fn.simulate = function (eventName, value) {
if (value) {
this.val(value);
}
TestUtils.Simulate[eventName](this[0]);
};
export { renderComponent, expect };

+ 7
- 0
webpack/webpack-dev.config.js View File

@ -0,0 +1,7 @@
module.exports = require('./webpack.config.js')({
isProduction: false,
devtool: 'source-map',
jsFileName: 'app.js',
cssFileName: 'app.css',
port: 3000,
});

+ 6
- 0
webpack/webpack-prod.config.js View File

@ -0,0 +1,6 @@
module.exports = require('./webpack.config.js')({
isProduction: true,
devtool: 'source-map',
jsFileName: 'app.[hash].js',
cssFileName: 'app.[hash].css',
});

+ 80
- 0
webpack/webpack.config.js View File

@ -0,0 +1,80 @@
const Path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = (options) => {
const ExtractSASS = new ExtractTextPlugin(`/styles/${options.cssFileName}`);
const webpackConfig = {
devtool: options.devtool,
entry: [
`webpack-dev-server/client?http://localhost:${+ options.port}`,
'webpack/hot/dev-server',
Path.join(__dirname, '../src/app/index'),
],
output: {
path: Path.join(__dirname, '../dist'),
filename: `/scripts/${options.jsFileName}`,
},
resolve: {
extensions: ['', '.js', '.jsx'],
},
module: {
loaders: [{
test: /.jsx?$/,
include: Path.join(__dirname, '../src/app'),
loader: 'babel',
}],
},
plugins: [
new Webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(options.isProduction ? 'production' : 'development'),
},
}),
new HtmlWebpackPlugin({
template: Path.join(__dirname, '../src/index.html'),
}),
],
};
if (options.isProduction) {
webpackConfig.entry = [Path.join(__dirname, '../src/app/index')];
webpackConfig.plugins.push(
new Webpack.optimize.OccurenceOrderPlugin(),
new Webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false,
},
}),
ExtractSASS
);
webpackConfig.module.loaders.push({
test: /\.scss$/,
loader: ExtractSASS.extract(['css', 'sass']),
});
} else {
webpackConfig.plugins.push(
new Webpack.HotModuleReplacementPlugin()
);
webpackConfig.module.loaders.push({
test: /\.scss$/,
loaders: ['style', 'css', 'sass'],
});
webpackConfig.devServer = {
contentBase: Path.join(__dirname, '../'),
hot: true,
port: options.port,
inline: true,
progress: true,
historyApiFallback: true,
};
}
return webpackConfig;
};

Loading…
Cancel
Save