Skip to content

Commit ebf5c06

Browse files
committed
feat: Added pages with full webpack bundling
1 parent d66248c commit ebf5c06

27 files changed

Lines changed: 1522 additions & 54 deletions

File tree

docs/WORKAROUNDS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## tsconfig.json
2+
3+
```json
4+
{
5+
"compilerOptions": {
6+
"module": "commonjs", // 'require' syntax makes HMR possible; 'import' doesn't
7+
"esModuleInterop": true // Allow default-importing stuff like path
8+
},
9+
"ts-node": {
10+
"require": ["tsconfig-paths/register"] // For path aliases
11+
}
12+
}
13+
```

package-lock.json

Lines changed: 1077 additions & 34 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
"notify-unpushed": "sh ./scripts/notify-unpushed.sh",
1111
"prettify": "prettier -w",
1212
"prepare": "sh ./scripts/setup.sh",
13-
"start": "ts-node --swc src/index.ts",
13+
"start": "npm run webpack-server & ts-node --swc src/index.ts",
1414
"test": "npm run lint && npm run tsc",
15-
"tsc": "tsc"
15+
"tsc": "tsc",
16+
"webpack-server": "webpack -c src/web/webpack/webpack.config.ts --watch"
1617
},
1718
"keywords": [],
1819
"author": "PartMan7",
@@ -29,15 +30,19 @@
2930
"ps-client": "^4.4.0",
3031
"react": "^18.2.0",
3132
"react-dom": "^18.2.0",
33+
"ts-loader": "^9.5.2",
3234
"ts-node": "^10.9.2",
3335
"tsconfig-paths": "^4.2.0",
34-
"typescript": "^5.1.6"
36+
"typescript": "^5.1.6",
37+
"webpack": "^5.98.0",
38+
"webpack-cli": "^6.0.1"
3539
},
3640
"devDependencies": {
3741
"@types/express": "^5.0.0",
3842
"@types/node": "^20.4.4",
3943
"@types/react": "^18.2.48",
4044
"@types/react-dom": "^18.2.18",
45+
"@types/webpack": "^5.28.5",
4146
"@typescript-eslint/eslint-plugin": "^6.2.0",
4247
"@typescript-eslint/parser": "^6.2.0",
4348
"eslint": "^8.45.0",

src/database/games.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import mongoose from 'mongoose';
1+
import mongoose, { type HydratedDocument } from 'mongoose';
22

33
import type { Player } from '@/ps/games/common';
44

@@ -48,6 +48,7 @@ const schema = new mongoose.Schema({
4848
default: Date.now,
4949
},
5050
log: [String],
51+
winCtx: mongoose.Schema.Types.Mixed,
5152
});
5253

5354
schema.index({ id: 1 });
@@ -61,9 +62,17 @@ export interface GameModel {
6162
started: Date | null;
6263
ended: Date;
6364
log: string[];
65+
winCtx?: unknown;
6466
}
6567
const model = mongoose.model('game', schema, 'games');
6668

