A music player that connects to your cloud/distributed storage.
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

at a0924e204985facf3a9eb44f5f0f8a4e53e1ee83 383 lines 9.8 kB view raw
1import type { RequestHandler } from "lume/core/server.ts"; 2 3import { dotenvRun } from "@dotenv-run/esbuild"; 4import lume from "lume/mod.ts"; 5 6import brotli from "lume/plugins/brotli.ts"; 7import esbuild from "lume/plugins/esbuild.ts"; 8import postcss from "lume/plugins/postcss.ts"; 9import sourceMaps from "lume/plugins/source_maps.ts"; 10 11import * as path from "@std/path"; 12import { ensureDirSync } from "@std/fs/ensure-dir"; 13import { walkSync } from "@std/fs/walk"; 14import { nodeModulesPolyfillPlugin } from "esbuild-plugins-node-modules-polyfill"; 15import { wasmLoader } from "esbuild-plugin-wasm"; 16import autoprefixer from "autoprefixer"; 17import cssnano from "cssnano"; 18 19import { create as createCID } from "~/common/cid.js"; 20 21const site = lume({ 22 dest: "./dist", 23 src: "./src", 24 server: { 25 debugBar: false, 26 middlewares: [], 27 }, 28}); 29 30export default site; 31 32//////////////////////////////////////////// 33// JS 34//////////////////////////////////////////// 35 36site.use(esbuild({ 37 extensions: [".js"], 38 options: { 39 alias: { 40 "@automerge/automerge": "https://esm.sh/@automerge/automerge@^3.2.3", 41 }, 42 bundle: true, 43 format: "esm", 44 minify: true, 45 external: ["./file-tree.json", "@awesome.me/webawesome/*"], 46 platform: "browser", 47 plugins: [ 48 // @ts-ignore 49 dotenvRun({ 50 files: [".env"], 51 }), 52 // Force @atcute/uint8array to use the browser entry (dist/index.js) 53 // instead of the Node entry (dist/index.node.js) which imports from 54 // node:crypto. The @deno/loader Workspace defaults to platform "node", 55 // causing the "node" export condition to match before "default". 56 { 57 name: "atcute-uint8array-browser", 58 setup(build) { 59 build.onLoad( 60 { filter: /@atcute\+uint8array.*index\.node\.js$/ }, 61 async (args) => { 62 const browserPath = args.path.replace( 63 "index.node.js", 64 "index.js", 65 ); 66 const contents = await Deno.readTextFile(browserPath); 67 return { contents, loader: "js" }; 68 }, 69 ); 70 }, 71 }, 72 { 73 name: "atcute-tid-browser", 74 setup(build) { 75 build.onLoad( 76 { filter: /@atcute\+tid.*random-node\.js$/ }, 77 async (args) => { 78 const browserPath = args.path.replace( 79 "random-node.js", 80 "random-web.js", 81 ); 82 const contents = await Deno.readTextFile(browserPath); 83 return { contents, loader: "js" }; 84 }, 85 ); 86 }, 87 }, 88 { 89 name: "atcute-multibase-browser", 90 setup(build) { 91 build.onLoad( 92 { filter: /@atcute[+/]multibase.*-node\.js$/ }, 93 async (args) => { 94 const browserPath = args.path.replace( 95 "-node.js", 96 "-web.js", 97 ); 98 const contents = await Deno.readTextFile(browserPath); 99 return { contents, loader: "js" }; 100 }, 101 ); 102 }, 103 }, 104 // nanoid ships a browser entry (index.browser.js) but esbuild resolves 105 // the default condition (index.js) which uses Buffer.allocUnsafe. 106 { 107 name: "nanoid-browser", 108 setup(build) { 109 build.onLoad( 110 { filter: /nanoid\/index\.js$/ }, 111 async (args) => { 112 const browserPath = args.path.replace( 113 "index.js", 114 "index.browser.js", 115 ); 116 const contents = await Deno.readTextFile(browserPath); 117 return { contents, loader: "js" }; 118 }, 119 ); 120 }, 121 }, 122 nodeModulesPolyfillPlugin({ 123 fallback: "empty", 124 modules: [], 125 }), 126 wasmLoader(), 127 ], 128 splitting: true, 129 target: "esnext", 130 }, 131})); 132 133site.add([".js"]); 134 135// *.inline.js files are inlined into their companion HTML at build/serve time. 136// Exclude them from the regular build so esbuild doesn't try to bundle them. 137site.ignore((p) => p.endsWith(".inline.js")); 138 139//////////////////////////////////////////// 140// CSS 141//////////////////////////////////////////// 142 143site.use(postcss({ 144 plugins: [ 145 autoprefixer(), 146 cssnano({ 147 preset: "default", 148 }), 149 ], 150})); 151 152site.add([".css"]); 153 154site.remoteFile( 155 "vendor/98.css", 156 import.meta.resolve("./node_modules/98.css/dist/98.css"), 157); 158 159//////////////////////////////////////////// 160// BINARY ASSETS 161//////////////////////////////////////////// 162 163site.add("/favicons", "/"); 164site.add("/fonts"); 165site.add("/images"); 166site.add("/testing"); 167site.add([".woff2"]); 168 169site.remoteFile( 170 "vendor/ms_sans_serif.woff2", 171 import.meta.resolve( 172 "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", 173 ), 174); 175 176site.remoteFile( 177 "vendor/ms_sans_serif_bold.woff2", 178 import.meta.resolve( 179 "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", 180 ), 181); 182 183site.remoteFile( 184 "fonts/98.css/ms_sans_serif.woff2", 185 import.meta.resolve( 186 "./node_modules/98.css/fonts/converted/ms_sans_serif.woff2", 187 ), 188); 189 190site.remoteFile( 191 "fonts/98.css/ms_sans_serif_bold.woff2", 192 import.meta.resolve( 193 "./node_modules/98.css/fonts/converted/ms_sans_serif_bold.woff2", 194 ), 195); 196 197//////////////////////////////////////////// 198// DEFINITIONS 199//////////////////////////////////////////// 200 201site.add("/definitions"); 202 203// HELPERS 204 205site.filter("facetURI", (text) => { 206 if (text.includes("://")) { 207 return text; 208 } else { 209 return `diffuse://${text}`; 210 } 211}); 212 213site.filter("facetLoaderURL", (text) => { 214 let key = "path"; 215 216 if (text.includes("://")) { 217 key = "uri"; 218 } 219 220 return `l/?${key}=${encodeURIComponent(text)}`; 221}); 222 223//////////////////////////////////////////// 224// PHOSPHOR ICONS 225//////////////////////////////////////////// 226 227function phosphor(path: string) { 228 site.remoteFile( 229 `vendor/@phosphor-icons/web/${path}`, 230 import.meta.resolve(`./node_modules/@phosphor-icons/web/src/${path}`), 231 ); 232 233 site.add(`vendor/@phosphor-icons/web/${path}`); 234} 235 236["bold", "duotone", "fill", "light", "regular", "light"].forEach((v) => { 237 const f = v === "regular" ? "" : `-${v[0].toUpperCase()}${v.slice(1)}`; 238 phosphor(`${v}/selection.json`); 239 phosphor(`${v}/style.css`); 240 phosphor(`${v}/Phosphor${f}.svg`); 241 phosphor(`${v}/Phosphor${f}.ttf`); 242 phosphor(`${v}/Phosphor${f}.woff`); 243 phosphor(`${v}/Phosphor${f}.woff2`); 244}); 245 246//////////////////////////////////////////// 247// WEB AWESOME 248//////////////////////////////////////////// 249 250for ( 251 const f of walkSync("./node_modules/@awesome.me/webawesome/dist-cdn/", { 252 includeDirs: false, 253 }) 254) { 255 const relativePath = f.path.replace( 256 /^node_modules\/@awesome\.me\/webawesome\/dist-cdn\//, 257 "", 258 ); 259 260 const destPath = `vendor/@awesome.me/webawesome/${relativePath}`; 261 262 site.remoteFile( 263 destPath, 264 import.meta.resolve( 265 `./node_modules/@awesome.me/webawesome/dist-cdn/${relativePath}`, 266 ), 267 ); 268 269 site.copy(destPath); 270} 271 272//////////////////////////////////////////// 273// MISC 274//////////////////////////////////////////// 275 276site.add([".html"]); 277site.add([".json"]); 278site.add([".webmanifest"]); 279 280site.remoteFile( 281 "architecture.txt", 282 import.meta.resolve("./docs/ARCHITECTURE.md"), 283); 284 285site.add("architecture.txt"); 286 287site.script("copy-type-defs", () => { 288 for ( 289 const f of walkSync( 290 "./src/", 291 { includeDirs: false, exts: [".d.ts"] }, 292 ) 293 ) { 294 const dest = "dist/" + f.path.replace(/^src\//, ""); 295 const dir = path.dirname(dest); 296 ensureDirSync(dir); 297 Deno.copyFileSync(f.path, dest); 298 } 299}); 300 301site.addEventListener("afterBuild", () => { 302 // site.run("copy-type-defs"); 303}); 304 305//////////////////////////////////////////// 306// FILE TREE 307//////////////////////////////////////////// 308 309site.addEventListener("afterBuild", async () => { 310 const RAW = 0x55; 311 312 async function buildFileTree( 313 dir: string, 314 prefix = "", 315 ): Promise<Record<string, string>> { 316 const tree: Record<string, string> = {}; 317 318 for (const entry of Deno.readDirSync(dir)) { 319 const entryPath = path.join(dir, entry.name); 320 const entryKey = prefix ? `${prefix}/${entry.name}` : entry.name; 321 if (entry.isDirectory) { 322 Object.assign(tree, await buildFileTree(entryPath, entryKey)); 323 } else { 324 const data = Deno.readFileSync(entryPath); 325 tree[entryKey] = await createCID(RAW, data); 326 } 327 } 328 329 return tree; 330 } 331 332 const tree = await buildFileTree("dist/"); 333 const sorted = Object.fromEntries( 334 Object.keys(tree).sort().map((k) => [k, tree[k]]), 335 ); 336 337 Deno.writeTextFileSync( 338 "./dist/file-tree.json", 339 JSON.stringify(sorted, null, 2), 340 ); 341}); 342 343//////////////////////////////////////////// 344// INLINE JS FOR FACETS 345//////////////////////////////////////////// 346 347const SCRIPT_SRC_RE = 348 /<script type="module" src="([^"]+\.inline\.js)"><\/script>/; 349 350site.process([".html"], (pages) => { 351 for (const page of pages) { 352 const content = page.text; 353 if (!content) continue; 354 const match = SCRIPT_SRC_RE.exec(content); 355 if (!match) continue; 356 357 const jsPath = path.join("src", match[1]); 358 try { 359 page.text = htmlWithInlineJs({ content, jsPath, match: match[0] }); 360 } catch { 361 // leave as-is if the source file can't be read 362 } 363 } 364}); 365 366function htmlWithInlineJs({ content, match, jsPath }: { 367 content: string; 368 match: string; 369 jsPath: string; 370}): string { 371 const js = 372 Deno.readTextFileSync(jsPath).split("\n").map((line) => ` ${line}`).join( 373 "\n", 374 ).trimEnd() + "\n"; 375 return content.replace(match, `<script type="module">\n${js}</script>`); 376} 377 378//////////////////////////////////////////// 379// COMPRESSION 380//////////////////////////////////////////// 381 382site.use(brotli()); 383site.use(sourceMaps());