@eighty4/dank 0.0.4-1 → 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 +41 -70
- package/lib/build_tag.ts +3 -3
- package/lib/config.ts +372 -11
- package/lib/dank.ts +21 -150
- package/lib/define.ts +6 -4
- package/lib/developer.ts +146 -0
- package/lib/dirs.ts +83 -0
- package/lib/errors.ts +6 -0
- package/lib/esbuild.ts +19 -29
- package/lib/flags.ts +15 -121
- 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 +118 -270
- package/lib/services.ts +8 -8
- package/lib/watch.ts +39 -0
- package/lib_js/bin.js +79 -85
- package/lib_js/build.js +63 -86
- package/lib_js/build_tag.js +20 -21
- package/lib_js/config.js +237 -18
- package/lib_js/dank.js +5 -122
- package/lib_js/define.js +8 -5
- package/lib_js/dirs.js +61 -0
- package/lib_js/errors.js +9 -0
- package/lib_js/esbuild.js +155 -167
- package/lib_js/flags.js +30 -123
- package/lib_js/html.js +280 -231
- package/lib_js/http.js +176 -195
- package/lib_js/public.js +45 -46
- package/lib_js/registry.js +260 -0
- package/lib_js/serve.js +109 -251
- package/lib_js/services.js +152 -171
- package/lib_js/watch.js +35 -0
- package/lib_types/dank.d.ts +13 -1
- package/package.json +10 -4
- package/client/esbuild.js +0 -91
- package/lib_js/metadata.js +0 -210
package/lib/flags.ts
CHANGED
|
@@ -1,84 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export type DankBuild = {
|
|
6
|
-
dirs: ProjectDirs
|
|
7
|
-
minify: boolean
|
|
8
|
-
production: boolean
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type ProjectDirs = {
|
|
12
|
-
buildRoot: string
|
|
13
|
-
buildWatch: string
|
|
14
|
-
buildDist: string
|
|
15
|
-
pages: string
|
|
16
|
-
pagesResolved: string
|
|
17
|
-
projectResolved: string
|
|
18
|
-
projectRootAbs: string
|
|
19
|
-
public: string
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function resolveBuildFlags(): DankBuild {
|
|
23
|
-
const flags: DankBuild = {
|
|
24
|
-
dirs: defaultProjectDirs(cwd()),
|
|
25
|
-
minify: willMinify(),
|
|
26
|
-
production: isProductionBuild(),
|
|
27
|
-
}
|
|
28
|
-
return {
|
|
29
|
-
get dirs(): ProjectDirs {
|
|
30
|
-
return flags.dirs
|
|
31
|
-
},
|
|
32
|
-
get minify(): boolean {
|
|
33
|
-
return flags.minify
|
|
34
|
-
},
|
|
35
|
-
get production(): boolean {
|
|
36
|
-
return flags.production
|
|
37
|
-
},
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type DankServe = DankBuild & {
|
|
42
|
-
dankPort: number
|
|
43
|
-
esbuildPort: number
|
|
1
|
+
export type DankFlags = {
|
|
2
|
+
dankPort?: number
|
|
3
|
+
esbuildPort?: number
|
|
44
4
|
logHttp: boolean
|
|
5
|
+
minify: boolean
|
|
45
6
|
preview: boolean
|
|
7
|
+
production: boolean
|
|
46
8
|
}
|
|
47
9
|
|
|
48
|
-
export function
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
dankPort: dankPort(c, preview),
|
|
53
|
-
esbuildPort: esbuildPort(c),
|
|
10
|
+
export function resolveFlags(): Readonly<DankFlags> {
|
|
11
|
+
return Object.freeze({
|
|
12
|
+
dankPort: resolveDankPort(),
|
|
13
|
+
esbuildPort: resolveEsbuildPort(),
|
|
54
14
|
logHttp: willLogHttp(),
|
|
55
15
|
minify: willMinify(),
|
|
56
|
-
preview,
|
|
16
|
+
preview: isPreviewBuild(),
|
|
57
17
|
production: isProductionBuild(),
|
|
58
|
-
}
|
|
59
|
-
return {
|
|
60
|
-
get dirs(): ProjectDirs {
|
|
61
|
-
return flags.dirs
|
|
62
|
-
},
|
|
63
|
-
get dankPort(): number {
|
|
64
|
-
return flags.dankPort
|
|
65
|
-
},
|
|
66
|
-
get esbuildPort(): number {
|
|
67
|
-
return flags.esbuildPort
|
|
68
|
-
},
|
|
69
|
-
get logHttp(): boolean {
|
|
70
|
-
return flags.logHttp
|
|
71
|
-
},
|
|
72
|
-
get minify(): boolean {
|
|
73
|
-
return flags.minify
|
|
74
|
-
},
|
|
75
|
-
get preview(): boolean {
|
|
76
|
-
return flags.preview
|
|
77
|
-
},
|
|
78
|
-
get production(): boolean {
|
|
79
|
-
return flags.production
|
|
80
|
-
},
|
|
81
|
-
}
|
|
18
|
+
})
|
|
82
19
|
}
|
|
83
20
|
|
|
84
21
|
// `dank serve` will pre-bundle and use service worker
|
|
@@ -90,21 +27,18 @@ const isPreviewBuild = () =>
|
|
|
90
27
|
const isProductionBuild = () =>
|
|
91
28
|
process.env.PRODUCTION === 'true' || process.argv.includes('--production')
|
|
92
29
|
|
|
93
|
-
// `dank serve`
|
|
94
|
-
|
|
95
|
-
function dankPort(c: DankConfig, preview: boolean): number {
|
|
30
|
+
// `dank serve` port for frontend webserver
|
|
31
|
+
function resolveDankPort(): number | undefined {
|
|
96
32
|
if (process.env.DANK_PORT?.length) {
|
|
97
33
|
return parsePortEnvVar('DANK_PORT')
|
|
98
34
|
}
|
|
99
|
-
return preview ? c.previewPort || c.port || 4000 : c.port || 3000
|
|
100
35
|
}
|
|
101
36
|
|
|
102
|
-
// `dank serve`
|
|
103
|
-
function
|
|
37
|
+
// `dank serve` port for esbuild bundler integration
|
|
38
|
+
function resolveEsbuildPort(): number | undefined {
|
|
104
39
|
if (process.env.ESBUILD_PORT?.length) {
|
|
105
40
|
return parsePortEnvVar('ESBUILD_PORT')
|
|
106
41
|
}
|
|
107
|
-
return c.esbuild?.port || 3995
|
|
108
42
|
}
|
|
109
43
|
|
|
110
44
|
function parsePortEnvVar(name: string): number {
|
|
@@ -116,46 +50,6 @@ function parsePortEnvVar(name: string): number {
|
|
|
116
50
|
}
|
|
117
51
|
}
|
|
118
52
|
|
|
119
|
-
export function defaultProjectDirs(projectRootAbs: string): ProjectDirs {
|
|
120
|
-
const pages = 'pages'
|
|
121
|
-
const dirs: ProjectDirs = {
|
|
122
|
-
buildRoot: 'build',
|
|
123
|
-
buildDist: join('build', 'dist'),
|
|
124
|
-
buildWatch: join('build', 'watch'),
|
|
125
|
-
pages,
|
|
126
|
-
pagesResolved: resolve(join(projectRootAbs, pages)),
|
|
127
|
-
projectResolved: resolve(projectRootAbs),
|
|
128
|
-
projectRootAbs,
|
|
129
|
-
public: 'public',
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
get buildRoot(): string {
|
|
133
|
-
return dirs.buildRoot
|
|
134
|
-
},
|
|
135
|
-
get buildDist(): string {
|
|
136
|
-
return dirs.buildDist
|
|
137
|
-
},
|
|
138
|
-
get buildWatch(): string {
|
|
139
|
-
return dirs.buildWatch
|
|
140
|
-
},
|
|
141
|
-
get pages(): string {
|
|
142
|
-
return dirs.pages
|
|
143
|
-
},
|
|
144
|
-
get pagesResolved(): string {
|
|
145
|
-
return dirs.pagesResolved
|
|
146
|
-
},
|
|
147
|
-
get projectResolved(): string {
|
|
148
|
-
return dirs.projectResolved
|
|
149
|
-
},
|
|
150
|
-
get projectRootAbs(): string {
|
|
151
|
-
return dirs.projectRootAbs
|
|
152
|
-
},
|
|
153
|
-
get public(): string {
|
|
154
|
-
return dirs.public
|
|
155
|
-
},
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
53
|
const willMinify = () =>
|
|
160
54
|
isProductionBuild() ||
|
|
161
55
|
process.env.MINIFY === 'true' ||
|
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
|
}
|