restarted with less bloat

This commit is contained in:
2025-09-15 15:44:59 +02:00
commit 6e58fc2a55
15 changed files with 385 additions and 0 deletions

38
.gitignore vendored Normal file
View File

@ -0,0 +1,38 @@
# dependencies (bun install)
node_modules
# big data
/assets
/static/tiles
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# Ŋəmap
To setup dependencies:
```bash
# Grab the latest jxl base map, its path is hereafter `themap`
bun install
bun devscripts/generate_tiles.ts themap threadcount
# This will take a while and a lot ram of per thread. If threadcount is not set, default is 8 (uses ~20GB of ram in my tests)
```
To start a development server:
```bash
bun dev
```
To run for production:
```bash
bun devscripts/optimise_assets.ts threadcount
# Will take a bit once again, but not as much ram, set threadcount to around the number of CPU threads you have, if not set default is 16 threads
bun build
bun start
```
This project was created using `bun init` in bun v1.2.21. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

17
bun-env.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
// Generated by `bun init`
declare module "*.svg" {
/**
* A path to the SVG file
*/
const path: `${string}.svg`;
export = path;
}
declare module "*.module.css" {
/**
* A record of class names to their corresponding CSS module classes
*/
const classes: { readonly [key: string]: string };
export = classes;
}

54
bun.lock Normal file
View File

@ -0,0 +1,54 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "bun-react-template",
"dependencies": {
"@types/leaflet": "^1.9.20",
"@types/react-leaflet": "^3.0.0",
"leaflet": "^1.9.4",
"react": "^19",
"react-dom": "^19",
"react-leaflet": "^5.0.0",
},
"devDependencies": {
"@types/bun": "latest",
"@types/react": "^19",
"@types/react-dom": "^19",
},
},
},
"packages": {
"@react-leaflet/core": ["@react-leaflet/core@3.0.0", "", { "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ=="],
"@types/bun": ["@types/bun@1.2.21", "", { "dependencies": { "bun-types": "1.2.21" } }, "sha512-NiDnvEqmbfQ6dmZ3EeUO577s4P5bf4HCTXtI6trMc6f6RzirY5IrF3aIookuSpyslFzrnvv2lmEWv5HyC1X79A=="],
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
"@types/leaflet": ["@types/leaflet@1.9.20", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-rooalPMlk61LCaLOvBF2VIf9M47HgMQqi5xQ9QRi7c8PkdIe0WrIi5IxXUXQjAdL0c+vcQ01mYWbthzmp9GHWw=="],
"@types/node": ["@types/node@24.4.0", "", { "dependencies": { "undici-types": "~7.11.0" } }, "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ=="],
"@types/react": ["@types/react@19.1.13", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ=="],
"@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="],
"@types/react-leaflet": ["@types/react-leaflet@3.0.0", "", { "dependencies": { "react-leaflet": "*" } }, "sha512-p8R9mVKbCDDqOdW+M6GyJJuFn6q+IgDFYavFiOIvaWHuOe5kIHZEtCy1pfM43JIA6JiB3D/aDoby7C51eO+XSg=="],
"bun-types": ["bun-types@1.2.21", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-sa2Tj77Ijc/NTLS0/Odjq/qngmEPZfbfnOERi0KRUYhT9R8M4VBioWVmMWE5GrYbKMc+5lVybXygLdibHaqVqw=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"leaflet": ["leaflet@1.9.4", "", {}, "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="],
"react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="],
"react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="],
"react-leaflet": ["react-leaflet@5.0.0", "", { "dependencies": { "@react-leaflet/core": "^3.0.0" }, "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw=="],
"scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="],
"undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="],
}
}

2
bunfig.toml Normal file
View File

@ -0,0 +1,2 @@
[serve.static]
env = "BUN_PUBLIC_*"

View File

@ -0,0 +1,29 @@
import { $, sleep, spawn } from "bun";
let path = process.argv[2];
let cur_procs : number = 0;
const max_procs = (process.argv.length >= 4 && process.argv[3] != undefined) ? +process.argv[3] : 8
await $`mkdir -p /tmp/nguhmap`
for (let zoomlevel = 0; zoomlevel <= 4; ++zoomlevel) {
let size = 1024 << zoomlevel
console.log(`Resizing image for zoomlevel ${zoomlevel}: ${size}x${size}`)
await $`magick ${path} -resize ${size}x${size} /tmp/nguhmap/${zoomlevel - 4}.png`
let file = `/tmp/nguhmap/${zoomlevel - 4}`
console.log(`Cutting tiles for zoomlevel ${zoomlevel - 4}`)
await $`mkdir -p src/tiles/${zoomlevel - 4}`
for (let rx = 0; rx < 1 << (zoomlevel + 1); rx++) {
for (let ry = 0; ry < 1 << (zoomlevel + 1) ; ry++) {
while (cur_procs >= max_procs) {await sleep(200)}
cur_procs++
console.log(`region: ${rx - (1 << zoomlevel)}/${ry - (1 << zoomlevel)}`)
spawn(
[`magick`, `${file}.png`, `-crop`, `512x512+${rx*512}+${ry*512}`, `src/tiles/${zoomlevel - 4}/${rx - (1 << zoomlevel)}_${ry - (1 << zoomlevel)}.png`],
{
onExit: () => {cur_procs--}
})
}
}
}
while (cur_procs > 0) {await sleep(1000)}
await $`rm -rf /tmp/nguhmap`

View File

