summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/_data/env.js3
-rw-r--r--src/_data/metadata.js19
-rw-r--r--src/_includes/base.njk52
-rw-r--r--src/_includes/page.njk7
-rw-r--r--src/_includes/post.njk9
-rw-r--r--src/about.njk14
-rw-r--r--src/blog.njk17
-rw-r--r--src/client/index.ts20
-rw-r--r--src/gen/atom.njk27
-rw-r--r--src/gen/error.njk13
-rw-r--r--src/gen/feedjson.11ty.js35
-rw-r--r--src/gen/gen.11tydata.js9
-rw-r--r--src/gen/manifest.11ty.js28
-rw-r--r--src/gen/metadata.11ty.js13
-rw-r--r--src/gen/robot.njk6
-rw-r--r--src/gen/sitemap.njk12
-rw-r--r--src/index.njk6
-rw-r--r--src/post/post.11tydata.js8
-rw-r--r--src/server/app.ts31
-rw-r--r--src/server/build.ts31
-rw-r--r--src/server/content/about.ts32
-rw-r--r--src/server/content/blog.ts35
-rw-r--r--src/server/content/feed.ts32
-rw-r--r--src/server/content/index.ts20
-rw-r--r--src/server/content/robots.ts9
-rw-r--r--src/server/content/sitemap.ts16
-rw-r--r--src/server/content/webmanifest.ts26
-rw-r--r--src/server/errHanadler.ts85
-rw-r--r--src/server/img.ts83
-rw-r--r--src/server/metadata.ts18
-rw-r--r--src/server/router.ts5
-rw-r--r--src/server/server.ts23
-rw-r--r--src/server/template/Base.ts117
-rw-r--r--src/server/template/Page.ts23
-rw-r--r--src/server/template/Post.ts48
-rw-r--r--src/server/template/atom.ts114
-rw-r--r--src/server/template/header.ts10
-rw-r--r--src/server/template/html.ts602
-rw-r--r--src/server/template/sitemap.ts31
-rw-r--r--src/server/template/syntax.ts18
-rw-r--r--src/server/template/table.ts32
-rw-r--r--src/server/template/vdom.ts105
-rw-r--r--src/server/template/xml.ts56
-rw-r--r--src/server/utils/createUrl.ts5
-rw-r--r--src/server/utils/curl.ts7
-rw-r--r--src/server/utils/isDefined.ts3
-rw-r--r--src/server/utils/isDevel.ts5
-rw-r--r--src/server/utils/relDir.ts9
-rw-r--r--src/server/utils/relUrl.ts5
-rw-r--r--src/server/utils/schema.ts6
-rw-r--r--src/server/utils/setStingRoute.ts6
-rw-r--r--src/server/utils/strHandler.ts8
-rw-r--r--src/worker/sw.ts51
-rw-r--r--src/worker/tsconfig.json10
54 files changed, 1737 insertions, 278 deletions
diff --git a/src/_data/env.js b/src/_data/env.js
deleted file mode 100644
index a497313..0000000
--- a/src/_data/env.js
+++ /dev/null
@@ -1,3 +0,0 @@
-const process = require("process");
-
-module.exports = () => process.env; \ No newline at end of file
diff --git a/src/_data/metadata.js b/src/_data/metadata.js
deleted file mode 100644
index 7f07bb6..0000000
--- a/src/_data/metadata.js
+++ /dev/null
@@ -1,19 +0,0 @@
-module.exports = {
- title: "Sudomsg",
- url: "https://sudomsg.xyz/",
- language: "en-GB",
- theme: "#8b0000",
- description: "Messages from root",
- feed: {
- atom: "/feed.xml",
- json: "/feed.json"
- },
- author: {
- name: "Marc Pervaz Boocha",
- email: "mboocha@sudomsg.xyz",
- github: "https://github.com/marcthe12",
- linkedin: "https://www.linkedin.com/in/marc-pervaz-boocha-200706236/",
- image: "/favicon/512.png",
- url: "/about/#marc-pervaz-boocha"
- }
-};
diff --git a/src/_includes/base.njk b/src/_includes/base.njk
deleted file mode 100644
index 19cf0d8..0000000
--- a/src/_includes/base.njk
+++ /dev/null
@@ -1,52 +0,0 @@
----
-nav:
- Home: "/"
- Blog: "/blog"
- About: "/about"
- Git: "/cgit"
----
-<!DOCTYPE html>
-<html lang="{{ metadata.language }}">
- <head>
- <meta charset="utf-8">
- <meta property="og:locale content=en_GB">
- <title property="og:title">{{ title or metadata.title }}</title>
- <meta property="og:site_name" content="{{ metadata.title }}">
- <meta name="author" content="{{ metadata.author.name }}">
- <meta name="description" property="og:description content="{{ description or data.metadata }}">
- <meta property="og:type" content="website">
- {% if keywords %}
- <meta name="keywords" contents="{{ keywords | join }}">
- {% endif %}
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="twitter:card" content="summary">
- <meta property=og:url content="{{ page.url | absoluteUrl(metadata.url)}}">
- <link rel="canonical" href="{{ page.url | absoluteUrl(metadata.url)}}">
- <link rel="alternate" href="{{ metadata.feed.atom }}" type="application/atom+xml" title="{{ metadata.title }}">
- <link rel="alternate" href="{{ metadata.feed.json }}" type="application/feed+json" title="{{ metadata.title }}">
- <meta property="og:image" content="/favicon/1024.png">
- <link rel="icon" href="/favicon.ico" sizes="any">
- <link rel="icon" href="/favicon/icon.svg" type="image/svg+xml">
- <link rel="apple-touch-icon" href="/favicon/192.png">
- <link rel="manifest" href="/app.webmanifest">
- <link rel="stylesheet" href="/assets/index.css">
- <link rel="stylesheet" href="/vendor/prism.css">
- <script type="module" src="/assets/index.js" defer></script>
- </head>
- <body>
- <header>
- <nav>
- <a href="#" id="nav-toogle">Sudomsg</a>
- {% for name, location in nav %}
- <a class="navlinks" href="{{location}}" >{{name}}</a>
- {% endfor %}
- </nav>
- </header>
- <main>
- {{ content | safe }}
- </main>
- <footer>
- Subscribe: <a href="{{metadata.feed.atom}}">RSS</a> <a href="{{ metadata.feed.json }}">JSON</a>
- </footer>
- </body>
-</html>
diff --git a/src/_includes/page.njk b/src/_includes/page.njk
deleted file mode 100644
index 263e09c..0000000
--- a/src/_includes/page.njk
+++ /dev/null
@@ -1,7 +0,0 @@
----
-layout: base
----
-<article>
- <h1 id="{{title | slug }}">{{title}}</h1>
- {{content|safe}}
-</article>
diff --git a/src/_includes/post.njk b/src/_includes/post.njk
deleted file mode 100644
index f613809..0000000
--- a/src/_includes/post.njk
+++ /dev/null
@@ -1,9 +0,0 @@
----
-layout: page
-tags:
- - posts
----
-<small>
- <time datetime="{{page.date | htmlDateString }}">{{ page.date | readableDate}}</time> - <a rel=author href="{{ metadata.author.url }}">{{ metadata.author.name }}</a>
-</small>
-{{content|safe}}
diff --git a/src/about.njk b/src/about.njk
deleted file mode 100644
index ee867fd..0000000
--- a/src/about.njk
+++ /dev/null
@@ -1,14 +0,0 @@
----
-layout: page
-title: About Me
----
-
-<h2>{{ metadata.author.name }}</h2>
-<img alt="A Photo of me" src="{{ metadata.author.image}}" class="side">
-<p>I am an analytical and passionate second year CHRIST University student pursuing B. Tech in Computer Engineering in Bengaluru. I am eager to further my knowledge, develop my skills and gain experience to convert my interest in computers into a fulfilling career.
-<p>Contact Details
-<ul>
-<li><a href="mailto:{{ metadata.author.email }}">Email</a>
-<li><a href="{{ metadata.author.github}}">Github</a>
-<li><a href="{{ metadata.author.linkedin}}">LinkedIn</a>
-</ol>
diff --git a/src/blog.njk b/src/blog.njk
deleted file mode 100644
index 9998e70..0000000
--- a/src/blog.njk
+++ /dev/null
@@ -1,17 +0,0 @@
----
-layout: base
-title: Blog
----
-<div role="feed" aria-busy="false">
-{% for post in collections.posts %}
- <article aria-posinset="{{loop.index}}" aria-setsize="{{loop.len}}">
- <h1>
- <a href="{{post.url}}">{{post.data.title}}</a>
- </h1>
- <small>
- <time datetime="{{page.date.toISOString()}}">{{page.date.toDateString()}}</time> - <a rel=author href="{{metadata.author.url}}">{{ metadata.author.name }}</a>
- </small>
- <p>{{post.data.description}}</p>
- </article>
-{% endfor %}
-</div>
diff --git a/src/client/index.ts b/src/client/index.ts
new file mode 100644
index 0000000..cee201b
--- /dev/null
+++ b/src/client/index.ts
@@ -0,0 +1,20 @@
+window.addEventListener("load", async function () {
+ try {
+ const navlinks : HTMLAnchorElement[] = Array.from(document.getElementsByClassName("navlinks")) as HTMLAnchorElement[]
+
+ document.getElementById("nav-toogle")?.addEventListener("click", async () => {
+ navlinks.forEach(navlink => { navlink.style.display === "hidden" ? "block" : "hidden" })
+ return false
+ })
+
+ Array.from(document.getElementsByTagName("time")).forEach(time => {
+ time.textContent = new Date(time.dateTime).toLocaleDateString(undefined,{dateStyle:"full"})
+ })
+
+ if ("serviceWorker" in navigator) {
+ await navigator.serviceWorker.register("/sw.js", { type: "module" })
+ }
+ } catch (error) {
+ console.error(error)
+ }
+})
diff --git a/src/gen/atom.njk b/src/gen/atom.njk
deleted file mode 100644
index a603195..0000000
--- a/src/gen/atom.njk
+++ /dev/null
@@ -1,27 +0,0 @@
----
-permalink: "/feed.xml"
----
-<?xml version="1.0" encoding="utf-8"?>
-<feed xmlns="http://www.w3.org/2005/Atom">
- <title>{{ metadata.title }}</title>
- <subtitle>${data.metadata.description}</subtitle>
- {% set absoluteUrl %}{{ metadata.feed.path | url | absoluteUrl(metadata.url) }}{% endset %}
- <link href="{{ metadata.feed.path | absoluteUrl(metadata.url) }}" rel="self"/>
- <link href="{{ metadata.url }}"/>
- <updated>{{ collections.posts | getNewestCollectionItemDate | dateToRfc3339 }}</updated>
- <id>{{ metadata.feed.id }}</id>
- <author>
- <name>{{ metadata.author.name }}</name>
- <email>{{ metadata.author.email }}</email>
- </author>
- {% for post in collections.posts | reverse %}
- {% set absolutePostUrl %}{{ post.url | absoluteUrl(metadata.url) }}{% endset %}
- <entry>
- <title>{{ post.data.title }}</title>
- <link href="{{ absolutePostUrl }}"/>
- <updated>{{ post.date | dateToRfc3339 }}</updated>
- <id>{{ absolutePostUrl }}</id>
- <content type="html">{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }}</content>
- </entry>
- {% endfor %}
-</feed>
diff --git a/src/gen/error.njk b/src/gen/error.njk
deleted file mode 100644
index dc5599c..0000000
--- a/src/gen/error.njk
+++ /dev/null
@@ -1,13 +0,0 @@
----
-layout: base
-pagination:
- data: err
- size: 1
-err:
- offline: The Page is offline
- 404: Not Found
-permalink: "/{{pagination.items}}.html"
----
-
-<h1 style="color: rgb(139 0 0)">ERROR: {{pagination.items}}</h1>
-<p>{{err[pagination.items]}}</p>
diff --git a/src/gen/feedjson.11ty.js b/src/gen/feedjson.11ty.js
deleted file mode 100644
index 7ddbf51..0000000
--- a/src/gen/feedjson.11ty.js
+++ /dev/null
@@ -1,35 +0,0 @@
-module.exports = class {
- data() {
- return {
- permalink: "/feed.json"
- };
- }
-
- async render(data) {
- const out = {
- version: "https://jsonfeed.org/version/1.1",
- title: data.metadata.title,
- language: data.metadata.language,
- home_page_url: data.metadata.url,
- feed_url: data.page.url,
- description: data.metadata.description,
- author: {
- name: data.metadata.author.name,
- url: data.metadata.author.url
- },
- items: (data.collections.posts || []).map(
- async function (e) {
- const absolutePostUrl = this.absoluteUrl(this.url(e.url), data.metadata.url)
- return {
- id: absolutePostUrl,
- url: absolutePostUrl,
- title: e.data.title,
- date_published: this.dateToRfc3339(e.date),
- content_html: htmlToAbsoluteUrls(e.templateContent, absolutePostUrl),
- }
- }
- )
- }
- return JSON.stringify(out)
- }
-}
diff --git a/src/gen/gen.11tydata.js b/src/gen/gen.11tydata.js
deleted file mode 100644
index 5694649..0000000
--- a/src/gen/gen.11tydata.js
+++ /dev/null
@@ -1,9 +0,0 @@
-const path = require("path");
-
-module.exports = {
- eleventyExcludeFromCollections: true,
- permalink: false,
- eleventyComputed: {
- //permalink: data => data.permalink || `/${path.relative("/gen", data.page.filePathStem)}.${data.page.outputFileExtension}`
- }
-}
diff --git a/src/gen/manifest.11ty.js b/src/gen/manifest.11ty.js
deleted file mode 100644
index e41df83..0000000
--- a/src/gen/manifest.11ty.js
+++ /dev/null
@@ -1,28 +0,0 @@
-module.exports = class {
- data() {
- return {
- permalink: "/app.webmanifest"
- };
- }
-
- render(data) {
- return JSON.stringify({
- $schema: "https://json.schemastore.org/web-manifest-combined.json",
- name: data.metadata.title,
- lang: data.metadata.language,
- start_url: "/",
- id: "/",
- scope: "/",
- display: "minimal-ui",
- background_color: data.metadata.theme,
- theme_color: data.metadata.theme,
- description: data.metadata.description,
- icons: [192, 512, 1024].map(size => ({
- src: `/favicon/${size}.png`,
- type: "image/png",
- sizes: `${size}x${size}`,
- purpose: "maskable"
- }))
- });
- }
-}; \ No newline at end of file
diff --git a/src/gen/metadata.11ty.js b/src/gen/metadata.11ty.js
deleted file mode 100644
index 56bf6af..0000000
--- a/src/gen/metadata.11ty.js
+++ /dev/null
@@ -1,13 +0,0 @@
-module.exports = class {
- data() {
- return {
- //permalink: "/assets/metadata.js"
- permalink: false
- };
- }
-
- render() {
- const date = new Date()
- return `export default = ${date.toISOString()}`
- }
-};
diff --git a/src/gen/robot.njk b/src/gen/robot.njk
deleted file mode 100644
index faf9e2f..0000000
--- a/src/gen/robot.njk
+++ /dev/null
@@ -1,6 +0,0 @@
----
-permalink: /robot.txt
----
-User-agent: *
-Disallow:
-Sitemap: {{ "/sitemap.xml" | absoluteUrl(data.metadata.url)}}
diff --git a/src/gen/sitemap.njk b/src/gen/sitemap.njk
deleted file mode 100644
index c2030de..0000000
--- a/src/gen/sitemap.njk
+++ /dev/null
@@ -1,12 +0,0 @@
----
-permalink: "/sitemap.xml"
----
-<?xml version="1.0" encoding="utf-8"?>
-<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
-{% for page in collections.all %}
- <url>
- <loc>{{ page.url | absoluteUrl(metadata.url) }}</loc>
- <lastmod>{{ page.date | htmlDateString }}</lastmod>
- </url>
-{% endfor %}
-</urlset>
diff --git a/src/index.njk b/src/index.njk
deleted file mode 100644
index a56bc07..0000000
--- a/src/index.njk
+++ /dev/null
@@ -1,6 +0,0 @@
----
-layout: page
-title: Welcome
----
-
-<p> Hello
diff --git a/src/post/post.11tydata.js b/src/post/post.11tydata.js
deleted file mode 100644
index 6ae489a..0000000
--- a/src/post/post.11tydata.js
+++ /dev/null
@@ -1,8 +0,0 @@
-const slugify = require("@sindresorhus/slugify");
-
-module.exports = () => ({
- layout: "post",
- eleventyComputed: {
- permalink: data => `/posts/${slugify(data.title)}/`
- }
-});
diff --git a/src/server/app.ts b/src/server/app.ts
new file mode 100644
index 0000000..40183e9
--- /dev/null
+++ b/src/server/app.ts
@@ -0,0 +1,31 @@
+import express from "express"
+import relDir from "./utils/relDir.js"
+import { errHandler, notFound, offline } from "./errHanadler.js"
+
+const dir = relDir(import.meta.url)
+
+await import("./build.js")
+export const app = express()
+
+app.set("views", false)
+app.set("etag", "strong")
+app.set("x-powered-by", false)
+app.set("trust proxy", true)
+
+app.use((await import("morgan")).default(":remote-addr :method :url :http-version :status :response-time ms"))
+app.use((await import("./router.js")).default)
+app.use("/offline", offline)
+app.use((await import("./img.js")).default(dir("../../assets")))
+app.use(express.static(dir("../../assets"), { index: false }))
+app.use(express.static(dir("../client"), { index: false }))
+app.use(express.static(dir("../worker"), { index: false }))
+app.use((await import("./img.js")).default(dir("/static")))
+app.use(express.static(dir("/static"), { index: false }))
+app.get("/favicon.ico", (_req, res) => {
+ res.status(204).send()
+})
+
+app.use(notFound)
+app.use(errHandler)
+
+export default app
diff --git a/src/server/build.ts b/src/server/build.ts
new file mode 100644
index 0000000..d6ed1ab
--- /dev/null
+++ b/src/server/build.ts
@@ -0,0 +1,31 @@
+import { posts } from "./template/Post.js"
+import { pages } from "./template/Base.js"
+
+const postMod = [
+]
+
+const pageMod = [
+ ...postMod,
+ "./content/about.js",
+ "./content/index.js"
+]
+
+const routes = [
+ ...pageMod,
+ "./content/webmanifest.js",
+ "./content/robots.js",
+ "./content/blog.js",
+ "./content/sitemap.js",
+ "./content/feed.js",
+]
+
+function datecmp(a: { date_mod?: Date| undefined }, b: { date_mod?: Date | undefined }): number {
+ return (a.date_mod ?? new Date()).valueOf() - (b.date_mod ?? new Date()).valueOf()
+}
+
+await Promise.all(postMod.map(mod => import(mod)))
+posts.sort(datecmp).reverse()
+await Promise.all(pageMod.map(mod => import(mod)))
+pages.sort(datecmp).reverse()
+await Promise.all(routes.map(mod => import(mod)))
+
diff --git a/src/server/content/about.ts b/src/server/content/about.ts
new file mode 100644
index 0000000..4a988e3
--- /dev/null
+++ b/src/server/content/about.ts
@@ -0,0 +1,32 @@
+import Page from "../template/Page.js"
+import metadata from "../metadata.js"
+import { c } from "../template/vdom.js"
+import h from "../template/header.js"
+import schema from "../utils/schema.js"
+import { img, picture, source, div, p, span, ul, li, a } from "../template/html.js"
+
+Page({
+ title: "About Me",
+ url: "/about",
+ content() {
+ return c(div, { itemscope: true, itemtype: schema("Person") },
+ c(h, { level: 2, itemprop: "name" }, metadata.author.name),
+ c(picture, { class: "side" },
+ c(source, { srcset: [128, 256, 512].map(width => `${metadata.author.image}?format=png&width=${width} ${width}w`).join(", ") }),
+ c(img, { itemprop: "image", alt: "A Photo of me", src: metadata.author.image })
+ ),
+ c(p, { itemprop: "description" }, "I am an analytical and passionate third year ",
+ c(span, { itemprop: "affiliation", itemtype: schema("CollegeOrUniversity"), itemscope: true }, c(span, { itemprop: "name" }, "CHRIST University")),
+ " ", c(span, {}, "student"), ` pursuing B. Tech in Computer Engineering in Bengaluru. I am eager to further my knowledge, develop my skills ",
+ "and gain experience to convert my interest in computers into a fulfilling career.`),
+ c(p, {}, "Contact Details"),
+ c(ul, {},
+ c(li, {}, c(a, { href: `mailto:${metadata.author.email}`, itemprop: "email" }, "Email")),
+ c(li, {}, c(a, { href: "https://github.com/marcthe12", itemprop: "sameas" }, "Github")),
+ c(li, {}, c(a, { href: "https://www.linkedin.com/in/marc-pervaz-boocha-200706236/", itemprop: "sameas" }, "LinkedIn")),
+ ),
+ )
+ }
+}).setupRoute()
+
+
diff --git a/src/server/content/blog.ts b/src/server/content/blog.ts
new file mode 100644
index 0000000..373b1ca
--- /dev/null
+++ b/src/server/content/blog.ts
@@ -0,0 +1,35 @@
+import { posts } from "../template/Post.js"
+import Base from "../template/Base.js"
+import { c } from "../template/vdom.js"
+import h from "../template/header.js"
+import relUrl from "../utils/relUrl.js"
+import schema from "../utils/schema.js"
+import metadata from "../metadata.js"
+import { div, article, a, small, time, span, p } from "../template/html.js"
+
+Base({
+ title: "Blog Posts",
+ url: "/blog",
+ content() {
+ return c(div, { itemscope: true, itemtype: schema("Blog"), role: "feed", "aria-busy": "false" },
+ ...posts.map((post, index, { length }) => {
+ const { date_mod, date_pub, title, description, url } = post
+ return c(article, { itemprop: "blogPost", itemscope: true, itemtype: schema("BlogPosting"), "aria-posinset": index, "aria-setsize": length },
+ c(h, { level: 1, itemprop: "headline" }, c(a, { href: relUrl(url), itemprop: "sameAs" }, title)),
+ c(small, {},
+ date_pub != date_mod ? [
+ c(time, { datetime: date_mod.toISOString(), itemprop: "dateModified" }, date_mod.toDateString()),
+ "- Modified "
+ ] : [],
+ c(time, { datetime: date_pub.toISOString(), itemprop: "datePublished" }, date_pub.toDateString())
+ , " - ",
+ c(span, { itemprop: "author", itemscope: true, itemtype: schema("Person") },
+ c(a, { href: metadata.author.url, rel: "author", itemprop: "url" },
+ c(span, { itemprop: "name" }, metadata.author.name))
+ ),
+ ),
+ c(p, { itemprop: "abstract" }, description)
+ )
+ }))
+ }
+}).setupRoute()
diff --git a/src/server/content/feed.ts b/src/server/content/feed.ts
new file mode 100644
index 0000000..b0d6c0f
--- /dev/null
+++ b/src/server/content/feed.ts
@@ -0,0 +1,32 @@
+import curl from "../utils/curl.js"
+import setStingRoute from "../utils/setStingRoute.js"
+import { posts } from "../template/Post.js"
+import metadata from "../metadata.js"
+import { doctype } from "../template/xml.js"
+import { c } from "../template/vdom.js"
+import { feed, title, link, summary, id, author, subtitle, updated, name, email, entry } from "../template/atom.js"
+
+setStingRoute("/feed", ["application/atom+xml", "xml"], async () => doctype({},
+ c(feed, { xmlns: new URL("http://www.w3.org/2005/Atom"), "xml:lang": metadata.language },
+ c(title, {}, metadata.title),
+ c(subtitle, {}, metadata.description),
+ c(link, { href: curl(metadata.feed.atom), rel: "self" }),
+ c(link, { href: metadata.url }),
+ ...(posts[0] ? [c(updated, { date: posts[0].date_mod })] : []),
+ c(id, { id: metadata.url }),
+ c(author, {},
+ c(name, {}, metadata.author.name),
+ c(email, {}, metadata.author.email),
+ ),
+ ...posts.map(({ url, date_mod, title: Title, description }) =>
+ c(entry, {},
+ c(title, {}, Title),
+ c(link, { href: url }),
+ c(updated, { date: date_mod }),
+ c(id, { id: url }),
+ c(summary, {}, description)
+ )
+ )
+ )
+))
+
diff --git a/src/server/content/index.ts b/src/server/content/index.ts
new file mode 100644
index 0000000..6eaa3de
--- /dev/null
+++ b/src/server/content/index.ts
@@ -0,0 +1,20 @@
+import { c } from "../template/vdom.js"
+import code from "../template/syntax.js"
+import Page from "../template/Page.js"
+import { pre } from "../template/html.js"
+
+Page({
+ title: "Welcome",
+ url: "/",
+ isHighlight: true,
+ content() {
+ return c(pre, { class: "pad", },
+ "$ ", c(code, { lang: "bash" }, "sudo cat ~root/msg"),
+ `
+Hello World!
+Welcome to my blog.
+I occassion leave my writings here. Hope you will enjoy them :).
+$`
+ )
+ }
+}).setupRoute()
diff --git a/src/server/content/robots.ts b/src/server/content/robots.ts
new file mode 100644
index 0000000..ad8e7ab
--- /dev/null
+++ b/src/server/content/robots.ts
@@ -0,0 +1,9 @@
+import curl from "../utils/curl.js"
+import setStingRoute from "../utils/setStingRoute.js"
+
+setStingRoute("/robots.txt", "robots.txt", async () => Object.entries({
+ "User-agent": "*",
+ Disallow: "",
+ Sitemap: curl("sitemap.xml")
+}).map(([key, val]) => `${key}: ${val}`).join("\n")
+)
diff --git a/src/server/content/sitemap.ts b/src/server/content/sitemap.ts
new file mode 100644
index 0000000..c9e3ef5
--- /dev/null
+++ b/src/server/content/sitemap.ts
@@ -0,0 +1,16 @@
+import { pages } from "../template/Base.js"
+import setStingRoute from "../utils/setStingRoute.js"
+import { doctype } from "../template/xml.js"
+import { c } from "../template/vdom.js"
+import { lastmod, loc, url, urlset } from "../template/sitemap.js"
+
+setStingRoute("/sitemap.xml", "sitemap.xml", async () => doctype({},
+ c(urlset, { xmlns: new URL("http://www.sitemaps.org/schemas/sitemap/0.9") },
+ ...pages.map(page =>
+ c(url, {},
+ c(loc, {}, (page.url?.href) ?? ""),
+ ...(page.date_mod ? [c(lastmod, {}, page.date_mod.toISOString())] : [])
+ )
+ )
+ )
+))
diff --git a/src/server/content/webmanifest.ts b/src/server/content/webmanifest.ts
new file mode 100644
index 0000000..6e4031e
--- /dev/null
+++ b/src/server/content/webmanifest.ts
@@ -0,0 +1,26 @@
+import metadata from "../metadata.js"
+import { contentType } from "mime-types"
+import setStingRoute from "../utils/setStingRoute.js"
+
+setStingRoute("/app.webmanifest", "app.webmanifest", async () => ({
+ $schema: "https://json.schemastore.org/web-manifest-combined.json",
+ name: metadata.title,
+ lang: metadata.language,
+ start_url: "/",
+ id: "/", scope: "/",
+ display: "minimal-ui",
+ background_color: metadata.theme,
+ theme_color: metadata.theme,
+ description: metadata.description,
+ icons: ["png", "svg"].flatMap(format => format == "svg" ? {
+ src: "/favicon.svg",
+ type: contentType(format),
+ sizes: "any",
+ purpose: "any maskable"
+ } : [192, 512, 1024].map(size => ({
+ src: `/favicon.svg?format=${format}&width=${size}`,
+ type: contentType(format),
+ sizes: `${size}x${size}`,
+ purpose: "maskable"
+ })))
+}))
diff --git a/src/server/errHanadler.ts b/src/server/errHanadler.ts
new file mode 100644
index 0000000..84f9b26
--- /dev/null
+++ b/src/server/errHanadler.ts
@@ -0,0 +1,85 @@
+import Base from "./template/Base.js"
+import createError from "http-errors"
+import t, { c } from "./template/vdom.js"
+import h from "./template/header.js"
+import { STATUS_CODES } from "node:http"
+import app from "./app.js"
+import isDevel from "./utils/isDevel.js"
+import isDefined from "./utils/isDefined.js"
+import type express from "express"
+import type { HttpError } from "http-errors"
+
+interface ErrObj {
+ code: number | string,
+ msg: string,
+ debug?: string | undefined
+}
+
+async function errorTemplate(data: ErrObj): Promise<{ html: string; text: string; obj: ErrObj }> {
+ const { code, msg, debug } = data
+ const arg = {
+ ...data,
+ title: msg,
+ content() {
+ return [
+ c(h, { level: 1, style: "color: darkred" }, `ERROR: ${code}`),
+ t("p", {}, msg),
+ isDefined(debug) && isDevel(app) ? t("p", {}, t("pre", {}, debug)) : ""
+ ]
+ }
+ }
+ return {
+ html: await Base(arg).render(),
+ text: `${code} - ${msg}
+${debug || ""}`,
+ obj: data
+ }
+
+}
+
+async function errRender(res: express.Response, opt: ErrObj) {
+ const msg = await errorTemplate(opt)
+ res.format({
+ html: () => {
+ res.send(msg.html)
+ },
+ json: () => {
+ res.send(msg.obj)
+ },
+ default: () => {
+ res.type("txt").send(msg.text)
+ }
+ })
+}
+
+export const errHandler = async function (
+ err: HttpError,
+ _req: express.Request,
+ res: express.Response,
+ _next: express.NextFunction
+) {
+ res.status(err.status || 500)
+
+ await errRender(res, {
+ code: res.statusCode,
+ msg: res.statusMessage || STATUS_CODES[res.statusCode] || "Unknown",
+ debug: err.stack
+ })
+
+ console.error(err + (err.stack ?? ""))
+}
+
+export function notFound(_req: express.Request,
+ _res: express.Response,
+ next: express.NextFunction
+) {
+ next(createError(404))
+}
+
+export async function offline(_req: express.Request,
+ res: express.Response) {
+ await errRender(res, {
+ code: "Offline",
+ msg: "Can not reach the website. Check your Network",
+ })
+} \ No newline at end of file
diff --git a/src/server/img.ts b/src/server/img.ts
new file mode 100644
index 0000000..7006c3d
--- /dev/null
+++ b/src/server/img.ts
@@ -0,0 +1,83 @@
+import sharp from "sharp"
+import { createReadStream } from "node:fs"
+import { extname, join } from "node:path"
+import createError from "http-errors"
+import type express from "express"
+
+function transform(format: keyof sharp.FormatEnum, width?: number, height?: number): sharp.Sharp {
+ var transform = sharp().withMetadata()
+
+ if (format) {
+ transform = transform.toFormat(format)
+ }
+
+ if (width || height) {
+ transform = transform.resize({ width, height })
+ }
+
+ return transform
+}
+
+function errorHand(next: express.NextFunction) {
+ return function (err: NodeJS.ErrnoException) {
+ switch (err.code) {
+ case "ENOENT":
+ next()
+ break
+ case "EISDIR":
+ case "EPERM":
+ next(createError(401))
+ break
+ default:
+ next(err)
+ }
+ }
+}
+
+export default function (path: string) {
+ return async function (req: express.Request, res: express.Response, next: express.NextFunction) {
+ const err_handler = errorHand(next)
+
+ const filepath = join(path, req.path)
+ const ext = extname(filepath).substring(1)
+ if (![
+ "svg", "heic", "heif", "avif", "jpeg", "jpg", "jpe", "tile", "dz",
+ "png", "raw", "tiff", "tif", "webp", "gif", "jp2", "jpx", "j2k",
+ "j2c", "jxl"
+ ].includes(ext)) {
+ return next()
+ }
+ const width = parseInt(String(req.query['width']))
+ const height = parseInt(String(req.query['height']))
+ var { format = ext } = req.query as { format?: string }
+ if (format == "svg") {
+ if (ext == "svg") {
+ const stream = createReadStream(filepath)
+ stream.on("error", err_handler)
+ res.format({
+ [format]: function () {
+ return stream.pipe(res)
+ }
+ })
+ } else {
+ return next(createError(400))
+ }
+ return
+ }
+
+ const stream = createReadStream(filepath)
+ stream.on("error", err_handler)
+ res.format({
+ [format]: function () {
+ return stream.pipe(
+ transform(
+ format as keyof sharp.FormatEnum,
+ !isNaN(width) ? width : undefined,
+ !isNaN(height) ? height : undefined
+ )
+ ).pipe(res)
+ }
+ })
+ }
+}
+
diff --git a/src/server/metadata.ts b/src/server/metadata.ts
new file mode 100644
index 0000000..607c7eb
--- /dev/null
+++ b/src/server/metadata.ts
@@ -0,0 +1,18 @@
+import { URL } from "node:url"
+
+export default {
+ title: "Sudomsg",
+ url: new URL("https://sudomsg.xyz/"),
+ language: "en-GB",
+ theme: "#8b0000",
+ description: "Messages from root",
+ feed: {
+ atom: "/feed",
+ },
+ author: {
+ name: "Marc Pervaz Boocha",
+ email: "mboocha@sudomsg.xyz",
+ image: "/favicon.svg",
+ url: "/about/#marc-pervaz-boocha"
+ }
+}
diff --git a/src/server/router.ts b/src/server/router.ts
new file mode 100644
index 0000000..8d2626c
--- /dev/null
+++ b/src/server/router.ts
@@ -0,0 +1,5 @@
+import { Router } from "express"
+
+export default Router()
+
+
diff --git a/src/server/server.ts b/src/server/server.ts
new file mode 100644
index 0000000..add22e2
--- /dev/null
+++ b/src/server/server.ts
@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+import { createServer } from "node:http"
+
+const server = createServer()
+server.on("request", (await import("./app.js")).default)
+server.on("error", console.error)
+server.on("listening", function (this: typeof server) {
+ const addr = this.address()
+ console.log(`Listening on ${addr ? typeof addr === "string" ? addr : `${addr.port} on ${addr.address}` : "Unknown Socket"}`)
+})
+server.on("close", function () {
+ console.log("HTTP server closed")
+})
+
+server.listen({
+ host: "::",
+ port: 8080
+})
+
+process.on("SIGTERM", function () {
+ console.warn("SIGTERM signal received: closing HTTP server")
+ server.close()
+})
diff --git a/src/server/template/Base.ts b/src/server/template/Base.ts
new file mode 100644
index 0000000..e5128ee
--- /dev/null
+++ b/src/server/template/Base.ts
@@ -0,0 +1,117 @@
+import metadata from "../metadata.js"
+import curl from "../utils/curl.js"
+import setStingRoute from "../utils/setStingRoute.js"
+import isDefined from "../utils/isDefined.js"
+import { Attribute, doctype, html as html_1, head, meta, title as title_1, link, script, body, header, nav, a, main, footer } from "./html.js"
+import { c, node } from "./vdom.js"
+import type { URL } from "url"
+
+export var pages: (ReturnType<typeof Base>)[] = []
+
+type content<T extends node = node> = T | (() => T)
+
+interface BasePageI<T extends node = node> {
+ url?: string | URL | undefined,
+ content: content<T>,
+ date_mod?: Date | undefined,
+ date_pub?: Date
+}
+
+export interface BaseI<T extends node = node> extends BasePageI<T> {
+ title?: string,
+ description?: string,
+ keywords?: string[],
+ isHighlight?: boolean
+}
+
+export function content<Type extends BasePageI>({ data }: Attribute & { data: Type }): node {
+ return data.content instanceof Function ? data.content() : data.content
+}
+
+function BasePage<Type extends BasePageI>(data: Type): Type & {
+ setupRoute(url?: URL): void;
+ render(): Promise<string>;
+ date_mod: Type["date_pub"];
+ url: URL | undefined
+} {
+ return {
+ ...data,
+ setupRoute(url?: URL): void {
+ url ??= this.url
+ if (isDefined(url)) {
+ setStingRoute(url.pathname, "html", this.render.bind(this))
+ } else {
+ throw new Error();
+
+ }
+ },
+ async render(): Promise<string> {
+ console.profile()
+ const a = doctype(c(content, { data: this }))
+ console.profileEnd()
+ return a
+ },
+ date_mod: data.date_mod ?? data.date_pub,
+ url: isDefined(data.url) ? curl(data.url) : undefined,
+ }
+}
+
+export default function Base<Type extends BaseI>(data: Type): ReturnType<typeof BasePage<Type>> {
+ const arg = {
+ ...data,
+ content() {
+ var { title, description, keywords, url, isHighlight = true } = this
+ if (!url) {
+ isHighlight = false
+ }
+ return c(html_1, { lang: metadata.language },
+ c(head, {},
+ ...(url ? [c(meta, { property: "og:locale", content: metadata.language })] : []),
+ c(title_1, { property: "og:title" }, title || metadata.title),
+ c(meta, { name: "theme-color", content: metadata.theme }),
+ ...(url ? [
+ c(meta, { name: "description", property: "og:description", content: description || metadata.description }),
+ ...(Array.isArray(keywords) ? [c(meta, { name: "keywords", contents: keywords })] : []),
+ c(meta, { property: "og:url", content: url }),
+ c(link, { rel: "canonical", href: url }),
+ c(meta, { property: "og:site_name", content: metadata.title }),
+ c(meta, { name: "author", content: metadata.author.name }),
+ c(meta, { property: "og:type", content: "website" }),
+ c(meta, { name: "viewport", content: "width=device-width, initial-scale=1" }),
+ c(meta, { name: "twitter:card", content: "summary" }),
+ c(link, { rel: "alternate", href: metadata.feed.atom, type: "application/atom+xml", title: metadata.title }),
+ c(meta, { property: "og:image", content: "/favicon.svg?format=png&width=1024" })
+ ] : []),
+ c(link, { rel: "icon", href: "/favicon.ico", sizes: "any" }),
+ c(link, { rel: "icon", href: "/favicon.svg", type: "image/svg+xml" }),
+ c(link, { rel: "apple-touch-icon", href: "/favicon.svg?format:png&width=192" }),
+ c(link, { rel: "manifest", href: "/app.webmanifest" }),
+ c(link, { rel: "stylesheet", href: "/index.css" }),
+ c(script, { type: "module", src: "/index.js" }),
+ ...(isHighlight ? [c(link, { rel: "stylesheet", href: "/syntax.css" })] : [])
+ ),
+ c(body, {},
+ c(header, {}, c(nav, {},
+ c(a, { href: "#", id: "nav-toogle" }, "Sudomsg"),
+ ...Object.entries({
+ Home: "/",
+ Blog: "/blog",
+ About: "/about",
+ Git: "/cgit",
+ }).map(([name, href]) => c(a, { href, class: "navlinks" }, name))
+ )),
+ c(main, {}, c(content, { data })),
+ c(footer, {},
+ "Subscribe: ",
+ c(a, { href: metadata.feed.atom, type: "application/atom+xml" }, "Atom")
+ )
+ )
+ )
+ }
+ }
+ const base = BasePage(arg)
+ if (isDefined(base.url)) {
+ pages.push(base)
+ }
+ return base
+}
diff --git a/src/server/template/Page.ts b/src/server/template/Page.ts
new file mode 100644
index 0000000..ec76e97
--- /dev/null
+++ b/src/server/template/Page.ts
@@ -0,0 +1,23 @@
+import Base, { BaseI, content } from "./Base.js"
+import h from "./header.js"
+import { article } from "./html.js"
+import { c, node } from "./vdom.js"
+
+export interface PageI<T extends node = node> extends BaseI<T> {
+ title: string
+}
+
+export default function Page<Type extends PageI>(data: Type): ReturnType<typeof Base<Type>> {
+ const arg = {
+ ...data,
+ content() {
+ var { title } = this
+ return c(article, {},
+ c(h, { level: 1 }, title),
+ c(content, { data })
+ )
+ }
+
+ }
+ return Base(arg)
+} \ No newline at end of file
diff --git a/src/server/template/Post.ts b/src/server/template/Post.ts
new file mode 100644
index 0000000..c348f8b
--- /dev/null
+++ b/src/server/template/Post.ts
@@ -0,0 +1,48 @@
+import schema from "../utils/schema.js"
+import metadata from "../metadata.js"
+import Base, { content } from "./Base.js"
+import h from "./header.js"
+import slugify from "@sindresorhus/slugify"
+import { c } from "./vdom.js"
+import type { PageI } from "./Page.js"
+import { article, small, time, span, a, p, div } from "./html.js"
+
+export var posts: ReturnType<typeof Post>[] = []
+
+interface PostI extends Omit<PageI, "url"> {
+ title: string,
+ description: string,
+ date_pub: Date,
+ date_mod?: Date
+}
+
+export default function Post<Type extends PostI>(data: Type): ReturnType<typeof Base<Type & {url: string;}>> {
+ var args = {
+ ...data,
+ content() {
+ var { date_mod, title, date_pub, description } = this
+ return c(article, { itemscope: true, itemtype: schema("BlogPosting") },
+ c(h, { level: 1, itemprop: "headline" }, title),
+ c(small, {},
+ ...(date_mod ? [
+ c(time, { datetime: date_mod.toISOString(), itemprop: "dateModified" }, date_mod.toDateString()),
+ "- Modified "
+ ] : []),
+ c(time, { datetime: date_pub.toISOString(), itemprop: "datePublished" }, date_pub.toDateString())
+ , " - ",
+ c(span, { itemprop: "author", itemscope: true, itemtype: schema("Person") },
+ c(a, { href: metadata.author.url, rel: "author", itemprop: "url" },
+ c(span, { itemprop: "name" }, metadata.author.name))
+ ),
+ c(p, { itemprop: "abstract" }, description)
+ ),
+ c(div, { itemprop: "articleBody" }, c(content, { data }))
+ )
+ },
+ url: `/posts/${slugify(data.title)}`
+ }
+ const post = Base(args)
+ posts.push(post)
+ return post
+}
+
diff --git a/src/server/template/atom.ts b/src/server/template/atom.ts
new file mode 100644
index 0000000..82c9fe4
--- /dev/null
+++ b/src/server/template/atom.ts
@@ -0,0 +1,114 @@
+import type { element, node } from "./vdom.js";
+import t, { Base, Lang, Attribute as Attr } from "./xml.js";
+
+export interface Attribute extends Attr, Base, Lang { }
+
+export function feed(attr: Attribute,
+ ...content: element<"id" | "title" | "updated" | "author" |
+ "link" | "author" | "category" | "contributor" | "generator"
+ | "icon" | "logo" | "rights" | "subtitle" | "entry">[]
+) {
+ return t("feed", attr, ...content);
+}
+
+export function entry(
+ attr: Attribute,
+ ...content: element<
+ "id" | "title" | "updated" | "author" | "content" | "link" |
+ "summary" | "category" | "contributor" | "rights" | "published" | "source">[]
+) {
+ return t("entry", attr, ...content);
+}
+
+export function author(attr: Attribute, ...content: [element<"name" | "email">, element<"name" | "email">]) {
+ return t("author", attr, ...content);
+}
+
+export function contributor(attr: Attribute, ...content: [element<"name" | "email">, element<"name" | "email">]) {
+ return t("contributor", attr, ...content);
+}
+
+export function id({ id, ...prop }: Attribute & { id: URL }) {
+ return t("id", prop, id.href);
+}
+
+export function updated({ date, ...prop }: Attribute & { date: Date }) {
+ return t("updated", prop, date.toISOString());
+}
+
+export function update({ date, ...prop }: Attribute & { date: Date } ) {
+ return t("update", prop, date.toISOString());
+}
+
+export function icon(attr: Attribute, ...content: string[]) {
+ return t("icon", attr, ...content);
+}
+
+export function logo(attr: Attribute, ...content: string[]) {
+ return t("logo", attr, ...content);
+}
+
+export function name(attr: Attribute, content: string) {
+ return t("name", attr, content);
+}
+
+export function email(attr: Attribute, content: string) {
+ return t("email", attr, content);
+}
+
+interface TextAttribute extends Attribute {
+ type?: string;
+}
+
+export function title(attr: TextAttribute, ...content: node[]) {
+ return t("title", attr, ...content);
+}
+
+export function subtitle(attr: TextAttribute, ...content: string[]) {
+ return t("subtitle", attr, ...content);
+}
+
+export function summary(attr: TextAttribute, ...content: string[]) {
+ return t("summary", attr, ...content);
+}
+
+export function rights(attr: TextAttribute, ...content: string[]) {
+ return t("rights", attr, ...content);
+}
+
+export function content(attr: TextAttribute, ...child: node[]) {
+ return t("content", attr, ...child);
+}
+
+
+interface generatorAttribute extends Attribute {
+ uri?: string;
+ version?: string
+}
+
+export function generator(attr: generatorAttribute, ...content: string[]) {
+ return t("generator", attr, ...content);
+}
+
+interface linkAttribute extends Attribute {
+ href: URL;
+ rel?: "alternate" | "enclosure" | "self" | "via";
+ type?: string;
+ hreflang?: string;
+ title?: string;
+ length?: number;
+}
+
+export function link(attr: linkAttribute) {
+ return t("link", { ...attr, href: attr.href.href, length: "" + (attr.length ?? "") });
+}
+
+interface catAttribute extends Attribute {
+ term: string;
+ scheme?: URL;
+ label?: string;
+}
+
+export function category(attr: catAttribute) {
+ return t("category", { ...attr, href: attr.scheme?.href });
+}
diff --git a/src/server/template/header.ts b/src/server/template/header.ts
new file mode 100644
index 0000000..04da87d
--- /dev/null
+++ b/src/server/template/header.ts
@@ -0,0 +1,10 @@
+import slugify from "@sindresorhus/slugify"
+import t, { Attribute, getText, node } from "./vdom.js"
+
+interface head extends Attribute {
+ level: 1 | 2 | 3 | 4 | 5 | 6
+}
+
+export default function ({ level, ...attr }: head, ...content: node[]) {
+ return t(`h${level}`, { id: slugify(getText(content)), ...attr }, ...content)
+}
diff --git a/src/server/template/html.ts b/src/server/template/html.ts
new file mode 100644
index 0000000..ff0a0cc
--- /dev/null
+++ b/src/server/template/html.ts
@@ -0,0 +1,602 @@
+import isDefined from "../utils/isDefined.js"
+import tag, { render as rend, prenderdata, node, Attribute as Attr, element } from "./vdom.js"
+
+export function doctype(...content: node[]) {
+ return `<!DOCTYPE html>${render(content).content}`
+}
+
+export const render = rend("html", function (node, content) {
+ const close = [
+ "img",
+ "input",
+ "br",
+ "hr",
+ "frame",
+ "area",
+ "base",
+ "basefont",
+ "col",
+ "isindex",
+ "link",
+ "meta",
+ "param",
+ "html",
+ "body",
+ "p",
+ "li",
+ "dt",
+ "dd",
+ "option",
+ "thead",
+ "th",
+ "tbody",
+ "tr",
+ "td",
+ "tfoot",
+ "colgroup",
+ ].includes(node.name.toLowerCase()) ? undefined : `</${node.name}>`
+ var list = Object.entries(node.attr).map(([prop, val]) => {
+ if (!isDefined(val)) {
+ return ""
+ }
+
+ if (typeof val == "boolean") {
+ return val ? prop : ""
+ }
+ var v: string = Array.isArray(val) ? val.join(" ") : "" + val
+
+ if (/[\s?"'<=>`]/.test(v)) {
+ v = `"${v}"`
+ }
+ return `${prop}=${v}`
+ }).join(" ")
+ if (list) {
+ list = ` ${list} `
+ }
+ const open = `<${node.name}${list}>`
+ return `${open}${content ?? ""}${close ?? ""}`
+})
+
+export const data = prenderdata("html")
+
+export interface Attribute extends Attr {
+ class?: string | string[],
+ id?: string,
+ accesskey?: string
+ dir?: "ltr" | "rtr" | "auto"
+ draggable?: "true" | "false" | "auto"
+ lang?: string
+ role?: string
+ is?: string
+ itemid?: URL
+ itemprop?: string
+ itemref?: string[]
+ itemtype?: URL
+ itemscope?: boolean
+ property?: string
+}
+
+export default function t<T extends string = string>(name: T, attr?: Attribute, ...content: node[]) {
+ return tag(name, {
+ ...attr,
+ itemtype: attr?.itemtype?.href,
+ itemid: attr?.itemtype?.href
+ }, ...content)
+}
+
+export function body(attr: Attribute, ...content: node[]) {
+ return t("body", attr, ...content);
+}
+
+export function head(attr: Attribute, ...content: element[]) {
+ return t("head", attr, ...content);
+}
+
+export function html(attr: Attribute, ...content: node[]) {
+ return t("html", attr, ...content);
+}
+
+export function a(attr: Attribute, ...content: node[]) {
+ return t("a", attr, ...content);
+}
+
+
+export function abbr(attr: Attribute, ...content: node[]) {
+ return t("abbr", attr, ...content);
+}
+
+
+export function address(attr: Attribute, ...content: node[]) {
+ return t("address", attr, ...content);
+}
+
+
+export function area(attr: Attribute) {
+ return t("area", attr);
+}
+
+
+export function article(attr: Attribute, ...content: node[]) {
+ return t("article", attr, ...content);
+}
+
+
+export function aside(attr: Attribute, ...content: node[]) {
+ return t("aside", attr, ...content);
+}
+
+
+export function audio(attr: Attribute, ...content: node[]) {
+ return t("audio", attr, ...content);
+}
+
+
+export function b(attr: Attribute, ...content: node[]) {
+ return t("b", attr, ...content);
+}
+
+
+export function base(attr: Attribute, ...content: node[]) {
+ return t("base", attr, ...content);
+}
+
+
+export function bdi(attr: Attribute, ...content: node[]) {
+ return t("bdi", attr, ...content);
+}
+
+
+export function bdo(attr: Attribute, ...content: node[]) {
+ return t("bdo", attr, ...content);
+}
+
+
+export function blockquote(attr: Attribute, ...content: node[]) {
+ return t("blockquote", attr, ...content);
+}
+
+export function br(attr: Attribute) {
+ return t("br", attr);
+}
+
+
+export function button(attr: Attribute, ...content: node[]) {
+ return t("button", attr, ...content);
+}
+
+
+export function canvas(attr: Attribute, ...content: node[]) {
+ return t("canvas", attr, ...content);
+}
+
+
+export function caption(attr: Attribute, ...content: node[]) {
+ return t("caption", attr, ...content);
+}
+
+
+export function cite(attr: Attribute, ...content: node[]) {
+ return t("cite", attr, ...content);
+}
+
+
+export function code(attr: Attribute, ...content: node[]) {
+ return t("code", attr, ...content);
+}
+
+
+export function col(attr: Attribute) {
+ return t("col", attr);
+}
+
+
+export function colgroup(attr: Attribute, ...content: node[]) {
+ return t("colgroup", attr, ...content);
+}
+
+
+export function hdata(attr: Attribute, ...content: node[]) {
+ return t("data", attr, ...content);
+}
+
+
+export function datalist(attr: Attribute, ...content: node[]) {
+ return t("datalist", attr, ...content);
+}
+
+
+export function dd(attr: Attribute, ...content: node[]) {
+ return t("dd", attr, ...content);
+}
+
+
+export function del(attr: Attribute, ...content: node[]) {
+ return t("del", attr, ...content);
+}
+
+
+export function details(attr: Attribute, ...content: node[]) {
+ return t("details", attr, ...content);
+}
+
+
+export function dfn(attr: Attribute, ...content: node[]) {
+ return t("dfn", attr, ...content);
+}
+
+
+export function dialog(attr: Attribute, ...content: node[]) {
+ return t("dialog", attr, ...content);
+}
+
+
+export function div(attr: Attribute, ...content: node[]) {
+ return t("div", attr, ...content);
+}
+
+
+export function dl(attr: Attribute, ...content: node[]) {
+ return t("dl", attr, ...content);
+}
+
+
+export function dt(attr: Attribute, ...content: node[]) {
+ return t("dt", attr, ...content);
+}
+
+
+export function em(attr: Attribute, ...content: node[]) {
+ return t("em", attr, ...content);
+}
+
+
+export function embed(attr: Attribute) {
+ return t("embed", attr);
+}
+
+
+export function fieldset(attr: Attribute, ...content: node[]) {
+ return t("fieldset", attr, ...content);
+}
+
+
+export function figcaption(attr: Attribute, ...content: node[]) {
+ return t("figcaption", attr, ...content);
+}
+
+
+export function figure(attr: Attribute, ...content: node[]) {
+ return t("figure", attr, ...content);
+}
+
+
+export function footer(attr: Attribute, ...content: node[]) {
+ return t("footer", attr, ...content);
+}
+
+
+export function form(attr: Attribute, ...content: node[]) {
+ return t("form", attr, ...content);
+}
+
+export function header(attr: Attribute, ...content: node[]) {
+ return t("header", attr, ...content);
+}
+
+
+export function hgroup(attr: Attribute, ...content: node[]) {
+ return t("hgroup", attr, ...content);
+}
+
+
+export function hr(attr: Attribute) {
+ return t("hr", attr);
+}
+
+export function i(attr: Attribute, ...content: node[]) {
+ return t("i", attr, ...content);
+}
+
+
+export function iframe(attr: Attribute, ...content: node[]) {
+ return t("iframe", attr, ...content);
+}
+
+
+export function img(attr: Attribute) {
+ return t("img", attr);
+}
+
+
+export function input(attr: Attribute) {
+ return t("input", attr);
+}
+
+
+export function ins(attr: Attribute, ...content: node[]) {
+ return t("ins", attr, ...content);
+}
+
+
+export function kbd(attr: Attribute, ...content: node[]) {
+ return t("kbd", attr, ...content);
+}
+
+
+export function label(attr: Attribute, ...content: node[]) {
+ return t("label", attr, ...content);
+}
+
+
+export function legend(attr: Attribute, ...content: node[]) {
+ return t("legend", attr, ...content);
+}
+
+
+export function li(attr: Attribute, ...content: node[]) {
+ return t("li", attr, ...content);
+}
+
+
+export function link(attr: Attribute) {
+ return t("link", attr);
+}
+
+
+export function main(attr: Attribute, ...content: node[]) {
+ return t("main", attr, ...content);
+}
+
+
+export function map(attr: Attribute, ...content: node[]) {
+ return t("map", attr, ...content);
+}
+
+
+export function mark(attr: Attribute, ...content: node[]) {
+ return t("mark", attr, ...content);
+}
+
+
+export function menu(attr: Attribute, ...content: node[]) {
+ return t("menu", attr, ...content);
+}
+
+
+export function meta(attr: Attribute) {
+ return t("meta", attr);
+}
+
+
+export function meter(attr: Attribute, ...content: node[]) {
+ return t("meter", attr, ...content);
+}
+
+
+export function nav(attr: Attribute, ...content: node[]) {
+ return t("nav", attr, ...content);
+}
+
+
+export function noscript(attr: Attribute, ...content: node[]) {
+ return t("noscript", attr, ...content);
+}
+
+
+export function object(attr: Attribute, ...content: node[]) {
+ return t("object", attr, ...content);
+}
+
+
+export function ol(attr: Attribute, ...content: node[]) {
+ return t("ol", attr, ...content);
+}
+
+
+export function optgroup(attr: Attribute, ...content: node[]) {
+ return t("optgroup", attr, ...content);
+}
+
+
+export function option(attr: Attribute, ...content: node[]) {
+ return t("option", attr, ...content);
+}
+
+
+export function output(attr: Attribute, ...content: node[]) {
+ return t("output", attr, ...content);
+}
+
+
+export function p(attr: Attribute, ...content: node[]) {
+ return t("p", attr, ...content);
+}
+
+
+export function picture(attr: Attribute, ...content: node[]) {
+ return t("picture", attr, ...content);
+}
+
+
+export function pre(attr: Attribute, ...content: node[]) {
+ return t("pre", attr, ...content);
+}
+
+
+export function progress(attr: Attribute, ...content: node[]) {
+ return t("progress", attr, ...content);
+}
+
+
+export function q(attr: Attribute, ...content: node[]) {
+ return t("q", attr, ...content);
+}
+
+
+export function rp(attr: Attribute, ...content: node[]) {
+ return t("rp", attr, ...content);
+}
+
+
+export function rt(attr: Attribute, ...content: node[]) {
+ return t("rt", attr, ...content);
+}
+
+
+export function ruby(attr: Attribute, ...content: node[]) {
+ return t("ruby", attr, ...content);
+}
+
+
+export function s(attr: Attribute, ...content: node[]) {
+ return t("s", attr, ...content);
+}
+
+
+export function samp(attr: Attribute, ...content: node[]) {
+ return t("samp", attr, ...content);
+}
+
+
+export function script(attr: Attribute, ...content: node[]) {
+ return t("script", attr, ...content);
+}
+
+
+export function section(attr: Attribute, ...content: node[]) {
+ return t("section", attr, ...content);
+}
+
+
+export function select(attr: Attribute, ...content: node[]) {
+ return t("select", attr, ...content);
+}
+
+
+export function slot(attr: Attribute, ...content: node[]) {
+ return t("slot", attr, ...content);
+}
+
+
+export function small(attr: Attribute, ...content: node[]) {
+ return t("small", attr, ...content);
+}
+
+
+export function source(attr: Attribute) {
+ return t("source", attr);
+}
+
+
+export function span(attr: Attribute, ...content: node[]) {
+ return t("span", attr, ...content);
+}
+
+
+export function strong(attr: Attribute, ...content: node[]) {
+ return t("strong", attr, ...content);
+}
+
+
+export function style(attr: Attribute, ...content: node[]) {
+ return t("style", attr, ...content);
+}
+
+
+export function sub(attr: Attribute, ...content: node[]) {
+ return t("sub", attr, ...content);
+}
+
+
+export function summary(attr: Attribute, ...content: node[]) {
+ return t("summary", attr, ...content);
+}
+
+
+export function sup(attr: Attribute, ...content: node[]) {
+ return t("sup", attr, ...content);
+}
+
+
+export function table(attr: Attribute, ...content: node[]) {
+ return t("table", attr, ...content);
+}
+
+
+export function tbody(attr: Attribute, ...content: node[]) {
+ return t("tbody", attr, ...content);
+}
+
+
+export function td(attr: Attribute, ...content: node[]) {
+ return t("td", attr, ...content);
+}
+
+
+export function template(attr: Attribute, ...content: node[]) {
+ return t("template", attr, ...content);
+}
+
+
+export function textarea(attr: Attribute, ...content: node[]) {
+ return t("textarea", attr, ...content);
+}
+
+
+export function tfoot(attr: Attribute, ...content: node[]) {
+ return t("tfoot", attr, ...content);
+}
+
+
+export function th(attr: Attribute, ...content: node[]) {
+ return t("th", attr, ...content);
+}
+
+
+export function thead(attr: Attribute, ...content: node[]) {
+ return t("thead", attr, ...content);
+}
+
+
+export function time(attr: Attribute, ...content: node[]) {
+ return t("time", attr, ...content);
+}
+
+
+export function title(attr: Attribute, ...content: node[]) {
+ return t("title", attr, ...content);
+}
+
+
+export function tr(attr: Attribute, ...content: node[]) {
+ return t("tr", attr, ...content);
+}
+
+
+export function track(attr: Attribute) {
+ return t("track", attr);
+}
+
+
+export function u(attr: Attribute, ...content: node[]) {
+ return t("u", attr, ...content);
+}
+
+
+export function ul(attr: Attribute, ...content: node[]) {
+ return t("ul", attr, ...content);
+}
+
+
+export function hvar(attr: Attribute, ...content: node[]) {
+ return t("var", attr, ...content);
+}
+
+export function video(attr: Attribute, ...content: node[]) {
+ return t("video", attr, ...content);
+}
+
+
+export function wbr(attr: Attribute) {
+ return t("wbr", attr);
+} \ No newline at end of file
diff --git a/src/server/template/sitemap.ts b/src/server/template/sitemap.ts
new file mode 100644
index 0000000..0a59ec5
--- /dev/null
+++ b/src/server/template/sitemap.ts
@@ -0,0 +1,31 @@
+import type { element } from "./vdom.js";
+import t, { Attribute } from "./xml.js";
+
+export function urlset(attr: Attribute, ...content: element<'url'>[]) {
+ return t("urlset", attr, ...content);
+}
+
+export function url(
+ attr: Attribute,
+ ...content: element<"loc" | "lastmod" | "changefreq" | "priority">[]
+) {
+ return t("url", attr, ...content);
+}
+
+export function loc(attr: Attribute, ...content: string[]) {
+ return t("loc", attr, ...content);
+}
+
+export function lastmod(attr: Attribute, content: string) {
+ return t("lastmod", attr, content);
+}
+
+export function changefreq(attr: Attribute,
+ content: "always" | "hourly" | "daily" | "weekly" | "monthly" | "yearly" | "never"
+) {
+ return t("changefreq", attr, content);
+}
+
+export function priority(attr: Attribute, content: string) {
+ return t("priority", attr, content);
+} \ No newline at end of file
diff --git a/src/server/template/syntax.ts b/src/server/template/syntax.ts
new file mode 100644
index 0000000..82b37e8
--- /dev/null
+++ b/src/server/template/syntax.ts
@@ -0,0 +1,18 @@
+import hljs from "highlight.js"
+import { data, Attribute, code as Code } from "./html.js"
+import { c } from "./vdom.js"
+
+interface Syntax extends Attribute {
+ lang: string
+}
+
+export default function code({ lang, ...attr }: Syntax, content: string) {
+ return c(
+ Code,
+ {
+ class: attr.class ?? "hljs",
+ ...attr
+ },
+ data(hljs.highlight(content,{ language: lang }).value)
+ )
+} \ No newline at end of file
diff --git a/src/server/template/table.ts b/src/server/template/table.ts
new file mode 100644
index 0000000..34f2771
--- /dev/null
+++ b/src/server/template/table.ts
@@ -0,0 +1,32 @@
+import { c, node } from "./vdom.js";
+import { Attribute, caption, table as Table, td, th, thead, tr } from "./html.js";
+
+interface TableAttr extends Attribute {
+ header: boolean;
+ data: node[][];
+ caption?: string;
+}
+
+export default function table({ header, data, caption: captio, ...attr }: TableAttr) {
+ const capt = captio ? c(caption, {}, captio) : undefined;
+ if (header) {
+ const [head, ...tbldata] = data;
+ return c(Table, attr,
+ ...tbldata.map(
+ row => c(tr, {}, ...row.map(
+ key => c(td, {}, key ?? "")
+ ))
+ ),
+ c(thead, {}, c(tr, {}, ...(head ?? []).map(e => c(th, {}, e ?? "")))),
+ ...(capt ? [capt] : [])
+ );
+ } else {
+ return c(Table, attr, ...data.map(
+ row => c(tr, {}, ...row.map(
+ key => c(td, {}, key ?? "")
+ ))
+ ),
+ ...(capt ? [capt] : [])
+ );
+ }
+}
diff --git a/src/server/template/vdom.ts b/src/server/template/vdom.ts
new file mode 100644
index 0000000..899565b
--- /dev/null
+++ b/src/server/template/vdom.ts
@@ -0,0 +1,105 @@
+export type component<
+ T extends Attribute = Attribute,
+ U extends node[] = node[],
+ R extends node = node
+> = ((attr: T, ...content: U) => R)
+
+export interface Attribute {
+ [key: string]: unknown,
+}
+
+export interface element<T extends string = string> {
+ name: T,
+ attr: Attribute,
+ content: node[],
+}
+
+export function iselement<T extends string = string>(e: unknown): e is element<T> {
+ return typeof e === "object" && e !== null && "name" in e;
+}
+
+export interface typed_data<T extends string = string> {
+ content: string,
+ type: T
+}
+
+export function istyped<T extends string = string>(e: unknown): e is typed_data<T> {
+ return typeof e === "object" && e !== null && "type" in e;
+}
+
+export type node = element | typed_data | node[] | string
+
+export function c<
+ T extends Attribute = Attribute,
+ U extends node[] = node[],
+ R extends node = node
+>(name: component<T, U, R>, attr: T, ...content: U): R {
+ return name(attr, ...content)
+}
+
+export default function t<T extends string = string>(name: T, attr?: Attribute, ...content: node[]): element<T> {
+ return {
+ name,
+ attr: attr ?? ({} as Attribute),
+ content
+ }
+}
+
+export function frag<T extends node[] = node[]>(_attr: Attribute, ...content: T) {
+ return content
+}
+
+export function join({sep = ""}: Attribute & {sep?: string}, ...content: string[]) {
+ return content.join(sep)
+}
+
+export function prenderdata<T extends string = string>(type: T) {
+ return function (content: string): typed_data<T> {
+ return {
+ content,
+ type
+ }
+ }
+}
+
+export function render<T extends string = string>(type: T, rendFunc: (node: element, content: string) => string) {
+ const data = prenderdata(type)
+ return function rend(Node: node): typed_data<T> {
+ if (Array.isArray(Node)) {
+ return data(Node.map(element => rend(element).content ?? "").join(""))
+ }
+
+ if (typeof Node == "string") {
+ return data(Node)
+ }
+
+ if (iselement(Node)) {
+ return data(rendFunc(Node, rend(Node.content).content))
+ }
+
+ if (istyped<T>(Node)) {
+ if (Node.type == type) {
+ return Node
+ } else {
+ throw new Error(Node.type + "is not valid. The type must be:" + type)
+ }
+ }
+ throw new TypeError("" + Node)
+ }
+}
+
+export function getText(node: node): string {
+ if (Array.isArray(node)) {
+ return node.map(element => getText(element) ?? "").join("")
+ }
+
+ if (typeof node == "string") {
+ return node
+ }
+
+ if (iselement(node)) {
+ return getText(node.content)
+ }
+
+ throw new TypeError("" + node)
+}
diff --git a/src/server/template/xml.ts b/src/server/template/xml.ts
new file mode 100644
index 0000000..fa6dbb4
--- /dev/null
+++ b/src/server/template/xml.ts
@@ -0,0 +1,56 @@
+import isDefined from "../utils/isDefined.js"
+import tag, { render, prenderdata, node, element, Attribute as Attr } from "./vdom.js"
+
+type encoding = "UTF-8" | "UTF-16" | "ISO-10646-UCS-2" | "ISO-10646-UCS-4" | "ISO-8859-1" | "ISO-8859-2" | "ISO-8859-3" | "ISO-8859-4" |
+ "ISO-8859-5" | "ISO-8859-6" | "ISO-8859-7" | "ISO-8859-8" | "ISO-8859-9" | "ISO-2022-JP" | "Shift_JIS" | "EUC-JP"
+
+interface xmlDeclaration {
+ version?: "1.0",
+ encoding?: encoding
+ standalone?: boolean
+}
+
+export function doctype(options: xmlDeclaration, content: node): string {
+ const version = `version=${options.version ?? `"1.0"`} `
+ const encoding = isDefined(options.encoding) ? `encoding="${options.encoding}" ` : ""
+ const standalone = isDefined(options.standalone) ? `standalone="${options.standalone ? "yes" : "no"}" ` : ""
+ return `<?xml ${version}${encoding}${standalone}?>${xmlrender(content).content}`
+}
+
+export const xmlrender = render("xml", function (node: element, content: string) {
+ var list = Object.entries(node.attr).map(([prop, val]): string => {
+ if (!isDefined(val)) {
+ return ""
+ }
+ var v: string = Array.isArray(val) ? val.join(" ") : "" + val
+ return `${prop}="${v}"`
+ }).join(" ")
+ if (list) {
+ list = ` ${list} `
+ }
+ const open = `<${node.name}${list}>`
+ const close = `</${node.name}>`
+ if (content || !close) {
+ return `${open}${content || ""}${close}`
+ } else {
+ return `<${node.name}${list}/>`
+ }
+})
+
+export const xmldata = prenderdata("xml")
+
+export interface Attribute extends Attr {
+ xmlns?: URL
+}
+
+export interface Base extends Attribute {
+ ["xml:base"]?: string
+}
+
+export interface Lang extends Attribute {
+ ["xml:lang"]?: string
+}
+
+export default function t<T extends string = string>(name: T, attr?: Attribute, ...content: node[]) {
+ return tag(name, { ...attr, xmlns: attr?.xmlns?.href }, ...content)
+}
diff --git a/src/server/utils/createUrl.ts b/src/server/utils/createUrl.ts
new file mode 100644
index 0000000..2a05665
--- /dev/null
+++ b/src/server/utils/createUrl.ts
@@ -0,0 +1,5 @@
+import { URL } from "node:url";
+
+export default function createUrl(url: string | URL, base?: string | URL | undefined): URL {
+ return url instanceof URL ? url : new URL(url, base);
+}
diff --git a/src/server/utils/curl.ts b/src/server/utils/curl.ts
new file mode 100644
index 0000000..e422493
--- /dev/null
+++ b/src/server/utils/curl.ts
@@ -0,0 +1,7 @@
+import type { URL } from "node:url";
+import metadata from "../metadata.js";
+import createUrl from "./createUrl.js";
+
+export default function curl(path: string | URL) {
+ return createUrl(path, metadata.url);
+}
diff --git a/src/server/utils/isDefined.ts b/src/server/utils/isDefined.ts
new file mode 100644
index 0000000..fe42bdf
--- /dev/null
+++ b/src/server/utils/isDefined.ts
@@ -0,0 +1,3 @@
+export default function isDefined<T>(val: T | undefined | null): val is T {
+ return val !== undefined && val !== null;
+}
diff --git a/src/server/utils/isDevel.ts b/src/server/utils/isDevel.ts
new file mode 100644
index 0000000..6d03f4d
--- /dev/null
+++ b/src/server/utils/isDevel.ts
@@ -0,0 +1,5 @@
+import type express from "express";
+
+export default function isDevel(app: express.Express) {
+ return app.get("env") === "development";
+}
diff --git a/src/server/utils/relDir.ts b/src/server/utils/relDir.ts
new file mode 100644
index 0000000..1e3cb3c
--- /dev/null
+++ b/src/server/utils/relDir.ts
@@ -0,0 +1,9 @@
+import { dirname, join } from "node:path";
+import { URL, fileURLToPath } from "node:url";
+
+export default function relDir(url: URL | string) {
+ const base = dirname(fileURLToPath(url));
+ return function (dirname: string) {
+ return join(base, dirname);
+ };
+}
diff --git a/src/server/utils/relUrl.ts b/src/server/utils/relUrl.ts
new file mode 100644
index 0000000..5345ebf
--- /dev/null
+++ b/src/server/utils/relUrl.ts
@@ -0,0 +1,5 @@
+import type { URL } from "node:url";
+
+export default function relUrl(url: URL) {
+ return url.pathname + url.search + url.hash;
+}
diff --git a/src/server/utils/schema.ts b/src/server/utils/schema.ts
new file mode 100644
index 0000000..989d163
--- /dev/null
+++ b/src/server/utils/schema.ts
@@ -0,0 +1,6 @@
+import type { URL } from "node:url";
+import createUrl from "./createUrl.js";
+
+export default function schema(type: string | URL): URL {
+ return createUrl(type, "http://schema.org/");
+}
diff --git a/src/server/utils/setStingRoute.ts b/src/server/utils/setStingRoute.ts
new file mode 100644
index 0000000..e0fbd94
--- /dev/null
+++ b/src/server/utils/setStingRoute.ts
@@ -0,0 +1,6 @@
+import router from "../router.js";
+import strHandler from "./strHandler.js";
+
+export default function setStingRoute<T>(url: string, type: string | string[], content: () => Promise<T>): void {
+ router.get(url, strHandler(type, content));
+}
diff --git a/src/server/utils/strHandler.ts b/src/server/utils/strHandler.ts
new file mode 100644
index 0000000..5be21e0
--- /dev/null
+++ b/src/server/utils/strHandler.ts
@@ -0,0 +1,8 @@
+import type express from "express";
+
+export default function strHandler<T>(type: string | string[], content: () => Promise<T>) {
+ return async (_req: express.Request, res: express.Response) => {
+ const data = await content();
+ res.format(Object.fromEntries((Array.isArray(type) ? type : [type]).map(t => [t, () => res.send(data)])));
+ };
+}
diff --git a/src/worker/sw.ts b/src/worker/sw.ts
new file mode 100644
index 0000000..e125879
--- /dev/null
+++ b/src/worker/sw.ts
@@ -0,0 +1,51 @@
+declare var self: ServiceWorkerGlobalScope;
+export {};
+
+
+const sw_cache = {
+ offline: "/offline",
+ default: [
+ "/index.js",
+ "/index.css",
+ "/app.webmanifest",
+ "/favicon.svg"
+ ],
+ store: "app",
+}
+
+async function install() {
+ const cache = await self.caches.open(sw_cache.store)
+ return cache.addAll([sw_cache.offline, ...sw_cache.default])
+}
+
+async function activate() {
+ const keys = await self.caches.keys()
+ return Promise.all(keys.map(key => {
+ return key !== sw_cache.store ? self.caches.delete(key) : undefined
+ }))
+}
+
+async function req(event: FetchEvent) {
+ const cache = await caches.open(sw_cache.store)
+ const cachedResponse = await cache.match(event.request)
+ var networkResponse
+ try {
+ networkResponse = await fetch(event.request)
+ if (networkResponse.ok) {
+ cache.put(event.request, networkResponse.clone())
+ }
+ } catch(error) {
+ console.error(error)
+ if (event.request.mode === "navigate") {
+ networkResponse = await caches.match("/offline")
+ }
+ }
+ return (cachedResponse || networkResponse) as Response
+
+}
+
+self.addEventListener("install", event => event.waitUntil(install()))
+
+self.addEventListener("activate", event => event.waitUntil(activate()))
+
+self.addEventListener("fetch", event => event.respondWith(req(event)))
diff --git a/src/worker/tsconfig.json b/src/worker/tsconfig.json
new file mode 100644
index 0000000..f485c2e
--- /dev/null
+++ b/src/worker/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "importHelpers": true,
+ "lib": ["es2022", "webworker"],
+ "composite": true,
+ "sourceMap": true
+ },
+}