packaton 0.0.22 → 0.0.24

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "packaton",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "type": "module",
5
5
  "author": "Eric Fortis",
6
6
  "license": "MIT",
@@ -18,6 +18,6 @@
18
18
  "terser": "^5"
19
19
  },
20
20
  "devDependencies": {
21
- "terser": "5.44.1"
21
+ "terser": "5.46.1"
22
22
  }
23
23
  }
@@ -6,7 +6,7 @@ import { docs } from '../app.js'
6
6
 
7
7
  export const devClientWatcher = new class extends EventEmitter {
8
8
  emit(file) { super.emit('RELOAD', file) }
9
- subscribe(listener) { this.once('RELOAD', listener) }
9
+ subscribe(listener) { this.on('RELOAD', listener) }
10
10
  unsubscribe(listener) { this.removeListener('RELOAD', listener) }
11
11
  }
12
12
 
@@ -1,28 +1,37 @@
1
1
  const WATCH_API = '/packaton/watch-dev'
2
2
 
3
- longPollDevChanges()
4
- async function longPollDevChanges() {
5
- try {
6
- const response = await fetch(WATCH_API)
7
- if (!response.ok)
8
- throw response.statusText
9
-
10
- const file = await response.json() || ''
11
- if (file.endsWith('.css')) {
12
- await hotReloadCSS(file)
13
- longPollDevChanges()
14
- }
3
+ let es = null
4
+ let timer = null
5
+
6
+ window.addEventListener('beforeunload', teardown)
7
+ connect()
8
+ function connect() {
9
+ if (es) return
10
+
11
+ clearTimeout(timer)
12
+ es = new EventSource(WATCH_API)
13
+
14
+ es.onmessage = function (event) {
15
+ const file = event.data
16
+ if (file.endsWith('.css'))
17
+ hotReloadCSS(file)
15
18
  else if (file)
16
19
  location.reload()
17
- else // server timeout
18
- longPollDevChanges()
19
20
  }
20
- catch (error) {
21
- console.error('hot reload', error?.message || error)
22
- setTimeout(longPollDevChanges, 3000)
21
+
22
+ es.onerror = function () {
23
+ console.error('hot reload')
24
+ teardown()
25
+ timer = setTimeout(connect, 3000)
23
26
  }
24
27
  }
25
28
 
29
+ function teardown() {
30
+ clearTimeout(timer)
31
+ es?.close()
32
+ es = null
33
+ }
34
+
26
35
  async function hotReloadCSS(file) {
27
36
  let link = document.querySelector(`link[href^="/${file}"]`)
28
37
  if (link) {
@@ -33,7 +33,7 @@ export function remapMediaInHTML(mediaHashes, html, mediaRelUrl) {
33
33
  }
34
34
 
35
35
 
36
- function escapeRegex(literal) {
36
+ function escapeRegex(literal = '') {
37
37
  return literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
38
38
  }
39
39
 
@@ -5,25 +5,25 @@ import { remapMediaInHTML } from './media-remaper.js'
5
5
 
6
6
  describe('Media Remapper', () => {
7
7
  const mHashes = new Map([
8
- ['alpha.png', '0xFA.png'],
9
- ['beta.png', '0xFB.png'],
10
- ['chi.png', '0xFC.png']
8
+ ['/media/alpha.png', '/media/0xFA.png'],
9
+ ['/media/beta.png', '/media/0xFB.png'],
10
+ ['/media/chi.png', '/media/0xFC.png']
11
11
  ])
12
12
 
13
13
  test('Throws when the file does not exist', () =>
14
- throws(() => remapMediaInHTML(mHashes, `<video src="media/missing.mp4">`)))
14
+ throws(() => remapMediaInHTML(mHashes, `<video src="/media/missing.mp4">`)))
15
15
 
16
16
  test('Acceptance', () =>
17
17
  equal(remapMediaInHTML(mHashes, `
18
- <img src="media/alpha.png">
19
- <img src="media/alpha.png">
20
- <img src="media/beta.png">
21
- <video poster="media/chi.png">`),
18
+ <img src="/media/alpha.png">
19
+ <img src="/media/alpha.png">
20
+ <img src="/media/beta.png">
21
+ <video poster="/media/chi.png">`),
22
22
  `
23
- <img src="media/0xFA.png">
24
- <img src="media/0xFA.png">
25
- <img src="media/0xFB.png">
26
- <video poster="media/0xFC.png">`))
23
+ <img src="/media/0xFA.png">
24
+ <img src="/media/0xFA.png">
25
+ <img src="/media/0xFB.png">
26
+ <video poster="/media/0xFC.png">`))
27
27
 
28
28
 
29
- })
29
+ })
package/src/router.js CHANGED
@@ -33,16 +33,16 @@ export function router({ srcPath, ignore, mode }) {
33
33
  })
34
34
 
35
35
  else if (url === API.watchDev)
36
- longPollDevHotReload(req, response)
36
+ sseDevHotReload(req, response)
37
37
 
38
38
  else if (url === WATCHER_DEV)
39
39
  serveAsset(response, join(import.meta.dirname, url))
40
40
 
41
41
  else if (docs.hasRoute(url))
42
- await serveDocument(response, docs.fileFor(url), isDev)
42
+ await serveDocument(response, docs.fileFor(url), url, isDev)
43
43
 
44
44
  else if (docs.hasRoute(join(url, 'index')))
45
- await serveDocument(response, docs.fileFor(join(url, 'index')), isDev)
45
+ await serveDocument(response, docs.fileFor(join(url, 'index')), '', isDev)
46
46
 
47
47
  else if (req.headers.range)
48
48
  await servePartialContent(response, req.headers, join(srcPath, url))
@@ -56,10 +56,10 @@ export function router({ srcPath, ignore, mode }) {
56
56
  }
57
57
  }
58
58
 
59
- async function serveDocument(response, file, isDev) {
59
+ async function serveDocument(response, file, url, isDev) {
60
60
  let html = file.endsWith('.html')
61
61
  ? await readFile(file, 'utf8')
62
- : (await import(file + '?' + Date.now())).default()
62
+ : (await import(file + '?' + Date.now())).default(url)
63
63
  if (isDev)
64
64
  html += `<script type="module" src="${WATCHER_DEV}"></script>`
65
65
  response.setHeader('Content-Type', mimeFor('html'))
@@ -67,22 +67,30 @@ async function serveDocument(response, file, isDev) {
67
67
  }
68
68
 
69
69
 
70
- const LONG_POLL_SERVER_TIMEOUT = 8000
70
+ function sseDevHotReload(req, response) {
71
+ response.writeHead(200, {
72
+ 'Content-Type': 'text/event-stream',
73
+ 'Cache-Control': 'no-cache',
74
+ 'Connection': 'keep-alive',
75
+ })
76
+ response.flushHeaders()
71
77
 
72
- function longPollDevHotReload(req, response) {
73
- function onDevChange(file) {
74
- devClientWatcher.unsubscribe(onDevChange)
75
- sendJSON(response, file)
78
+ function onDevChange(file = '') {
79
+ response.write(`data: ${file}\n\n`)
76
80
  }
77
- response.setTimeout(LONG_POLL_SERVER_TIMEOUT, () => {
78
- devClientWatcher.unsubscribe(onDevChange)
79
- sendJSON(response, '')
80
- })
81
- req.on('error', () => {
82
- devClientWatcher.unsubscribe(onDevChange)
83
- response.destroy()
84
- })
81
+
85
82
  devClientWatcher.subscribe(onDevChange)
83
+
84
+ const keepAlive = setInterval(() => {
85
+ response.write(': ping\n\n')
86
+ }, 10_000)
87
+
88
+ req.on('close', cleanup)
89
+ req.on('error', cleanup)
90
+ function cleanup() {
91
+ clearInterval(keepAlive)
92
+ devClientWatcher.unsubscribe(onDevChange)
93
+ }
86
94
  }
87
95
 
88
96