fuzzyworld

dao-game / commit

Some esbuild-related changes:

fc2227239d196658a7cb2dcc00b732b6a0da9379 · Pavel Safronov · 2022-03-04 16:02:39 -0800 · parents 711f34cdddfb

Some esbuild-related changes:
- reorganized files/dirs, we now have a public dir and the created bundle goes there
- using ts-node for running tools, so the tools can be written in TS
- added launch.json configuration that starts the server, then navigates to it with a debug browser
- TS debugging seems to work without issues with this setup
 .gitignore                          |   8 +-
 .vscode/launch.json                 |   5 +-
 .vscode/settings.json               |   1 +
 .vscode/tasks.json                  |  28 +++
 package-lock.json                   | 390 ++++++++++++++++++++++++++----------
 package.json                        |  14 +-
 index.html => public/index.html     |  54 +++--
 styles.css => public/styles.css     |   0
 yin-yang.svg => public/yin-yang.svg |   0
 readme.md                           |   4 +-
 scripts/esbuild-watch.js            |   6 -
 scripts/esbuild.js                  |   5 -
 game.ts => src/game.ts              |   0
 tools/esbuild-build-and-serve.ts    |  44 ++++
 tools/esbuild-watch.ts              |   8 +
 tools/esbuild.ts                    |   7 +
 16 files changed, 431 insertions(+), 143 deletions(-)

