@oidoid/void 0.1.0-2 → 0.1.0-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/package.json +2 -3
- package/tools/void.js +143 -0
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Stephen Niedzielski <stephen@oidoid.com> (https://oidoid.com)",
|
|
3
3
|
"bin": {
|
|
4
|
-
"
|
|
5
|
-
"void": "bin/void"
|
|
4
|
+
"void": "tools/void.js"
|
|
6
5
|
},
|
|
7
6
|
"bugs": "https://github.com/oidoid/void/issues",
|
|
8
7
|
"description": "Tiny 2D game engine.",
|
|
@@ -69,5 +68,5 @@
|
|
|
69
68
|
},
|
|
70
69
|
"type": "module",
|
|
71
70
|
"types": "dist/index.d.ts",
|
|
72
|
-
"version": "0.1.0-
|
|
71
|
+
"version": "0.1.0-3"
|
|
73
72
|
}
|
package/tools/void.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings
|
|
2
|
+
// Bundles sources into a single HTML file for distribution and development.
|
|
3
|
+
//
|
|
4
|
+
// void --html=file --out=dir [--watch] sprite...
|
|
5
|
+
// --watch Run development server. Serve on http://localhost:1234 and reload on
|
|
6
|
+
// code change.
|
|
7
|
+
//
|
|
8
|
+
// --no-warnings shebang works around JSON import warnings. See
|
|
9
|
+
// https://github.com/nodejs/node/issues/27355 and
|
|
10
|
+
// https://github.com/nodejs/node/issues/40940.
|
|
11
|
+
|
|
12
|
+
import {execFile} from 'child_process'
|
|
13
|
+
import esbuild from 'esbuild'
|
|
14
|
+
import {JSDOM} from 'jsdom'
|
|
15
|
+
import fs from 'node:fs/promises'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import pkg from '../package.json' assert {type: 'json'}
|
|
18
|
+
import {parseAtlas} from '../src/atlas/atlas-parser.js'
|
|
19
|
+
|
|
20
|
+
const args = process.argv.filter(arg => !arg.startsWith('--'))
|
|
21
|
+
const opts = Object.fromEntries(
|
|
22
|
+
process.argv.filter(arg => arg.startsWith('--')).map(arg => arg.split('='))
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
const watch = '--watch' in opts
|
|
26
|
+
const inFilename = opts['--html']
|
|
27
|
+
if (!inFilename) throw Error('missing input')
|
|
28
|
+
const outDir = opts['--out']
|
|
29
|
+
if (!outDir) throw Error('missing output')
|
|
30
|
+
const doc = new JSDOM(await fs.readFile(inFilename, 'utf8')).window.document
|
|
31
|
+
let srcFilename = /** @type {HTMLScriptElement|null} */ (
|
|
32
|
+
doc.querySelector('script[type="module"][src]')
|
|
33
|
+
)?.src
|
|
34
|
+
if (!srcFilename) throw Error('missing script source')
|
|
35
|
+
srcFilename = `${path.dirname(inFilename)}/${srcFilename}`
|
|
36
|
+
const sprites = args.splice(2)
|
|
37
|
+
if (!sprites.length) throw Error('missing sprites')
|
|
38
|
+
|
|
39
|
+
const atlasPNGFilename = `${await fs.mkdtemp('/tmp/', {encoding: 'utf8'})}/atlas.png`
|
|
40
|
+
const [err, stdout, stderr] = await new Promise(resolve =>
|
|
41
|
+
execFile(
|
|
42
|
+
'aseprite',
|
|
43
|
+
[
|
|
44
|
+
'--batch',
|
|
45
|
+
'--color-mode=indexed',
|
|
46
|
+
'--filename-format={title}--{tag}--{frame}',
|
|
47
|
+
// '--ignore-empty', Breaks --tagname-format.
|
|
48
|
+
'--list-slices',
|
|
49
|
+
'--list-tags',
|
|
50
|
+
'--merge-duplicates',
|
|
51
|
+
`--sheet=${atlasPNGFilename}`,
|
|
52
|
+
'--sheet-pack',
|
|
53
|
+
'--tagname-format={title}--{tag}',
|
|
54
|
+
...sprites
|
|
55
|
+
],
|
|
56
|
+
(err, stdout, stderr) => resolve([err, stdout, stderr])
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
process.stderr.write(stderr)
|
|
60
|
+
if (err) throw err
|
|
61
|
+
|
|
62
|
+
const atlasJSON = JSON.stringify(parseAtlas(JSON.parse(stdout)))
|
|
63
|
+
const atlasURI =
|
|
64
|
+
await `data:image/png;base64,${(await fs.readFile(atlasPNGFilename)).toString('base64')}`
|
|
65
|
+
|
|
66
|
+
/** @type {Parameters<esbuild.PluginBuild['onEnd']>[0]} */
|
|
67
|
+
async function pluginOnEnd(result) {
|
|
68
|
+
const copy = /** @type {Document} */ (doc.cloneNode(true))
|
|
69
|
+
const manifestEl = /** @type {HTMLLinkElement|null} */ (
|
|
70
|
+
copy.querySelector('link[href][rel="manifest"]')
|
|
71
|
+
)
|
|
72
|
+
if (manifestEl) {
|
|
73
|
+
const manifestFilename = `${path.dirname(inFilename)}/${manifestEl.href}`
|
|
74
|
+
const manifest = JSON.parse(await fs.readFile(manifestFilename, 'utf8'))
|
|
75
|
+
for (const icon of manifest.icons) {
|
|
76
|
+
if (!icon.src) throw Error('missing manifest icon src')
|
|
77
|
+
if (!icon.type) throw Error('missing manifest icon type')
|
|
78
|
+
const file = await fs.readFile(
|
|
79
|
+
`${path.dirname(manifestFilename)}/${icon.src}`
|
|
80
|
+
)
|
|
81
|
+
icon.src = `data:${icon.type};base64,${file.toString('base64')}`
|
|
82
|
+
}
|
|
83
|
+
if (watch) manifest.start_url = 'http://localhost:1234'
|
|
84
|
+
manifest.version = pkg.version
|
|
85
|
+
manifestEl.href = `data:application/json,${encodeURIComponent(
|
|
86
|
+
JSON.stringify(manifest)
|
|
87
|
+
)}`
|
|
88
|
+
}
|
|
89
|
+
const iconEl = /** @type {HTMLLinkElement|null} */ (
|
|
90
|
+
copy.querySelector('link[href][rel="icon"][type]')
|
|
91
|
+
)
|
|
92
|
+
if (iconEl) {
|
|
93
|
+
const file = await fs.readFile(`${path.dirname(inFilename)}/${iconEl.href}`)
|
|
94
|
+
iconEl.href = `data:${iconEl.type};base64,${file.toString('base64')}`
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let js = ''
|
|
98
|
+
if (watch)
|
|
99
|
+
js +=
|
|
100
|
+
"new globalThis.EventSource('/esbuild').addEventListener('change', () => globalThis.location.reload());"
|
|
101
|
+
|
|
102
|
+
const outFiles =
|
|
103
|
+
result.outputFiles?.filter(file => file.path.endsWith('.js')) ?? []
|
|
104
|
+
if (outFiles.length > 1) throw Error('cannot concatenate JavaScript files')
|
|
105
|
+
if (outFiles[0]) js += outFiles[0].text
|
|
106
|
+
|
|
107
|
+
const scriptEl = /** @type {HTMLScriptElement|null} */ (
|
|
108
|
+
copy.querySelector('script[type="module"][src]')
|
|
109
|
+
)
|
|
110
|
+
if (!scriptEl) throw Error('missing script')
|
|
111
|
+
scriptEl.removeAttribute('src')
|
|
112
|
+
scriptEl.textContent = `
|
|
113
|
+
const atlasURI = '${atlasURI}'
|
|
114
|
+
const atlas = ${atlasJSON}
|
|
115
|
+
${js}
|
|
116
|
+
`
|
|
117
|
+
const outFilename = `${outDir}/${
|
|
118
|
+
watch ? 'index' : `${path.basename(inFilename, '.html')}-v${pkg.version}`
|
|
119
|
+
}.html`
|
|
120
|
+
await fs.mkdir(path.dirname(outFilename), {recursive: true})
|
|
121
|
+
await fs.writeFile(
|
|
122
|
+
outFilename,
|
|
123
|
+
`<!doctype html>${copy.documentElement.outerHTML}`
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @type {esbuild.BuildOptions} */
|
|
128
|
+
const buildOpts = {
|
|
129
|
+
bundle: true,
|
|
130
|
+
entryPoints: [srcFilename],
|
|
131
|
+
format: 'esm',
|
|
132
|
+
logLevel: `info`, // Print the port and build demarcations.
|
|
133
|
+
minify: !watch,
|
|
134
|
+
outdir: outDir,
|
|
135
|
+
plugins: [{name: 'void', setup: build => build.onEnd(pluginOnEnd)}],
|
|
136
|
+
sourcemap: 'linked',
|
|
137
|
+
target: 'es2022', // https://esbuild.github.io/content-types/#tsconfig-json
|
|
138
|
+
write: false // Written by plugin.
|
|
139
|
+
}
|
|
140
|
+
if (watch) {
|
|
141
|
+
const ctx = await esbuild.context(buildOpts)
|
|
142
|
+
await Promise.race([ctx.watch(), ctx.serve({port: 1234, servedir: 'dist'})])
|
|
143
|
+
} else await esbuild.build(buildOpts)
|