6769
export function uploadGame(game: GameModel): Promise<GameModel> {
6870
return model.create(game);
6971
}
72+
73+
export async function getGameById(gameType: string, gameId: string): Promise<HydratedDocument<GameModel>> {
74+
const id = gameId.toUpperCase().replace(/^#?/, '#');
75+
const game = await model.findOne({ game: gameType, id });
76+
if (!game) throw new Error(`Unable to find a game of ${gameType} with ID ${id}.`);
77+
return game;
78+
}

src/ps/games/connectfour/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export class ConnectFour extends Game<State> {
4343
this.log.push({ action: 'play', time: new Date(), turn, ctx: col });
4444

4545
if (this.won(col, turn)) {
46-
const other = this.next(turn);
46+
const other = this.getNext(turn);
4747
this.winCtx = { type: 'win', winner: this.players[turn], loser: this.players[other] };
4848
this.end();
4949
return true;

src/ps/games/game.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ export class Game<State extends BaseState> {
261261
const forfeitPlayer = this.onForfeitPlayer?.(player, ctx);
262262
if (forfeitPlayer?.success === false) return forfeitPlayer;
263263
player.out = true;
264+
this.log.push({ action: staffAction ? 'dq' : 'forfeit', turn: player.turn, time: new Date(), ctx: null });
264265
return {
265266
success: true,
266267
data: {
@@ -317,7 +318,7 @@ export class Game<State extends BaseState> {
317318
}
318319

319320
// Only gets next turn. No side effects.
320-
next(current = this.turn): State['turn'] {
321+
getNext(current = this.turn): State['turn'] {
321322
const baseIndex = this.turns.indexOf(current!);
322323
return this.turns[(baseIndex + 1) % this.turns.length];
323324
}
@@ -326,7 +327,7 @@ export class Game<State extends BaseState> {
326327
nextPlayer(): State['turn'] | null {
327328
let current = this.turn;
328329
do {
329-
current = this.next(current);
330+
current = this.getNext(current);
330331
const currentPlayer = this.players[current];
331332
if (!currentPlayer) throw new Error(`Could not find ${current} in ${Object.keys(this.players)} from ${this.turns}`);
332333
if (currentPlayer.out) continue;
@@ -337,6 +338,7 @@ export class Game<State extends BaseState> {
337338
this.setTimer('Next turn');
338339
return current;
339340
}
341+
this.log.push({ action: 'skip', turn: current, time: new Date(), ctx: null });
340342
} while (current !== this.turn);
341343
this.clearTimer();
342344
return null;
@@ -388,6 +390,7 @@ export class Game<State extends BaseState> {
388390
created: this.createdAt,
389391
started: this.startedAt!,
390392
ended: this.endedAt!,
393+
winCtx: 'winCtx' in this ? this.winCtx : null,
391394
};
392395
uploadGame(model).catch(err => {
393396
log(err);

src/ps/games/othello/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export class Othello extends Game<State> {
5454
play([i, j]: [number, number], turn: Turn, board: Board): boolean;
5555
play([i, j]: [number, number], turn: Turn, board = this.state.board): Board | null | boolean {
5656
const isActual = board === this.state.board;
57-
const other = this.next(turn);
57+
const other = this.getNext(turn);
5858
if (isActual && this.turn !== turn) throw new ChatError(this.$T('GAME.IMPOSTOR_ALERT'));
5959

6060
if (board[i][j]) return null;
@@ -126,17 +126,17 @@ export class Othello extends Game<State> {
126126
}
127127
const winningSide = scores.W > scores.B ? 'W' : 'B';
128128
const winner = this.players[winningSide];
129-
const loser = this.players[this.next(winningSide)];
129+
const loser = this.players[this.getNext(winningSide)];
130130
this.winCtx = {
131131
type: 'win',
132132
winner: { ...winner, score: scores[winningSide] },
133-
loser: { ...loser, score: scores[this.next(winningSide)] },
133+
loser: { ...loser, score: scores[this.getNext(winningSide)] },
134134
};
135135
return this.$T('GAME.WON_AGAINST', {
136136
winner: `${winner.name} (${winningSide})`,
137137
game: this.meta.name,
138-
loser: `${loser.name} (${this.next(winningSide)})`,
139-
ctx: ` [${scores[winningSide]}-${scores[this.next(winningSide)]}]`,
138+
loser: `${loser.name} (${this.getNext(winningSide)})`,
139+
ctx: ` [${scores[winningSide]}-${scores[this.getNext(winningSide)]}]`,
140140
});
141141
}
142142

src/sentinel/sentinel.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { cachebuster } from '@/utils/cachebuster';
77
import { debounce } from '@/utils/debounce';
88
import { fsPath } from '@/utils/fsPath';
99

10+
import type { FSWatcher } from 'chokidar';
11+
1012
type ListenerType = 'commands' | 'games';
1113
type Register = { label: ListenerType; pattern: RegExp; reload: (filepaths: string[]) => Promise<void> | void; debounce?: number };
1214
type Listener = { label: ListenerType; pattern: RegExp; reload: (filepaths: string) => Promise<void> | void };
1315

14-
interface EmitterEvents {
16+
export interface EmitterEvents {
1517
trigger: [label: ListenerType, file: string];
1618
start: [label: ListenerType, files: string[]];
1719
complete: [label: ListenerType, files: string[]];
@@ -27,7 +29,7 @@ class Emitter extends EventEmitter {
2729
}
2830
}
2931

30-
export default function createSentinel() {
32+
export default function createSentinel(): { emitter: Emitter; sentinel: FSWatcher } {
3133
const emitter = new Emitter();
3234

3335
const registers: Register[] = [

src/types/common.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@ export type RecursiveArray<T> = BaseRecursiveArray<T>[];
55
export type RecursivePartial<T> = {
66
[P in keyof T]?: T[P] extends (infer U)[] ? RecursivePartial<U>[] : T[P] extends object | undefined ? RecursivePartial<T[P]> : T[P];
77
};
8+
9+
// Mongoose output as JSON
10+
export type SerializedInstance<T extends object> = {
11+
[key in keyof T]: Exclude<T[key], undefined> extends Map<infer K extends string | number | symbol, infer V>
12+
? Record<K, V> | (T[key] extends undefined ? undefined : never)
13+
: Exclude<T[key], null | undefined> extends Date
14+
? string | (T[key] extends null ? null : never) | (T[key] extends undefined ? undefined : never)
15+
: T[key];
16+
};

src/types/web.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { ReactElement } from 'react';
33

44
export type UIRouteHandler = (
55
req: Request,
6-
res: { [key in keyof Response as key extends 'render' ? never : key]: Response[key] } & { render: Render }
6+
res: { [key in keyof Response as key extends 'render' ? never : key]: Response[key] } & { render: Render; getBundle: GetBundle }
77
) => void;
88

99
// Note: This isn't the actual type that's imported, but since we override render to support JSX...
@@ -17,3 +17,4 @@ export type UIRoute = {
1717
};
1818

1919
export type Render = (jsx: ReactElement, title: string, hydrate: boolean) => Promise<Response>;
20+
export type GetBundle = (filePath: string, title: string) => Promise<Response>;

0 commit comments

Comments
 (0)