diff --git a/.gitignore b/.gitignore
index 41ee2c8..9b60cca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,8 @@
+# node modules
 /node_modules/*
-/dist/*
\ No newline at end of file
+
+# git
+/.git/*
+
+# artifacts
+/public/bundle.js
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index faf7542..b9fd332 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -8,8 +8,9 @@
             "type": "pwa-chrome",
             "request": "launch",
             "name": "Launch Chrome against localhost",
-            "url": "http://127.0.0.1:5500/",
-            "webRoot": "${workspaceFolder}"
+            "url": "http://localhost:3000/",
+            "webRoot": "${workspaceFolder}/src",
+            "preLaunchTask": "task: npm: start"
         }
     ]
 }
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 768f2c5..1e2d4fa 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -2,6 +2,7 @@
     "cSpell.words": [
         "esbuild",
         "outfile",
+        "servedir",
         "Todos"
     ]
 }
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..cfcf68b
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,28 @@
+{
+	"version": "2.0.0",
+	"tasks": [
+		{
+			"label": "task: npm: start",
+			"type": "shell",
+			"isBackground": true,
+			"command": "npm run start",
+			"problemMatcher": [
+				{
+					"pattern": [
+						{
+							"regexp": ".",
+							"file": 1,
+							"location": 2,
+							"message": 3
+						}
+					],
+					"background": {
+						"activeOnStart": true,
+						"beginsPattern": ".",
+						"endsPattern": ".",
+					}
+				}
+			]
+		},
+	]
+}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 92f77e9..56d4fff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,14 +9,92 @@
       "version": "1.0.0",
       "license": "ISC",
       "dependencies": {
-        "esbuild": "^0.14.25",
-        "react": "^17.0.2",
-        "react-dom": "^17.0.2"
+        "esbuild": "^0.14.25"
       },
       "devDependencies": {
-        "rimraf": "^3.0.2"
+        "rimraf": "^3.0.2",
+        "ts-node": "^10.6.0"
       }
     },
+    "node_modules/@cspotcode/source-map-consumer": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
+      "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 12"
+      }
+    },
+    "node_modules/@cspotcode/source-map-support": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
+      "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
+      "dev": true,
+      "dependencies": {
+        "@cspotcode/source-map-consumer": "0.8.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/@tsconfig/node10": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
+      "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node12": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
+      "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node14": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
+      "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
+      "dev": true
+    },
+    "node_modules/@tsconfig/node16": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
+      "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
+      "dev": true
+    },
+    "node_modules/@types/node": {
+      "version": "17.0.21",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
+      "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
+      "dev": true,
+      "peer": true
+    },
+    "node_modules/acorn": {
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
+      "dev": true,
+      "bin": {
+        "acorn": "bin/acorn"
+      },
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/acorn-walk": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
+    },
     "node_modules/balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -39,6 +117,21 @@
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
       "dev": true
     },
+    "node_modules/create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true
+    },
+    "node_modules/diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
     "node_modules/esbuild": {
       "version": "0.14.25",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.25.tgz",
@@ -415,21 +508,11 @@
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
       "dev": true
     },
-    "node_modules/js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
-    },
-    "node_modules/loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "dependencies": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      },
-      "bin": {
-        "loose-envify": "cli.js"
-      }
+    "node_modules/make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
     },
     "node_modules/minimatch": {
       "version": "3.1.2",
@@ -443,14 +526,6 @@
         "node": "*"
       }
     },
-    "node_modules/object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -469,31 +544,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/react": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
-      "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      },
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
-    "node_modules/react-dom": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
-      "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
-      "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1",
-        "scheduler": "^0.20.2"
-      },
-      "peerDependencies": {
-        "react": "17.0.2"
-      }
-    },
     "node_modules/rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -509,23 +559,149 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/scheduler": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
-      "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+    "node_modules/ts-node": {
+      "version": "10.6.0",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.6.0.tgz",
+      "integrity": "sha512-CJen6+dfOXolxudBQXnVjRVvYTmTWbyz7cn+xq2XTsvnaXbHqr4gXSCNbS2Jj8yTZMuGwUoBESLaOkLascVVvg==",
+      "dev": true,
       "dependencies": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
+        "@cspotcode/source-map-support": "0.7.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.0",
+        "yn": "3.1.1"
+      },
+      "bin": {
+        "ts-node": "dist/bin.js",
+        "ts-node-cwd": "dist/bin-cwd.js",
+        "ts-node-script": "dist/bin-script.js",
+        "ts-node-transpile-only": "dist/bin-transpile.js",
+        "ts-script": "dist/bin-script-deprecated.js"
+      },
+      "peerDependencies": {
+        "@swc/core": ">=1.2.50",
+        "@swc/wasm": ">=1.2.50",
+        "@types/node": "*",
+        "typescript": ">=2.7"
+      },
+      "peerDependenciesMeta": {
+        "@swc/core": {
+          "optional": true
+        },
+        "@swc/wasm": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/typescript": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
+      "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
+      "dev": true,
+      "peer": true,
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=4.2.0"
       }
     },
+    "node_modules/v8-compile-cache-lib": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
+      "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==",
+      "dev": true
+    },
     "node_modules/wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
       "dev": true
+    },
+    "node_modules/yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
     }
   },
   "dependencies": {
+    "@cspotcode/source-map-consumer": {
+      "version": "0.8.0",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
+      "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
+      "dev": true
+    },
+    "@cspotcode/source-map-support": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
+      "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
+      "dev": true,
+      "requires": {
+        "@cspotcode/source-map-consumer": "0.8.0"
+      }
+    },
+    "@tsconfig/node10": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
+      "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
+      "dev": true
+    },
+    "@tsconfig/node12": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
+      "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
+      "dev": true
+    },
+    "@tsconfig/node14": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
+      "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
+      "dev": true
+    },
+    "@tsconfig/node16": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
+      "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
+      "dev": true
+    },
+    "@types/node": {
+      "version": "17.0.21",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.21.tgz",
+      "integrity": "sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ==",
+      "dev": true,
+      "peer": true
+    },
+    "acorn": {
+      "version": "8.7.0",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+      "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==",
+      "dev": true
+    },
+    "acorn-walk": {
+      "version": "8.2.0",
+      "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
+      "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
+      "dev": true
+    },
+    "arg": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+      "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+      "dev": true
+    },
     "balanced-match": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -548,6 +724,18 @@
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
       "dev": true
     },
+    "create-require": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+      "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+      "dev": true
+    },
+    "diff": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+      "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+      "dev": true
+    },
     "esbuild": {
       "version": "0.14.25",
       "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.25.tgz",
@@ -731,18 +919,11 @@
       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
       "dev": true
     },
-    "js-tokens": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
-      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
-    },
-    "loose-envify": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
-      "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
-      "requires": {
-        "js-tokens": "^3.0.0 || ^4.0.0"
-      }
+    "make-error": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+      "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+      "dev": true
     },
     "minimatch": {
       "version": "3.1.2",
@@ -753,11 +934,6 @@
         "brace-expansion": "^1.1.7"
       }
     },
-    "object-assign": {
-      "version": "4.1.1",
-      "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
-      "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
-    },
     "once": {
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -773,25 +949,6 @@
       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
       "dev": true
     },
-    "react": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
-      "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
-      "requires": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      }
-    },
-    "react-dom": {
-      "version": "17.0.2",
-      "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
-      "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
-      "requires": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1",
-        "scheduler": "^0.20.2"
-      }
-    },
     "rimraf": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@@ -801,20 +958,51 @@
         "glob": "^7.1.3"
       }
     },
-    "scheduler": {
-      "version": "0.20.2",
-      "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
-      "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+    "ts-node": {
+      "version": "10.6.0",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.6.0.tgz",
+      "integrity": "sha512-CJen6+dfOXolxudBQXnVjRVvYTmTWbyz7cn+xq2XTsvnaXbHqr4gXSCNbS2Jj8yTZMuGwUoBESLaOkLascVVvg==",
+      "dev": true,
       "requires": {
-        "loose-envify": "^1.1.0",
-        "object-assign": "^4.1.1"
-      }
+        "@cspotcode/source-map-support": "0.7.0",
+        "@tsconfig/node10": "^1.0.7",
+        "@tsconfig/node12": "^1.0.7",
+        "@tsconfig/node14": "^1.0.0",
+        "@tsconfig/node16": "^1.0.2",
+        "acorn": "^8.4.1",
+        "acorn-walk": "^8.1.1",
+        "arg": "^4.1.0",
+        "create-require": "^1.1.0",
+        "diff": "^4.0.1",
+        "make-error": "^1.1.1",
+        "v8-compile-cache-lib": "^3.0.0",
+        "yn": "3.1.1"
+      }
+    },
+    "typescript": {
+      "version": "4.6.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz",
+      "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==",
+      "dev": true,
+      "peer": true
+    },
+    "v8-compile-cache-lib": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz",
+      "integrity": "sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==",
+      "dev": true
     },
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
       "dev": true
+    },
+    "yn": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+      "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+      "dev": true
     }
   }
 }
diff --git a/package.json b/package.json
index 31e9984..a518360 100644
--- a/package.json
+++ b/package.json
@@ -4,9 +4,10 @@
   "description": "Dao (game)",
   "main": "app.js",
   "scripts": {
-    "clean": "rimraf dist",
-    "build": "node scripts/esbuild.js",
-    "build-watch": "node scripts/esbuild-watch.js",
+    "clean": "rimraf public/bundle.js",
+    "build": "ts-node tools/esbuild.js",
+    "build-watch": "ts-node tools/esbuild-watch.ts",
+    "start": "ts-node tools/esbuild-build-and-serve.ts",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "repository": {
@@ -20,11 +21,10 @@
   },
   "homepage": "https://gitlab.com/fuzzy-rp/dao-game#readme",
   "dependencies": {
-    "esbuild": "^0.14.25",
-    "react": "^17.0.2",
-    "react-dom": "^17.0.2"
+    "esbuild": "^0.14.25"
   },
   "devDependencies": {
-    "rimraf": "^3.0.2"
+    "rimraf": "^3.0.2",
+    "ts-node": "^10.6.0"
   }
 }
\ No newline at end of file
diff --git a/index.html b/public/index.html
similarity index 58%
rename from index.html
rename to public/index.html
index 8ab1737..033ea5a 100644
--- a/index.html
+++ b/public/index.html
@@ -4,23 +4,35 @@
     <head>
         <meta charset="utf-8">
         <title>Dao Game</title>
-        <meta name="viewport" content="width=device-width, initial-scale=1">
-        <link rel="icon" href="/yin-yang.svg" type="image/svg+xml">
-        <link rel="stylesheet" href="styles.css" />
-        <script defer src="dist/out.js"></script>
+        <meta name="viewport"
+              content="width=device-width, initial-scale=1">
+        <link rel="icon"
+              href="yin-yang.svg"
+              type="image/svg+xml">
+        <link rel="stylesheet"
+              href="styles.css" />
+        <script defer
+                src="bundle.js"></script>
     </head>
 
     <body>
+
         <div class="invisible">
-            <p class='piece red-piece' id='default-red-piece'></p>
-            <p class='piece blue-piece' id='default-blue-piece'></p>
+            <p class='piece red-piece'
+               id='default-red-piece'></p>
+            <p class='piece blue-piece'
+               id='default-blue-piece'></p>
         </div>
         <main>
+
             <div class="controls">
+
                 <span class="red-turn-text">
-                    Red's turn
+                    Red's turn!
                 </span>
+
                 <span class="divider">|</span>
+
                 <span class="blue-turn-text">
                     Blue's turn
                 </span>
@@ -30,24 +42,30 @@
             </table>
 
             <div class="controls">
-                <button type="button" class="button" id="undo-button">
+                <button type="button"
+                        class="button"
+                        id="undo-button">
                     Undo
                 </button>
+
                 <span class="divider">|</span>
-                <button type="button" class="button" id="reset-button">
+                <button type="button"
+                        class="button"
+                        id="reset-button">
                     Reset
                 </button>
             </div>
         </main>
         <hr />
+
         <div class="title">
             <p>
-                Board game <a
-                    href='https://en.wikipedia.org/wiki/Dao_(game)'
-                    target='_blank'
-                    rel="noopener">Dao</a>.
+                Board game <a href='https://en.wikipedia.org/wiki/Dao_(game)'
+                   target='_blank'
+                   rel="noopener">Dao</a>.
             </p>
         </div>
+
         <div class="info">
             The game consists of only one simple rule and three ways
             to
@@ -62,16 +80,16 @@
             square anywhere on the board.
         </div>
         <hr />
+
         <div class="info">
             <p>
                 Here is the
-                <a
-                    href='https://gitlab.com/fuzzy-rp/dao-game'
-                    target="_blank"
-                    rel="noopener">source
+                <a href='https://gitlab.com/fuzzy-rp/dao-game'
+                   target="_blank"
+                   rel="noopener">source
                     code</a>
             </p>
         </div>
     </body>
 
-</html>
\ No newline at end of file
+</html>
diff --git a/styles.css b/public/styles.css
similarity index 100%
rename from styles.css
rename to public/styles.css
diff --git a/yin-yang.svg b/public/yin-yang.svg
similarity index 100%
rename from yin-yang.svg
rename to public/yin-yang.svg
diff --git a/readme.md b/readme.md
index 260086c..a5af0f1 100644
--- a/readme.md
+++ b/readme.md
@@ -2,9 +2,7 @@
 
 ## Running
 
-1. Start "Live Server"
-2. Launch default debug
-3. Sometimes, presence of breakpoints breaks the debug page entirely
+1. `npm run start`
 
 ## Gitlab Pages
 
diff --git a/scripts/esbuild-watch.js b/scripts/esbuild-watch.js
deleted file mode 100644
index e60082f..0000000
--- a/scripts/esbuild-watch.js
+++ /dev/null
@@ -1,6 +0,0 @@
-require('esbuild').build({
-    entryPoints: ['game.ts'],
-    bundle: true,
-    outfile: 'dist/out.js',
-    watch: true
-}).catch(() => process.exit(1))
\ No newline at end of file
diff --git a/scripts/esbuild.js b/scripts/esbuild.js
deleted file mode 100644
index d1ae731..0000000
--- a/scripts/esbuild.js
+++ /dev/null
@@ -1,5 +0,0 @@
-require('esbuild').build({
-    entryPoints: ['game.ts'],
-    bundle: true,
-    outfile: 'dist/out.js'
-}).catch(() => process.exit(1))
\ No newline at end of file
diff --git a/game.ts b/src/game.ts
similarity index 100%
rename from game.ts
rename to src/game.ts
diff --git a/tools/esbuild-build-and-serve.ts b/tools/esbuild-build-and-serve.ts
new file mode 100644
index 0000000..3be8fb1
--- /dev/null
+++ b/tools/esbuild-build-and-serve.ts
@@ -0,0 +1,44 @@
+import childProcess from 'child_process';
+import esbuild from 'esbuild';
+import http from 'http';
+
+const hostPort = 3000;
+
+const serveOptions: esbuild.ServeOptions = {
+    servedir: 'public'
+};
+const buildOptions: esbuild.BuildOptions = {
+    entryPoints: ['src/game.ts'],
+    bundle: true,
+    outfile: 'public/bundle.js',
+    sourcemap: true
+};
+esbuild.serve(serveOptions, buildOptions)
+    .then(result => {
+        const { host, port } = result;
+        http
+            .createServer((req, res) => {
+                const options: http.RequestOptions = {
+                    hostname: host,
+                    port: port,
+                    path: req.url,
+                    method: req.method,
+                    headers: req.headers
+                };
+                const proxyReq = http.request(options, proxyRes => {
+                    if (proxyRes.statusCode === 404) {
+                        res.writeHead(404, { 'Content-Type': 'text/html' });
+                        res.end('<h1>A custom 404 page</h1>');
+                        return;
+                    }
+
+                    res.writeHead(proxyRes.statusCode!, proxyRes.headers);
+                    proxyRes.pipe(res, { end: true });
+                });
+
+                req.pipe(proxyReq, { end: true });
+            })
+            .listen(hostPort, () => {
+                childProcess.exec(`start http://localhost:${hostPort}/`);
+            });
+    });
\ No newline at end of file
diff --git a/tools/esbuild-watch.ts b/tools/esbuild-watch.ts
new file mode 100644
index 0000000..602f1c3
--- /dev/null
+++ b/tools/esbuild-watch.ts
@@ -0,0 +1,8 @@
+import esbuild from 'esbuild';
+
+esbuild.build({
+    entryPoints: ['src/game.ts'],
+    bundle: true,
+    outfile: 'public/bundle.js',
+    watch: true,
+}).catch(() => process.exit(1))
\ No newline at end of file
diff --git a/tools/esbuild.ts b/tools/esbuild.ts
new file mode 100644
index 0000000..24ef6db
--- /dev/null
+++ b/tools/esbuild.ts
@@ -0,0 +1,7 @@
+import esbuild from 'esbuild';
+
+esbuild.build({
+    entryPoints: ['src/game.ts'],
+    bundle: true,
+    outfile: 'public/bundle.js',
+}).catch(() => process.exit(1))
\ No newline at end of file