@ -0,0 +1,25 @@
import { sleep, spawn } from "bun";
import { readdir } from "node:fs/promises"
let cur_subprocs = 0;
const max_subprocs = (process.argv.length >= 3 && process.argv[2] != undefined) ? +process.argv[2] : 8
const assets_dir = "./static/"
let assets_files = (await readdir(assets_dir, {recursive: true})).map(it => assets_dir + it)
console.log(assets_files)
for (const index in assets_files)
{
const file = assets_files[index]
if (file == undefined) continue;
if (file.match(/\.png$/)) {
while (cur_subprocs >= max_subprocs) {await sleep(200)}
cur_subprocs++
console.log(`optimizing ${file} (${index} / ${assets_files.length}): ${cur_subprocs} processes running`)
spawn(["optipng", "-o7", file], {
onExit: () => {cur_subprocs--},
stdout: "ignore",
stderr: "ignore"
})
} else {
console.log(`Ignoring file ${file}: not an optimisable filetype`)
}
}
while (cur_subprocs > 0) {await sleep(1000)}

24
package.json Normal file
View File

@ -0,0 +1,24 @@
{
"name": "bun-react-template",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"dev": "bun --hot src/index.tsx",
"build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'",
"start": "NODE_ENV=production bun src/index.tsx"
},
"dependencies": {
"@types/leaflet": "^1.9.20",
"@types/react-leaflet": "^3.0.0",
"leaflet": "^1.9.4",
"react": "^19",
"react-dom": "^19",
"react-leaflet": "^5.0.0"
},
"devDependencies": {
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/bun": "latest"
}
}

46
src/App.tsx Normal file
View File

@ -0,0 +1,46 @@
import { MapContainer, Marker, TileLayer, Tooltip } from "react-leaflet";
import L, { marker } from "leaflet";
import { useCallback, useState } from "react";
import "./index.css";
export function App() {
const [coords, set_coords] = useState({x:0, y:0})
const [next_label, set_next_label] = useState("")
const [markers, set_markers] = useState([
{label: "Spawn", x: 0, y: 0}
])
let map = useCallback((it: L.Map|null) => {
if (it == null) return;
it.on('mousemove', (e) => {set_coords({x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat)})})
it.on('click', (e) => set_markers([...markers, {label: next_label, x: Math.round(e.latlng.lng), y: Math.round(e.latlng.lat)}]))
}, [coords, markers, next_label])
return (<>
<MapContainer id="map"
center={[0,0]}
maxBounds={[[-8192,-8192], [8192,8192]]}
crs={L.CRS.Simple}
zoom={0}
ref={map}>
<TileLayer
url="/tiles/{z}/{x}/{y}"
tileSize={512}
minZoom={-4}
maxNativeZoom={0}/>
{
markers.map((it, i) => (
<Marker key={i} position={[it.y, it.x]} icon={new L.Icon({iconUrl: "/legend/city.svg", iconAnchor: [8,8]})}>
<Tooltip>{it.label}</Tooltip>
</Marker>
))
}
</MapContainer>
<p>Mouse is at {coords.x}, {-coords.y}</p>
<label htmlFor="nextmarker">Next Marker Label</label>
<input id="nextmarker" type="text" onChange={(e) => set_next_label(e.target.value)} value={next_label} />
</>)
}
export default App;

26
src/frontend.tsx Normal file
View File

@ -0,0 +1,26 @@
/**
* This file is the entry point for the React app, it sets up the root
* element and renders the App component to the DOM.
*
* It is included in `src/index.html`.
*/
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
const elem = document.getElementById("root")!;
const app = (
<StrictMode>
<App />
</StrictMode>
);
if (import.meta.hot) {
// With hot module reloading, `import.meta.hot.data` is persisted.
const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(app);
} else {
// The hot module reloading API is not available in production.
createRoot(elem).render(app);
}

5
src/index.css Normal file
View File

@ -0,0 +1,5 @@
* {padding: 0; margin: 0;}
#map {
width: 100vw;
height: 90vh;
}

15
src/index.html Normal file
View File

@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<title>Nguhmap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./frontend.tsx"></script>
</body>
</html>

31
src/index.tsx Normal file
View File

@ -0,0 +1,31 @@
import { serve, file, type BunRequest } from "bun";
import index from "./index.html";
const server = serve({
routes: {
// Serve index.html for all unmatched routes.
"/": index,
"/tiles/:z/:x/:y": (req : BunRequest<"/tiles/:z/:x/:y">) => {
const { z, x, y} = req.params;
return new Response(file(`./static/tiles/${z}/${x}_${y}.png`), {
headers: {
"Content-Type": "image/png"
}
})
},
"/legend/:item": (req: BunRequest<"/legend/:item">) => {
const {item} = req.params
return new Response(file(`./static/legend/${item}`))
}
},
development: process.env.NODE_ENV !== "production" && {
// Enable browser hot reloading in development
hmr: true,
// Echo console logs from the browser to the server
console: true,
},
});
console.log(`🚀 Server running at ${server.url}`);

11
static/legend/city.svg Normal file
View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16px"
height="16px"
viewBox="0 0 16 16"
version="1.1"
>
<circle style="fill: #000" r="8" transform="translate(8,8)" />
<circle style="fill: #fff" r="6" transform="translate(8,8)" />
</svg>

After

Width:  |  Height:  |  Size: 311 B

36
tsconfig.json Normal file
View File

@ -0,0 +1,36 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "Preserve",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
},
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
},
"exclude": ["dist", "node_modules"]
}