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": { "lint": {
"exclude": ["framework/meta"], // OLD "exclude": ["src"], // OLD
"rules": { "rules": {
"exclude": [ "exclude": [
"no-explicit-any" // TODO "no-explicit-any" // TODO

View file

@ -97,3 +97,4 @@ import * as fs from "./fs.ts";
import type { Context, Next } from "hono"; import type { Context, Next } from "hono";
import type { StatusCode } from "hono/utils/http-status"; import type { StatusCode } from "hono/utils/http-status";
import type { BuiltAsset, BuiltAssetMap, View } from "./incremental.ts"; 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. // This file implements client-side bundling, mostly wrapping esbuild.
import process from "node:process";
const plugins: esbuild.Plugin[] = [ const plugins: esbuild.Plugin[] = [
// There are currently no plugins needed by 'paperclover.net' // 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 esbuild from "esbuild";
import * as path from "node:path";
import process from "node:process";
import { Incremental } from "./incremental.ts"; import { Incremental } from "./incremental.ts";

View file

@ -35,7 +35,7 @@ declare global {
[name: string]: Record<string, unknown>; [name: string]: Record<string, unknown>;
} }
interface ElementChildrenAttribute { interface ElementChildrenAttribute {
children: {}; children: unknown;
} }
type Element = engine.Node; type Element = engine.Node;
type ElementType = keyof IntrinsicElements | engine.Component; 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. // bare client import statements to it's own usage.
if (src.match(/^\s*client\s+import\s+["']/m)) { if (src.match(/^\s*client\s+import\s+["']/m)) {
src = src.replace( src = src.replace(
/^\s*client\s+import\s+("[^"]+|'[^']+)[^\n]+/m, /^\s*client\s+import\s+("[^"]+"|'[^']+')[^\n]+/m,
"<CloverScriptInclude src=$1 />", "<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 = marko.compileSync(src, filepath).code;
src = src.replace("marko/debug/html", "#ssr/marko"); src = src.replace("marko/debug/html", "#ssr/marko");
return loadEsbuildCode(module, filepath, src); return loadEsbuildCode(module, filepath, src);

View file

@ -1,29 +1,30 @@
const db = new Map( const entries = fs.readFileSync(
fs.readFileSync(path.join(import.meta.dirname, "mime.txt"), "utf8") path.join(import.meta.dirname, "mime.txt"),
.split("\n").filter(Boolean).map((line) => "utf8",
line.split(/\s+/) as [string, string] )
), .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: * Accepts:
* - Full file path * - Full file path or basename
* - Extension (with or without dot) * - Extension (with or without dot)
*/ */
export function contentTypeFor(file: string) { export function contentTypeFor(file: string) {
if (file.includes("/") || file.includes("\\")) { const slash = file.indexOf("/");
// Some file names are special cased. if (slash !== -1) file = file.slice(slash + 1);
switch (path.basename(file)) {
case "rss.xml":
return "application/rss+xml";
}
file = path.extname(file);
}
const dot = file.indexOf("."); const dot = file.indexOf(".");
if (dot === -1) file = "." + file; if (dot === -1) file = "." + file;
else if (dot > 0) file = file.slice(dot); else if (dot > 0) {
return db.get(file) ?? "application/octet-stream"; 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"; 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> { interface QueueOptions<T, R> {
name: string; name: string;
fn: (item: T, spin: Spinner) => Promise<R>; fn: (item: T, spin: Spinner) => Promise<R>;
@ -204,3 +199,8 @@ export class OnceMap<T> {
return result; 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" "license": "ISC"
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.14.1", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"

View file

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

View file

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

17
run.js
View file

@ -1,6 +1,5 @@
// This file allows using Node.js in combination with // This file allows using Node.js in combination with
// all available plugins. Usage: "node run <script>" // all available plugins. Usage: "node run <script>"
import * as path from "node:path";
import * as util from "node:util"; import * as util from "node:util";
import process from "node:process"; 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]"); console.error("usage: node run <script> [...args]");
process.exit(1); 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)]; process.argv = [process.argv[0], ...process.argv.slice(2)];
hot.load(file).main?.(); hot.load(found).main?.();
} }
export { hot }; export { hot };