@eighty4/dank 0.0.1-2 → 0.0.1-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.
@@ -0,0 +1,77 @@
1
+ new EventSource('http://127.0.0.1:2999/esbuild').addEventListener('change', (e) => {
2
+ const change = JSON.parse(e.data);
3
+ const cssUpdates = change.updated.filter(p => p.endsWith('.css'));
4
+ if (cssUpdates.length) {
5
+ console.log('esbuild css updates', cssUpdates);
6
+ const cssLinks = {};
7
+ for (const elem of document.getElementsByTagName('link')) {
8
+ if (elem.getAttribute('rel') === 'stylesheet') {
9
+ const url = new URL(elem.href);
10
+ if ((url.host = location.host)) {
11
+ cssLinks[url.pathname] = elem;
12
+ }
13
+ }
14
+ }
15
+ let swappedCss = false;
16
+ for (const cssUpdate of cssUpdates) {
17
+ const cssLink = cssLinks[cssUpdate];
18
+ if (cssLink) {
19
+ const next = cssLink.cloneNode();
20
+ next.href = `${cssUpdate}?${Math.random().toString(36).slice(2)}`;
21
+ next.onload = () => cssLink.remove();
22
+ cssLink.parentNode.insertBefore(next, cssLink.nextSibling);
23
+ swappedCss = true;
24
+ }
25
+ }
26
+ if (swappedCss) {
27
+ addCssUpdateIndicator();
28
+ }
29
+ }
30
+ if (cssUpdates.length < change.updated.length) {
31
+ const jsUpdates = change.updated.filter(p => !p.endsWith('.css'));
32
+ const jsScripts = new Set();
33
+ for (const elem of document.getElementsByTagName('script')) {
34
+ if (elem.src.length) {
35
+ const url = new URL(elem.src);
36
+ if ((url.host = location.host)) {
37
+ jsScripts.add(url.pathname);
38
+ }
39
+ }
40
+ }
41
+ if (jsUpdates.some(jsUpdate => jsScripts.has(jsUpdate))) {
42
+ console.log('esbuild js updates require reload');
43
+ addJsReloadIndicator();
44
+ }
45
+ }
46
+ });
47
+ function addCssUpdateIndicator() {
48
+ const indicator = createUpdateIndicator('green', '9999');
49
+ indicator.style.transition = 'opacity ease-in-out .38s';
50
+ indicator.style.opacity = '0';
51
+ indicator.ontransitionend = () => {
52
+ if (indicator.style.opacity === '1') {
53
+ indicator.style.opacity = '0';
54
+ }
55
+ else {
56
+ indicator.remove();
57
+ indicator.onload = null;
58
+ indicator.ontransitionend = null;
59
+ }
60
+ };
61
+ document.body.appendChild(indicator);
62
+ setTimeout(() => (indicator.style.opacity = '1'), 0);
63
+ }
64
+ function addJsReloadIndicator() {
65
+ document.body.appendChild(createUpdateIndicator('orange', '9000'));
66
+ }
67
+ function createUpdateIndicator(color, zIndex) {
68
+ const indicator = document.createElement('div');
69
+ indicator.style.border = '6px dashed ' + color;
70
+ indicator.style.zIndex = zIndex;
71
+ indicator.style.position = 'fixed';
72
+ indicator.style.top = indicator.style.left = '1px';
73
+ indicator.style.height = indicator.style.width = 'calc(100% - 2px)';
74
+ indicator.style.boxSizing = 'border-box';
75
+ return indicator;
76
+ }
77
+ export {};
package/lib/esbuild.ts CHANGED
@@ -32,7 +32,7 @@ export async function esbuildDevContext(
32
32
  return await esbuild.context({
33
33
  define,
34
34
  entryNames: '[dir]/[name]',
35
- entryPoints,
35
+ entryPoints: removeEntryPointOutExt(entryPoints),
36
36
  outdir,
37
37
  ...webpageBuildOptions,
38
38
  })
@@ -54,6 +54,9 @@ export async function esbuildWebpages(
54
54
  return buildResult.metafile
55
55
  }
56
56
 
57
+ // esbuild will append the .js or .css to output filenames
58
+ // keeping extension on entryPoints data for consistency
59
+ // and removing and mapping entryPoints to pass to esbuild
57
60
  function removeEntryPointOutExt(
58
61
  entryPoints: Array<{ in: string; out: string }>,
59
62
  ) {
package/lib/html.ts CHANGED
@@ -85,6 +85,19 @@ export class HtmlEntrypoint {
85
85
  }
86
86
  }
87
87
 
88
+ appendScript(clientJS: string) {
89
+ const scriptNode = parseFragment(
90
+ `<script type="module">${clientJS}</script>`,
91
+ ).childNodes[0]
92
+ const htmlNode = this.#document.childNodes.find(
93
+ node => node.nodeName === 'html',
94
+ ) as ParentNode
95
+ const headNode = htmlNode.childNodes.find(
96
+ node => node.nodeName === 'head',
97
+ ) as ParentNode | undefined
98
+ defaultTreeAdapter.appendChild(headNode || htmlNode, scriptNode)
99
+ }
100
+
88
101
  async writeTo(buildDir: string): Promise<void> {
89
102
  await writeFile(
90
103
  join(buildDir, this.#url, 'index.html'),
package/lib/serve.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { mkdir, rm } from 'node:fs/promises'
2
- import { join } from 'node:path'
1
+ import { mkdir, readFile, rm } from 'node:fs/promises'
2
+ import { join, resolve } from 'node:path'
3
3
  import { buildWebsite } from './build.ts'
4
4
  import type { DankConfig } from './dank.ts'
5
5
  import { createGlobalDefinitions } from './define.ts'
@@ -47,6 +47,11 @@ async function startEsbuildWatch(c: DankConfig): Promise<{ port: number }> {
47
47
  await mkdir(watchDir, { recursive: true })
48
48
  await copyAssets(watchDir)
49
49
 
50
+ const clientJS = await readFile(
51
+ resolve(import.meta.dirname, join('..', 'client', 'esbuild.js')),
52
+ 'utf-8',
53
+ )
54
+
50
55
  const entryPointUrls: Set<string> = new Set()
51
56
  const entryPoints: Array<{ in: string; out: string }> = []
52
57
 
@@ -70,6 +75,7 @@ async function startEsbuildWatch(c: DankConfig): Promise<{ port: number }> {
70
75
  })
71
76
  })
72
77
  html.rewriteHrefs()
78
+ html.appendScript(clientJS)
73
79
  await html.writeTo(watchDir)
74
80
  return html
75
81
  }),
