@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/client/client.js +1 -0
- package/lib/bin.ts +8 -11
- package/lib/build.ts +43 -70
- package/lib/build_tag.ts +3 -3
- package/lib/config.ts +184 -29
- package/lib/dank.ts +7 -0
- package/lib/define.ts +6 -4
- package/lib/developer.ts +33 -4
- package/lib/dirs.ts +83 -0
- package/lib/errors.ts +6 -0
- package/lib/esbuild.ts +19 -29
- package/lib/flags.ts +16 -122
- package/lib/html.ts +196 -112
- package/lib/http.ts +59 -43
- package/lib/public.ts +10 -10
- package/lib/{metadata.ts → registry.ts} +216 -83
- package/lib/serve.ts +101 -336
- package/lib/services.ts +8 -8
- package/lib/watch.ts +39 -0
- package/lib_js/bin.js +8 -10
- package/lib_js/build.js +31 -45
- package/lib_js/build_tag.js +2 -2
- package/lib_js/config.js +108 -29
- package/lib_js/define.js +3 -3
- package/lib_js/dirs.js +61 -0
- package/lib_js/errors.js +9 -0
- package/lib_js/esbuild.js +17 -19
- package/lib_js/flags.js +9 -98
- package/lib_js/html.js +127 -64
- package/lib_js/http.js +18 -18
- package/lib_js/public.js +9 -9
- package/lib_js/{metadata.js → registry.js} +121 -51
- package/lib_js/serve.js +53 -177
- package/lib_js/services.js +6 -6
- package/lib_js/watch.js +35 -0
- package/lib_types/dank.d.ts +5 -0
- package/package.json +6 -4
- package/client/esbuild.js +0 -1
|
@@ -1,52 +1,13 @@
|
|
|
1
1
|
import EventEmitter from 'node:events'
|
|
2
2
|
import { writeFile } from 'node:fs/promises'
|
|
3
|
-
import {
|
|
3
|
+
import { join } from 'node:path'
|
|
4
4
|
import type { BuildResult } from 'esbuild'
|
|
5
|
+
import type { ResolvedDankConfig } from './config.ts'
|
|
6
|
+
import { LOG } from './developer.ts'
|
|
7
|
+
import { Resolver, type DankDirectories } from './dirs.ts'
|
|
5
8
|
import type { EntryPoint } from './esbuild.ts'
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
export type ResolveError = 'outofbounds'
|
|
9
|
-
|
|
10
|
-
export type Resolver = {
|
|
11
|
-
// `p` is expected to be a relative path resolvable from the project dir
|
|
12
|
-
isProjectSubpathInPagesDir(p: string): boolean
|
|
13
|
-
|
|
14
|
-
// `p` is expected to be a relative path resolvable from the pages dir
|
|
15
|
-
isPagesSubpathInPagesDir(p: string): boolean
|
|
16
|
-
|
|
17
|
-
// resolve a pages subpath from a resource within the pages directory by a relative href
|
|
18
|
-
// `from` is expected to be a pages resource fs path starting with `pages/` and ending with filename
|
|
19
|
-
// the result will be a pages subpath and will not have the pages dir prefix
|
|
20
|
-
// returns 'outofbounds' if the relative path does not resolve to a file within the pages dir
|
|
21
|
-
resolveHrefInPagesDir(from: string, href: string): string | ResolveError
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
class ResolverImpl implements Resolver {
|
|
25
|
-
#dirs: ProjectDirs
|
|
26
|
-
|
|
27
|
-
constructor(dirs: ProjectDirs) {
|
|
28
|
-
this.#dirs = dirs
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
isProjectSubpathInPagesDir(p: string): boolean {
|
|
32
|
-
return resolve(join(this.#dirs.projectResolved, p)).startsWith(
|
|
33
|
-
this.#dirs.pagesResolved,
|
|
34
|
-
)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
isPagesSubpathInPagesDir(p: string): boolean {
|
|
38
|
-
return this.isProjectSubpathInPagesDir(join(this.#dirs.pages, p))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
resolveHrefInPagesDir(from: string, href: string): string | ResolveError {
|
|
42
|
-
const p = join(dirname(from), href)
|
|
43
|
-
if (this.isProjectSubpathInPagesDir(p)) {
|
|
44
|
-
return p
|
|
45
|
-
} else {
|
|
46
|
-
return 'outofbounds'
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
9
|
+
import { HtmlEntrypoint } from './html.ts'
|
|
10
|
+
import type { PageMapping } from './dank.ts'
|
|
50
11
|
|
|
51
12
|
// summary of a website build
|
|
52
13
|
export type WebsiteManifest = {
|
|
@@ -75,31 +36,55 @@ type WorkerManifest = {
|
|
|
75
36
|
// path to bundled entrypoint dependent on `clientScript`
|
|
76
37
|
dependentEntryPoint: string
|
|
77
38
|
workerEntryPoint: string
|
|
39
|
+
workerCtor: 'Worker' | 'SharedWorker'
|
|
78
40
|
workerUrl: string
|
|
79
41
|
workerUrlPlaceholder: string
|
|
80
42
|
}
|
|
81
43
|
|
|
82
44
|
export type WebsiteRegistryEvents = {
|
|
45
|
+
entrypoints: []
|
|
46
|
+
webpage: [entrypoint: HtmlEntrypoint]
|
|
83
47
|
workers: []
|
|
84
48
|
}
|
|
85
49
|
|
|
50
|
+
type WebpageRegistration = {
|
|
51
|
+
pageUrl: `/${string}`
|
|
52
|
+
fsPath: string
|
|
53
|
+
html: HtmlEntrypoint
|
|
54
|
+
bundles: Array<EntryPoint>
|
|
55
|
+
urlRewrite?: UrlRewrite
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type UrlRewrite = {
|
|
59
|
+
pattern: RegExp
|
|
60
|
+
url: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type UrlRewriteProvider = {
|
|
64
|
+
urlRewrites: Array<UrlRewrite>
|
|
65
|
+
}
|
|
66
|
+
|
|
86
67
|
// manages website resources during `dank build` and `dank serve`
|
|
87
68
|
export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
88
|
-
|
|
89
|
-
// paths of bundled esbuild outputs
|
|
69
|
+
// paths of bundled esbuild outputs, as built by esbuild
|
|
90
70
|
#bundles: Set<string> = new Set()
|
|
71
|
+
#c: ResolvedDankConfig
|
|
91
72
|
// public dir assets
|
|
92
73
|
#copiedAssets: Set<string> | null = null
|
|
93
74
|
// map of entrypoints to their output path
|
|
94
75
|
#entrypointHrefs: Record<string, string | null> = {}
|
|
95
|
-
#
|
|
96
|
-
#resolver: Resolver
|
|
76
|
+
#pages: Record<`/${string}`, WebpageRegistration> = {}
|
|
77
|
+
readonly #resolver: Resolver
|
|
97
78
|
#workers: Array<WorkerManifest> | null = null
|
|
98
79
|
|
|
99
|
-
constructor(
|
|
80
|
+
constructor(config: ResolvedDankConfig) {
|
|
100
81
|
super()
|
|
101
|
-
this.#
|
|
102
|
-
this.#resolver = new
|
|
82
|
+
this.#c = config
|
|
83
|
+
this.#resolver = new Resolver(config.dirs)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get config(): ResolvedDankConfig {
|
|
87
|
+
return this.#c
|
|
103
88
|
}
|
|
104
89
|
|
|
105
90
|
set copiedAssets(copiedAssets: Array<string> | null) {
|
|
@@ -107,29 +92,88 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
107
92
|
copiedAssets === null ? null : new Set(copiedAssets)
|
|
108
93
|
}
|
|
109
94
|
|
|
110
|
-
|
|
111
|
-
this.#
|
|
95
|
+
get htmlEntrypoints(): Array<HtmlEntrypoint> {
|
|
96
|
+
return Object.values(this.#pages).map(p => p.html)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get pageUrls(): Array<string> {
|
|
100
|
+
return Object.keys(this.#pages)
|
|
112
101
|
}
|
|
113
102
|
|
|
114
103
|
get resolver(): Resolver {
|
|
115
104
|
return this.#resolver
|
|
116
105
|
}
|
|
117
106
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
107
|
+
get urlRewrites(): Array<UrlRewrite> {
|
|
108
|
+
return Object.values(this.#pages)
|
|
109
|
+
.filter(
|
|
110
|
+
(pr): pr is WebpageRegistration & { urlRewrite: UrlRewrite } =>
|
|
111
|
+
typeof pr.urlRewrite !== 'undefined',
|
|
112
|
+
)
|
|
113
|
+
.map(pr => pr.urlRewrite)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get webpageEntryPoints(): Array<EntryPoint> {
|
|
117
|
+
const unique: Set<EntryPoint['in']> = new Set()
|
|
118
|
+
return Object.values(this.#pages)
|
|
119
|
+
.flatMap(p => p.bundles)
|
|
120
|
+
.filter(entryPoint => {
|
|
121
|
+
if (unique.has(entryPoint.in)) {
|
|
122
|
+
return false
|
|
123
|
+
} else {
|
|
124
|
+
unique.add(entryPoint.in)
|
|
125
|
+
return true
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get webpageAndWorkerEntryPoints(): Array<EntryPoint> {
|
|
131
|
+
const unique: Set<EntryPoint['in']> = new Set()
|
|
132
|
+
const pageBundles = Object.values(this.#pages).flatMap(p => p.bundles)
|
|
133
|
+
const workerBundles = this.workerEntryPoints
|
|
134
|
+
const bundles = workerBundles
|
|
135
|
+
? [...pageBundles, ...workerBundles]
|
|
136
|
+
: pageBundles
|
|
137
|
+
return bundles.filter(entryPoint => {
|
|
138
|
+
if (unique.has(entryPoint.in)) {
|
|
139
|
+
return false
|
|
140
|
+
} else {
|
|
141
|
+
unique.add(entryPoint.in)
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
get workerEntryPoints(): Array<EntryPoint> | null {
|
|
148
|
+
return (
|
|
149
|
+
this.#workers?.map(({ workerEntryPoint }) => ({
|
|
150
|
+
in: workerEntryPoint,
|
|
151
|
+
out: workerEntryPoint
|
|
152
|
+
.replace(/^pages[\//]/, '')
|
|
153
|
+
.replace(/\.(mj|t)s$/, '.js'),
|
|
154
|
+
})) || null
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
get workers(): Array<WorkerManifest> | null {
|
|
159
|
+
return this.#workers
|
|
160
|
+
}
|
|
125
161
|
|
|
126
162
|
buildRegistry(): BuildRegistry {
|
|
127
|
-
return new BuildRegistry(
|
|
163
|
+
return new BuildRegistry(
|
|
164
|
+
this.#c.dirs,
|
|
165
|
+
this.#resolver,
|
|
166
|
+
this.#onBuildManifest,
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
configSync() {
|
|
171
|
+
this.#configDiff()
|
|
128
172
|
}
|
|
129
173
|
|
|
130
174
|
files(): Set<string> {
|
|
131
175
|
const files = new Set<string>()
|
|
132
|
-
for (const pageUrl of this.#
|
|
176
|
+
for (const pageUrl of Object.keys(this.#pages))
|
|
133
177
|
files.add(pageUrl === '/' ? '/index.html' : `${pageUrl}/index.html`)
|
|
134
178
|
for (const f of this.#bundles) files.add(f)
|
|
135
179
|
if (this.#copiedAssets) for (const f of this.#copiedAssets) files.add(f)
|
|
@@ -145,27 +189,12 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
145
189
|
}
|
|
146
190
|
}
|
|
147
191
|
|
|
148
|
-
workerEntryPoints(): Array<EntryPoint> | null {
|
|
149
|
-
return (
|
|
150
|
-
this.#workers?.map(({ workerEntryPoint }) => ({
|
|
151
|
-
in: workerEntryPoint,
|
|
152
|
-
out: workerEntryPoint
|
|
153
|
-
.replace(/^pages[\//]/, '')
|
|
154
|
-
.replace(/\.(mj|t)s$/, '.js'),
|
|
155
|
-
})) || null
|
|
156
|
-
)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
workers(): Array<WorkerManifest> | null {
|
|
160
|
-
return this.#workers
|
|
161
|
-
}
|
|
162
|
-
|
|
163
192
|
async writeManifest(buildTag: string): Promise<WebsiteManifest> {
|
|
164
193
|
const manifest = this.#manifest(buildTag)
|
|
165
194
|
await writeFile(
|
|
166
195
|
join(
|
|
167
|
-
this.#
|
|
168
|
-
this.#
|
|
196
|
+
this.#c.dirs.projectRootAbs,
|
|
197
|
+
this.#c.dirs.buildRoot,
|
|
169
198
|
'website.json',
|
|
170
199
|
),
|
|
171
200
|
JSON.stringify(
|
|
@@ -181,11 +210,103 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
181
210
|
return manifest
|
|
182
211
|
}
|
|
183
212
|
|
|
213
|
+
#configDiff() {
|
|
214
|
+
const updatePages: ResolvedDankConfig['pages'] = this.#c.devPages
|
|
215
|
+
? { ...this.#c.pages, ...this.#c.devPages }
|
|
216
|
+
: { ...this.#c.pages }
|
|
217
|
+
const prevPages = new Set(Object.keys(this.#pages))
|
|
218
|
+
for (const [urlPath, mapping] of Object.entries(updatePages)) {
|
|
219
|
+
const existingPage = prevPages.delete(urlPath as `/${string}`)
|
|
220
|
+
if (existingPage) {
|
|
221
|
+
this.#configPageUpdate(urlPath as `/${string}`, mapping)
|
|
222
|
+
} else {
|
|
223
|
+
this.#configPageAdd(urlPath as `/${string}`, mapping)
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
for (const prevPage of prevPages) {
|
|
227
|
+
this.#configPageRemove(prevPage as `/${string}`)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#configPageAdd(urlPath: `/${string}`, mapping: PageMapping) {
|
|
232
|
+
LOG({
|
|
233
|
+
realm: 'registry',
|
|
234
|
+
message: 'added page',
|
|
235
|
+
data: {
|
|
236
|
+
urlPath,
|
|
237
|
+
srcPath: mapping.webpage,
|
|
238
|
+
},
|
|
239
|
+
})
|
|
240
|
+
const html = new HtmlEntrypoint(
|
|
241
|
+
this.#c,
|
|
242
|
+
this.#resolver,
|
|
243
|
+
urlPath as `/${string}`,
|
|
244
|
+
mapping.webpage,
|
|
245
|
+
)
|
|
246
|
+
const urlRewrite = mapping.pattern
|
|
247
|
+
? { pattern: mapping.pattern, url: urlPath }
|
|
248
|
+
: undefined
|
|
249
|
+
this.#pages[urlPath as `/${string}`] = {
|
|
250
|
+
pageUrl: urlPath as `/${string}`,
|
|
251
|
+
fsPath: mapping.webpage,
|
|
252
|
+
html,
|
|
253
|
+
urlRewrite,
|
|
254
|
+
bundles: [],
|
|
255
|
+
}
|
|
256
|
+
html.on('entrypoints', entrypoints =>
|
|
257
|
+
this.#setWebpageBundles(html.url, entrypoints),
|
|
258
|
+
)
|
|
259
|
+
this.emit('webpage', html)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
#configPageUpdate(urlPath: `/${string}`, mapping: PageMapping) {
|
|
263
|
+
const existingRegistration = this.#pages[urlPath as `/${string}`]
|
|
264
|
+
if (existingRegistration.fsPath !== mapping.webpage) {
|
|
265
|
+
this.#configPageRemove(urlPath)
|
|
266
|
+
this.#configPageAdd(urlPath, mapping)
|
|
267
|
+
} else if (
|
|
268
|
+
existingRegistration.urlRewrite?.pattern.source !==
|
|
269
|
+
mapping.pattern?.source
|
|
270
|
+
) {
|
|
271
|
+
if (mapping.pattern) {
|
|
272
|
+
existingRegistration.urlRewrite = {
|
|
273
|
+
url: urlPath,
|
|
274
|
+
pattern: mapping.pattern,
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
existingRegistration.urlRewrite = undefined
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
LOG({
|
|
281
|
+
realm: 'registry',
|
|
282
|
+
message: 'updated page src',
|
|
283
|
+
data: {
|
|
284
|
+
urlPath,
|
|
285
|
+
newSrcPath: mapping.webpage,
|
|
286
|
+
oldSrcPath: this.#pages[urlPath as `/${string}`].fsPath,
|
|
287
|
+
},
|
|
288
|
+
})
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#configPageRemove(urlPath: `/${string}`) {
|
|
292
|
+
const registration = this.#pages[urlPath]
|
|
293
|
+
LOG({
|
|
294
|
+
realm: 'registry',
|
|
295
|
+
message: 'removed page',
|
|
296
|
+
data: {
|
|
297
|
+
urlPath,
|
|
298
|
+
srcPath: registration.fsPath,
|
|
299
|
+
},
|
|
300
|
+
})
|
|
301
|
+
registration.html.removeAllListeners()
|
|
302
|
+
delete this.#pages[urlPath]
|
|
303
|
+
}
|
|
304
|
+
|
|
184
305
|
#manifest(buildTag: string): WebsiteManifest {
|
|
185
306
|
return {
|
|
186
307
|
buildTag,
|
|
187
308
|
files: this.files(),
|
|
188
|
-
pageUrls: new Set(this.#
|
|
309
|
+
pageUrls: new Set(Object.keys(this.#pages)),
|
|
189
310
|
}
|
|
190
311
|
}
|
|
191
312
|
|
|
@@ -246,20 +367,32 @@ export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
|
246
367
|
this.emit('workers')
|
|
247
368
|
}
|
|
248
369
|
}
|
|
370
|
+
|
|
371
|
+
#setWebpageBundles(url: `/${string}`, bundles: Array<EntryPoint>) {
|
|
372
|
+
this.#pages[url].bundles = bundles
|
|
373
|
+
this.emit('entrypoints')
|
|
374
|
+
}
|
|
249
375
|
}
|
|
250
376
|
|
|
251
377
|
// result accumulator of an esbuild `build` or `Context.rebuild`
|
|
252
378
|
export class BuildRegistry {
|
|
379
|
+
#dirs: DankDirectories
|
|
253
380
|
#onComplete: OnBuildComplete
|
|
254
381
|
#resolver: Resolver
|
|
255
382
|
#workers: Array<Omit<WorkerManifest, 'dependentEntryPoint'>> | null = null
|
|
256
383
|
|
|
257
384
|
constructor(
|
|
258
|
-
|
|
385
|
+
dirs: DankDirectories,
|
|
386
|
+
resolver: Resolver,
|
|
259
387
|
onComplete: (manifest: BuildManifest) => void,
|
|
260
388
|
) {
|
|
389
|
+
this.#dirs = dirs
|
|
261
390
|
this.#onComplete = onComplete
|
|
262
|
-
this.#resolver =
|
|
391
|
+
this.#resolver = resolver
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
get dirs(): DankDirectories {
|
|
395
|
+
return this.#dirs
|
|
263
396
|
}
|
|
264
397
|
|
|
265
398
|
get resolver(): Resolver {
|