diff --git a/framework/backend/entry-node.ts b/framework/backend/entry-node.ts index 3402f2230d789f341a20528a7ac34a9e9fd87c7b..899d4aef43c0ee2cf6315326533ce4088634f6c0 100644 --- a/framework/backend/entry-node.ts +++ b/framework/backend/entry-node.ts @@ -8,7 +8,9 @@ const server = serve({ fetch: app.default.fetch, port: Number(process.env.PORT ?? 3000), }, ({ address, port }) => { - if (address === "::") address = "localhost"; + if (address === "::" || address === "127.0.0.1") { + address = os.hostname().replace(/\.local$/, ""); + } console.info(url.format({ protocol, hostname: address, @@ -36,6 +38,7 @@ declare global { var CLOVER_SERVER_ENTRY: string; } -import url from "node:url"; +import * as os from "os"; +import * as url from "node:url"; import { serve } from "@hono/node-server"; import process from "node:process"; diff --git a/framework/bundle.ts b/framework/bundle.ts index cc05cef76ad33ac39a515d4e0fe4f92e9713b7ac..5c4bcfa82dd8b6b06d9f64874ee582179d95a96e 100644 --- a/framework/bundle.ts +++ b/framework/bundle.ts @@ -39,6 +39,18 @@ export async function bundleClientJavaScript( const clientPlugins: esbuild.Plugin[] = [ projectRelativeResolution(), markoViaBuildCache(), + { + name: "mark css external", + setup(b) { + b.onResolve({ filter: /\.css$/ }, () => ({ + path: ".", + namespace: "dropped", + })); + b.onLoad({ filter: /./, namespace: "dropped" }, () => ({ + contents: "", + })); + }, + }, ]; using _ = io.start(`bundle ${entryPoints.length} files`); @@ -60,6 +72,7 @@ export async function bundleClientJavaScript( outdir: "out!", plugins: clientPlugins, write: false, + external: ["/js/scripts/vendor/dash.js", "/js/scripts/vendor/hls.js"], define: { ASSERT: "console.assert", MIME_INLINE_DATA: JSON.stringify(mime.rawEntriesText), @@ -290,7 +303,7 @@ export async function bundleServerJavaScript({ "globalThis.CLOVER_SERVER_ENTRY": JSON.stringify(entry), }, external: Object.keys(pkg.dependencies).filter( - (x) => !x.startsWith("@paperclover"), + (x) => x !== "lib", ), }); await trackEsbuild(io, metafile); diff --git a/framework/engine/marko-runtime.ts b/framework/engine/marko-runtime.ts index 95cd4dbcd4e0b0e09cba5e0db96c0678c8493fe7..beb038ad467c32635fc7dcaf2bb756ceee8a077c 100644 --- a/framework/engine/marko-runtime.ts +++ b/framework/engine/marko-runtime.ts @@ -27,7 +27,6 @@ export const _template: typeof marko._template = ( return markoResult.then(render.raw); } const rr = markoResult.toString(); - console.log({ rr }); return render.raw(rr); } wrap.render = renderFn; diff --git a/framework/engine/render.ts b/framework/engine/render.ts index 9f733cc41335045c8fbe0fd7ff23792caf52b695..8fa81556fd16cdd0a329bdfe4a46ebc6a12504d4 100644 --- a/framework/engine/render.ts +++ b/framework/engine/render.ts @@ -6,6 +6,8 @@ // Add-ons to the rendering engine can provide opaque data, And retrieve it // within component calls with 'getAddonData'. For example, 'sitegen' uses this // to track needed client scripts without introducing patches to the engine. +// +// This module is not production ready export let current: State | null = null; export function setCurrent(r: State | null) { current = r ?? null; @@ -212,8 +214,7 @@ export function stringifyNode(node: ResolvedNode): string { } function stringifyElement(element: ResolvedElement) { const { 1: tag, 2: props, 3: children } = element; - let out = "<" + tag; - let needSpace = true; + let out = "<" + tag + " "; for (const prop in props) { const value = props[prop]; if (!value || typeof value === "function") continue; @@ -249,10 +250,10 @@ function stringifyElement(element: ResolvedElement) { case "key": continue; } - if (needSpace) ((out += " "), (needSpace = !attr.endsWith('"'))); + if (!out.endsWith('"')) out += " "; out += attr; } - out += ">"; + out = out.trimEnd() + ">"; if (children) out += stringifyNode(children); if ( tag !== "br" && diff --git a/framework/generate.ts b/framework/generate.ts index 7e558e72efd51ea09aad279ea1cb1e20d56ccbb8..66d530a2a7bf93a4893de64dd65a6cb6de663958 100644 --- a/framework/generate.ts +++ b/framework/generate.ts @@ -369,10 +369,8 @@ export async function assembleAndWritePage( pageNode.passive = false; - const scripts = scriptIds - .map((ref) => UNWRAP(scriptMap[ref], `Missing script ${ref}`)) - .map((x) => `{${x}}`) - .join("\n"); + const scripts = sg.joinScripts(scriptIds + .map((ref) => UNWRAP(scriptMap[ref], `Missing script ${ref}`))); const buffer = sg.wrapDocument({ body: html, diff --git a/framework/incremental.ts b/framework/incremental.ts index c7f91df9e857cc3a899c4b6a14d1f1657bf21f63..58fbba1fd402b254b3f068138b51a648f3437551 100644 --- a/framework/incremental.ts +++ b/framework/incremental.ts @@ -496,7 +496,7 @@ export class Io { return resolved; } async readFile(file: string) { - return fs.readFile(await this.trackFile(file), "utf-8"); + return fs.readFile(await this.trackFile(file)); } async readJson(file: string) { return JSON.parse(await this.readFile(file)) as T; diff --git a/framework/lib/lru.ts b/framework/lib/lru.ts index 628b125b9c926c72d3e0126fc0d6473481afed09..f58d2e9ed78af6f1608e052258ff3b998bfba55d 100644 --- a/framework/lib/lru.ts +++ b/framework/lib/lru.ts @@ -94,7 +94,8 @@ export class Lru extends Map { this.#used += size; try { this.ensureUnusedCapacity(0); - } catch { + } catch (err) { + console.error(err); ASSERT(false, "capacity is already verified"); } super.set(key, value); diff --git a/framework/lib/meta.ts b/framework/lib/meta.ts index 846551e663a2bd492cf07481c10c606f8bc39283..da20a51429068b2b88472738ac8ab00fed3aec6e 100644 --- a/framework/lib/meta.ts +++ b/framework/lib/meta.ts @@ -214,7 +214,7 @@ function renderMetadata(meta: Meta, template: Template): string { if (denyRobots) { out += ``; } - if (description) { + if (viewport) { out += ``; } if (canonical) { diff --git a/framework/lib/mime.ts b/framework/lib/mime.ts index af270bd3b3c5ac447dd8af59b5bfca336bceff3d..3913ebbab69054ed12b26c2d03992c8edfe332d9 100644 --- a/framework/lib/mime.ts +++ b/framework/lib/mime.ts @@ -18,7 +18,7 @@ const fullNames = new Map(entries.filter((x) => !x[0].startsWith("."))); export function contentTypeFor(file: string) { const slash = file.indexOf("/"); if (slash !== -1) file = file.slice(slash + 1); - const dot = file.indexOf("."); + const dot = file.lastIndexOf("."); if (dot === -1) file = "." + file; else if (dot > 0) { let entry = fullNames.get(file); diff --git a/framework/lib/mime.txt b/framework/lib/mime.txt index ad6220bb80616c843b28316e8f814a89ca8d8d89..06af2f96260e0f3add34e7d81ba7dab195b0c310 100644 --- a/framework/lib/mime.txt +++ b/framework/lib/mime.txt @@ -42,7 +42,7 @@ .jpg image/jpeg .jpgv video/jpeg .jxl image/jxl -.js application/javascript +.js application/javascript;charset=utf-8 .json application/json .latex application/x-latex .list text/plain diff --git a/framework/lib/sitegen.ts b/framework/lib/sitegen.ts index 6f1bd0312f683b16373dad0126d622cd329ee85e..a25a464f5f82c8f3ac787e80f1abe5891d7a08b5 100644 --- a/framework/lib/sitegen.ts +++ b/framework/lib/sitegen.ts @@ -50,9 +50,7 @@ export type FontVars = Record; export type FontVariableAxis = number | [min: number, max: number]; export type FontRange = string | number | { start: number; end: number }; -export const userData = render.userData(() => { - throw new Error("This function can only be used in a page (static or view)"); -}); +export const userData = render.userData(() => initRender()); export interface SitegenRender { scripts: Set; @@ -78,13 +76,21 @@ export function wrapDocument({ inlineCss: string; scripts: string; }) { - return `${head}${ + return `${head}${ inlineCss ? `` : "" }${body}${ scripts ? `` : "" }`; } +export function joinScripts(scriptSources: string[]) { + const { length } = scriptSources; + if (scriptSources[0] == null) return ""; + if (length === 1) return UNWRAP(scriptSources[0]); + return "[" + scriptSources.map((source) => `()=>{${source}}`).join(",") + + "].map(e=>e())"; +} + import * as render from "#engine/render"; import type * as meta from "./meta.ts"; import type * as css from "../css.ts"; diff --git a/framework/lib/subprocess.ts b/framework/lib/subprocess.ts index 6d2eec5905452d043dd59e9f53ea2bdfbfafd2f8..959e235d59fb3db67b863b75d7867458f6dd7920 100644 --- a/framework/lib/subprocess.ts +++ b/framework/lib/subprocess.ts @@ -3,76 +3,80 @@ const execFileRaw = util.promisify(child_process.execFile); export const exec: typeof execFileRaw = (( - ...args: Parameters + ...args: Parameters ) => - execFileRaw(...args).catch((e: any) => { - if (e?.message?.startsWith?.("Command failed")) { - if (e.code > 2 ** 31) e.code |= 0; - const code = e.signal ? `signal ${e.signal}` : `code ${e.code}`; - e.message = `${e.cmd.split(" ")[0]} failed with ${code}`; - } - throw e; - })) as any; + execFileRaw(...args).catch((e: any) => { + if (e?.message?.startsWith?.("Command failed")) { + if (e.code > 2 ** 31) e.code |= 0; + const code = e.signal ? `signal ${e.signal}` : `code ${e.code}`; + e.message = `${e.cmd.split(" ")[0]} failed with ${code}`; + } + throw e; + })) as any; -const activePids = new Set(); +// const activePids = new Set(); export interface SpawnOptions extends child_process.SpawnOptions { - cmd: string[]; + cmd: string[]; } export function spawn({ cmd, ...rest }: SpawnOptions) { - rest.detached ??= true; - const process = child_process.spawn(cmd[0]!, cmd.slice(1), rest); - const pid = rest.detached && process.pid ? -process.pid : process.pid; - if (pid) { - activePids.add(pid); - process.on("exit", () => void activePids.delete(pid)); - } - return process; + // rest.detached ??= true; + const process = child_process.spawn(cmd[0]!, cmd.slice(1), rest); + // const pid = rest.detached && process.pid ? -process.pid : process.pid; + // if (pid) { + // activePids.add(pid); + // process.on("exit", () => void activePids.delete(pid)); + // } + return process; } export interface ForkOptions extends child_process.ForkOptions { - cmd: string[]; + cmd: string[]; } export function fork({ cmd, ...rest }: ForkOptions) { - rest.detached ??= true; - const process = child_process.fork(cmd[0]!, cmd.slice(1), rest); - const pid = rest.detached && process.pid ? -process.pid : process.pid; - if (pid) { - activePids.add(pid); - process.on("exit", () => void activePids.delete(pid)); - } - return process; + // rest.detached ??= true; + const process = child_process.fork(cmd[0]!, cmd.slice(1), rest); + // const pid = rest.detached && process.pid ? -process.pid : process.pid; + // if (pid) { + // activePids.add(pid); + // process.on("exit", () => void activePids.delete(pid)); + // } + return process; } export async function spawnAndWait(opts: SpawnOptions) { - opts.stdio ??= ["ignore", "inherit", "inherit"]; - const process = spawn(opts); - const output = Promise.all([ - process.stdout ? Array.fromAsync(process.stdout).then(Buffer.concat) : null, - process.stderr ? Array.fromAsync(process.stderr).then(Buffer.concat) : null, - ]); - let [code, signal] = await events.EventEmitter.once(process, "exit") as [ - number | null, - string | null, - ]; - if (code !== 0) { - if (code !== null && code > 2 ** 31) code |= 0; - const exit = signal ? `signal ${signal}` : `code ${code}`; - throw new Error(`${opts.cmd[0]} failed with ${exit}`); - } - const [stdout, stderr] = await output; - return { stdout, stderr }; + opts.stdio ??= ["ignore", "inherit", "inherit"]; + const process = spawn(opts); + const output = Promise.all([ + process.stdout + ? Array.fromAsync(process.stdout).then(Buffer.concat) + : null, + process.stderr + ? Array.fromAsync(process.stderr).then(Buffer.concat) + : null, + ]); + let [code, signal] = (await events.EventEmitter.once(process, "exit")) as [ + number | null, + string | null, + ]; + if (code !== 0) { + if (code !== null && code > 2 ** 31) code |= 0; + const exit = signal ? `signal ${signal}` : `code ${code}`; + throw new Error(`${opts.cmd[0]} failed with ${exit}`); + } + const [stdout, stderr] = await output; + return { stdout, stderr }; } -process.on("exit", () => { - for (const pid of activePids) { - console.info(`Kill ${Math.abs(pid)}`); - process.kill(pid, 9); - } -}); +// process.on("exit", () => { +// for (const pid of activePids) { +// console.info(`Kill ${Math.abs(pid)}`); +// process.kill(pid, 9); +// } +// }); -import * as process from "node:process"; +// import * as process from "node:process"; import * as events from "node:events"; import * as util from "node:util"; import * as child_process from "node:child_process"; diff --git a/framework/lib/view.ts b/framework/lib/view.ts index 7a424790a8fd8cdedf66674704a6801fe5fc604e..708295686a663e5376dcbcd491de7a15672a8172 100644 --- a/framework/lib/view.ts +++ b/framework/lib/view.ts @@ -83,7 +83,7 @@ export async function renderToString( body, head: await renderedMetaPromise, inlineCss, - scripts: joinScripts( + scripts: sg.joinScripts( Array.from( sitegen!.scripts, (id) => UNWRAP(codegen.scripts[id], `Missing script ${id}`), @@ -108,13 +108,6 @@ export function regenerate(tag: RegenKey) { } } -function joinScripts(scriptSources: string[]) { - const { length } = scriptSources; - if (length === 0) return ""; - if (0 in scriptSources) return scriptSources[0]; - return scriptSources.map((source) => `{${source}}`).join(";"); -} - import * as meta from "./meta.ts"; import type * as hono from "#hono"; import * as render from "#engine/render"; diff --git a/run.js b/run.js index a78f57f33964cf781b0df3bf81f75d7d33e3537c..c891dc5606619eb95ea60ea6abf9dec74d9e605f 100644 --- a/run.js +++ b/run.js @@ -1,8 +1,9 @@ // This file allows using Node.js in combination with // all available plugins. Usage: "node run