add content type library

This commit is contained in:
chloe caruso 2025-06-08 12:38:25 -07:00
parent 7242c6eb89
commit 46a67453a1
12 changed files with 153 additions and 40 deletions

View file

@ -1,6 +1,6 @@
{
"lint": {
"exclude": ["framework/meta"], // OLD
"exclude": ["src"], // OLD
"rules": {
"exclude": [
"no-explicit-any" // TODO

View file

@ -97,3 +97,4 @@ import * as fs from "./fs.ts";
import type { Context, Next } from "hono";
import type { StatusCode } from "hono/utils/http-status";
import type { BuiltAsset, BuiltAssetMap, View } from "./incremental.ts";
import { Buffer } from "node:buffer";

View file

@ -1,5 +1,4 @@
// This file implements client-side bundling, mostly wrapping esbuild.
import process from "node:process";
const plugins: esbuild.Plugin[] = [
// There are currently no plugins needed by 'paperclover.net'
];
@ -80,6 +79,7 @@ export async function bundleClientJavaScript(
}
}
import * as path from "node:path";
import * as esbuild from "esbuild";
import * as path from "node:path";
import process from "node:process";
import { Incremental } from "./incremental.ts";

View file

@ -35,7 +35,7 @@ declare global {
[name: string]: Record<string, unknown>;
}
interface ElementChildrenAttribute {
children: {};
children: unknown;
}
type Element = engine.Node;
type ElementType = keyof IntrinsicElements | engine.Component;

View file

@ -157,11 +157,13 @@ function loadMarko(module: NodeJS.Module, filepath: string) {
// bare client import statements to it's own usage.
if (src.match(/^\s*client\s+import\s+["']/m)) {
src = src.replace(
/^\s*client\s+import\s+("[^"]+|'[^']+)[^\n]+/m,
/^\s*client\s+import\s+("[^"]+"|'[^']+')[^\n]+/m,
"<CloverScriptInclude src=$1 />",
) + '\nimport { Script as CloverScriptInclude } from "#sitegen";';
) + '\nimport { Script as CloverScriptInclude } from "#sitegen";\n';
}
console.log(src);
console.log("---");
src = marko.compileSync(src, filepath).code;
src = src.replace("marko/debug/html", "#ssr/marko");
return loadEsbuildCode(module, filepath, src);

View file

@ -1,29 +1,30 @@
const db = new Map(
fs.readFileSync(path.join(import.meta.dirname, "mime.txt"), "utf8")
.split("\n").filter(Boolean).map((line) =>
line.split(/\s+/) as [string, string]
),
);
const entries = fs.readFileSync(
path.join(import.meta.dirname, "mime.txt"),
"utf8",
)
.split("\n")
.map((line) => line.trim())
.filter((line) => line && !line.startsWith("#"))
.map((line) => line.split(/\s+/, 2) as [string, string]);
const extensions = new Map(entries.filter((x) => x[0].startsWith(".")));
const fullNames = new Map(entries.filter((x) => !x[0].startsWith(".")));
/**
* Accepts:
* - Full file path
* - Full file path or basename
* - Extension (with or without dot)
*/
export function contentTypeFor(file: string) {
if (file.includes("/") || file.includes("\\")) {
// Some file names are special cased.
switch (path.basename(file)) {
case "rss.xml":
return "application/rss+xml";
}
file = path.extname(file);
}
const slash = file.indexOf("/");
if (slash !== -1) file = file.slice(slash + 1);
const dot = file.indexOf(".");
if (dot === -1) file = "." + file;
else if (dot > 0) file = file.slice(dot);
return db.get(file) ?? "application/octet-stream";
else if (dot > 0) {
let entry = fullNames.get(file);
if (entry) return entry;
file = file.slice(dot);
}
return extensions.get(file) ?? "application/octet-stream";
}
import * as fs from "./fs.ts";

99
framework/mime.txt Normal file
View file

@ -0,0 +1,99 @@
# media types
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/MIME_types
.aac audio/x-aac
.aif audio/x-aiff
.aifc audio/x-aiff
.aiff audio/x-aiff
.asm text/x-asm
.avi video/x-msvideo
.bat application/x-msdownload
.c text/x-c
.chat text/x-clover-chatlog
.class application/java-vm
.cmd application/x-msdownload
.com application/x-msdownload
.conf text/plain
.cpp text/x-c
.css text/css
.csv text/csv
.cxx text/x-c
.def text/plain
.diff text/plain
.dll application/x-msdownload
.dmg application/octet-stream
.doc application/msword
.docx application/vnd.openxmlformats-officedocument.wordprocessingml.document
.epub application/epub+zip
.exe application/x-msdownload
.flv video/x-flv
.fbx application/fbx
.gz application/x-gzip
.h text/x-c
.h264 video/h264
.hh text/x-c
.htm text/html;charset=utf-8
.html text/html;charset=utf-8
.ico image/x-icon
.ics text/calendar
.in text/plain
.jar application/java-archive
.java text/x-java-source
.jpeg image/jpeg
.jpg image/jpeg
.jpgv video/jpeg
.jxl image/jxl
.js application/javascript
.json application/json
.latex application/x-latex
.list text/plain
.log text/plain
.m4a audio/mp4
.man text/troff
.mid audio/midi
.midi audio/midi
.mov video/quicktime
.mp3 audio/mpeg
.mp4 video/mp4
.msh model/mesh
.msi application/x-msdownload
.obj application/octet-stream
.ogg audio/ogg
.otf application/x-font-otf
.pdf application/pdf
.png image/png
.ppt application/vnd.ms-powerpoint
.pptx application/vnd.openxmlformats-officedocument.presentationml.presentation
.psd image/vnd.adobe.photoshop
.py text/x-python
.rar application/x-rar-compressed
.rss application/rss+xml
.rtf application/rtf
.rtx text/richtext
.s text/x-asm
.pem application/x-pem-file"
.ser application/java-serialized-object
.sh application/x-sh
.sig application/pgp-signature
.silo model/mesh
.svg image/svg+xml
.t text/troff
.tar application/x-tar
.text text/plain
.tgz application/x-gzip
.tif image/tiff
.tiff image/tiff
.torrent application/x-bittorrent
.ttc application/x-font-ttf
.ttf application/x-font-ttf
.txt text/plain
.urls text/uri-list
.v text/x-v
.wav audio/x-wav
.wmv video/x-ms-wmv
.xls application/vnd.ms-excel
.xlsx application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xml application/xml
.xps application/vnd.ms-xpsdocument
# special cased based on file name
rss.xml application/rss+xml

View file

@ -1,8 +1,3 @@
import { Progress } from "@paperclover/console/Progress";
import { Spinner } from "@paperclover/console/Spinner";
import * as path from "node:path";
import process from "node:process";
interface QueueOptions<T, R> {
name: string;
fn: (item: T, spin: Spinner) => Promise<R>;
@ -204,3 +199,8 @@ export class OnceMap<T> {
return result;
}
}
import { Progress } from "@paperclover/console/Progress";
import { Spinner } from "@paperclover/console/Spinner";
import * as path from "node:path";
import process from "node:process";

6
package-lock.json generated
View file

@ -1027,9 +1027,9 @@
"license": "ISC"
},
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==",
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"

View file

@ -1,5 +1,4 @@
{
"private": true,
"type": "module",
"dependencies": {
"@hono/node-server": "^1.14.3",
@ -23,9 +22,9 @@
"#ssr/jsx-runtime": "./framework/engine/jsx-runtime.ts",
"#ssr/marko": "./framework/engine/marko-runtime.ts",
"#marko/html": {
"development": "marko/debug/html",
"production": "marko/html",
"types": "marko/html"
"types": "marko/html",
"production": "marko/production",
"node": "marko/debug/html"
},
"#hono/platform": {
"bun": "hono/bun",

View file

@ -18,7 +18,7 @@ hot.load("node:repl").start({
.catch((err) => {
// TODO: improve @paperclover/console's ability to print AggregateError
// and errors with extra random properties
console.error(inspect(err));
console.error(util.inspect(err));
})
.then((result) => done(null, result));
},

17
run.js
View file

@ -1,6 +1,5 @@
// This file allows using Node.js in combination with
// all available plugins. Usage: "node run <script>"
import * as path from "node:path";
import * as util from "node:util";
import process from "node:process";
@ -47,9 +46,21 @@ if (process.argv[1].startsWith(import.meta.filename.slice(0, -".js".length))) {
console.error("usage: node run <script> [...args]");
process.exit(1);
}
const file = path.resolve(process.argv[2]);
let found;
for (const dir of ["./", "./src/", "./framework/"]) {
try {
found = hot.resolveFrom(import.meta.filename, dir + process.argv[2]);
break;
} catch (e) {
continue;
}
}
if (!found) {
console.error("Cannot find script: " + process.argv[2]);
process.exit(1);
}
process.argv = [process.argv[0], ...process.argv.slice(2)];
hot.load(file).main?.();
hot.load(found).main?.();
}
export { hot };