@eighty4/dank 0.0.4-0 → 0.0.4-2
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 +1 -91
- package/lib/bin.ts +1 -1
- package/lib/build.ts +8 -5
- package/lib/config.ts +211 -5
- package/lib/dank.ts +14 -150
- package/lib/developer.ts +117 -0
- package/lib/esbuild.ts +146 -118
- package/lib/flags.ts +10 -4
- package/lib/html.ts +10 -14
- package/lib/metadata.ts +65 -40
- package/lib/serve.ts +94 -10
- package/lib_js/bin.js +80 -84
- package/lib_js/build.js +72 -81
- package/lib_js/build_tag.js +20 -21
- package/lib_js/config.js +158 -18
- package/lib_js/dank.js +5 -122
- package/lib_js/define.js +8 -5
- package/lib_js/esbuild.js +161 -164
- package/lib_js/flags.js +115 -114
- package/lib_js/html.js +214 -233
- package/lib_js/http.js +174 -193
- package/lib_js/metadata.js +183 -191
- package/lib_js/public.js +45 -46
- package/lib_js/serve.js +212 -230
- package/lib_js/services.js +152 -171
- package/lib_types/dank.d.ts +8 -1
- package/package.json +5 -1
package/lib/esbuild.ts
CHANGED
|
@@ -3,7 +3,8 @@ import esbuild, {
|
|
|
3
3
|
type BuildContext,
|
|
4
4
|
type BuildOptions,
|
|
5
5
|
type BuildResult,
|
|
6
|
-
type
|
|
6
|
+
type Location,
|
|
7
|
+
type OnLoadArgs,
|
|
7
8
|
type PartialMessage,
|
|
8
9
|
type Plugin,
|
|
9
10
|
type PluginBuild,
|
|
@@ -40,14 +41,17 @@ export async function esbuildWebpages(
|
|
|
40
41
|
entryPoints: Array<EntryPoint>,
|
|
41
42
|
c?: EsbuildConfig,
|
|
42
43
|
): Promise<void> {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
try {
|
|
45
|
+
await esbuild.build({
|
|
46
|
+
define,
|
|
47
|
+
entryNames: '[dir]/[name]-[hash]',
|
|
48
|
+
entryPoints: mapEntryPointPaths(entryPoints),
|
|
49
|
+
outdir: b.dirs.buildDist,
|
|
50
|
+
...commonBuildOptions(b, r, c),
|
|
51
|
+
})
|
|
52
|
+
} catch (ignore) {
|
|
53
|
+
process.exit(1)
|
|
54
|
+
}
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export async function esbuildWorkers(
|
|
@@ -57,18 +61,21 @@ export async function esbuildWorkers(
|
|
|
57
61
|
entryPoints: Array<EntryPoint>,
|
|
58
62
|
c?: EsbuildConfig,
|
|
59
63
|
): Promise<void> {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
try {
|
|
65
|
+
await esbuild.build({
|
|
66
|
+
define,
|
|
67
|
+
entryNames: '[dir]/[name]-[hash]',
|
|
68
|
+
entryPoints: mapEntryPointPaths(entryPoints),
|
|
69
|
+
outdir: b.dirs.buildDist,
|
|
70
|
+
...commonBuildOptions(b, r, c),
|
|
71
|
+
splitting: false,
|
|
72
|
+
metafile: true,
|
|
73
|
+
write: true,
|
|
74
|
+
assetNames: 'assets/[name]-[hash]',
|
|
75
|
+
})
|
|
76
|
+
} catch (ignore) {
|
|
77
|
+
process.exit(1)
|
|
78
|
+
}
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
function commonBuildOptions(
|
|
@@ -112,29 +119,8 @@ function mapEntryPointPaths(entryPoints: Array<EntryPoint>) {
|
|
|
112
119
|
})
|
|
113
120
|
}
|
|
114
121
|
|
|
115
|
-
function esbuildResultChecks(buildResult: BuildResult) {
|
|
116
|
-
if (buildResult.errors.length) {
|
|
117
|
-
buildResult.errors.forEach(msg => esbuildPrintMessage(msg, 'warning'))
|
|
118
|
-
process.exit(1)
|
|
119
|
-
}
|
|
120
|
-
if (buildResult.warnings.length) {
|
|
121
|
-
buildResult.warnings.forEach(msg => esbuildPrintMessage(msg, 'warning'))
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function esbuildPrintMessage(msg: Message, category: 'error' | 'warning') {
|
|
126
|
-
const location = msg.location
|
|
127
|
-
? ` (${msg.location.file}L${msg.location.line}:${msg.location.column})`
|
|
128
|
-
: ''
|
|
129
|
-
console.error(`esbuild ${category}${location}:`, msg.text)
|
|
130
|
-
msg.notes.forEach(note => {
|
|
131
|
-
console.error(' ', note.text)
|
|
132
|
-
if (note.location) console.error(' ', note.location)
|
|
133
|
-
})
|
|
134
|
-
}
|
|
135
|
-
|
|
136
122
|
const WORKER_CTOR_REGEX =
|
|
137
|
-
/new(?:\s|\r?\n)+Worker(?:\s|\r?\n)*\((?:\s|\r?\n)*(?<url
|
|
123
|
+
/new(?:\s|\r?\n)+(?<ctor>(?:Shared)?Worker)(?:\s|\r?\n)*\((?:\s|\r?\n)*(?<url>.*?)(?:\s|\r?\n)*(?<end>[\),])/g
|
|
138
124
|
const WORKER_URL_REGEX = /^('.*'|".*")$/
|
|
139
125
|
|
|
140
126
|
export function workersPlugin(r: BuildRegistry): Plugin {
|
|
@@ -154,85 +140,68 @@ export function workersPlugin(r: BuildRegistry): Plugin {
|
|
|
154
140
|
for (const workerCtorMatch of contents.matchAll(
|
|
155
141
|
WORKER_CTOR_REGEX,
|
|
156
142
|
)) {
|
|
157
|
-
|
|
158
|
-
if (WORKER_URL_REGEX.test(workerUrlString)) {
|
|
159
|
-
const preamble = contents.substring(
|
|
160
|
-
0,
|
|
161
|
-
workerCtorMatch.index,
|
|
162
|
-
)
|
|
163
|
-
const lineIndex = preamble.lastIndexOf('\n') || 0
|
|
164
|
-
const lineCommented = /\/\//.test(
|
|
165
|
-
preamble.substring(lineIndex),
|
|
166
|
-
)
|
|
167
|
-
if (lineCommented) continue
|
|
168
|
-
const blockCommentIndex = preamble.lastIndexOf('/*')
|
|
169
|
-
const blockCommented =
|
|
170
|
-
blockCommentIndex !== -1 &&
|
|
171
|
-
preamble
|
|
172
|
-
.substring(blockCommentIndex)
|
|
173
|
-
.indexOf('*/') === -1
|
|
174
|
-
if (blockCommented) continue
|
|
175
|
-
const clientScript = args.path
|
|
176
|
-
.replace(absWorkingDir, '')
|
|
177
|
-
.substring(1)
|
|
178
|
-
const workerUrl = workerUrlString.substring(
|
|
179
|
-
1,
|
|
180
|
-
workerUrlString.length - 1,
|
|
181
|
-
)
|
|
182
|
-
// todo out of bounds error on path resolve
|
|
183
|
-
const workerEntryPoint = r.resolve(
|
|
184
|
-
clientScript,
|
|
185
|
-
workerUrl,
|
|
186
|
-
)
|
|
187
|
-
const workerUrlPlaceholder = workerEntryPoint
|
|
188
|
-
.replace(/^pages/, '')
|
|
189
|
-
.replace(/\.(t|m?j)s$/, '.js')
|
|
190
|
-
const workerCtorReplacement = `new Worker('${workerUrlPlaceholder}')`
|
|
191
|
-
contents =
|
|
192
|
-
contents.substring(
|
|
193
|
-
0,
|
|
194
|
-
workerCtorMatch.index + offset,
|
|
195
|
-
) +
|
|
196
|
-
workerCtorReplacement +
|
|
197
|
-
contents.substring(
|
|
198
|
-
workerCtorMatch.index +
|
|
199
|
-
workerCtorMatch[0].length +
|
|
200
|
-
offset,
|
|
201
|
-
)
|
|
202
|
-
offset +=
|
|
203
|
-
workerCtorReplacement.length -
|
|
204
|
-
workerCtorMatch[0].length
|
|
205
|
-
r.addWorker({
|
|
206
|
-
clientScript,
|
|
207
|
-
workerEntryPoint,
|
|
208
|
-
workerUrl,
|
|
209
|
-
workerUrlPlaceholder,
|
|
210
|
-
})
|
|
211
|
-
} else {
|
|
143
|
+
if (!WORKER_URL_REGEX.test(workerCtorMatch.groups!.url)) {
|
|
212
144
|
if (!errors) errors = []
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
145
|
+
errors.push(
|
|
146
|
+
invalidWorkerUrlCtorArg(
|
|
147
|
+
locationFromMatch(
|
|
148
|
+
args,
|
|
149
|
+
contents,
|
|
150
|
+
workerCtorMatch,
|
|
151
|
+
),
|
|
152
|
+
workerCtorMatch,
|
|
153
|
+
),
|
|
216
154
|
)
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
if (isIndexCommented(contents, workerCtorMatch.index)) {
|
|
158
|
+
continue
|
|
159
|
+
}
|
|
160
|
+
const clientScript = args.path
|
|
161
|
+
.replace(absWorkingDir, '')
|
|
162
|
+
.substring(1)
|
|
163
|
+
const workerUrl = workerCtorMatch.groups!.url.substring(
|
|
164
|
+
1,
|
|
165
|
+
workerCtorMatch.groups!.url.length - 1,
|
|
166
|
+
)
|
|
167
|
+
const workerEntryPoint = r.resolver.resolveHrefInPagesDir(
|
|
168
|
+
clientScript,
|
|
169
|
+
workerUrl,
|
|
170
|
+
)
|
|
171
|
+
if (workerEntryPoint === 'outofbounds') {
|
|
172
|
+
if (!errors) errors = []
|
|
173
|
+
errors.push(
|
|
174
|
+
outofboundsWorkerUrlCtorArg(
|
|
175
|
+
locationFromMatch(
|
|
176
|
+
args,
|
|
177
|
+
contents,
|
|
178
|
+
workerCtorMatch,
|
|
179
|
+
),
|
|
180
|
+
workerCtorMatch,
|
|
181
|
+
),
|
|
224
182
|
)
|
|
225
|
-
|
|
226
|
-
id: 'worker-url-unresolvable',
|
|
227
|
-
location: {
|
|
228
|
-
lineText,
|
|
229
|
-
line,
|
|
230
|
-
column,
|
|
231
|
-
file: args.path,
|
|
232
|
-
length: workerCtorMatch[0].length,
|
|
233
|
-
},
|
|
234
|
-
})
|
|
183
|
+
continue
|
|
235
184
|
}
|
|
185
|
+
const workerUrlPlaceholder = workerEntryPoint
|
|
186
|
+
.replace(/^pages/, '')
|
|
187
|
+
.replace(/\.(t|m?j)s$/, '.js')
|
|
188
|
+
const workerCtorReplacement = `new ${workerCtorMatch.groups!.ctor}('${workerUrlPlaceholder}'${workerCtorMatch.groups!.end}`
|
|
189
|
+
contents =
|
|
190
|
+
contents.substring(0, workerCtorMatch.index + offset) +
|
|
191
|
+
workerCtorReplacement +
|
|
192
|
+
contents.substring(
|
|
193
|
+
workerCtorMatch.index +
|
|
194
|
+
workerCtorMatch[0].length +
|
|
195
|
+
offset,
|
|
196
|
+
)
|
|
197
|
+
offset +=
|
|
198
|
+
workerCtorReplacement.length - workerCtorMatch[0].length
|
|
199
|
+
r.addWorker({
|
|
200
|
+
clientScript,
|
|
201
|
+
workerEntryPoint,
|
|
202
|
+
workerUrl,
|
|
203
|
+
workerUrlPlaceholder,
|
|
204
|
+
})
|
|
236
205
|
}
|
|
237
206
|
const loader = args.path.endsWith('ts') ? 'ts' : 'js'
|
|
238
207
|
return { contents, errors, loader }
|
|
@@ -246,3 +215,62 @@ export function workersPlugin(r: BuildRegistry): Plugin {
|
|
|
246
215
|
},
|
|
247
216
|
}
|
|
248
217
|
}
|
|
218
|
+
|
|
219
|
+
function isIndexCommented(contents: string, index: number) {
|
|
220
|
+
const preamble = contents.substring(0, index)
|
|
221
|
+
const lineIndex = preamble.lastIndexOf('\n') || 0
|
|
222
|
+
const lineCommented = /\/\//.test(preamble.substring(lineIndex))
|
|
223
|
+
if (lineCommented) {
|
|
224
|
+
return true
|
|
225
|
+
}
|
|
226
|
+
const blockCommentIndex = preamble.lastIndexOf('/*')
|
|
227
|
+
const blockCommented =
|
|
228
|
+
blockCommentIndex !== -1 &&
|
|
229
|
+
preamble.substring(blockCommentIndex).indexOf('*/') === -1
|
|
230
|
+
return blockCommented
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function locationFromMatch(
|
|
234
|
+
args: OnLoadArgs,
|
|
235
|
+
contents: string,
|
|
236
|
+
match: RegExpExecArray,
|
|
237
|
+
): Partial<Location> {
|
|
238
|
+
const preamble = contents.substring(0, match.index)
|
|
239
|
+
const line = preamble.match(/\n/g)?.length || 0
|
|
240
|
+
let lineIndex = preamble.lastIndexOf('\n')
|
|
241
|
+
lineIndex = lineIndex === -1 ? 0 : lineIndex + 1
|
|
242
|
+
const column = preamble.length - lineIndex
|
|
243
|
+
const lineText = contents.substring(
|
|
244
|
+
lineIndex,
|
|
245
|
+
contents.indexOf('\n', lineIndex) || contents.length,
|
|
246
|
+
)
|
|
247
|
+
return {
|
|
248
|
+
lineText,
|
|
249
|
+
line,
|
|
250
|
+
column,
|
|
251
|
+
file: args.path,
|
|
252
|
+
length: match[0].length,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function outofboundsWorkerUrlCtorArg(
|
|
257
|
+
location: Partial<Location>,
|
|
258
|
+
workerCtorMatch: RegExpExecArray,
|
|
259
|
+
): PartialMessage {
|
|
260
|
+
return {
|
|
261
|
+
id: 'worker-url-outofbounds',
|
|
262
|
+
text: `The ${workerCtorMatch.groups!.ctor} constructor URL arg \`${workerCtorMatch.groups!.url}\` cannot resolve to a path outside of the pages directory`,
|
|
263
|
+
location,
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function invalidWorkerUrlCtorArg(
|
|
268
|
+
location: Partial<Location>,
|
|
269
|
+
workerCtorMatch: RegExpExecArray,
|
|
270
|
+
): PartialMessage {
|
|
271
|
+
return {
|
|
272
|
+
id: 'worker-url-unresolvable',
|
|
273
|
+
text: `The ${workerCtorMatch.groups!.ctor} constructor URL arg \`${workerCtorMatch.groups!.url}\` must be a relative module path`,
|
|
274
|
+
location,
|
|
275
|
+
}
|
|
276
|
+
}
|
package/lib/flags.ts
CHANGED
|
@@ -8,12 +8,13 @@ export type DankBuild = {
|
|
|
8
8
|
production: boolean
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
type ProjectDirs = {
|
|
11
|
+
export type ProjectDirs = {
|
|
12
12
|
buildRoot: string
|
|
13
13
|
buildWatch: string
|
|
14
14
|
buildDist: string
|
|
15
15
|
pages: string
|
|
16
16
|
pagesResolved: string
|
|
17
|
+
projectResolved: string
|
|
17
18
|
projectRootAbs: string
|
|
18
19
|
public: string
|
|
19
20
|
}
|
|
@@ -86,7 +87,7 @@ const isPreviewBuild = () =>
|
|
|
86
87
|
|
|
87
88
|
// `dank build` will minify sources and append git release tag to build tag
|
|
88
89
|
// `dank serve` will pre-bundle with service worker and minify
|
|
89
|
-
const isProductionBuild = () =>
|
|
90
|
+
export const isProductionBuild = () =>
|
|
90
91
|
process.env.PRODUCTION === 'true' || process.argv.includes('--production')
|
|
91
92
|
|
|
92
93
|
// `dank serve` dank port for frontend webserver
|
|
@@ -116,12 +117,14 @@ function parsePortEnvVar(name: string): number {
|
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
export function defaultProjectDirs(projectRootAbs: string): ProjectDirs {
|
|
120
|
+
const pages = 'pages'
|
|
119
121
|
const dirs: ProjectDirs = {
|
|
120
122
|
buildRoot: 'build',
|
|
121
123
|
buildDist: join('build', 'dist'),
|
|
122
124
|
buildWatch: join('build', 'watch'),
|
|
123
|
-
pages
|
|
124
|
-
pagesResolved: resolve(join(projectRootAbs,
|
|
125
|
+
pages,
|
|
126
|
+
pagesResolved: resolve(join(projectRootAbs, pages)),
|
|
127
|
+
projectResolved: resolve(projectRootAbs),
|
|
125
128
|
projectRootAbs,
|
|
126
129
|
public: 'public',
|
|
127
130
|
}
|
|
@@ -141,6 +144,9 @@ export function defaultProjectDirs(projectRootAbs: string): ProjectDirs {
|
|
|
141
144
|
get pagesResolved(): string {
|
|
142
145
|
return dirs.pagesResolved
|
|
143
146
|
},
|
|
147
|
+
get projectResolved(): string {
|
|
148
|
+
return dirs.projectResolved
|
|
149
|
+
},
|
|
144
150
|
get projectRootAbs(): string {
|
|
145
151
|
return dirs.projectRootAbs
|
|
146
152
|
},
|
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
|
|
3
|
+
import { dirname, join, relative } from 'node:path'
|
|
4
4
|
import { extname } from 'node:path/posix'
|
|
5
5
|
import {
|
|
6
6
|
defaultTreeAdapter,
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
} from 'parse5'
|
|
12
12
|
import type { EntryPoint } from './esbuild.ts'
|
|
13
13
|
import type { DankBuild } from './flags.ts'
|
|
14
|
+
import type { Resolver } from './metadata.ts'
|
|
14
15
|
|
|
15
16
|
type CommentNode = DefaultTreeAdapterTypes.CommentNode
|
|
16
17
|
type Document = DefaultTreeAdapterTypes.Document
|
|
@@ -25,6 +26,7 @@ type CollectedImports = {
|
|
|
25
26
|
|
|
26
27
|
type PartialReference = {
|
|
27
28
|
commentNode: CommentNode
|
|
29
|
+
// path within pages dir omitting pages/ segment
|
|
28
30
|
fsPath: string
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -77,20 +79,24 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
77
79
|
#document: Document = defaultTreeAdapter.createDocument()
|
|
78
80
|
// todo cache entrypoints set for quicker diffing
|
|
79
81
|
// #entrypoints: Set<string> = new Set()
|
|
82
|
+
// path within pages dir omitting pages/ segment
|
|
80
83
|
#fsPath: string
|
|
81
84
|
#partials: Array<PartialContent> = []
|
|
85
|
+
#resolver: Resolver
|
|
82
86
|
#scripts: Array<ImportedScript> = []
|
|
83
87
|
#update: Object = Object()
|
|
84
88
|
#url: string
|
|
85
89
|
|
|
86
90
|
constructor(
|
|
87
91
|
build: DankBuild,
|
|
92
|
+
resolver: Resolver,
|
|
88
93
|
url: string,
|
|
89
94
|
fsPath: string,
|
|
90
95
|
decorations?: Array<HtmlDecoration>,
|
|
91
96
|
) {
|
|
92
97
|
super({ captureRejections: true })
|
|
93
98
|
this.#build = build
|
|
99
|
+
this.#resolver = resolver
|
|
94
100
|
this.#decorations = decorations
|
|
95
101
|
this.#url = url
|
|
96
102
|
this.#fsPath = fsPath
|
|
@@ -297,7 +303,7 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
297
303
|
dirname(this.#fsPath),
|
|
298
304
|
partialSpecifier,
|
|
299
305
|
)
|
|
300
|
-
if (!isPagesSubpathInPagesDir(
|
|
306
|
+
if (!this.#resolver.isPagesSubpathInPagesDir(partialPath)) {
|
|
301
307
|
errorExit(
|
|
302
308
|
`partial ${partialSpecifier} in webpage ${this.#fsPath} cannot be outside of the pages directory`,
|
|
303
309
|
)
|
|
@@ -338,7 +344,7 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
338
344
|
elem: Element,
|
|
339
345
|
): ImportedScript {
|
|
340
346
|
const inPath = join(this.#build.dirs.pages, dirname(this.#fsPath), href)
|
|
341
|
-
if (!
|
|
347
|
+
if (!this.#resolver.isProjectSubpathInPagesDir(inPath)) {
|
|
342
348
|
errorExit(
|
|
343
349
|
`href ${href} in webpage ${this.#fsPath} cannot reference sources outside of the pages directory`,
|
|
344
350
|
)
|
|
@@ -362,17 +368,6 @@ export class HtmlEntrypoint extends EventEmitter<HtmlEntrypointEvents> {
|
|
|
362
368
|
}
|
|
363
369
|
}
|
|
364
370
|
|
|
365
|
-
// check if relative dir is a subpath of pages dir when joined with pages dir
|
|
366
|
-
// used if the joined pages dir path is only used for the pages dir check
|
|
367
|
-
function isPagesSubpathInPagesDir(build: DankBuild, subpath: string): boolean {
|
|
368
|
-
return isPathInPagesDir(build, join(build.dirs.pages, subpath))
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
// check if subpath joined with pages dir is a subpath of pages dir
|
|
372
|
-
function isPathInPagesDir(build: DankBuild, p: string): boolean {
|
|
373
|
-
return resolve(p).startsWith(build.dirs.pagesResolved)
|
|
374
|
-
}
|
|
375
|
-
|
|
376
371
|
function getAttr(elem: Element, name: string) {
|
|
377
372
|
return elem.attrs.find(attr => attr.name === name)
|
|
378
373
|
}
|
|
@@ -411,6 +406,7 @@ function rewriteHrefs(scripts: Array<ImportedScript>, hrefs?: HtmlHrefs) {
|
|
|
411
406
|
}
|
|
412
407
|
}
|
|
413
408
|
|
|
409
|
+
// todo evented error handling so HtmlEntrypoint can be unit tested
|
|
414
410
|
function errorExit(msg: string): never {
|
|
415
411
|
console.log(`\u001b[31merror:\u001b[0m`, msg)
|
|
416
412
|
process.exit(1)
|
package/lib/metadata.ts
CHANGED
|
@@ -1,12 +1,51 @@
|
|
|
1
1
|
import EventEmitter from 'node:events'
|
|
2
2
|
import { writeFile } from 'node:fs/promises'
|
|
3
|
-
import { dirname, join, resolve
|
|
3
|
+
import { dirname, join, resolve } from 'node:path'
|
|
4
4
|
import type { BuildResult } from 'esbuild'
|
|
5
5
|
import type { EntryPoint } from './esbuild.ts'
|
|
6
|
-
import type { DankBuild } from './flags.ts'
|
|
6
|
+
import type { DankBuild, ProjectDirs } from './flags.ts'
|
|
7
|
+
|
|
8
|
+
export type ResolveError = 'outofbounds'
|
|
7
9
|
|
|
8
10
|
export type Resolver = {
|
|
9
|
-
|
|
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
|
+
}
|
|
10
49
|
}
|
|
11
50
|
|
|
12
51
|
// summary of a website build
|
|
@@ -45,10 +84,7 @@ export type WebsiteRegistryEvents = {
|
|
|
45
84
|
}
|
|
46
85
|
|
|
47
86
|
// manages website resources during `dank build` and `dank serve`
|
|
48
|
-
export class WebsiteRegistry
|
|
49
|
-
extends EventEmitter<WebsiteRegistryEvents>
|
|
50
|
-
implements Resolver
|
|
51
|
-
{
|
|
87
|
+
export class WebsiteRegistry extends EventEmitter<WebsiteRegistryEvents> {
|
|
52
88
|
#build: DankBuild
|
|
53
89
|
// paths of bundled esbuild outputs
|
|
54
90
|
#bundles: Set<string> = new Set()
|
|
@@ -57,11 +93,26 @@ export class WebsiteRegistry
|
|
|
57
93
|
// map of entrypoints to their output path
|
|
58
94
|
#entrypointHrefs: Record<string, string | null> = {}
|
|
59
95
|
#pageUrls: Array<string> = []
|
|
96
|
+
#resolver: Resolver
|
|
60
97
|
#workers: Array<WorkerManifest> | null = null
|
|
61
98
|
|
|
62
99
|
constructor(build: DankBuild) {
|
|
63
100
|
super()
|
|
64
101
|
this.#build = build
|
|
102
|
+
this.#resolver = new ResolverImpl(build.dirs)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
set copiedAssets(copiedAssets: Array<string> | null) {
|
|
106
|
+
this.#copiedAssets =
|
|
107
|
+
copiedAssets === null ? null : new Set(copiedAssets)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
set pageUrls(pageUrls: Array<string>) {
|
|
111
|
+
this.#pageUrls = pageUrls
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
get resolver(): Resolver {
|
|
115
|
+
return this.#resolver
|
|
65
116
|
}
|
|
66
117
|
|
|
67
118
|
// bundleOutputs(type?: 'css' | 'js'): Array<string> {
|
|
@@ -94,10 +145,6 @@ export class WebsiteRegistry
|
|
|
94
145
|
}
|
|
95
146
|
}
|
|
96
147
|
|
|
97
|
-
resolve(from: string, href: string): string {
|
|
98
|
-
return resolveImpl(this.#build, from, href)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
148
|
workerEntryPoints(): Array<EntryPoint> | null {
|
|
102
149
|
return (
|
|
103
150
|
this.#workers?.map(({ workerEntryPoint }) => ({
|
|
@@ -134,15 +181,6 @@ export class WebsiteRegistry
|
|
|
134
181
|
return manifest
|
|
135
182
|
}
|
|
136
183
|
|
|
137
|
-
set copiedAssets(copiedAssets: Array<string> | null) {
|
|
138
|
-
this.#copiedAssets =
|
|
139
|
-
copiedAssets === null ? null : new Set(copiedAssets)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
set pageUrls(pageUrls: Array<string>) {
|
|
143
|
-
this.#pageUrls = pageUrls
|
|
144
|
-
}
|
|
145
|
-
|
|
146
184
|
#manifest(buildTag: string): WebsiteManifest {
|
|
147
185
|
return {
|
|
148
186
|
buildTag,
|
|
@@ -211,17 +249,21 @@ export class WebsiteRegistry
|
|
|
211
249
|
}
|
|
212
250
|
|
|
213
251
|
// result accumulator of an esbuild `build` or `Context.rebuild`
|
|
214
|
-
export class BuildRegistry
|
|
215
|
-
#build: DankBuild
|
|
252
|
+
export class BuildRegistry {
|
|
216
253
|
#onComplete: OnBuildComplete
|
|
254
|
+
#resolver: Resolver
|
|
217
255
|
#workers: Array<Omit<WorkerManifest, 'dependentEntryPoint'>> | null = null
|
|
218
256
|
|
|
219
257
|
constructor(
|
|
220
258
|
build: DankBuild,
|
|
221
259
|
onComplete: (manifest: BuildManifest) => void,
|
|
222
260
|
) {
|
|
223
|
-
this.#build = build
|
|
224
261
|
this.#onComplete = onComplete
|
|
262
|
+
this.#resolver = new ResolverImpl(build.dirs)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
get resolver(): Resolver {
|
|
266
|
+
return this.#resolver
|
|
225
267
|
}
|
|
226
268
|
|
|
227
269
|
// resolve web worker imported by a webpage module
|
|
@@ -264,21 +306,4 @@ export class BuildRegistry implements Resolver {
|
|
|
264
306
|
workers,
|
|
265
307
|
})
|
|
266
308
|
}
|
|
267
|
-
|
|
268
|
-
resolve(from: string, href: string): string {
|
|
269
|
-
return resolveImpl(this.#build, from, href)
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function resolveImpl(build: DankBuild, from: string, href: string): string {
|
|
274
|
-
const { pagesResolved, projectRootAbs } = build.dirs
|
|
275
|
-
const fromDir = dirname(from)
|
|
276
|
-
const resolvedFromProjectRoot = join(projectRootAbs, fromDir, href)
|
|
277
|
-
if (!resolve(resolvedFromProjectRoot).startsWith(pagesResolved)) {
|
|
278
|
-
throw Error(
|
|
279
|
-
`href ${href} cannot be resolved from pages${sep}${from} to a path outside of the pages directory`,
|
|
280
|
-
)
|
|
281
|
-
} else {
|
|
282
|
-
return join(fromDir, href)
|
|
283
|
-
}
|
|
284
309
|
}
|