Merge branch 'cypress' into master

master
Thomas Hintz 4 years ago
commit b2d0dc3268

@ -17,7 +17,7 @@
# along with the Alpha Centauri Farming project. If not, see # along with the Alpha Centauri Farming project. If not, see
# <https://www.gnu.org/licenses/>. # <https://www.gnu.org/licenses/>.
.PHONY: clean install interactive .PHONY: clean install interactive cypress
assets := assets/game/acf/ assets := assets/game/acf/
@ -25,6 +25,10 @@ dev:
npx webpack --config webpack.dev.js --env.assets ./$(assets) npx webpack --config webpack.dev.js --env.assets ./$(assets)
rundev: rundev:
webpack-dev-server --open --config webpack.dev.js --env.assets ./$(assets)
# make interactive
rundevserver:
make interactive make interactive
prod: src/server/farm prod: src/server/farm
@ -35,7 +39,7 @@ install:
npm install npm install
interactive: interactive:
cd dist/ && csi -include-path $(assets) -include-path ../src/server -s farm.scm csi -include-path $(assets) -include-path src/server -s src/server/farm.scm
src/server/farm: src/server/farm.scm src/server/db.scm src/server/farm: src/server/farm.scm src/server/db.scm
cd src/server/ && csc -include-path ../../$(assets) -O3 farm.scm cd src/server/ && csc -include-path ../../$(assets) -O3 farm.scm
@ -49,6 +53,9 @@ runprod:
upload: upload:
rsync -rtvz dist/ $(SERVER):~/farm rsync -rtvz dist/ $(SERVER):~/farm
cypress:
npm run cypress:open
clean: clean:
rm -f *~ res/js/app.js rm -f *~ res/js/app.js

@ -0,0 +1,3 @@
{
"baseUrl": "http://localhost:8080"
}

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

@ -0,0 +1,25 @@
describe('My First Test', () => {
it('Visits the Kitchen Sink', () => {
cy.visit('/')
cy.contains('Begin').click()
cy.contains('New Game').click()
cy.contains('Login').click()
cy.get('form [name="username"]').type('test')
cy.get('form [name="password"]').type('food')
cy.get('button[type="submit"]').click()
cy.get('form [name="gameName"]').type('test')
cy.get('button[type="submit"]').click()
cy.get('span[class="add-ai"]').click()
cy.contains('Ready to start').click()
cy.contains('Start Game').click()
cy.get('.show .action-item').first().click()
})
})

