@eighty4/dank 0.0.3 → 0.0.4-1
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/esbuild.js +8 -5
- package/lib/build.ts +133 -72
- package/lib/{tag.ts → build_tag.ts} +3 -3
- package/lib/dank.ts +147 -11
- package/lib/define.ts +4 -5
- package/lib/esbuild.ts +239 -60
- package/lib/flags.ts +154 -10
- package/lib/html.ts +320 -116
- package/lib/http.ts +195 -47
- package/lib/metadata.ts +309 -0
- package/lib/public.ts +21 -13
- package/lib/serve.ts +205 -144
- package/lib/services.ts +28 -4
- package/lib_js/build.js +82 -57
- package/lib_js/{tag.js → build_tag.js} +2 -3
- package/lib_js/dank.js +73 -5
- package/lib_js/define.js +3 -5
- package/lib_js/esbuild.js +166 -54
- package/lib_js/flags.js +123 -8
- package/lib_js/html.js +197 -87
- package/lib_js/http.js +111 -30
- package/lib_js/metadata.js +210 -0
- package/lib_js/public.js +19 -11
- package/lib_js/serve.js +135 -110
- package/lib_js/services.js +13 -2
- package/lib_types/dank.d.ts +18 -1
- package/package.json +7 -1
- package/lib/manifest.ts +0 -61
- package/lib_js/manifest.js +0 -37
package/lib/serve.ts
CHANGED
|
@@ -1,55 +1,95 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
mkdir,
|
|
3
|
+
readFile,
|
|
4
|
+
rm,
|
|
5
|
+
watch as _watch,
|
|
6
|
+
writeFile,
|
|
7
|
+
} from 'node:fs/promises'
|
|
2
8
|
import { extname, join, resolve } from 'node:path'
|
|
3
9
|
import type { BuildContext } from 'esbuild'
|
|
4
10
|
import { buildWebsite } from './build.ts'
|
|
5
11
|
import { loadConfig } from './config.ts'
|
|
6
12
|
import type { DankConfig } from './dank.ts'
|
|
7
13
|
import { createGlobalDefinitions } from './define.ts'
|
|
8
|
-
import { esbuildDevContext } from './esbuild.ts'
|
|
9
|
-
import {
|
|
14
|
+
import { esbuildDevContext, type EntryPoint } from './esbuild.ts'
|
|
15
|
+
import { resolveServeFlags, type DankServe } from './flags.ts'
|
|
10
16
|
import { HtmlEntrypoint } from './html.ts'
|
|
11
17
|
import {
|
|
12
18
|
createBuiltDistFilesFetcher,
|
|
13
19
|
createDevServeFilesFetcher,
|
|
14
|
-
|
|
20
|
+
startWebServer,
|
|
21
|
+
type PageRouteState,
|
|
22
|
+
type UrlRewrite,
|
|
15
23
|
} from './http.ts'
|
|
24
|
+
import { WebsiteRegistry } from './metadata.ts'
|
|
16
25
|
import { startDevServices, updateDevServices } from './services.ts'
|
|
17
26
|
|
|
18
|
-
const isPreview = isPreviewBuild()
|
|
19
|
-
|
|
20
|
-
// alternate port for --preview bc of service worker
|
|
21
|
-
const PORT = isPreview ? 4000 : 3000
|
|
22
|
-
|
|
23
|
-
// port for esbuild.serve
|
|
24
|
-
const ESBUILD_PORT = 2999
|
|
25
|
-
|
|
26
27
|
export async function serveWebsite(c: DankConfig): Promise<never> {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const serve = resolveServeFlags(c)
|
|
29
|
+
await rm(serve.dirs.buildRoot, { force: true, recursive: true })
|
|
30
|
+
const abortController = new AbortController()
|
|
31
|
+
process.once('exit', () => abortController.abort())
|
|
32
|
+
if (serve.preview) {
|
|
33
|
+
await startPreviewMode(c, serve, abortController.signal)
|
|
30
34
|
} else {
|
|
31
|
-
|
|
32
|
-
await startDevMode(c, abortController.signal)
|
|
35
|
+
await startDevMode(c, serve, abortController.signal)
|
|
33
36
|
}
|
|
34
37
|
return new Promise(() => {})
|
|
35
38
|
}
|
|
36
39
|
|
|
37
|
-
async function startPreviewMode(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
async function startPreviewMode(
|
|
41
|
+
c: DankConfig,
|
|
42
|
+
serve: DankServe,
|
|
43
|
+
signal: AbortSignal,
|
|
44
|
+
) {
|
|
45
|
+
const manifest = await buildWebsite(c, serve)
|
|
46
|
+
const frontend = createBuiltDistFilesFetcher(serve.dirs.buildDist, manifest)
|
|
47
|
+
const devServices = startDevServices(c, signal)
|
|
48
|
+
startWebServer(serve, frontend, devServices.http, {
|
|
49
|
+
urls: Object.keys(c.pages),
|
|
50
|
+
urlRewrites: collectUrlRewrites(c),
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function collectUrlRewrites(c: DankConfig): Array<UrlRewrite> {
|
|
55
|
+
return Object.keys(c.pages)
|
|
56
|
+
.sort()
|
|
57
|
+
.map(url => {
|
|
58
|
+
const mapping = c.pages[url as `/${string}`]
|
|
59
|
+
return typeof mapping !== 'object' || !mapping.pattern
|
|
60
|
+
? null
|
|
61
|
+
: { url, pattern: mapping.pattern }
|
|
62
|
+
})
|
|
63
|
+
.filter(mapping => mapping !== null)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
type BuildContextState =
|
|
67
|
+
| BuildContext
|
|
68
|
+
| 'starting'
|
|
69
|
+
| 'dirty'
|
|
70
|
+
| 'disposing'
|
|
71
|
+
| null
|
|
72
|
+
|
|
73
|
+
type EntrypointsState = {
|
|
74
|
+
entrypoints: Array<EntryPoint>
|
|
75
|
+
pathsIn: Set<string>
|
|
42
76
|
}
|
|
43
77
|
|
|
44
78
|
// todo changing partials triggers update on html pages
|
|
45
|
-
async function startDevMode(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
79
|
+
async function startDevMode(
|
|
80
|
+
c: DankConfig,
|
|
81
|
+
serve: DankServe,
|
|
82
|
+
signal: AbortSignal,
|
|
83
|
+
) {
|
|
84
|
+
await mkdir(serve.dirs.buildWatch, { recursive: true })
|
|
85
|
+
const registry = new WebsiteRegistry(serve)
|
|
86
|
+
const clientJS = await loadClientJS(serve.esbuildPort)
|
|
87
|
+
const pagesByUrlPath: Record<string, HtmlEntrypoint> = {}
|
|
88
|
+
const partialsByUrlPath: Record<string, Array<string>> = {}
|
|
89
|
+
const entryPointsByUrlPath: Record<string, EntrypointsState> = {}
|
|
90
|
+
let buildContext: BuildContextState = null
|
|
91
|
+
|
|
92
|
+
registry.on('workers', resetBuildContext)
|
|
53
93
|
|
|
54
94
|
watch('dank.config.ts', signal, async () => {
|
|
55
95
|
let updated: DankConfig
|
|
@@ -60,15 +100,17 @@ async function startDevMode(c: DankConfig, signal: AbortSignal) {
|
|
|
60
100
|
}
|
|
61
101
|
const prevPages = new Set(Object.keys(pagesByUrlPath))
|
|
62
102
|
await Promise.all(
|
|
63
|
-
Object.entries(updated.pages).map(async ([urlPath,
|
|
64
|
-
c.pages[urlPath as `/${string}`] =
|
|
65
|
-
|
|
103
|
+
Object.entries(updated.pages).map(async ([urlPath, mapping]) => {
|
|
104
|
+
c.pages[urlPath as `/${string}`] = mapping
|
|
105
|
+
const srcPath =
|
|
106
|
+
typeof mapping === 'string' ? mapping : mapping.webpage
|
|
107
|
+
if (!pagesByUrlPath[urlPath]) {
|
|
108
|
+
await addPage(urlPath, srcPath)
|
|
109
|
+
} else {
|
|
66
110
|
prevPages.delete(urlPath)
|
|
67
|
-
if (pagesByUrlPath[urlPath].
|
|
111
|
+
if (pagesByUrlPath[urlPath].fsPath !== srcPath) {
|
|
68
112
|
await updatePage(urlPath)
|
|
69
113
|
}
|
|
70
|
-
} else {
|
|
71
|
-
await addPage(urlPath, srcPath)
|
|
72
114
|
}
|
|
73
115
|
}),
|
|
74
116
|
)
|
|
@@ -79,114 +121,165 @@ async function startDevMode(c: DankConfig, signal: AbortSignal) {
|
|
|
79
121
|
updateDevServices(updated)
|
|
80
122
|
})
|
|
81
123
|
|
|
82
|
-
watch(
|
|
124
|
+
watch(serve.dirs.pages, signal, filename => {
|
|
83
125
|
if (extname(filename) === '.html') {
|
|
84
126
|
for (const [urlPath, srcPath] of Object.entries(c.pages)) {
|
|
85
127
|
if (srcPath === filename) {
|
|
86
128
|
updatePage(urlPath)
|
|
87
129
|
}
|
|
88
130
|
}
|
|
131
|
+
for (const [urlPath, partials] of Object.entries(
|
|
132
|
+
partialsByUrlPath,
|
|
133
|
+
)) {
|
|
134
|
+
if (partials.includes(filename)) {
|
|
135
|
+
updatePage(urlPath, filename)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
89
138
|
}
|
|
90
139
|
})
|
|
91
140
|
|
|
92
141
|
await Promise.all(
|
|
93
|
-
Object.entries(c.pages).map(([urlPath,
|
|
94
|
-
|
|
95
|
-
|
|
142
|
+
Object.entries(c.pages).map(async ([urlPath, mapping]) => {
|
|
143
|
+
const srcPath =
|
|
144
|
+
typeof mapping === 'string' ? mapping : mapping.webpage
|
|
145
|
+
await addPage(urlPath, srcPath)
|
|
146
|
+
return new Promise(res =>
|
|
147
|
+
pagesByUrlPath[urlPath].once('entrypoints', res),
|
|
148
|
+
)
|
|
149
|
+
}),
|
|
96
150
|
)
|
|
97
151
|
|
|
98
152
|
async function addPage(urlPath: string, srcPath: string) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
srcPath,
|
|
153
|
+
await mkdir(join(serve.dirs.buildWatch, urlPath), { recursive: true })
|
|
154
|
+
const htmlEntrypoint = (pagesByUrlPath[urlPath] = new HtmlEntrypoint(
|
|
155
|
+
serve,
|
|
156
|
+
registry.resolver,
|
|
104
157
|
urlPath,
|
|
158
|
+
srcPath,
|
|
159
|
+
[{ type: 'script', js: clientJS }],
|
|
160
|
+
))
|
|
161
|
+
htmlEntrypoint.on('entrypoints', entrypoints => {
|
|
162
|
+
const pathsIn = new Set(entrypoints.map(e => e.in))
|
|
163
|
+
if (
|
|
164
|
+
!entryPointsByUrlPath[urlPath] ||
|
|
165
|
+
!matchingEntrypoints(
|
|
166
|
+
entryPointsByUrlPath[urlPath].pathsIn,
|
|
167
|
+
pathsIn,
|
|
168
|
+
)
|
|
169
|
+
) {
|
|
170
|
+
entryPointsByUrlPath[urlPath] = { entrypoints, pathsIn }
|
|
171
|
+
resetBuildContext()
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
htmlEntrypoint.on('partial', partial => {
|
|
175
|
+
if (!partialsByUrlPath[urlPath]) {
|
|
176
|
+
partialsByUrlPath[urlPath] = []
|
|
177
|
+
}
|
|
178
|
+
partialsByUrlPath[urlPath].push(partial)
|
|
105
179
|
})
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
180
|
+
htmlEntrypoint.on(
|
|
181
|
+
'partials',
|
|
182
|
+
partials => (partialsByUrlPath[urlPath] = partials),
|
|
183
|
+
)
|
|
184
|
+
htmlEntrypoint.on('output', html =>
|
|
185
|
+
writeFile(join(serve.dirs.buildWatch, urlPath, 'index.html'), html),
|
|
109
186
|
)
|
|
110
|
-
if (buildContext !== null) {
|
|
111
|
-
resetBuildContext()
|
|
112
|
-
}
|
|
113
187
|
}
|
|
114
188
|
|
|
115
189
|
function deletePage(urlPath: string) {
|
|
190
|
+
pagesByUrlPath[urlPath].removeAllListeners()
|
|
116
191
|
delete pagesByUrlPath[urlPath]
|
|
117
192
|
delete entryPointsByUrlPath[urlPath]
|
|
118
193
|
resetBuildContext()
|
|
119
194
|
}
|
|
120
195
|
|
|
121
|
-
async function updatePage(urlPath: string) {
|
|
122
|
-
|
|
123
|
-
clientJS,
|
|
124
|
-
outDir: watchDir,
|
|
125
|
-
pagesDir: 'pages',
|
|
126
|
-
srcPath: c.pages[urlPath as `/${string}`],
|
|
127
|
-
urlPath,
|
|
128
|
-
})
|
|
129
|
-
const entryPointUrls = new Set(update.entryPoints.map(e => e.in))
|
|
130
|
-
if (!hasSameValues(entryPointUrls, entryPointsByUrlPath[urlPath])) {
|
|
131
|
-
entryPointsByUrlPath[urlPath] = entryPointUrls
|
|
132
|
-
resetBuildContext()
|
|
133
|
-
}
|
|
196
|
+
async function updatePage(urlPath: string, partial?: string) {
|
|
197
|
+
pagesByUrlPath[urlPath].emit('change', partial)
|
|
134
198
|
}
|
|
135
199
|
|
|
136
|
-
function collectEntrypoints(): Array<
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
.flatMap(
|
|
200
|
+
function collectEntrypoints(): Array<EntryPoint> {
|
|
201
|
+
const unique: Set<string> = new Set()
|
|
202
|
+
const pageBundles = Object.values(entryPointsByUrlPath)
|
|
203
|
+
.flatMap(entrypointState => entrypointState.entrypoints)
|
|
140
204
|
.filter(entryPoint => {
|
|
141
|
-
if (
|
|
205
|
+
if (unique.has(entryPoint.in)) {
|
|
142
206
|
return false
|
|
143
207
|
} else {
|
|
144
|
-
|
|
208
|
+
unique.add(entryPoint.in)
|
|
145
209
|
return true
|
|
146
210
|
}
|
|
147
211
|
})
|
|
212
|
+
const workerBundles = registry.workerEntryPoints()
|
|
213
|
+
if (workerBundles) {
|
|
214
|
+
return [...pageBundles, ...workerBundles]
|
|
215
|
+
} else {
|
|
216
|
+
return pageBundles
|
|
217
|
+
}
|
|
148
218
|
}
|
|
149
219
|
|
|
150
220
|
function resetBuildContext() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
221
|
+
switch (buildContext) {
|
|
222
|
+
case 'starting':
|
|
223
|
+
buildContext = 'dirty'
|
|
224
|
+
return
|
|
225
|
+
case 'dirty':
|
|
226
|
+
case 'disposing':
|
|
227
|
+
return
|
|
157
228
|
}
|
|
158
229
|
if (buildContext !== null) {
|
|
159
|
-
const
|
|
230
|
+
const disposing = buildContext.dispose()
|
|
160
231
|
buildContext = 'disposing'
|
|
161
|
-
|
|
232
|
+
disposing.then(() => {
|
|
162
233
|
buildContext = null
|
|
163
234
|
resetBuildContext()
|
|
164
235
|
})
|
|
165
236
|
} else {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
237
|
+
buildContext = 'starting'
|
|
238
|
+
startEsbuildWatch(c, registry, serve, collectEntrypoints()).then(
|
|
239
|
+
ctx => {
|
|
240
|
+
if (buildContext === 'dirty') {
|
|
241
|
+
buildContext = 'disposing'
|
|
242
|
+
ctx.dispose().then(() => {
|
|
243
|
+
buildContext = null
|
|
244
|
+
resetBuildContext()
|
|
245
|
+
})
|
|
246
|
+
} else {
|
|
247
|
+
buildContext = ctx
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
)
|
|
174
251
|
}
|
|
175
252
|
}
|
|
176
253
|
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
254
|
+
// function removePartialFromPage(partial: string, urlPath: string) {
|
|
255
|
+
// const deleteIndex = urlPathsByPartials[partial].indexOf(urlPath)
|
|
256
|
+
// if (deleteIndex !== -1) {
|
|
257
|
+
// if (urlPathsByPartials[partial].length === 1) {
|
|
258
|
+
// delete urlPathsByPartials[partial]
|
|
259
|
+
// } else {
|
|
260
|
+
// urlPathsByPartials[partial].splice(deleteIndex, 1)
|
|
261
|
+
// }
|
|
262
|
+
// }
|
|
263
|
+
// }
|
|
264
|
+
|
|
265
|
+
// inital start of esbuild ctx
|
|
266
|
+
resetBuildContext()
|
|
267
|
+
|
|
268
|
+
// todo this page route state could be built on change and reused
|
|
269
|
+
const pageRoutes: PageRouteState = {
|
|
270
|
+
get urls(): Array<string> {
|
|
271
|
+
return Object.keys(c.pages)
|
|
272
|
+
},
|
|
273
|
+
get urlRewrites(): Array<UrlRewrite> {
|
|
274
|
+
return collectUrlRewrites(c)
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
const frontend = createDevServeFilesFetcher(pageRoutes, serve)
|
|
278
|
+
const devServices = startDevServices(c, signal)
|
|
279
|
+
startWebServer(serve, frontend, devServices.http, pageRoutes)
|
|
187
280
|
}
|
|
188
281
|
|
|
189
|
-
function
|
|
282
|
+
function matchingEntrypoints(a: Set<string>, b: Set<string>): boolean {
|
|
190
283
|
if (a.size !== b.size) {
|
|
191
284
|
return false
|
|
192
285
|
}
|
|
@@ -198,73 +291,41 @@ function hasSameValues(a: Set<string>, b: Set<string>): boolean {
|
|
|
198
291
|
return true
|
|
199
292
|
}
|
|
200
293
|
|
|
201
|
-
type WebpageInputs = {
|
|
202
|
-
clientJS: string
|
|
203
|
-
outDir: string
|
|
204
|
-
pagesDir: string
|
|
205
|
-
srcPath: string
|
|
206
|
-
urlPath: string
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
type WebpageMetadata = {
|
|
210
|
-
entryPoints: Array<{ in: string; out: string }>
|
|
211
|
-
srcPath: string
|
|
212
|
-
urlPath: string
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
async function processWebpage(inputs: WebpageInputs): Promise<WebpageMetadata> {
|
|
216
|
-
const html = await HtmlEntrypoint.readFrom(
|
|
217
|
-
inputs.urlPath,
|
|
218
|
-
join(inputs.pagesDir, inputs.srcPath),
|
|
219
|
-
)
|
|
220
|
-
await html.injectPartials()
|
|
221
|
-
if (inputs.urlPath !== '/') {
|
|
222
|
-
await mkdir(join(inputs.outDir, inputs.urlPath), { recursive: true })
|
|
223
|
-
}
|
|
224
|
-
const entryPoints: Array<{ in: string; out: string }> = []
|
|
225
|
-
html.collectScripts().forEach(scriptImport => {
|
|
226
|
-
entryPoints.push({
|
|
227
|
-
in: scriptImport.in,
|
|
228
|
-
out: scriptImport.out,
|
|
229
|
-
})
|
|
230
|
-
})
|
|
231
|
-
html.rewriteHrefs()
|
|
232
|
-
html.appendScript(inputs.clientJS)
|
|
233
|
-
await html.writeTo(inputs.outDir)
|
|
234
|
-
return {
|
|
235
|
-
entryPoints,
|
|
236
|
-
srcPath: inputs.srcPath,
|
|
237
|
-
urlPath: inputs.urlPath,
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
294
|
async function startEsbuildWatch(
|
|
242
|
-
|
|
295
|
+
c: DankConfig,
|
|
296
|
+
registry: WebsiteRegistry,
|
|
297
|
+
serve: DankServe,
|
|
298
|
+
entryPoints: Array<EntryPoint>,
|
|
243
299
|
): Promise<BuildContext> {
|
|
244
300
|
const ctx = await esbuildDevContext(
|
|
245
|
-
|
|
301
|
+
serve,
|
|
302
|
+
registry,
|
|
303
|
+
createGlobalDefinitions(serve),
|
|
246
304
|
entryPoints,
|
|
247
|
-
|
|
305
|
+
c.esbuild,
|
|
248
306
|
)
|
|
249
307
|
|
|
250
308
|
await ctx.watch()
|
|
251
309
|
|
|
252
310
|
await ctx.serve({
|
|
253
311
|
host: '127.0.0.1',
|
|
254
|
-
port:
|
|
312
|
+
port: serve.esbuildPort,
|
|
255
313
|
cors: {
|
|
256
|
-
origin: '
|
|
314
|
+
origin: ['127.0.0.1', 'localhost'].map(
|
|
315
|
+
hostname => `http://${hostname}:${serve.dankPort}`,
|
|
316
|
+
),
|
|
257
317
|
},
|
|
258
318
|
})
|
|
259
319
|
|
|
260
320
|
return ctx
|
|
261
321
|
}
|
|
262
322
|
|
|
263
|
-
async function loadClientJS() {
|
|
264
|
-
|
|
323
|
+
async function loadClientJS(esbuildPort: number) {
|
|
324
|
+
const clientJS = await readFile(
|
|
265
325
|
resolve(import.meta.dirname, join('..', 'client', 'esbuild.js')),
|
|
266
326
|
'utf-8',
|
|
267
327
|
)
|
|
328
|
+
return clientJS.replace('3995', `${esbuildPort}`)
|
|
268
329
|
}
|
|
269
330
|
|
|
270
331
|
async function watch(
|
package/lib/services.ts
CHANGED
|
@@ -2,6 +2,16 @@ import { type ChildProcess, spawn } from 'node:child_process'
|
|
|
2
2
|
import { basename, isAbsolute, resolve } from 'node:path'
|
|
3
3
|
import type { DankConfig, DevService } from './dank.ts'
|
|
4
4
|
|
|
5
|
+
export type DevServices = {
|
|
6
|
+
http: HttpServices
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export type HttpServices = {
|
|
10
|
+
running: Array<HttpService>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type HttpService = NonNullable<DevService['http']>
|
|
14
|
+
|
|
5
15
|
// up to date representation of dank.config.ts services
|
|
6
16
|
const running: Array<{ s: DevService; process: ChildProcess | null }> = []
|
|
7
17
|
|
|
@@ -13,13 +23,23 @@ let updating: null | {
|
|
|
13
23
|
starting: Array<DevService>
|
|
14
24
|
} = null
|
|
15
25
|
|
|
16
|
-
export function startDevServices(
|
|
26
|
+
export function startDevServices(
|
|
27
|
+
c: DankConfig,
|
|
28
|
+
_signal: AbortSignal,
|
|
29
|
+
): DevServices {
|
|
17
30
|
signal = _signal
|
|
18
31
|
if (c.services?.length) {
|
|
19
32
|
for (const s of c.services) {
|
|
20
33
|
running.push({ s, process: startService(s) })
|
|
21
34
|
}
|
|
22
35
|
}
|
|
36
|
+
return {
|
|
37
|
+
http: {
|
|
38
|
+
get running(): Array<NonNullable<DevService['http']>> {
|
|
39
|
+
return running.map(({ s }) => s.http).filter(http => !!http)
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}
|
|
23
43
|
}
|
|
24
44
|
|
|
25
45
|
export function updateDevServices(c: DankConfig) {
|
|
@@ -138,9 +158,13 @@ function startService(s: DevService): ChildProcess {
|
|
|
138
158
|
spawned.stderr.on('data', chunk => printChunk(stderrLabel, chunk))
|
|
139
159
|
|
|
140
160
|
spawned.on('error', e => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
161
|
+
if (e.name !== 'AbortError') {
|
|
162
|
+
const cause =
|
|
163
|
+
'code' in e && e.code === 'ENOENT'
|
|
164
|
+
? 'program not found'
|
|
165
|
+
: e.message
|
|
166
|
+
opPrint(s, 'error: ' + cause)
|
|
167
|
+
}
|
|
144
168
|
removeFromRunning(s)
|
|
145
169
|
})
|
|
146
170
|
|
package/lib_js/build.js
CHANGED
|
@@ -1,70 +1,95 @@
|
|
|
1
|
-
import { mkdir, rm } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import { copyAssets } from "./public.js";
|
|
5
|
-
import { createBuildTag } from "./tag.js";
|
|
6
|
-
import { writeBuildManifest, writeMetafile } from "./manifest.js";
|
|
3
|
+
import { createBuildTag } from "./build_tag.js";
|
|
7
4
|
import { createGlobalDefinitions } from "./define.js";
|
|
5
|
+
import { esbuildWebpages, esbuildWorkers } from "./esbuild.js";
|
|
6
|
+
import { resolveBuildFlags } from "./flags.js";
|
|
8
7
|
import { HtmlEntrypoint } from "./html.js";
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
? isProductionBuild()
|
|
8
|
+
import { WebsiteRegistry } from "./metadata.js";
|
|
9
|
+
import { copyAssets } from "./public.js";
|
|
10
|
+
export async function buildWebsite(c, build = resolveBuildFlags()) {
|
|
11
|
+
const buildTag = await createBuildTag(build);
|
|
12
|
+
console.log(build.minify
|
|
13
|
+
? build.production
|
|
16
14
|
? 'minified production'
|
|
17
15
|
: 'minified'
|
|
18
16
|
: 'unminified', 'build', buildTag, 'building in ./build/dist');
|
|
19
|
-
await rm(
|
|
20
|
-
await mkdir(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const buildUrls = [];
|
|
24
|
-
buildUrls.push(...(await buildWebpages(distDir, createGlobalDefinitions(), c.pages)));
|
|
25
|
-
if (staticAssets) {
|
|
26
|
-
buildUrls.push(...staticAssets);
|
|
17
|
+
await rm(build.dirs.buildRoot, { recursive: true, force: true });
|
|
18
|
+
await mkdir(build.dirs.buildDist, { recursive: true });
|
|
19
|
+
for (const subdir of Object.keys(c.pages).filter(url => url !== '/')) {
|
|
20
|
+
await mkdir(join(build.dirs.buildDist, subdir), { recursive: true });
|
|
27
21
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
22
|
+
await mkdir(join(build.dirs.buildRoot, 'metafiles'), { recursive: true });
|
|
23
|
+
const registry = new WebsiteRegistry(build);
|
|
24
|
+
registry.pageUrls = Object.keys(c.pages);
|
|
25
|
+
registry.copiedAssets = await copyAssets(build);
|
|
26
|
+
await buildWebpages(c, registry, build, createGlobalDefinitions(build));
|
|
27
|
+
return await registry.writeManifest(buildTag);
|
|
34
28
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
// builds all webpage entrypoints in one esbuild.build context
|
|
30
|
+
// to support code splitting
|
|
31
|
+
// returns all built assets URLs and webpage URLs from DankConfig.pages
|
|
32
|
+
async function buildWebpages(c, registry, build, define) {
|
|
33
|
+
// create HtmlEntrypoint for each webpage and collect awaitable esbuild entrypoints
|
|
34
|
+
const loadingEntryPoints = [];
|
|
35
|
+
const htmlEntrypoints = [];
|
|
36
|
+
for (const [urlPath, mapping] of Object.entries(c.pages)) {
|
|
37
|
+
const fsPath = typeof mapping === 'string' ? mapping : mapping.webpage;
|
|
38
|
+
const html = new HtmlEntrypoint(build, registry.resolver, urlPath, fsPath);
|
|
39
|
+
loadingEntryPoints.push(new Promise(res => html.on('entrypoints', res)));
|
|
40
|
+
htmlEntrypoints.push(html);
|
|
41
|
+
}
|
|
42
|
+
// collect esbuild entrypoints from every HtmlEntrypoint
|
|
43
|
+
const uniqueEntryPoints = new Set();
|
|
44
|
+
const buildEntryPoints = [];
|
|
45
|
+
for (const pageEntryPoints of await Promise.all(loadingEntryPoints)) {
|
|
46
|
+
for (const entryPoint of pageEntryPoints) {
|
|
47
|
+
if (!uniqueEntryPoints.has(entryPoint.in)) {
|
|
48
|
+
buildEntryPoints.push(entryPoint);
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
|
-
html.collectScripts()
|
|
45
|
-
.filter(scriptImport => !entryPointUrls.has(scriptImport.in))
|
|
46
|
-
.forEach(scriptImport => {
|
|
47
|
-
entryPointUrls.add(scriptImport.in);
|
|
48
|
-
entryPoints.push({
|
|
49
|
-
in: scriptImport.in,
|
|
50
|
-
out: scriptImport.out,
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
return html;
|
|
54
|
-
}));
|
|
55
|
-
const metafile = await esbuildWebpages(define, entryPoints, distDir);
|
|
56
|
-
await writeMetafile(`pages.json`, metafile);
|
|
57
|
-
// todo these hrefs would have \ path separators on windows
|
|
58
|
-
const buildUrls = [...Object.keys(pages)];
|
|
59
|
-
const mapInToOutHrefs = {};
|
|
60
|
-
for (const [outputFile, { entryPoint }] of Object.entries(metafile.outputs)) {
|
|
61
|
-
const outputUrl = outputFile.replace(/^build\/dist/, '');
|
|
62
|
-
buildUrls.push(outputUrl);
|
|
63
|
-
mapInToOutHrefs[entryPoint] = outputUrl;
|
|
64
51
|
}
|
|
52
|
+
await esbuildWebpages(build, registry, define, buildEntryPoints, c.esbuild);
|
|
53
|
+
// todo recursively build workers on building workers that create workers
|
|
54
|
+
const workerEntryPoints = registry.workerEntryPoints();
|
|
55
|
+
if (workerEntryPoints?.length) {
|
|
56
|
+
await esbuildWorkers(build, registry, define, workerEntryPoints, c.esbuild);
|
|
57
|
+
}
|
|
58
|
+
await rewriteWorkerUrls(build, registry);
|
|
59
|
+
// write out html output with rewritten hrefs
|
|
65
60
|
await Promise.all(htmlEntrypoints.map(async (html) => {
|
|
66
|
-
html.
|
|
67
|
-
|
|
61
|
+
await writeFile(join(build.dirs.buildDist, html.url, 'index.html'), html.output(registry));
|
|
62
|
+
}));
|
|
63
|
+
}
|
|
64
|
+
export async function rewriteWorkerUrls(build, registry) {
|
|
65
|
+
const workers = registry.workers();
|
|
66
|
+
if (!workers) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const dependentBundlePaths = workers.map(w => registry.mappedHref(w.dependentEntryPoint));
|
|
70
|
+
const bundleOutputs = {};
|
|
71
|
+
// collect all js file contents concurrently
|
|
72
|
+
const readingFiles = Promise.all(dependentBundlePaths.map(async (p) => {
|
|
73
|
+
bundleOutputs[p] = await readFile(join(build.dirs.projectRootAbs, build.dirs.buildDist, p), 'utf8');
|
|
74
|
+
}));
|
|
75
|
+
// build regex replacements during file reads
|
|
76
|
+
const rewriteChains = {};
|
|
77
|
+
for (const p of dependentBundlePaths)
|
|
78
|
+
rewriteChains[p] = [];
|
|
79
|
+
for (const w of workers) {
|
|
80
|
+
rewriteChains[registry.mappedHref(w.dependentEntryPoint)].push(s => s.replace(createWorkerRegex(w.workerUrlPlaceholder), `new Worker('${registry.mappedHref(w.workerEntryPoint)}')`));
|
|
81
|
+
}
|
|
82
|
+
// wait for file reads
|
|
83
|
+
await readingFiles;
|
|
84
|
+
// run rewrite regex chain and write back to dist
|
|
85
|
+
await Promise.all(Object.entries(bundleOutputs).map(async ([p, content]) => {
|
|
86
|
+
let result = content;
|
|
87
|
+
for (const rewriteFn of rewriteChains[p]) {
|
|
88
|
+
result = rewriteFn(result);
|
|
89
|
+
}
|
|
90
|
+
await writeFile(join(build.dirs.projectRootAbs, build.dirs.buildDist, p), result);
|
|
68
91
|
}));
|
|
69
|
-
|
|
92
|
+
}
|
|
93
|
+
export function createWorkerRegex(workerUrl) {
|
|
94
|
+
return new RegExp(`new(?:\\s|\\r?\\n)+Worker(?:\\s|\\r?\\n)*\\((?:\\s|\\r?\\n)*['"]${workerUrl}['"](?:\\s|\\r?\\n)*\\)`, 'g');
|
|
70
95
|
}
|