package/lib_js/esbuild.js CHANGED
@@ -18,7 +18,7 @@ export async function esbuildDevContext(define, entryPoints, outdir) {
18
18
  return await esbuild.context({
19
19
  define,
20
20
  entryNames: '[dir]/[name]',
21
- entryPoints,
21
+ entryPoints: removeEntryPointOutExt(entryPoints),
22
22
  outdir,
23
23
  ...webpageBuildOptions,
24
24
  });
@@ -34,6 +34,9 @@ export async function esbuildWebpages(define, entryPoints, outdir) {
34
34
  esbuildResultChecks(buildResult);
35
35
  return buildResult.metafile;
36
36
  }
37
+ // esbuild will append the .js or .css to output filenames
38
+ // keeping extension on entryPoints data for consistency
39
+ // and removing and mapping entryPoints to pass to esbuild
37
40
  function removeEntryPointOutExt(entryPoints) {
38
41
  return entryPoints.map(entryPoint => {
39
42
  return {
package/lib_js/html.js CHANGED
@@ -52,6 +52,12 @@ export class HtmlEntrypoint {
52
52
  }
53
53
  }
54
54
  }
55
+ appendScript(clientJS) {
56
+ const scriptNode = parseFragment(`<script type="module">${clientJS}</script>`).childNodes[0];
57
+ const htmlNode = this.#document.childNodes.find(node => node.nodeName === 'html');
58
+ const headNode = htmlNode.childNodes.find(node => node.nodeName === 'head');
59
+ defaultTreeAdapter.appendChild(headNode || htmlNode, scriptNode);
60
+ }
55
61
  async writeTo(buildDir) {
56
62
  await writeFile(join(buildDir, this.#url, 'index.html'), serialize(this.#document));
57
63
  }
package/lib_js/serve.js CHANGED
@@ -1,5 +1,5 @@
1
- import { mkdir, rm } from 'node:fs/promises';
2
- import { join } from 'node:path';
1
+ import { mkdir, readFile, rm } from 'node:fs/promises';
2
+ import { join, resolve } from 'node:path';
3
3
  import { buildWebsite } from "./build.js";
4
4
  import { createGlobalDefinitions } from "./define.js";
5
5
  import { esbuildDevContext } from "./esbuild.js";
@@ -33,6 +33,7 @@ async function startEsbuildWatch(c) {
33
33
  const watchDir = join('build', 'watch');
34
34
  await mkdir(watchDir, { recursive: true });
35
35
  await copyAssets(watchDir);
36
+ const clientJS = await readFile(resolve(import.meta.dirname, join('..', 'client', 'esbuild.js')), 'utf-8');
36
37
  const entryPointUrls = new Set();
37
38
  const entryPoints = [];
38
39
  await Promise.all(Object.entries(c.pages).map(async ([url, srcPath]) => {
@@ -51,6 +52,7 @@ async function startEsbuildWatch(c) {
51
52
  });
52
53
  });
53
54
  html.rewriteHrefs();
55
+ html.appendScript(clientJS);
54
56
  await html.writeTo(watchDir);
55
57
  return html;
56
58
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eighty4/dank",
3
- "version": "0.0.1-2",
3
+ "version": "0.0.1-3",
4
4
  "type": "module",
5
5
  "bin": "./lib_js/bin.js",
6
6
  "exports": {
@@ -20,12 +20,13 @@
20
20
  "typescript": "^5.9.2"
21
21
  },
22
22
  "files": [
23
+ "client/esbuild.js",
23
24
  "lib/*.ts",
24
25
  "lib_js/*.js",
25
26
  "lib_types/*.d.ts"
26
27
  ],
27
28
  "scripts": {
28
- "build": "tsc && tsc -p tsconfig.exports.json",
29
+ "build": "tsc && tsc -p tsconfig.client.json && tsc -p tsconfig.exports.json",
29
30
  "fmt": "prettier --write .",
30
31
  "fmtcheck": "prettier --check .",
31
32
  "typecheck": "tsc --noEmit"