@ -0,0 +1,21 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add("login", (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

2425
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -13,7 +13,8 @@
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config webpack.dev.js", "build": "webpack --config webpack.dev.js",
"prod": "webpack --config webpack.prod.js" "prod": "webpack --config webpack.prod.js",
"cypress:open": "cypress open"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",
@ -24,6 +25,7 @@
"@babel/plugin-proposal-decorators": "^7.8.3", "@babel/plugin-proposal-decorators": "^7.8.3",
"@babel/preset-env": "^7.8.3", "@babel/preset-env": "^7.8.3",
"@babel/preset-react": "^7.8.3", "@babel/preset-react": "^7.8.3",
"@pmmmwh/react-refresh-webpack-plugin": "^0.4.1",
"autoprefixer": "^9.7.4", "autoprefixer": "^9.7.4",
"babel-loader": "^8.0.6", "babel-loader": "^8.0.6",
"babel-plugin-transform-decorators-legacy": "^1.3.5", "babel-plugin-transform-decorators-legacy": "^1.3.5",
@ -31,6 +33,7 @@
"copy-webpack-plugin": "^5.1.1", "copy-webpack-plugin": "^5.1.1",
"css-loader": "^3.4.2", "css-loader": "^3.4.2",
"css-url-relative-plugin": "^1.0.0", "css-url-relative-plugin": "^1.0.0",
"cypress": "^4.4.1",
"favicons-webpack-plugin": "^2.1.0", "favicons-webpack-plugin": "^2.1.0",
"file-loader": "^4.3.0", "file-loader": "^4.3.0",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
@ -38,11 +41,13 @@
"node-sass": "^4.13.1", "node-sass": "^4.13.1",
"optimize-css-assets-webpack-plugin": "^5.0.3", "optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^3.0.0", "postcss-loader": "^3.0.0",
"react-refresh": "^0.8.3",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"style-loader": "^1.1.3", "style-loader": "^1.1.3",
"terser-webpack-plugin": "^2.3.5", "terser-webpack-plugin": "^2.3.5",
"webpack": "^4.41.5", "webpack": "^4.41.5",
"webpack-cli": "^3.3.10", "webpack-cli": "^3.3.10",
"webpack-dev-server": "^3.11.0",
"webpack-merge": "^4.2.2" "webpack-merge": "^4.2.2"
}, },
"dependencies": { "dependencies": {

@ -34,7 +34,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faUser, faUsers, faTractor, faWindowRestore, import { faUser, faUsers, faTractor, faWindowRestore,
faDollarSign, faTimes, faExchangeAlt, faDollarSign, faTimes, faExchangeAlt,
faInfoCircle, faArrowUp, faArrowDown, faAward, faInfoCircle, faArrowUp, faArrowDown, faAward,
faBan, faArrowCircleLeft, faPlusCircle } from '@fortawesome/free-solid-svg-icons' faBan, faArrowCircleLeft, faPlusCircle, faBirthdayCake } from '@fortawesome/free-solid-svg-icons'
import { GroupBox, Row, Col, Button } from '../widgets.jsx' import { GroupBox, Row, Col, Button } from '../widgets.jsx'
import SpaceNode from './SpaceNode.jsx' import SpaceNode from './SpaceNode.jsx'
@ -49,7 +49,7 @@ import { buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, submitTradeDeny, submitTradeCancel, audit,
buyUncleBert, skip, endAiTurn, startGame, readyToStart, buyUncleBert, skip, endAiTurn, startGame, readyToStart,
leaveGame, kickPlayer, toggleRevealForTrade, leaveGame, kickPlayer, toggleRevealForTrade,
addAIPlayer } from './interface.js' addAIPlayer, birthdayBonusPlayer } from './interface.js'
let showScreenDelay = 2000; let showScreenDelay = 2000;
@ -177,18 +177,18 @@ class PlayerResources extends React.Component {
<ResourceUnit img={TractorImg} h='240' s='100' label='Tractors' <ResourceUnit img={TractorImg} h='240' s='100' label='Tractors'
amount={player.assets.tractor}> amount={player.assets.tractor}>
{player.assets.tractor} {player.assets.tractor}
</ResourceUnit>{' '}
{player.assets.birthday ? (
<ResourceUnit img={CakeImg} h='240' s='100' label='Birthday'
amount={player.assets.birthday ? player.assets.birthday : 0}>
{player.assets.birthday ? player.assets.birthday : 0}
</ResourceUnit> </ResourceUnit>
) : (<></>)}
</div> </div>
); );
} }
} }
/* {' '}
* <ResourceUnit img={CakeImg} h='240' s='100' label='Birthday'
* amount={player.assets.birthday ? player.assets.birthday : 0}>
* {player.assets.birthday ? player.assets.birthday : 0}
* </ResourceUnit> */
// http://stackoverflow.com/questions/149055 // http://stackoverflow.com/questions/149055
function formatMoney(n) { function formatMoney(n) {
return n.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').slice(0, -2); } return n.toFixed(1).replace(/(\d)(?=(\d{3})+\.)/g, '$1,').slice(0, -2); }
@ -1949,6 +1949,7 @@ class AlertOverlay extends React.Component {
<FontAwesomeIcon icon={faTimes} /> <FontAwesomeIcon icon={faTimes} />
</div> </div>
) : (<></>)} ) : (<></>)}
<div className='alert-container'>
<div className='alert-overlay-contents'> <div className='alert-overlay-contents'>
{this.props.children} {this.props.children}
<br /> <br />
@ -1966,6 +1967,7 @@ class AlertOverlay extends React.Component {
{!this.props.preventHiding ? (<a onClick={this.hide}>close</a>) : (<></>)} {!this.props.preventHiding ? (<a onClick={this.hide}>close</a>) : (<></>)}
</div> </div>
</div> </div>
</div>
); );
} }
} }
@ -2055,12 +2057,15 @@ class Info extends React.Component {
} }
} }
class StartGame extends React.Component { const StartGame = ({ game, player, toggleReady }) => {
render() { const { auditThreshold, downPayment, loanInterest, maxDebt, startingOtbs, startingCash, startingDebt } = game.settings;
const { auditThreshold, downPayment, loanInterest, maxDebt, startingOtbs, startingCash, startingDebt } = this.props.game.settings; const playerName = player.name;
const playerName = this.props.player.name; const { color } = player;
const { color } = this.props.player; const { name, host } = game;
const { name, host } = this.props.game;
const birthdayClass = (player) =>
player.assets.birthday ? 'birthday-selected' : '';
return ( return (
<> <>
<h1>Lobby</h1> <h1>Lobby</h1>
@ -2069,14 +2074,26 @@ class StartGame extends React.Component {
</p> </p>
<h3>Players</h3> <h3>Players</h3>
<ul> <ul>
<li><PlayerColorIcon color={color} /> {playerName}</li> <li>
{this.props.game.otherPlayers.map((p, i) => ( <PlayerColorIcon color={color} /> {playerName}
{playerName === host ? (
<span title="Birthday Bonus" className={'lobby-icon ' + birthdayClass(player)} onClick={() => birthdayBonusPlayer(playerName)}>
<FontAwesomeIcon icon={faBirthdayCake} />
</span>
) : (<></>)}
</li>
{game.otherPlayers.map((p, i) => (
<li key={i}> <li key={i}>
<PlayerColorIcon color={p.player.color} /> {p.player.name} <PlayerColorIcon color={p.player.color} /> {p.player.name}
{playerName === host ? ( {playerName === host ? (
<span title="Kick Player" className="kick-player" onClick={() => kickPlayer(p.player.name)}> <>
<span title="Kick Player" className="lobby-icon kick-player" onClick={() => kickPlayer(p.player.name)}>
<FontAwesomeIcon icon={faBan} /> <FontAwesomeIcon icon={faBan} />
</span> </span>
<span title="Birthday Bonus" className={"lobby-icon " + birthdayClass(p.player)} onClick={() => birthdayBonusPlayer(p.player.name)}>
<FontAwesomeIcon icon={faBirthdayCake} />
</span>
</>
): (<></>)} ): (<></>)}
</li> </li>
))} ))}
@ -2097,12 +2114,11 @@ class StartGame extends React.Component {
<li><b>Starting Debt</b>: ${formatMoney(startingDebt)}</li> <li><b>Starting Debt</b>: ${formatMoney(startingDebt)}</li>
</ul> </ul>
<label> <label>
<input type="checkbox" onChange={this.props.toggleReady} /> <input type="checkbox" onChange={toggleReady} />
Ready to start Ready to start
</label> </label>
</> </>
); );
}
} }
const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms', const SCREENS = { summary: 'summary', misc: 'misc', farms: 'farms',
@ -2246,7 +2262,7 @@ class BoardApp extends React.Component {
buttonText='Close' buttonText='Close'
hideHandler={() => 'nothing'} hideHandler={() => 'nothing'}
handler={() => { return false; }}> handler={() => { return false; }}>
<Fragment> <div className="game-over">
<h1>Game Over!</h1> <h1>Game Over!</h1>
{alert.contents.results.map((e, i) => ( {alert.contents.results.map((e, i) => (
<p key={i}>{e}</p> <p key={i}>{e}</p>
@ -2257,7 +2273,14 @@ class BoardApp extends React.Component {
<p>{alert.contents.stats.emergency}</p> <p>{alert.contents.stats.emergency}</p>
<p>{alert.contents.stats.highRoller}</p> <p>{alert.contents.stats.highRoller}</p>
<p>{alert.contents.stats.lowRoller}</p> <p>{alert.contents.stats.lowRoller}</p>
</Fragment> {alert.contents.stats.players.map(p => (
<div key={p.name}>
<p><b>{p.name} Harvests:</b></p>
<p>Total: ${formatMoney(p.harvestTotal)} Expenses: ${formatMoney(Math.abs(p.operatingExpenses))}</p>
<p>Avg ${formatMoney(p.harvestAverage)} rolling {p.harvestRoll} for {p.numHarvests} harvests</p>
<p>Hay: ${formatMoney(p.hay)} Grain: ${formatMoney(p.grain)} Fruit: ${formatMoney(p.fruit)} Cows: ${formatMoney(p.cows)}</p>
</div>))}
</div>
</AlertOverlay> </AlertOverlay>
); );
} else if (alert && alert.type === ALERTS.auditCalled) { } else if (alert && alert.type === ALERTS.auditCalled) {

@ -32,7 +32,7 @@ export { initialize, buy, roll, endTurn, loan, trade, submitTradeAccept,
submitTradeDeny, submitTradeCancel, audit, handleMessage, submitTradeDeny, submitTradeCancel, audit, handleMessage,
nextAction, buyUncleBert, actionsFinished, skip, endAiTurn, nextAction, buyUncleBert, actionsFinished, skip, endAiTurn,
startGame, readyToStart, leaveGame, kickPlayer, toggleRevealForTrade, startGame, readyToStart, leaveGame, kickPlayer, toggleRevealForTrade,
addAIPlayer } addAIPlayer, birthdayBonusPlayer }
let store; let store;
let movingTimer = 0; let movingTimer = 0;
@ -218,6 +218,10 @@ function kickPlayer(name) {
sendCommand({ type: 'kick-player', name }); sendCommand({ type: 'kick-player', name });
} }
function birthdayBonusPlayer(name) {
sendCommand({ type: 'birthday-bonus-player', name });
}
function addAIPlayer() { function addAIPlayer() {
sendCommand({ type: 'add-ai-player' }); sendCommand({ type: 'add-ai-player' });
} }

@ -180,7 +180,12 @@
(tax-person . 0) (tax-person . 0)
(emergency . 0) (emergency . 0)
(num-harvests . 0) (num-harvests . 0)
(harvest-rolls . 0))) (harvest-rolls . 0)
(operating-expenses . 0)
(hay . 0)
(grain . 0)
(fruit . 0)
(cows . 0)))
(alist-ref 'ai? args eqv? #f) (alist-ref 'ai? args eqv? #f)
(alist-ref 'processing-turn args eqv? #f)))) (alist-ref 'processing-turn args eqv? #f))))
@ -563,7 +568,7 @@
;; (session-set! (sid) 'game *game*) ;; (session-set! (sid) 'game *game*)
;; (set-startup-otbs (session-ref (sid) 'player) 2) ;; (set-startup-otbs (session-ref (sid) 'player) 2)
) )
(send-static-file "main.html") (send-static-file "index.html")
;; (with-headers `((connection close) ;; (with-headers `((connection close)
;; (content-type text/html)) ;; (content-type text/html))
;; (lambda () ;; (lambda ()
@ -1023,7 +1028,31 @@
(* (/ (alist-ref 'harvest-rolls (player-stats p)) (* (/ (alist-ref 'harvest-rolls (player-stats p))
(max (alist-ref 'num-harvests (player-stats p)) 1)) (max (alist-ref 'num-harvests (player-stats p)) 1))
10)) 10))
10)) ")")))))) 10)) ")")))
(players . ,(list->vector
(map (lambda (p)
(let ((stats (player-stats p)))
`((name . ,(player-name p))
(numHarvests . ,(alist-ref 'num-harvests stats))
(hay . ,(alist-ref 'hay stats))
(grain . ,(alist-ref 'grain stats))
(fruit . ,(alist-ref 'fruit stats))
(cows . ,(alist-ref 'cows stats))
(harvestAverage . ,(exact->inexact
(round
(/ (fold + 0 (map (lambda (c) (alist-ref c stats))
'(hay grain fruit cows)))
(alist-ref 'num-harvests stats)))))
(harvestTotal . ,(fold + 0 (map (lambda (c) (alist-ref c stats))
'(hay grain fruit cows))))
(operatingExpenses . ,(alist-ref 'operating-expenses stats))
(harvestRoll . ,(exact->inexact
(/ (round
(* (/ (alist-ref 'harvest-rolls stats)
(max (alist-ref 'num-harvests stats) 1))
10))
10))))))
(game-players game)))))))
type: "end-of-game"))) type: "end-of-game")))
(define (create-ws-response player event misc) (define (create-ws-response player event misc)
@ -1588,6 +1617,17 @@
(safe-set! (player-ready-to-start (*player*)) (not (player-ready-to-start (*player*)))) (safe-set! (player-ready-to-start (*player*)) (not (player-ready-to-start (*player*))))
(message-players! (*game*) (*player*) '() type: "update") (message-players! (*game*) (*player*) '() type: "update")
(create-ws-response (*player*) "update" '())) (create-ws-response (*player*) "update" '()))
((string=? type "birthday-bonus-player")
(let ((player (find (lambda (p)
(equal? (player-name p) (alist-ref 'name msg)))
(game-players (*game*)))))
(if (> (alist-ref 'birthday (player-assets player)) 0)
(set! (player-assets player)
(alist-update 'birthday 0 (player-assets player)))
(set! (player-assets player)
(alist-update 'birthday 1 (player-assets player))))
(message-players! (*game*) (*player*) '() type: "update")
(create-ws-response (*player*) "update" '())))
((string=? type "kick-player") ((string=? type "kick-player")
(let ((kicked-player (find (lambda (p) (let ((kicked-player (find (lambda (p)
(equal? (player-name p) (alist-ref 'name msg))) (equal? (player-name p) (alist-ref 'name msg)))
@ -1623,6 +1663,10 @@
type: "player-left-game") type: "player-left-game")
(create-ws-response (*player*) "left-game" '())) (create-ws-response (*player*) "left-game" '()))
((string=? type "start-game") ((string=? type "start-game")
(for-each (lambda (p)
(when (> (alist-ref 'birthday (player-assets p)) 0)
(set-startup-otbs (*game*) p 3)))
(game-players (*game*)))
(safe-set! (game-state (*game*)) 'pre-turn) (safe-set! (game-state (*game*)) 'pre-turn)
(db-update-game (game-id (*game*)) (symbol->string (game-state (*game*))) (db-update-game (game-id (*game*)) (symbol->string (game-state (*game*)))
(game->sexp (*game*))) (game->sexp (*game*)))
@ -2348,7 +2392,7 @@
(define (make-player-stat stat amount) (define (make-player-stat stat amount)
(lambda (p) (lambda (p)
(safe-set! (player-stats p) (safe-set! (player-stats p)
(alist-update stat (+ (alist-ref stat (player-stats p)) amount) (alist-update stat (+ (alist-ref stat (player-stats p) eqv? 0) amount)
(player-stats p))))) (player-stats p)))))
(define (get-actions player space) (define (get-actions player space)
@ -2572,6 +2616,7 @@
(begin (begin
((make-player-stat 'num-harvests 1) player) ((make-player-stat 'num-harvests 1) player)
((make-player-stat 'harvest-rolls rolled) player) ((make-player-stat 'harvest-rolls rolled) player)
((make-player-stat crop income) player)
(safe-set! (player-cash player) (safe-set! (player-cash player)
(+ (player-cash player) income)) (+ (player-cash player) income))
(safe-set! (player-harvest-mult player) 1) (safe-set! (player-harvest-mult player) 1)
@ -2585,6 +2630,9 @@
(player-name player)))) (player-name player))))
(game-players game))))) (game-players game)))))
((alist-ref 'action operating-expense) player) ((alist-ref 'action operating-expense) player)
((make-player-stat 'operating-expenses
(- (player-cash player) previous-cash))
player)
`((rolled . ,rolled) `((rolled . ,rolled)
(rolls . ,(list->vector (make-rolls 22))) (rolls . ,(list->vector (make-rolls 22)))
(income . ,income) (income . ,income)

@ -919,8 +919,6 @@ $trade-margin: 3rem;
color: white; } color: white; }
.alert-overlay-contents { .alert-overlay-contents {
max-height: 90vh;
overflow: auto;
background: $light-color; background: $light-color;
padding: 2rem; padding: 2rem;
display: flex; display: flex;
@ -928,6 +926,10 @@ $trade-margin: 3rem;
justify-content: center; justify-content: center;
align-items: center; } align-items: center; }
.alert-container {
max-height: 90vh;
overflow: auto; }
.moving { .moving {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -1049,11 +1051,16 @@ $intro-time: 6s;
position: absolute; position: absolute;
bottom: 1rem; } bottom: 1rem; }
.kick-player { .lobby-icon {
cursor: pointer; cursor: pointer;
color: red;
margin-left: 0.2rem; } margin-left: 0.2rem; }
.kick-player {
color: red; }
.birthday-selected {
color: blue; }
ul { ul {
margin-left: 0; margin-left: 0;
list-style-type: none; } list-style-type: none; }
@ -1062,3 +1069,9 @@ ul {
font-family: 'IndieFlower-Regular'; font-family: 'IndieFlower-Regular';
width: 0; width: 0;
height: 0; } height: 0; }
.game-over p {
margin-bottom: 0.2rem;
text-align: left;
width: 100%;
}

@ -28,10 +28,10 @@ const CssUrlRelativePlugin = require('css-url-relative-plugin')
module.exports = { module.exports = {
entry: { entry: {
app: './src/main.jsx', app: './src/index.jsx',
}, },
output: { output: {
filename: './assets/[name].[contenthash].js', filename: './assets/[name].[hash].js',
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
}, },
optimization: { optimization: {
@ -50,13 +50,13 @@ module.exports = {
new CleanWebpackPlugin(), new CleanWebpackPlugin(),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
title: 'Alpha Centauri Farming', title: 'Alpha Centauri Farming',
filename: 'main.html', filename: 'index.html',
meta: {viewport: 'width=device-width, initial-scale=1'}, meta: {viewport: 'width=device-width, initial-scale=1'},
}), }),
new FaviconsWebpackPlugin('./assets/img/tractor.svg'), new FaviconsWebpackPlugin('./assets/img/tractor.svg'),
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
filename: './assets/[name].[contenthash].css', filename: './assets/[name].[hash].css',
chunkFilename: './assets/[id].[contenthash].css', chunkFilename: './assets/[id].[hash].css',
}), }),
new CopyPlugin([ new CopyPlugin([
{ from: './src/server/farm.scm', to: './[name].[ext]' }, { from: './src/server/farm.scm', to: './[name].[ext]' },
@ -96,14 +96,14 @@ module.exports = {
test: /\.(svg|png|gif)$/, test: /\.(svg|png|gif)$/,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: './assets/img/[name].[contenthash].[ext]', name: './assets/img/[name].[hash].[ext]',
}, },
}, },
{ {
test: /\.(woff|woff2|eot|ttf|otf)$/, test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader', loader: 'file-loader',
options: { options: {
name: './assets/font/[name].[contenthash].[ext]', name: './assets/font/[name].[hash].[ext]',
}, },
}, },
{ {

@ -20,14 +20,46 @@ const merge = require('webpack-merge');
const common = require('./webpack.common.js'); const common = require('./webpack.common.js');
const path = require('path'); const path = require('path');
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
module.exports = function(env) { module.exports = function(env) {
return merge(common, { return merge(common, {
mode: 'development', mode: 'development',
devtool: 'inline-source-map', devtool: 'inline-source-map',
devServer: {
port: 9000,
contentBase: './dist',
hot: true,
proxy: {
'/websocket': {
target: 'ws://localhost:8080',
ws: true
},
},
},
resolve: { resolve: {
modules: [path.resolve(__dirname, 'src'), modules: [path.resolve(__dirname, 'src'),
path.resolve(__dirname, env.assets), path.resolve(__dirname, env.assets),
'node_modules'] 'node_modules']
}, },
plugins: [
new ReactRefreshWebpackPlugin()
],
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [
{
loader: require.resolve('babel-loader'),
options: {
plugins: [require.resolve('react-refresh/babel')],
},
},
],
},
],
},
}); });
} }

Loading…
Cancel
Save