From 034dfdadcfb1f838edd7506f4fcf1b4b6d5c3729 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Thu, 2 Jan 2025 17:10:52 -0700 Subject: [PATCH] Add dev server middleware for data (#740) * Add dev server middleware for data * Hide dotfiles and 404 on empty dir --- package.json | 4 ++ pnpm-lock.yaml | 174 ++++++++++++++++++++++++++++++++++++++++++++++ public/data | 1 - rsbuild.config.ts | 59 ++++++++++++++-- 4 files changed, 233 insertions(+), 5 deletions(-) delete mode 120000 public/data diff --git a/package.json b/package.json index 6e332aa19..e233f5ba9 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,17 @@ "@rsbuild/core": "^1.1.12", "@rsbuild/plugin-type-check": "^1.2.0", "@types/node": "^22.9.3", + "@types/parseurl": "^1.3.3", "@types/pngjs": "^6.0.5", + "@types/send": "^0.17.4", "@types/webxr": "^0.5.20", "@webgpu/types": "^0.1.51", "@xmldom/xmldom": "^0.9.5", "buffer": "^6.0.3", "onchange": "^7.1.0", + "parseurl": "^1.3.3", "pngjs": "^7.0.0", + "send": "^1.1.0", "tsx": "^4.19.2", "typescript": "^5.7.2", "wasm-pack": "^0.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 49e0b48db..e7116fa6e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,9 +30,15 @@ importers: '@types/node': specifier: ^22.9.3 version: 22.9.3 + '@types/parseurl': + specifier: ^1.3.3 + version: 1.3.3 '@types/pngjs': specifier: ^6.0.5 version: 6.0.5 + '@types/send': + specifier: ^0.17.4 + version: 0.17.4 '@types/webxr': specifier: ^0.5.20 version: 0.5.20 @@ -48,9 +54,15 @@ importers: onchange: specifier: ^7.1.0 version: 7.1.0 + parseurl: + specifier: ^1.3.3 + version: 1.3.3 pngjs: specifier: ^7.0.0 version: 7.0.0 + send: + specifier: ^1.1.0 + version: 1.1.0 tsx: specifier: ^4.19.2 version: 4.19.2 @@ -334,12 +346,21 @@ packages: '@types/dom-webcodecs@0.1.13': resolution: {integrity: sha512-O5hkiFIcjjszPIYyUSyvScyvrBoV3NOEEZx/pMlsu44TKzWNkLVBBxnxJz42in5n3QIolYOcBYFCPZZ0h8SkwQ==} + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/node@22.9.3': resolution: {integrity: sha512-F3u1fs/fce3FFk+DAxbxc78DF8x0cY09RRL8GnXLmkJ1jvx3TtPdWoTT5/NiYfI5ASqXBmfqJi9dZ3gxMx4lzw==} + '@types/parseurl@1.3.3': + resolution: {integrity: sha512-eamlh+uXpNIG2yVdl6UdBTR3B6jtmJl/JWTNTQAN1K3VH1s5F6UPOhgLwCnU9FJ7R/PnUsRN9zFSpMcxwa2Ndg==} + '@types/pngjs@6.0.5': resolution: {integrity: sha512-0k5eKfrA83JOZPppLtS2C7OUtyNAl2wKNxfyYl9Q5g9lPkgBl/9hNyAu6HuEH2J4XmIv2znEpkDd0SaZVxW6iQ==} + '@types/send@0.17.4': + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + '@types/webxr@0.5.20': resolution: {integrity: sha512-JGpU6qiIJQKUuVSKx1GtQnHJGxRjtfGIhzO2ilq43VZZS//f1h1Sgexbdk+Lq+7569a6EYhOWrUpIruR/1Enmg==} @@ -416,15 +437,46 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + esbuild@0.23.1: resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} engines: {node: '>=18'} hasBin: true + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -438,6 +490,10 @@ packages: debug: optional: true + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -464,6 +520,10 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + hyperdyperid@1.2.0: resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==} engines: {node: '>=10.18'} @@ -516,6 +576,14 @@ packages: resolution: {integrity: sha512-JUeY0F/fQZgIod31Ja1eJgiSxLn7BfQlCnqhwXFBzFHEw63OdLK7VJUJ7bnzNsWgCyoUP5tEp1VRY8rDaYzqOA==} engines: {node: '>= 4.0.0'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -540,10 +608,17 @@ packages: engines: {node: '>=10'} hasBin: true + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -551,6 +626,10 @@ packages: resolution: {integrity: sha512-ZJcqsPiWUAUpvmnJri5TPBooqJOPmC0ttN65juhN15Q8xA+Nbg3BaxBHXQ45EistKKlKElb0edmbPWnKSBkvMg==} hasBin: true + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -570,6 +649,10 @@ packages: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -588,6 +671,13 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + send@1.1.0: + resolution: {integrity: sha512-v67WcEouB5GxbTWL/4NeToqcZiAWEq90N888fczVArY8A79J0L4FD7vj5hm3eUMua5EpoQ59wa/oovY6TLvRUA==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -596,6 +686,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -610,6 +704,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + tree-dump@1.0.2: resolution: {integrity: sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==} engines: {node: '>=10.0'} @@ -857,14 +955,25 @@ snapshots: '@types/dom-webcodecs@0.1.13': {} + '@types/mime@1.3.5': {} + '@types/node@22.9.3': dependencies: undici-types: 6.19.8 + '@types/parseurl@1.3.3': + dependencies: + '@types/node': 22.9.3 + '@types/pngjs@6.0.5': dependencies: '@types/node': 22.9.3 + '@types/send@0.17.4': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 22.9.3 + '@types/webxr@0.5.20': {} '@types/wicg-file-system-access@2020.9.8': {} @@ -946,8 +1055,20 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + debug@4.4.0: + dependencies: + ms: 2.1.3 + deepmerge@4.3.1: {} + depd@2.0.0: {} + + destroy@1.2.0: {} + + ee-first@1.1.1: {} + + encodeurl@2.0.0: {} + esbuild@0.23.1: optionalDependencies: '@esbuild/aix-ppc64': 0.23.1 @@ -975,12 +1096,18 @@ snapshots: '@esbuild/win32-ia32': 0.23.1 '@esbuild/win32-x64': 0.23.1 + escape-html@1.0.3: {} + + etag@1.8.1: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 follow-redirects@1.15.9: {} + fresh@0.5.2: {} + fs-minipass@2.1.0: dependencies: minipass: 3.3.6 @@ -1009,6 +1136,14 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + hyperdyperid@1.2.0: {} ieee754@1.2.1: {} @@ -1049,6 +1184,12 @@ snapshots: tree-dump: 1.0.2(tslib@2.8.1) tslib: 2.8.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -1070,8 +1211,14 @@ snapshots: mkdirp@1.0.4: {} + ms@2.1.3: {} + normalize-path@3.0.0: {} + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -1086,6 +1233,8 @@ snapshots: ignore: 5.3.2 tree-kill: 1.2.2 + parseurl@1.3.3: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} @@ -1096,6 +1245,8 @@ snapshots: pngjs@7.0.0: {} + range-parser@1.2.1: {} + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -1110,12 +1261,33 @@ snapshots: dependencies: glob: 7.2.3 + send@1.1.0: + dependencies: + debug: 4.4.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime-types: 2.1.35 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + statuses@2.0.1: {} + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -1133,6 +1305,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + tree-dump@1.0.2(tslib@2.8.1): dependencies: tslib: 2.8.1 diff --git a/public/data b/public/data deleted file mode 120000 index 4909e06ef..000000000 --- a/public/data +++ /dev/null @@ -1 +0,0 @@ -../data \ No newline at end of file diff --git a/rsbuild.config.ts b/rsbuild.config.ts index 703e1b24d..3969a346f 100644 --- a/rsbuild.config.ts +++ b/rsbuild.config.ts @@ -1,6 +1,10 @@ -import { defineConfig } from '@rsbuild/core'; +import { defineConfig, type RequestHandler } from '@rsbuild/core'; import { pluginTypeCheck } from '@rsbuild/plugin-type-check'; import { execSync } from 'node:child_process'; +import { readdir } from 'node:fs'; +import type { ServerResponse } from 'node:http'; +import parseUrl from 'parseurl'; +import send from 'send'; let gitCommit = '(unknown)'; try { @@ -10,9 +14,6 @@ try { } export default defineConfig({ - server: { - htmlFallback: false, - }, source: { entry: { index: './src/main.ts', @@ -63,4 +64,54 @@ export default defineConfig({ }, }, }, + // Disable fallback to index for 404 responses. + server: { + htmlFallback: false, + }, + // Setup middleware to serve the `data` directory. + dev: { + setupMiddlewares: [ + (middlewares, _server) => { + middlewares.unshift(serveData); + return middlewares; + }, + ], + }, }); + +// Serve files from the `data` directory. +const serveData: RequestHandler = (req, res, next) => { + if (req.method !== 'GET' && req.method !== 'HEAD') { + next(); + return; + } + const matches = parseUrl(req)?.pathname?.match(/^\/data(\/.*)?$/); + if (!matches) { + next(); + return; + } + // The `send` package handles Range requests, conditional GET, + // ETag generation, Cache-Control, Last-Modified, and more. + const stream = send(req, matches[1] || '', { + index: false, + root: 'data', + }); + stream.on( + 'directory', + function handleDirectory( + this: send.SendStream, + res: ServerResponse, + path: string, + ) { + // Print directory listing + readdir(path, (err, list) => { + if (err) return this.error(500, err); + const filtered = list.filter((file) => !file.startsWith('.')); + if (filtered.length === 0) return this.error(404); + res.setHeader('Content-Type', 'text/plain; charset=UTF-8'); + res.end(`${filtered.join('\n')}\n`); + }); + }, + ); + stream.pipe(res); +};