packaton 0.0.17 → 0.0.22

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/index.d.ts CHANGED
@@ -10,11 +10,12 @@ export interface Config {
10
10
  port?: number
11
11
  onReady?: (address: string) => void
12
12
  hotReload?: boolean // For UI dev purposes only
13
+ watchIgnore?: Array<string|RegExp>
13
14
 
14
15
  // Production
15
16
  outputDir?: string
16
17
  outputExtension?: string
17
- minifyJS?: (js: string) => Promise<string>
18
+ minifyJS?: (js: string, isModule: boolean) => Promise<string>
18
19
  minifyCSS?: (css: string) => Promise<string>
19
20
  minifyHTML?: (html: string) => Promise<string>
20
21
  sitemapDomain?: string
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "packaton",
3
- "version": "0.0.17",
3
+ "version": "0.0.22",
4
4
  "type": "module",
5
5
  "author": "Eric Fortis",
6
6
  "license": "MIT",
package/src/app-dev.js CHANGED
@@ -1,4 +1,5 @@
1
- import http from 'node:http'
1
+ import { register } from 'node:module'
2
+ import { createServer } from 'node:http'
2
3
 
3
4
  import { router } from './router.js'
4
5
  import { watchDev } from './plugins-dev/WatcherDevClient.js'
@@ -10,10 +11,12 @@ import { watchDev } from './plugins-dev/WatcherDevClient.js'
10
11
  */
11
12
  export function devStaticPages(config) {
12
13
  return new Promise((resolve, reject) => {
14
+ register('./plugins-dev/cache-bust-resolver.js', import.meta.url)
15
+
13
16
  if (config.hotReload)
14
- watchDev(config.srcPath)
17
+ watchDev(config.srcPath, config.watchIgnore)
15
18
 
16
- const server = http.createServer(router(config))
19
+ const server = createServer(router(config))
17
20
  server.on('error', reject)
18
21
  server.listen(config.port, config.host, () => {
19
22
  const addr = `http://${server.address().address}:${server.address().port}`
package/src/config.js CHANGED
@@ -25,6 +25,7 @@ const schema = {
25
25
  port: [0, port => Number.isInteger(port) && port >= 0 && port < 2 ** 16], // 0 means auto-assigned
26
26
  onReady: [await openInBrowser, is(Function)],
27
27
  hotReload: [true, is(Boolean)],
28
+ watchIgnore: [[], Array.isArray], // TODO Array<string|RegExp>
28
29
 
29
30
  // Production
30
31
  outputExtension: ['.html', optional(String)],
@@ -10,8 +10,10 @@ export const devClientWatcher = new class extends EventEmitter {
10
10
  unsubscribe(listener) { this.removeListener('RELOAD', listener) }
11
11
  }
12
12
 
13
- export function watchDev(rootPath) {
13
+ export function watchDev(rootPath, watchIgnore) {
14
14
  watch(rootPath, { recursive: true }, (_, file) => {
15
+ if (watchIgnore.some(f => f === file)) // TODO handle regexes
16
+ return
15
17
  docs.onWatch(join(rootPath, file))
16
18
  devClientWatcher.emit(file)
17
19
  })
@@ -0,0 +1,14 @@
1
+ // We register this hook at runtime so it doesn’t interfere with non-dynamic imports.
2
+ export async function resolve(specifier, context, nextResolve) {
3
+ const result = await nextResolve(specifier, context);
4
+ if (result.url?.startsWith('file:')) {
5
+ const url = new URL(result.url)
6
+ url.searchParams.set('t', performance.now())
7
+ return {
8
+ ...result,
9
+ url: url.href,
10
+ shortCircuit: true
11
+ }
12
+ }
13
+ return result
14
+ }
@@ -9,7 +9,7 @@ async function longPollDevChanges() {
9
9
 
10
10
  const file = await response.json() || ''
11
11
  if (file.endsWith('.css')) {
12
- hotReloadCSS(file)
12
+ await hotReloadCSS(file)
13
13
  longPollDevChanges()
14
14
  }
15
15
  else if (file)
@@ -23,11 +23,15 @@ async function longPollDevChanges() {
23
23
  }
24
24
  }
25
25
 
26
- function hotReloadCSS(file) {
26
+ async function hotReloadCSS(file) {
27
27
  let link = document.querySelector(`link[href^="/${file}"]`)
28
28
  if (link) {
29
29
  const [url] = link.getAttribute('href').split('?')
30
30
  link.href = url + '?' + Date.now()
31
31
  }
32
+ else {
33
+ const mod = await import(`/${file}?${Date.now()}`, { with: { type: 'css' } })
34
+ document.adoptedStyleSheets = [mod.default]
35
+ }
32
36
  }
33
37
 
@@ -49,7 +49,7 @@ export class HtmlCompiler {
49
49
  }
50
50
  if (this.css) {
51
51
  this.css = await this.#minifyCSS(this.css)
52
- this.html = this.html.replace('</head>', `</head><style>${this.css}</style>`)
52
+ this.#appendToHead(`<style>${this.css}</style>`)
53
53
  }
54
54
  }
55
55
 
@@ -71,44 +71,36 @@ export class HtmlCompiler {
71
71
 
72
72
  this.scriptsModuleJs = await Promise.all(scripts
73
73
  .filter(([type]) => type === 'module')
74
- .map(([, body]) => this.#minifyJS(body)))
74
+ .map(([, body]) => this.#minifyJS(body, Boolean('isModule'))))
75
75
 
76
- this.scriptsNonJs = scripts.filter(([type]) => type !== 'application/javascript' && type !== 'module')
77
-
78
76
  this.scriptsNonJs = scripts
79
77
  .filter(([type]) => type !== 'application/javascript' && type !== 'module')
80
78
 
81
79
  if (this.scriptsJs)
82
- this.html = this.html.replace('</body>', `\n<script>${this.scriptsJs}</script></body>`)
80
+ this.#appendToBody(`<script>${this.scriptsJs}</script>`)
83
81
 
84
82
  for (const body of this.scriptsModuleJs)
85
- this.html = this.html.replace('</body>', `\n<script type="module">${body}</script></body>`)
83
+ this.#appendToBody(`<script type="module">${body}</script>`)
86
84
 
87
85
  for (const [type, body] of this.scriptsNonJs)
88
- this.html = this.html.replace('</body>', `\n<script type="${type}">${body}</script></body>`)
89
-
86
+ this.#appendToBody(`<script type="${type}">${body}</script>`)
90
87
  }
91
88
 
89
+
92
90
  csp() {
93
- const cssHash = this.css
94
- ? `'${this.hash256(this.css)}'`
95
- : '' // TODO maybe self?
96
- const jsScriptHash = this.scriptsJs
97
- ? `'${this.hash256(this.scriptsJs)}'`
98
- : '' // TODO maybe self?
91
+ const cssHash = this.css ? `'${this.hash256(this.css)}'` : '' // TODO maybe self?
99
92
 
93
+ const jsScriptHash = this.scriptsJs ? `'${this.hash256(this.scriptsJs)}'` : '' // TODO maybe self?
100
94
  const jsModulesHashes = this.scriptsModuleJs.map(body => `'${this.hash256(body)}'`).join(' ')
101
-
102
95
  const nonJsScriptHashes = this.scriptsNonJs.map(([, body]) => `'${this.hash256(body)}'`).join(' ')
103
-
104
- const externalScriptHashes = this.externalScripts.map(url => `${new URL(url).origin}`).join(' ')
105
-
106
96
  const inlineScriptsHashes = this.extractInlineScripts().map(body => `'${this.hash256(body)}'`).join(' ')
97
+ const externalScriptDomains = this.externalScripts.map(url => `${new URL(url).origin}`).join(' ')
98
+
107
99
  return [
108
100
  `default-src 'self'`,
109
101
  `img-src 'self' data:`, // data: is for Safari's video player icons and for CSS bg images
110
102
  `style-src ${cssHash}`,
111
- `script-src-elem ${nonJsScriptHashes} ${jsScriptHash} ${jsModulesHashes} ${externalScriptHashes} ${inlineScriptsHashes} 'self'`,
103
+ `script-src-elem ${nonJsScriptHashes} ${jsScriptHash} ${jsModulesHashes} ${externalScriptDomains} ${inlineScriptsHashes} 'self'`,
112
104
  `frame-ancestors 'none'`
113
105
  ].join('; ')
114
106
  }
@@ -119,10 +111,17 @@ export class HtmlCompiler {
119
111
  : ''
120
112
  }
121
113
 
114
+ #appendToHead(tag) {
115
+ this.html = this.html.replace('</head>', `\n${tag}</head>`)
116
+ }
117
+ #appendToBody(tag) {
118
+ this.html = this.html.replace('</body>', `\n${tag}</body>`)
119
+ }
122
120
  removeLineContaining(str) {
123
121
  this.html = this.html.replace(new RegExp('^.*' + str + '.*\n', 'm'), '')
124
122
  }
125
123
 
124
+
126
125
  extractStyleSheetHrefs() {
127
126
  const reExtractStyleSheets = /(?<=<link\s.*href=")[^"]+\.css/g
128
127
  return Array.from(this.html.matchAll(reExtractStyleSheets), m => m[0])
@@ -1,6 +1,6 @@
1
1
  import { minify } from 'terser'
2
2
 
3
3
 
4
- export async function minifyJS(code, options) {
5
- return (await minify(code, options)).code
4
+ export async function minifyJS(code, isModule) {
5
+ return (await minify(code, { module: isModule })).code
6
6
  }