@eighty4/dank 0.0.4-2 → 0.0.4-3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib_js/serve.js CHANGED
@@ -1,148 +1,61 @@
1
- import { mkdir, readFile, rm, watch as _watch, writeFile } from "node:fs/promises";
2
- import { extname, join, resolve } from "node:path";
1
+ import { mkdir, rm, writeFile } from "node:fs/promises";
2
+ import { extname, join } from "node:path";
3
3
  import { buildWebsite } from "./build.js";
4
4
  import { loadConfig } from "./config.js";
5
5
  import { createGlobalDefinitions } from "./define.js";
6
6
  import { esbuildDevContext } from "./esbuild.js";
7
- import { resolveServeFlags } from "./flags.js";
8
- import { HtmlEntrypoint } from "./html.js";
9
7
  import { createBuiltDistFilesFetcher, createDevServeFilesFetcher, startWebServer } from "./http.js";
10
- import { WebsiteRegistry } from "./metadata.js";
8
+ import { WebsiteRegistry } from "./registry.js";
11
9
  import { startDevServices, updateDevServices } from "./services.js";
12
- async function serveWebsite(c) {
13
- const serve = resolveServeFlags(c);
14
- await rm(serve.dirs.buildRoot, { force: true, recursive: true });
10
+ import { watch } from "./watch.js";
11
+ let c;
12
+ async function serveWebsite() {
13
+ c = await loadConfig("serve", process.cwd());
14
+ await rm(c.dirs.buildRoot, { force: true, recursive: true });
15
15
  const abortController = new AbortController();
16
16
  process.once("exit", () => abortController.abort());
17
- if (serve.preview) {
18
- await startPreviewMode(c, serve, abortController.signal);
17
+ if (c.flags.preview) {
18
+ await startPreviewMode(abortController.signal);
19
19
  } else {
20
- await startDevMode(c, serve, abortController.signal);
20
+ await startDevMode(abortController.signal);
21
21
  }
22
22
  return new Promise(() => {
23
23
  });
24
24
  }
25
- async function startPreviewMode(c, serve, signal) {
25
+ async function startPreviewMode(signal) {
26
26
  const manifest = await buildWebsite(c);
27
- const frontend = createBuiltDistFilesFetcher(serve.dirs.buildDist, manifest);
28
- const devServices = startDevServices(c, signal);
29
- startWebServer(serve, frontend, devServices.http, {
30
- urls: Object.keys(c.pages),
31
- urlRewrites: collectUrlRewrites(c)
32
- });
33
- }
34
- function collectUrlRewrites(c) {
35
- return Object.keys(c.pages).sort().map((url) => {
27
+ const frontend = createBuiltDistFilesFetcher(c.dirs, manifest);
28
+ const devServices = startDevServices(c.services, signal);
29
+ const urlRewrites = Object.keys(c.pages).sort().map((url) => {
36
30
  const mapping = c.pages[url];
37
31
  return typeof mapping !== "object" || !mapping.pattern ? null : { url, pattern: mapping.pattern };
38
32
  }).filter((mapping) => mapping !== null);
33
+ startWebServer(c.dankPort, c.flags, c.dirs, { urlRewrites }, frontend, devServices.http);
39
34
  }
40
- async function startDevMode(c, serve, signal) {
41
- await mkdir(serve.dirs.buildWatch, { recursive: true });
42
- const registry = new WebsiteRegistry(serve);
43
- const clientJS = await loadClientJS(serve.esbuildPort);
44
- const pagesByUrlPath = {};
45
- const partialsByUrlPath = {};
46
- const entryPointsByUrlPath = {};
35
+ async function startDevMode(signal) {
36
+ const registry = new WebsiteRegistry(c);
37
+ await mkdir(c.dirs.buildWatch, { recursive: true });
47
38
  let buildContext = null;
48
- registry.on("workers", () => {
49
- resetBuildContext();
50
- });
51
39
  watch("dank.config.ts", signal, async () => {
52
- let updated;
53
40
  try {
54
- updated = await loadConfig("serve");
41
+ await c.reload();
55
42
  } catch (ignore) {
56
43
  return;
57
44
  }
58
- const prevPages = new Set(Object.keys(pagesByUrlPath));
59
- await Promise.all(Object.entries(updated.pages).map(async ([urlPath, mapping]) => {
60
- c.pages[urlPath] = mapping;
61
- const srcPath = typeof mapping === "string" ? mapping : mapping.webpage;
62
- if (!pagesByUrlPath[urlPath]) {
63
- await addPage(urlPath, srcPath);
64
- } else {
65
- prevPages.delete(urlPath);
66
- if (pagesByUrlPath[urlPath].fsPath !== srcPath) {
67
- await updatePage(urlPath);
68
- }
69
- }
70
- }));
71
- for (const prevPage of Array.from(prevPages)) {
72
- delete c.pages[prevPage];
73
- deletePage(prevPage);
74
- }
75
- updateDevServices(updated);
45
+ registry.configSync();
46
+ updateDevServices(c.services);
76
47
  });
77
- watch(serve.dirs.pages, signal, (filename) => {
48
+ watch(c.dirs.pages, signal, (filename) => {
78
49
  if (extname(filename) === ".html") {
79
- for (const [urlPath, srcPath] of Object.entries(c.pages)) {
80
- if (srcPath === filename) {
81
- updatePage(urlPath);
82
- }
83
- }
84
- for (const [urlPath, partials] of Object.entries(partialsByUrlPath)) {
85
- if (partials.includes(filename)) {
86
- updatePage(urlPath, filename);
50
+ registry.htmlEntrypoints.forEach((html) => {
51
+ if (html.fsPath === filename) {
52
+ html.emit("change");
53
+ } else if (html.usesPartial(filename)) {
54
+ html.emit("change", filename);
87
55
  }
88
- }
56
+ });
89
57
  }
90
58
  });
91
- await Promise.all(Object.entries(c.pages).map(async ([urlPath, mapping]) => {
92
- const srcPath = typeof mapping === "string" ? mapping : mapping.webpage;
93
- await addPage(urlPath, srcPath);
94
- return new Promise((res) => pagesByUrlPath[urlPath].once("entrypoints", res));
95
- }));
96
- async function addPage(urlPath, srcPath) {
97
- await mkdir(join(serve.dirs.buildWatch, urlPath), { recursive: true });
98
- const htmlEntrypoint = pagesByUrlPath[urlPath] = new HtmlEntrypoint(serve, registry.resolver, urlPath, srcPath, [{ type: "script", js: clientJS }]);
99
- htmlEntrypoint.on("entrypoints", (entrypoints) => {
100
- const pathsIn = new Set(entrypoints.map((e) => e.in));
101
- if (!entryPointsByUrlPath[urlPath] || !matchingEntrypoints(entryPointsByUrlPath[urlPath].pathsIn, pathsIn)) {
102
- entryPointsByUrlPath[urlPath] = { entrypoints, pathsIn };
103
- resetBuildContext();
104
- }
105
- });
106
- htmlEntrypoint.on("partial", (partial) => {
107
- if (!partialsByUrlPath[urlPath]) {
108
- partialsByUrlPath[urlPath] = [];
109
- }
110
- partialsByUrlPath[urlPath].push(partial);
111
- });
112
- htmlEntrypoint.on("partials", (partials) => {
113
- partialsByUrlPath[urlPath] = partials;
114
- });
115
- htmlEntrypoint.on("output", (html) => {
116
- const path = join(serve.dirs.buildWatch, urlPath, "index.html");
117
- writeFile(path, html);
118
- });
119
- }
120
- function deletePage(urlPath) {
121
- pagesByUrlPath[urlPath].removeAllListeners();
122
- delete pagesByUrlPath[urlPath];
123
- delete entryPointsByUrlPath[urlPath];
124
- resetBuildContext();
125
- }
126
- async function updatePage(urlPath, partial) {
127
- pagesByUrlPath[urlPath].emit("change", partial);
128
- }
129
- function collectEntrypoints() {
130
- const unique = /* @__PURE__ */ new Set();
131
- const pageBundles = Object.values(entryPointsByUrlPath).flatMap((entrypointState) => entrypointState.entrypoints).filter((entryPoint) => {
132
- if (unique.has(entryPoint.in)) {
133
- return false;
134
- } else {
135
- unique.add(entryPoint.in);
136
- return true;
137
- }
138
- });
139
- const workerBundles = registry.workerEntryPoints();
140
- if (workerBundles) {
141
- return [...pageBundles, ...workerBundles];
142
- } else {
143
- return pageBundles;
144
- }
145
- }
146
59
  function resetBuildContext() {
147
60
  switch (buildContext) {
148
61
  case "starting":
@@ -161,7 +74,7 @@ async function startDevMode(c, serve, signal) {
161
74
  });
162
75
  } else {
163
76
  buildContext = "starting";
164
- startEsbuildWatch(c, registry, serve, collectEntrypoints()).then((ctx) => {
77
+ startEsbuildWatch(registry).then((ctx) => {
165
78
  if (buildContext === "dirty") {
166
79
  buildContext = "disposing";
167
80
  ctx.dispose().then(() => {
@@ -174,76 +87,39 @@ async function startDevMode(c, serve, signal) {
174
87
  });
175
88
  }
176
89
  }
90
+ registry.on("webpage", (html) => {
91
+ html.on("error", (e) => console.log(`\x1B[31merror:\x1B[0m`, e.message));
92
+ html.on("output", (output) => writeHtml(html, output));
93
+ });
94
+ registry.on("workers", () => {
95
+ resetBuildContext();
96
+ });
97
+ registry.configSync();
98
+ await Promise.all(registry.htmlEntrypoints.map((html) => html.process()));
99
+ registry.on("entrypoints", () => resetBuildContext());
177
100
  resetBuildContext();
178
- const pageRoutes = {
179
- get urls() {
180
- return Object.keys(c.pages);
181
- },
182
- get urlRewrites() {
183
- return collectUrlRewrites(c);
184
- }
185
- };
186
- const frontend = createDevServeFilesFetcher(pageRoutes, serve);
187
- const devServices = startDevServices(c, signal);
188
- startWebServer(serve, frontend, devServices.http, pageRoutes);
101
+ const frontend = createDevServeFilesFetcher(c.esbuildPort, c.dirs, registry);
102
+ const devServices = startDevServices(c.services, signal);
103
+ startWebServer(c.dankPort, c.flags, c.dirs, registry, frontend, devServices.http);
189
104
  }
190
- function matchingEntrypoints(a, b) {
191
- if (a.size !== b.size) {
192
- return false;
193
- }
194
- for (const v in a) {
195
- if (!b.has(v)) {
196
- return false;
197
- }
198
- }
199
- return true;
200
- }
201
- async function startEsbuildWatch(c, registry, serve, entryPoints) {
202
- const ctx = await esbuildDevContext(serve, registry, createGlobalDefinitions(serve), entryPoints, c.esbuild);
105
+ async function startEsbuildWatch(registry) {
106
+ const entryPoints = registry.webpageAndWorkerEntryPoints;
107
+ const ctx = await esbuildDevContext(registry, createGlobalDefinitions(c), entryPoints);
203
108
  await ctx.watch();
204
109
  await ctx.serve({
205
110
  host: "127.0.0.1",
206
- port: serve.esbuildPort,
111
+ port: c.esbuildPort,
207
112
  cors: {
208
- origin: ["127.0.0.1", "localhost"].map((hostname) => `http://${hostname}:${serve.dankPort}`)
113
+ origin: ["127.0.0.1", "localhost"].map((hostname) => `http://${hostname}:${c.dankPort}`)
209
114
  }
210
115
  });
211
116
  return ctx;
212
117
  }
213
- async function loadClientJS(esbuildPort) {
214
- const clientJS = await readFile(resolve(import.meta.dirname, join("..", "client", "esbuild.js")), "utf-8");
215
- return clientJS.replace("3995", `${esbuildPort}`);
216
- }
217
- async function watch(p, signal, fire) {
218
- const delayFire = 90;
219
- const timeout = 100;
220
- let changes = {};
221
- try {
222
- for await (const { filename } of _watch(p, {
223
- recursive: true,
224
- signal
225
- })) {
226
- if (filename) {
227
- if (!changes[filename]) {
228
- const now = Date.now();
229
- changes[filename] = now + delayFire;
230
- setTimeout(() => {
231
- const now2 = Date.now();
232
- for (const [filename2, then] of Object.entries(changes)) {
233
- if (then <= now2) {
234
- fire(filename2);
235
- delete changes[filename2];
236
- }
237
- }
238
- }, timeout);
239
- }
240
- }
241
- }
242
- } catch (e) {
243
- if (e.name !== "AbortError") {
244
- throw e;
245
- }
246
- }
118
+ async function writeHtml(html, output) {
119
+ const dir = join(c.dirs.buildWatch, html.url);
120
+ await mkdir(dir, { recursive: true });
121
+ const path = join(dir, "index.html");
122
+ await writeFile(path, output);
247
123
  }
248
124
  export {
249
125
  serveWebsite
@@ -3,10 +3,10 @@ import { basename, isAbsolute, resolve } from "node:path";
3
3
  const running = [];
4
4
  let signal;
5
5
  let updating = null;
6
- function startDevServices(c, _signal) {
6
+ function startDevServices(services, _signal) {
7
7
  signal = _signal;
8
- if (c.services?.length) {
9
- for (const s of c.services) {
8
+ if (services?.length) {
9
+ for (const s of services) {
10
10
  running.push({ s, process: startService(s) });
11
11
  }
12
12
  }
@@ -18,8 +18,8 @@ function startDevServices(c, _signal) {
18
18
  }
19
19
  };
20
20
  }
21
- function updateDevServices(c) {
22
- if (!c.services?.length) {
21
+ function updateDevServices(services) {
22
+ if (!services?.length) {
23
23
  if (running.length) {
24
24
  if (updating === null) {
25
25
  updating = { stopping: [], starting: [] };
@@ -39,7 +39,7 @@ function updateDevServices(c) {
39
39
  }
40
40
  const keep = [];
41
41
  const next = [];
42
- for (const s of c.services) {
42
+ for (const s of services) {
43
43
  let found = false;
44
44
  for (let i = 0; i < running.length; i++) {
45
45
  const p = running[i].s;
@@ -0,0 +1,35 @@
1
+ import { watch as createWatch } from "node:fs/promises";
2
+ async function watch(p, signal, fire) {
3
+ const delayFire = 90;
4
+ const timeout = 100;
5
+ let changes = {};
6
+ try {
7
+ for await (const { filename } of createWatch(p, {
8
+ recursive: true,
9
+ signal
10
+ })) {
11
+ if (filename) {
12
+ if (!changes[filename]) {
13
+ const now = Date.now();
14
+ changes[filename] = now + delayFire;
15
+ setTimeout(() => {
16
+ const now2 = Date.now();
17
+ for (const [filename2, then] of Object.entries(changes)) {
18
+ if (then <= now2) {
19
+ fire(filename2);
20
+ delete changes[filename2];
21
+ }
22
+ }
23
+ }, timeout);
24
+ }
25
+ }
26
+ }
27
+ } catch (e) {
28
+ if (e.name !== "AbortError") {
29
+ throw e;
30
+ }
31
+ }
32
+ }
33
+ export {
34
+ watch
35
+ };
@@ -2,6 +2,7 @@ import type { Plugin as EsbuildPlugin } from 'esbuild';
2
2
  export type DankConfig = {
3
3
  esbuild?: EsbuildConfig;
4
4
  pages: Record<`/${string}`, `${string}.html` | PageMapping>;
5
+ devPages?: Record<`/__${string}`, `${string}.html` | DevPageMapping>;
5
6
  port?: number;
6
7
  previewPort?: number;
7
8
  services?: Array<DevService>;
@@ -10,6 +11,10 @@ export type PageMapping = {
10
11
  pattern?: RegExp;
11
12
  webpage: `${string}.html`;
12
13
  };
14
+ export type DevPageMapping = {
15
+ label: string;
16
+ webpage: `${string}.html`;
17
+ };
13
18
  export type DevService = {
14
19
  command: string;
15
20
  cwd?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eighty4/dank",
3
- "version": "0.0.4-2",
3
+ "version": "0.0.4-3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Adam McKee Bennett <adam.be.g84d@gmail.com>",
@@ -37,18 +37,20 @@
37
37
  "typescript": "^5.9.2"
38
38
  },
39
39
  "files": [
40
- "client/esbuild.js",
40
+ "client/client.js",
41
41
  "lib/*.ts",
42
42
  "lib_js/*.js",
43
43
  "lib_types/*.d.ts"
44
44
  ],
45
45
  "scripts": {
46
- "build": "tsc && tsc -p tsconfig.client.json && tsc -p tsconfig.exports.json",
46
+ "build": "pnpm build:client && pnpm build:lib",
47
+ "build:client": "node scripts/build_client.ts",
48
+ "build:lib": "tsc && tsc -p tsconfig.exports.json",
47
49
  "build:release": "pnpm build && ./scripts/prepare_release.ts",
48
50
  "fmt": "prettier --write .",
49
51
  "fmtcheck": "prettier --check .",
50
52
  "test": "node --test \"test/**/*.test.ts\"",
51
53
  "test:rebuild": "pnpm build && pnpm test",
52
- "typecheck": "tsc --noEmit"
54
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.client.json --noEmit"
53
55
  }
54
56
  }
package/client/esbuild.js DELETED
@@ -1 +0,0 @@
1
- new EventSource("http://127.0.0.1:3995/esbuild").addEventListener("change",t=>{const{updated:i}=JSON.parse(t.data),e=new Set;for(const s of i)e.add(s);const c=Array.from(e).filter(s=>s.endsWith(".css"));if(c.length){console.log("esbuild css updates",c);const s={};for(const o of document.getElementsByTagName("link"))if(o.getAttribute("rel")==="stylesheet"){const n=new URL(o.href);(n.host=location.host)&&(s[n.pathname]=o)}let a=!1;for(const o of c){const n=s[o];if(n){const r=n.cloneNode();r.href=`${o}?${Math.random().toString(36).slice(2)}`,r.onload=()=>n.remove(),n.parentNode.insertBefore(r,n.nextSibling),a=!0}}a&&l()}if(c.length<e.size){const s=Array.from(e).filter(o=>!o.endsWith(".css")),a=new Set;for(const o of document.getElementsByTagName("script"))if(o.src.length){const n=new URL(o.src);(n.host=location.host)&&a.add(n.pathname)}s.some(o=>a.has(o))&&(console.log("esbuild js updates require reload"),p())}});function l(){const t=d("green","9999");t.style.opacity="0",t.animate([{opacity:0},{opacity:1},{opacity:1},{opacity:1},{opacity:.75},{opacity:.5},{opacity:.25},{opacity:0}],{duration:400,iterations:1,direction:"normal",easing:"linear"}),document.body.appendChild(t),Promise.all(t.getAnimations().map(i=>i.finished)).then(()=>t.remove())}function p(){const t=d("orange","9000");t.style.opacity="0",t.animate([{opacity:0},{opacity:1}],{duration:400,iterations:1,direction:"normal",easing:"ease-in"}),document.body.appendChild(t)}function d(t,i){const e=document.createElement("div");return e.style.border="6px dashed "+t,e.style.zIndex=i,e.style.position="fixed",e.style.top=e.style.left="1px",e.style.height=e.style.width="calc(100% - 2px)",e.style.boxSizing="border-box",e}export{l as addCssUpdateIndicator};