@eighty4/dank 0.0.4-2 → 0.0.4
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
package/lib/html.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import EventEmitter from 'node:events'
|
|
2
2
|
import { readFile } from 'node:fs/promises'
|
|
3
|
-
import { dirname, join, relative } from 'node:path'
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
4
4
|
import { extname } from 'node:path/posix'
|
|
5
5
|
import {
|
|
6
6
|
defaultTreeAdapter,
|
|
@@ -9,9 +9,11 @@ import {
|
|
|
9
9
|
parseFragment,
|
|
10
10
|
serialize,
|
|
11
11
|
} from 'parse5'
|
|
12
|
+
import type { ResolvedDankConfig } from './config.ts'
|
|
13
|
+
import { LOG } from './developer.ts'
|
|
14
|
+
import type { Resolver } from './dirs.ts'
|
|
15
|
+
import { DankError } from './errors.ts'
|
|
12
16
|
import type { EntryPoint } from './esbuild.ts'
|
|
13
|
-
import type { DankBuild } from './flags.ts'
|
|
14
|
-
import type { Resolver } from './metadata.ts'
|
|
15
17
|
|
|
16
18
|
type CommentNode = DefaultTreeAdapterTypes.CommentNode
|
|
17
19
|
type Document = DefaultTreeAdapterTypes.Document
|
|
@@ -28,6 +30,8 @@ type PartialReference = {
|
|
|
28
30
|
commentNode: CommentNode
|
|
29
31
|
// path within pages dir omitting pages/ segment
|
|
30
32
|
fsPath: string
|
|
33
|
+
// unresolved partial path read from commentNode text
|
|
34
|
+
specifier: string
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
type PartialContent = PartialReference & {
|
|
@@ -44,7 +48,7 @@ type ImportedScript = {
|
|
|
44
48
|
entrypoint: EntryPoint
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
|
|
51
|
+
type HtmlDecoration = {
|
|
48
52
|
type: 'script'
|
|
49
53
|
js: string
|
|
50
54
|
}
|
|
@@ -61,86 +65,103 @@ export type HtmlEntrypointEvents = {
|
|
|
61
65
|
// Dispatched from HtmlEntrypoint to notify `dank serve` of changes to esbuild entrypoints
|
|
62
66
|
// Parameter `entrypoints` is the esbuild mappings of the input and output paths
|
|
63
67
|
entrypoints: [entrypoints: Array<EntryPoint>]
|
|
68
|
+
// Dispatched from HtmlEntrypoint when processing HTML is aborted on error
|
|
69
|
+
error: [e: Error]
|
|
64
70
|
// Dispatched from HtmlEntrypoint to notify when new HtmlEntrypoint.#document output is ready for write
|
|
65
71
|
// Parameter `html` is the updated html content of the page ready to be output to the build dir
|
|
66
72
|
output: [html: string]
|
|
67
|
-
// Dispatched from HtmlEntrypoint to notify `dank serve` of a partial dependency for an HtmlEntrypoint
|
|
68
|
-
// Seemingly a duplicate of event `partials` but it keeps relevant state in sync during async io
|
|
69
|
-
// Parameter `partial` is the fs path to the partial
|
|
70
|
-
partial: [partial: string]
|
|
71
|
-
// Dispatched from HtmlEntrypoint to notify `dank serve` of completely resolved imported partials
|
|
72
|
-
// Parameter `partials` are the fs paths to the partials
|
|
73
|
-
partials: [partials: Array<string>]
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
77
|
-
#
|
|
78
|
-
#
|
|
76
|
+
#c: ResolvedDankConfig
|
|
77
|
+
#clientJS: ClientJS | null
|
|
79
78
|
#document: Document = defaultTreeAdapter.createDocument()
|
|
80
|
-
|
|
81
|
-
// #entrypoints: Set<string> = new Set()
|
|
79
|
+
#entrypoints: Set<string> = new Set()
|
|
82
80
|
// path within pages dir omitting pages/ segment
|
|
83
81
|
#fsPath: string
|
|
84
82
|
#partials: Array<PartialContent> = []
|
|
85
83
|
#resolver: Resolver
|
|
86
84
|
#scripts: Array<ImportedScript> = []
|
|
87
85
|
#update: Object = Object()
|
|
88
|
-
#url: string
|
|
86
|
+
#url: `/${string}`
|
|
89
87
|
|
|
90
88
|
constructor(
|
|
91
|
-
|
|
89
|
+
c: ResolvedDankConfig,
|
|
92
90
|
resolver: Resolver,
|
|
93
|
-
url: string
|
|
91
|
+
url: `/${string}`,
|
|
94
92
|
fsPath: string,
|
|
95
|
-
decorations?: Array<HtmlDecoration>,
|
|
96
93
|
) {
|
|
97
94
|
super({ captureRejections: true })
|
|
98
|
-
this.#
|
|
95
|
+
this.#c = c
|
|
96
|
+
this.#clientJS = ClientJS.initialize(c)
|
|
99
97
|
this.#resolver = resolver
|
|
100
|
-
this.#decorations = decorations
|
|
101
98
|
this.#url = url
|
|
102
99
|
this.#fsPath = fsPath
|
|
103
100
|
this.on('change', this.#onChange)
|
|
104
|
-
this.emit('change')
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
get fsPath(): string {
|
|
108
104
|
return this.#fsPath
|
|
109
105
|
}
|
|
110
106
|
|
|
111
|
-
get url(): string {
|
|
107
|
+
get url(): `/${string}` {
|
|
112
108
|
return this.#url
|
|
113
109
|
}
|
|
114
110
|
|
|
115
|
-
async
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
111
|
+
async process() {
|
|
112
|
+
await this.#onChange()
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
output(hrefs?: HtmlHrefs): string {
|
|
116
|
+
this.#injectPartials()
|
|
117
|
+
this.#rewriteHrefs(hrefs)
|
|
118
|
+
return serialize(this.#document)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
usesPartial(fsPath: string): boolean {
|
|
122
|
+
return this.#partials.some(partial => partial.fsPath === fsPath)
|
|
125
123
|
}
|
|
126
124
|
|
|
127
|
-
|
|
128
|
-
#onChange = async (_partial?: string) => {
|
|
125
|
+
#onChange = async () => {
|
|
129
126
|
const update = (this.#update = Object())
|
|
130
|
-
|
|
127
|
+
let html: string
|
|
128
|
+
try {
|
|
129
|
+
html = await this.#readFromPages(this.#fsPath)
|
|
130
|
+
} catch (e) {
|
|
131
|
+
this.#error(
|
|
132
|
+
`url \`${this.#url}\` html file \`${join(this.#c.dirs.pages, this.#fsPath)}\` does not exist`,
|
|
133
|
+
)
|
|
134
|
+
return
|
|
135
|
+
}
|
|
131
136
|
const document = parse(html)
|
|
132
137
|
const imports: CollectedImports = {
|
|
133
138
|
partials: [],
|
|
134
139
|
scripts: [],
|
|
135
140
|
}
|
|
141
|
+
let partials: Array<PartialContent>
|
|
136
142
|
this.#collectImports(document, imports)
|
|
137
|
-
const
|
|
143
|
+
const partialResults = await this.#resolvePartialContent(
|
|
144
|
+
imports.partials,
|
|
145
|
+
)
|
|
146
|
+
partials = partialResults.filter(p => p !== false)
|
|
147
|
+
// `dank serve` will not throw an error during a partial error, so this emits error and breaks out of `change` event handler
|
|
148
|
+
if (partials.length !== partialResults.length) {
|
|
149
|
+
this.#error(
|
|
150
|
+
`update to \`${join(this.#c.dirs.pages, this.#fsPath)}\` did not update to \`${this.#url}\` because of unresolved partials`,
|
|
151
|
+
)
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
if (this.#clientJS !== null) {
|
|
155
|
+
const decoration = await this.#clientJS.retrieve(
|
|
156
|
+
this.#c.esbuildPort,
|
|
157
|
+
)
|
|
158
|
+
this.#addScriptDecoration(document, decoration.js)
|
|
159
|
+
}
|
|
138
160
|
if (update !== this.#update) {
|
|
139
161
|
// another update has started so aborting this one
|
|
162
|
+
// only do synchronous work after this check
|
|
140
163
|
return
|
|
141
164
|
}
|
|
142
|
-
this.#addDecorations(document)
|
|
143
|
-
this.#update = update
|
|
144
165
|
this.#document = document
|
|
145
166
|
this.#partials = partials
|
|
146
167
|
this.#scripts = imports.scripts
|
|
@@ -148,30 +169,93 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
148
169
|
imports,
|
|
149
170
|
...partials.map(p => p.imports),
|
|
150
171
|
)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
172
|
+
LOG({
|
|
173
|
+
realm: 'html',
|
|
174
|
+
message: 'processed html entrypoint',
|
|
175
|
+
data: {
|
|
176
|
+
url: this.#url,
|
|
177
|
+
html: this.#fsPath,
|
|
178
|
+
entrypoints: entrypoints.map(ep => ep.in),
|
|
179
|
+
partials: partials.map(p => p.fsPath),
|
|
180
|
+
},
|
|
181
|
+
})
|
|
182
|
+
if (this.#haveEntrypointsChanged(entrypoints)) {
|
|
183
|
+
this.emit('entrypoints', entrypoints)
|
|
184
|
+
}
|
|
157
185
|
if (this.listenerCount('output')) {
|
|
158
186
|
this.emit('output', this.output())
|
|
159
187
|
}
|
|
160
188
|
}
|
|
161
189
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
190
|
+
#error(e: DankError | Error | string) {
|
|
191
|
+
let message: string
|
|
192
|
+
let error: Error
|
|
193
|
+
if (typeof e === 'string') {
|
|
194
|
+
message = e
|
|
195
|
+
error = new DankError(e)
|
|
196
|
+
} else {
|
|
197
|
+
message = e.message
|
|
198
|
+
error = e
|
|
199
|
+
}
|
|
200
|
+
LOG({
|
|
201
|
+
realm: 'html',
|
|
202
|
+
message: 'error processing html',
|
|
203
|
+
data: {
|
|
204
|
+
url: this.#url,
|
|
205
|
+
html: this.#fsPath,
|
|
206
|
+
error: message,
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
if (this.listenerCount('error')) {
|
|
210
|
+
this.emit('error', error)
|
|
211
|
+
} else {
|
|
212
|
+
throw error
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#haveEntrypointsChanged(entrypoints: Array<EntryPoint>) {
|
|
217
|
+
const set = new Set(entrypoints.map(entrypoint => entrypoint.in))
|
|
218
|
+
const changed = set.symmetricDifference(this.#entrypoints).size > 0
|
|
219
|
+
this.#entrypoints = set
|
|
220
|
+
return changed
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async #readFromPages(p: string): Promise<string> {
|
|
224
|
+
return await readFile(this.#resolver.absPagesPath(p), 'utf8')
|
|
225
|
+
}
|
|
226
|
+
|
|
165
227
|
async #resolvePartialContent(
|
|
166
228
|
partials: Array<PartialReference>,
|
|
167
|
-
): Promise<Array<PartialContent>> {
|
|
229
|
+
): Promise<Array<PartialContent | false>> {
|
|
168
230
|
return await Promise.all(
|
|
169
231
|
partials.map(async p => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
232
|
+
let html: string
|
|
233
|
+
if (isAbsolute(p.specifier)) {
|
|
234
|
+
this.#error(
|
|
235
|
+
`partials cannot be referenced with an absolute path like \`${p.specifier}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\``,
|
|
236
|
+
)
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
if (!this.#resolver.isPagesSubpathInPagesDir(p.fsPath)) {
|
|
240
|
+
this.#error(
|
|
241
|
+
`partials cannot be referenced from outside the pages dir like \`${p.specifier}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\``,
|
|
242
|
+
)
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
if (extname(p.fsPath) !== '.html') {
|
|
246
|
+
this.#error(
|
|
247
|
+
`partial path \`${p.fsPath}\` referenced by \`${this.#fsPath}\` is not a valid partial path`,
|
|
248
|
+
)
|
|
249
|
+
return false
|
|
250
|
+
}
|
|
251
|
+
try {
|
|
252
|
+
html = await this.#readFromPages(p.fsPath)
|
|
253
|
+
} catch (e) {
|
|
254
|
+
this.#error(
|
|
255
|
+
`partial \`${join('pages', p.fsPath)}\` imported by \`${join('pages', this.#fsPath)}\` does not exist`,
|
|
256
|
+
)
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
175
259
|
const fragment = parseFragment(html)
|
|
176
260
|
const imports: CollectedImports = {
|
|
177
261
|
partials: [],
|
|
@@ -181,10 +265,8 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
181
265
|
this.#rewritePartialRelativePaths(node, p.fsPath)
|
|
182
266
|
})
|
|
183
267
|
if (imports.partials.length) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
errorExit(
|
|
187
|
-
`partial ${p.fsPath} cannot recursively import partials`,
|
|
268
|
+
this.#error(
|
|
269
|
+
`partials cannot import another partial like \`${join(this.#c.dirs.pages, p.fsPath)}\``,
|
|
188
270
|
)
|
|
189
271
|
}
|
|
190
272
|
const content: PartialContent = {
|
|
@@ -219,35 +301,16 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
219
301
|
}
|
|
220
302
|
}
|
|
221
303
|
|
|
222
|
-
#
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const htmlNode = document.childNodes.find(
|
|
233
|
-
node => node.nodeName === 'html',
|
|
234
|
-
) as ParentNode
|
|
235
|
-
const headNode = htmlNode.childNodes.find(
|
|
236
|
-
node => node.nodeName === 'head',
|
|
237
|
-
) as ParentNode | undefined
|
|
238
|
-
defaultTreeAdapter.appendChild(
|
|
239
|
-
headNode || htmlNode,
|
|
240
|
-
scriptNode,
|
|
241
|
-
)
|
|
242
|
-
break
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
output(hrefs?: HtmlHrefs): string {
|
|
248
|
-
this.#injectPartials()
|
|
249
|
-
this.#rewriteHrefs(hrefs)
|
|
250
|
-
return serialize(this.#document)
|
|
304
|
+
#addScriptDecoration(document: Document, js: string) {
|
|
305
|
+
const scriptNode = parseFragment(`<script type="module">${js}</script>`)
|
|
306
|
+
.childNodes[0]
|
|
307
|
+
const htmlNode = document.childNodes.find(
|
|
308
|
+
node => node.nodeName === 'html',
|
|
309
|
+
) as ParentNode
|
|
310
|
+
const headNode = htmlNode.childNodes.find(
|
|
311
|
+
node => node.nodeName === 'head',
|
|
312
|
+
) as ParentNode | undefined
|
|
313
|
+
defaultTreeAdapter.appendChild(headNode || htmlNode, scriptNode)
|
|
251
314
|
}
|
|
252
315
|
|
|
253
316
|
// rewrites hrefs to content hashed urls
|
|
@@ -261,7 +324,7 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
261
324
|
|
|
262
325
|
async #injectPartials() {
|
|
263
326
|
for (const { commentNode, fragment } of this.#partials) {
|
|
264
|
-
if (!this.#
|
|
327
|
+
if (!this.#c.flags.production) {
|
|
265
328
|
defaultTreeAdapter.insertBefore(
|
|
266
329
|
commentNode.parentNode!,
|
|
267
330
|
defaultTreeAdapter.createCommentNode(commentNode.data),
|
|
@@ -275,7 +338,7 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
275
338
|
commentNode,
|
|
276
339
|
)
|
|
277
340
|
}
|
|
278
|
-
if (this.#
|
|
341
|
+
if (this.#c.flags.production) {
|
|
279
342
|
defaultTreeAdapter.detachNode(commentNode)
|
|
280
343
|
}
|
|
281
344
|
}
|
|
@@ -293,24 +356,11 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
293
356
|
if (childNode.nodeName === '#comment' && 'data' in childNode) {
|
|
294
357
|
const partialMatch = childNode.data.match(/\{\{(?<pp>.+)\}\}/)
|
|
295
358
|
if (partialMatch) {
|
|
296
|
-
const
|
|
297
|
-
if (partialSpecifier.startsWith('/')) {
|
|
298
|
-
errorExit(
|
|
299
|
-
`partial ${partialSpecifier} in webpage ${this.#fsPath} cannot be an absolute path`,
|
|
300
|
-
)
|
|
301
|
-
}
|
|
302
|
-
const partialPath = join(
|
|
303
|
-
dirname(this.#fsPath),
|
|
304
|
-
partialSpecifier,
|
|
305
|
-
)
|
|
306
|
-
if (!this.#resolver.isPagesSubpathInPagesDir(partialPath)) {
|
|
307
|
-
errorExit(
|
|
308
|
-
`partial ${partialSpecifier} in webpage ${this.#fsPath} cannot be outside of the pages directory`,
|
|
309
|
-
)
|
|
310
|
-
}
|
|
359
|
+
const specifier = partialMatch.groups!.pp.trim()
|
|
311
360
|
collection.partials.push({
|
|
312
|
-
fsPath: partialPath,
|
|
313
361
|
commentNode: childNode,
|
|
362
|
+
fsPath: join(dirname(this.#fsPath), specifier),
|
|
363
|
+
specifier: specifier,
|
|
314
364
|
})
|
|
315
365
|
}
|
|
316
366
|
} else if (childNode.nodeName === 'script') {
|
|
@@ -343,10 +393,10 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
343
393
|
href: string,
|
|
344
394
|
elem: Element,
|
|
345
395
|
): ImportedScript {
|
|
346
|
-
const inPath = join(this.#
|
|
396
|
+
const inPath = join(this.#c.dirs.pages, dirname(this.#fsPath), href)
|
|
347
397
|
if (!this.#resolver.isProjectSubpathInPagesDir(inPath)) {
|
|
348
|
-
|
|
349
|
-
`href
|
|
398
|
+
throw new DankError(
|
|
399
|
+
`href \`${href}\` in webpage \`${join(this.#c.dirs.pages, this.#fsPath)}\` cannot reference sources outside of the pages directory`,
|
|
350
400
|
)
|
|
351
401
|
}
|
|
352
402
|
let outPath = join(dirname(this.#fsPath), href)
|
|
@@ -406,8 +456,42 @@ function rewriteHrefs(scripts: Array<ImportedScript>, hrefs?: HtmlHrefs) {
|
|
|
406
456
|
}
|
|
407
457
|
}
|
|
408
458
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
459
|
+
class ClientJS {
|
|
460
|
+
static #instance: ClientJS | null = null
|
|
461
|
+
|
|
462
|
+
static initialize(c: ResolvedDankConfig): ClientJS | null {
|
|
463
|
+
if (c.mode === 'build' || c.flags.preview) {
|
|
464
|
+
return null
|
|
465
|
+
} else if (!ClientJS.#instance) {
|
|
466
|
+
ClientJS.#instance = new ClientJS(c.esbuildPort)
|
|
467
|
+
}
|
|
468
|
+
return ClientJS.#instance
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
#esbuildPort: number
|
|
472
|
+
#read: Promise<string>
|
|
473
|
+
#result: Promise<HtmlDecoration>
|
|
474
|
+
|
|
475
|
+
private constructor(esbuildPort: number) {
|
|
476
|
+
this.#esbuildPort = esbuildPort
|
|
477
|
+
this.#read = readFile(
|
|
478
|
+
resolve(import.meta.dirname, join('..', 'client', 'client.js')),
|
|
479
|
+
'utf-8',
|
|
480
|
+
)
|
|
481
|
+
this.#result = this.#read.then(this.#transform)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async retrieve(esbuildPort: number): Promise<HtmlDecoration> {
|
|
485
|
+
if (esbuildPort !== this.#esbuildPort) {
|
|
486
|
+
this.#result = this.#read.then(this.#transform)
|
|
487
|
+
}
|
|
488
|
+
return await this.#result
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
#transform = (js: string): HtmlDecoration => {
|
|
492
|
+
return {
|
|
493
|
+
type: 'script',
|
|
494
|
+
js: js.replace('3995', '' + this.#esbuildPort),
|
|
495
|
+
}
|
|
496
|
+
}
|
|
413
497
|
}
|
package/lib/http.ts
CHANGED
|
@@ -10,8 +10,14 @@ import {
|
|
|
10
10
|
import { extname, join } from 'node:path'
|
|
11
11
|
import { Readable } from 'node:stream'
|
|
12
12
|
import mime from 'mime'
|
|
13
|
-
import type {
|
|
14
|
-
import type {
|
|
13
|
+
import type { DankDirectories } from './dirs.ts'
|
|
14
|
+
import type { DankFlags } from './flags.ts'
|
|
15
|
+
import type {
|
|
16
|
+
UrlRewrite,
|
|
17
|
+
UrlRewriteProvider,
|
|
18
|
+
WebsiteManifest,
|
|
19
|
+
WebsiteRegistry,
|
|
20
|
+
} from './registry.ts'
|
|
15
21
|
import type { HttpServices } from './services.ts'
|
|
16
22
|
|
|
17
23
|
export type FrontendFetcher = (
|
|
@@ -21,25 +27,15 @@ export type FrontendFetcher = (
|
|
|
21
27
|
notFound: () => void,
|
|
22
28
|
) => void
|
|
23
29
|
|
|
24
|
-
// state needed to eval url rewriting after FrontendFetcher and before HttpServices
|
|
25
|
-
export type PageRouteState = {
|
|
26
|
-
// urls of html entrypoints
|
|
27
|
-
urls: Array<string>
|
|
28
|
-
urlRewrites: Array<UrlRewrite>
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export type UrlRewrite = {
|
|
32
|
-
pattern: RegExp
|
|
33
|
-
url: string
|
|
34
|
-
}
|
|
35
|
-
|
|
36
30
|
export function startWebServer(
|
|
37
|
-
|
|
31
|
+
port: number,
|
|
32
|
+
flags: DankFlags,
|
|
33
|
+
dirs: DankDirectories,
|
|
34
|
+
urlRewriteProvider: UrlRewriteProvider,
|
|
38
35
|
frontendFetcher: FrontendFetcher,
|
|
39
36
|
httpServices: HttpServices,
|
|
40
|
-
pageRoutes: PageRouteState,
|
|
41
37
|
) {
|
|
42
|
-
const serverAddress = 'http://localhost:' +
|
|
38
|
+
const serverAddress = 'http://localhost:' + port
|
|
43
39
|
const handler = (req: IncomingMessage, res: ServerResponse) => {
|
|
44
40
|
if (!req.url || !req.method) {
|
|
45
41
|
res.end()
|
|
@@ -52,19 +48,20 @@ export function startWebServer(
|
|
|
52
48
|
url,
|
|
53
49
|
headers,
|
|
54
50
|
httpServices,
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
flags,
|
|
52
|
+
dirs,
|
|
53
|
+
urlRewriteProvider,
|
|
57
54
|
res,
|
|
58
55
|
),
|
|
59
56
|
)
|
|
60
57
|
}
|
|
61
58
|
}
|
|
62
|
-
createServer(
|
|
63
|
-
|
|
59
|
+
createServer(flags.logHttp ? createLogWrapper(handler) : handler).listen(
|
|
60
|
+
port,
|
|
64
61
|
)
|
|
65
62
|
console.log(
|
|
66
|
-
|
|
67
|
-
`server is live at http://127.0.0.1:${
|
|
63
|
+
flags.preview ? 'preview' : 'dev',
|
|
64
|
+
`server is live at http://127.0.0.1:${port}`,
|
|
68
65
|
)
|
|
69
66
|
}
|
|
70
67
|
|
|
@@ -73,12 +70,18 @@ async function onNotFound(
|
|
|
73
70
|
url: URL,
|
|
74
71
|
headers: Headers,
|
|
75
72
|
httpServices: HttpServices,
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
flags: DankFlags,
|
|
74
|
+
dirs: DankDirectories,
|
|
75
|
+
urlRewriteProvider: UrlRewriteProvider,
|
|
78
76
|
res: ServerResponse,
|
|
79
77
|
) {
|
|
80
78
|
if (req.method === 'GET' && extname(url.pathname) === '') {
|
|
81
|
-
const urlRewrite = tryUrlRewrites(
|
|
79
|
+
const urlRewrite = tryUrlRewrites(
|
|
80
|
+
flags,
|
|
81
|
+
dirs,
|
|
82
|
+
urlRewriteProvider.urlRewrites,
|
|
83
|
+
url,
|
|
84
|
+
)
|
|
82
85
|
if (urlRewrite) {
|
|
83
86
|
streamFile(urlRewrite, res)
|
|
84
87
|
return
|
|
@@ -107,15 +110,20 @@ async function sendFetchResponse(res: ServerResponse, fetchResponse: Response) {
|
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
function tryUrlRewrites(
|
|
113
|
+
flags: DankFlags,
|
|
114
|
+
dirs: DankDirectories,
|
|
115
|
+
urlRewrites: Array<UrlRewrite>,
|
|
110
116
|
url: URL,
|
|
111
|
-
pageRoutes: PageRouteState,
|
|
112
|
-
serve: DankServe,
|
|
113
117
|
): string | null {
|
|
114
|
-
const urlRewrite =
|
|
118
|
+
const urlRewrite = urlRewrites.find(urlRewrite =>
|
|
115
119
|
urlRewrite.pattern.test(url.pathname),
|
|
116
120
|
)
|
|
117
121
|
return urlRewrite
|
|
118
|
-
? join(
|
|
122
|
+
? join(
|
|
123
|
+
flags.preview ? dirs.buildDist : dirs.buildWatch,
|
|
124
|
+
urlRewrite.url,
|
|
125
|
+
'index.html',
|
|
126
|
+
)
|
|
119
127
|
: null
|
|
120
128
|
}
|
|
121
129
|
|
|
@@ -178,7 +186,7 @@ function createLogWrapper(handler: RequestListener): RequestListener {
|
|
|
178
186
|
}
|
|
179
187
|
|
|
180
188
|
export function createBuiltDistFilesFetcher(
|
|
181
|
-
|
|
189
|
+
dirs: DankDirectories,
|
|
182
190
|
manifest: WebsiteManifest,
|
|
183
191
|
): FrontendFetcher {
|
|
184
192
|
return (
|
|
@@ -188,34 +196,42 @@ export function createBuiltDistFilesFetcher(
|
|
|
188
196
|
notFound: () => void,
|
|
189
197
|
) => {
|
|
190
198
|
if (manifest.pageUrls.has(url.pathname)) {
|
|
191
|
-
streamFile(
|
|
199
|
+
streamFile(
|
|
200
|
+
join(
|
|
201
|
+
dirs.projectResolved,
|
|
202
|
+
dirs.buildDist,
|
|
203
|
+
url.pathname,
|
|
204
|
+
'index.html',
|
|
205
|
+
),
|
|
206
|
+
res,
|
|
207
|
+
)
|
|
192
208
|
} else if (manifest.files.has(url.pathname)) {
|
|
193
|
-
streamFile(
|
|
209
|
+
streamFile(
|
|
210
|
+
join(dirs.projectResolved, dirs.buildDist, url.pathname),
|
|
211
|
+
res,
|
|
212
|
+
)
|
|
194
213
|
} else {
|
|
195
214
|
notFound()
|
|
196
215
|
}
|
|
197
216
|
}
|
|
198
217
|
}
|
|
199
218
|
|
|
200
|
-
// todo replace PageRouteState with WebsiteRegistry
|
|
201
219
|
export function createDevServeFilesFetcher(
|
|
202
|
-
|
|
203
|
-
|
|
220
|
+
esbuildPort: number,
|
|
221
|
+
dirs: DankDirectories,
|
|
222
|
+
registry: WebsiteRegistry,
|
|
204
223
|
): FrontendFetcher {
|
|
205
|
-
const proxyAddress = 'http://127.0.0.1:' +
|
|
224
|
+
const proxyAddress = 'http://127.0.0.1:' + esbuildPort
|
|
206
225
|
return (
|
|
207
226
|
url: URL,
|
|
208
227
|
_headers: Headers,
|
|
209
228
|
res: ServerResponse,
|
|
210
229
|
notFound: () => void,
|
|
211
230
|
) => {
|
|
212
|
-
if (
|
|
213
|
-
streamFile(
|
|
214
|
-
join(serve.dirs.buildWatch, url.pathname, 'index.html'),
|
|
215
|
-
res,
|
|
216
|
-
)
|
|
231
|
+
if (registry.pageUrls.includes(url.pathname)) {
|
|
232
|
+
streamFile(join(dirs.buildWatch, url.pathname, 'index.html'), res)
|
|
217
233
|
} else {
|
|
218
|
-
const maybePublicPath = join(
|
|
234
|
+
const maybePublicPath = join(dirs.public, url.pathname)
|
|
219
235
|
exists(maybePublicPath).then(fromPublic => {
|
|
220
236
|
if (fromPublic) {
|
|
221
237
|
streamFile(maybePublicPath, res)
|
package/lib/public.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { copyFile, mkdir, readdir, stat } from 'node:fs/promises'
|
|
2
2
|
import { platform } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
|
-
import type {
|
|
4
|
+
import type { DankDirectories } from './dirs.ts'
|
|
5
5
|
|
|
6
6
|
export async function copyAssets(
|
|
7
|
-
|
|
7
|
+
dirs: DankDirectories,
|
|
8
8
|
): Promise<Array<string> | null> {
|
|
9
9
|
try {
|
|
10
|
-
const stats = await stat(
|
|
10
|
+
const stats = await stat(dirs.public)
|
|
11
11
|
if (stats.isDirectory()) {
|
|
12
|
-
await mkdir(
|
|
13
|
-
return await recursiveCopyAssets(
|
|
12
|
+
await mkdir(dirs.buildDist, { recursive: true })
|
|
13
|
+
return await recursiveCopyAssets(dirs)
|
|
14
14
|
} else {
|
|
15
15
|
throw Error('./public cannot be a file')
|
|
16
16
|
}
|
|
@@ -22,13 +22,13 @@ export async function copyAssets(
|
|
|
22
22
|
const IGNORE = platform() === 'darwin' ? ['.DS_Store'] : []
|
|
23
23
|
|
|
24
24
|
async function recursiveCopyAssets(
|
|
25
|
-
|
|
25
|
+
dirs: DankDirectories,
|
|
26
26
|
dir: string = '',
|
|
27
27
|
): Promise<Array<string>> {
|
|
28
28
|
const copied: Array<string> = []
|
|
29
|
-
const to = join(
|
|
29
|
+
const to = join(dirs.buildDist, dir)
|
|
30
30
|
let madeDir = dir === ''
|
|
31
|
-
const listingDir = join(
|
|
31
|
+
const listingDir = join(dirs.public, dir)
|
|
32
32
|
for (const p of await readdir(listingDir)) {
|
|
33
33
|
if (IGNORE.includes(p)) {
|
|
34
34
|
continue
|
|
@@ -36,10 +36,10 @@ async function recursiveCopyAssets(
|
|
|
36
36
|
try {
|
|
37
37
|
const stats = await stat(join(listingDir, p))
|
|
38
38
|
if (stats.isDirectory()) {
|
|
39
|
-
copied.push(...(await recursiveCopyAssets(
|
|
39
|
+
copied.push(...(await recursiveCopyAssets(dirs, join(dir, p))))
|
|
40
40
|
} else {
|
|
41
41
|
if (!madeDir) {
|
|
42
|
-
await mkdir(join(
|
|
42
|
+
await mkdir(join(dirs.buildDist, dir), {
|
|
43
43
|
recursive: true,
|
|
44
44
|
})
|
|
45
45
|
madeDir = true
|