@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/client/client.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
new EventSource("http://127.0.0.1:3995/esbuild").addEventListener("change",o=>{let{updated:a}=JSON.parse(o.data),e=new Set;for(let s of a)e.add(s);let d=Array.from(e).filter(s=>s.endsWith(".css"));if(d.length){console.log("esbuild css updates",d);let s={};for(let t of document.getElementsByTagName("link"))if(t.getAttribute("rel")==="stylesheet"){let n=new URL(t.href);(n.host=location.host)&&(s[n.pathname]=t)}let r=!1;for(let t of d){let n=s[t];if(n){let c=n.cloneNode();c.href=`${t}?${Math.random().toString(36).slice(2)}`,c.onload=()=>n.remove(),n.parentNode.insertBefore(c,n.nextSibling),r=!0}}r&&p()}if(d.length<e.size){let s=Array.from(e).filter(t=>!t.endsWith(".css")),r=new Set;for(let t of document.getElementsByTagName("script"))if(t.src.length){let n=new URL(t.src);(n.host=location.host)&&r.add(n.pathname)}s.some(t=>r.has(t))&&(console.log("esbuild js updates require reload"),y())}});function p(){let o=l("green","23995");o.style.opacity="0",o.animate([{opacity:0},{opacity:1},{opacity:1},{opacity:1},{opacity:.75},{opacity:.5},{opacity:.25},{opacity:0}],{duration:400,iterations:1,direction:"normal",easing:"linear"}),document.body.appendChild(o),Promise.all(o.getAnimations().map(a=>a.finished)).then(()=>o.remove())}var i=null;function y(){i||(i=l("orange","33995"),i.style.opacity="0",i.style.pointerEvents="none",i.animate([{opacity:0},{opacity:1}],{duration:400,iterations:1,direction:"normal",easing:"ease-in",fill:"forwards"}),document.body.appendChild(i))}function l(o,a){let e=document.createElement("div");return e.style.border="6px dashed "+o,e.style.zIndex=a,e.style.position="fixed",e.style.top=e.style.left="1px",e.style.height=e.style.width="calc(100% - 2px)",e.style.boxSizing="border-box",e}
|
package/lib/bin.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { buildWebsite } from './build.ts'
|
|
4
|
-
import {
|
|
4
|
+
import { DankError } from './errors.ts'
|
|
5
5
|
import { serveWebsite } from './serve.ts'
|
|
6
6
|
|
|
7
7
|
function printHelp(task?: 'build' | 'serve'): never {
|
|
@@ -71,16 +71,14 @@ const task: 'build' | 'serve' = (function resolveTask() {
|
|
|
71
71
|
return task
|
|
72
72
|
})()
|
|
73
73
|
|
|
74
|
-
const c = await loadConfig(task)
|
|
75
|
-
|
|
76
74
|
try {
|
|
77
75
|
switch (task) {
|
|
78
76
|
case 'build':
|
|
79
|
-
await buildWebsite(
|
|
77
|
+
await buildWebsite()
|
|
80
78
|
console.log(green('done'))
|
|
81
79
|
process.exit(0)
|
|
82
80
|
case 'serve':
|
|
83
|
-
await serveWebsite(
|
|
81
|
+
await serveWebsite()
|
|
84
82
|
}
|
|
85
83
|
} catch (e: unknown) {
|
|
86
84
|
errorExit(e)
|
|
@@ -88,13 +86,12 @@ try {
|
|
|
88
86
|
|
|
89
87
|
function printError(e: unknown) {
|
|
90
88
|
if (e !== null) {
|
|
91
|
-
if (
|
|
92
|
-
console.error(red('error:'), e)
|
|
93
|
-
} else if (e instanceof Error) {
|
|
89
|
+
if (e instanceof DankError) {
|
|
94
90
|
console.error(red('error:'), e.message)
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
} else if (e instanceof Error) {
|
|
92
|
+
console.error(red('error:'), e.stack ?? e.message)
|
|
93
|
+
} else {
|
|
94
|
+
console.error(red('error:'), e)
|
|
98
95
|
}
|
|
99
96
|
}
|
|
100
97
|
}
|
package/lib/build.ts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { createBuildTag } from './build_tag.ts'
|
|
4
|
-
import type
|
|
4
|
+
import { loadConfig, type ResolvedDankConfig } from './config.ts'
|
|
5
5
|
import { type DefineDankGlobal, createGlobalDefinitions } from './define.ts'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { HtmlEntrypoint } from './html.ts'
|
|
9
|
-
import { type WebsiteManifest, WebsiteRegistry } from './metadata.ts'
|
|
6
|
+
import type { DankDirectories } from './dirs.ts'
|
|
7
|
+
import { esbuildWebpages, esbuildWorkers } from './esbuild.ts'
|
|
10
8
|
import { copyAssets } from './public.ts'
|
|
9
|
+
import { type WebsiteManifest, WebsiteRegistry } from './registry.ts'
|
|
11
10
|
|
|
12
|
-
export async function buildWebsite(
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
export async function buildWebsite(
|
|
12
|
+
c?: ResolvedDankConfig,
|
|
13
|
+
): Promise<WebsiteManifest> {
|
|
14
|
+
if (!c) {
|
|
15
|
+
c = await loadConfig('build', process.cwd())
|
|
16
|
+
}
|
|
17
|
+
const buildTag = await createBuildTag(c.flags)
|
|
15
18
|
console.log(
|
|
16
|
-
|
|
17
|
-
?
|
|
19
|
+
c.flags.minify
|
|
20
|
+
? c.flags.production
|
|
18
21
|
? 'minified production'
|
|
19
22
|
: 'minified'
|
|
20
23
|
: 'unminified',
|
|
@@ -22,85 +25,52 @@ export async function buildWebsite(c: DankConfig): Promise<WebsiteManifest> {
|
|
|
22
25
|
buildTag,
|
|
23
26
|
'building in ./build/dist',
|
|
24
27
|
)
|
|
25
|
-
await rm(
|
|
26
|
-
await mkdir(
|
|
28
|
+
await rm(c.dirs.buildRoot, { recursive: true, force: true })
|
|
29
|
+
await mkdir(c.dirs.buildDist, { recursive: true })
|
|
27
30
|
for (const subdir of Object.keys(c.pages).filter(url => url !== '/')) {
|
|
28
|
-
await mkdir(join(
|
|
31
|
+
await mkdir(join(c.dirs.buildDist, subdir), { recursive: true })
|
|
29
32
|
}
|
|
30
|
-
await mkdir(join(
|
|
31
|
-
const registry =
|
|
32
|
-
registry.pageUrls = Object.keys(c.pages)
|
|
33
|
-
registry.copiedAssets = await copyAssets(build)
|
|
34
|
-
await buildWebpages(c, registry, build, createGlobalDefinitions(build))
|
|
33
|
+
await mkdir(join(c.dirs.buildRoot, 'metafiles'), { recursive: true })
|
|
34
|
+
const registry = await buildWebpages(c, createGlobalDefinitions(c))
|
|
35
35
|
return await registry.writeManifest(buildTag)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// builds all webpage entrypoints in one esbuild.build context
|
|
39
|
-
// to support code splitting
|
|
38
|
+
// builds all webpage entrypoints in one esbuild.build context to support code splitting
|
|
40
39
|
// returns all built assets URLs and webpage URLs from DankConfig.pages
|
|
41
40
|
async function buildWebpages(
|
|
42
|
-
c:
|
|
43
|
-
registry: WebsiteRegistry,
|
|
44
|
-
build: DankBuild,
|
|
41
|
+
c: ResolvedDankConfig,
|
|
45
42
|
define: DefineDankGlobal,
|
|
46
|
-
) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const html = new HtmlEntrypoint(
|
|
53
|
-
build,
|
|
54
|
-
registry.resolver,
|
|
55
|
-
urlPath,
|
|
56
|
-
fsPath,
|
|
57
|
-
)
|
|
58
|
-
loadingEntryPoints.push(new Promise(res => html.on('entrypoints', res)))
|
|
59
|
-
htmlEntrypoints.push(html)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// collect esbuild entrypoints from every HtmlEntrypoint
|
|
63
|
-
const uniqueEntryPoints: Set<string> = new Set()
|
|
64
|
-
const buildEntryPoints: Array<EntryPoint> = []
|
|
65
|
-
for (const pageEntryPoints of await Promise.all(loadingEntryPoints)) {
|
|
66
|
-
for (const entryPoint of pageEntryPoints) {
|
|
67
|
-
if (!uniqueEntryPoints.has(entryPoint.in)) {
|
|
68
|
-
buildEntryPoints.push(entryPoint)
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
await esbuildWebpages(build, registry, define, buildEntryPoints, c.esbuild)
|
|
43
|
+
): Promise<WebsiteRegistry> {
|
|
44
|
+
const registry = new WebsiteRegistry(c)
|
|
45
|
+
registry.configSync()
|
|
46
|
+
registry.copiedAssets = await copyAssets(c.dirs)
|
|
47
|
+
await Promise.all(registry.htmlEntrypoints.map(html => html.process()))
|
|
48
|
+
await esbuildWebpages(registry, define, registry.webpageEntryPoints)
|
|
74
49
|
|
|
75
50
|
// todo recursively build workers on building workers that create workers
|
|
76
|
-
const workerEntryPoints = registry.workerEntryPoints
|
|
51
|
+
const workerEntryPoints = registry.workerEntryPoints
|
|
77
52
|
if (workerEntryPoints?.length) {
|
|
78
|
-
await esbuildWorkers(
|
|
79
|
-
build,
|
|
80
|
-
registry,
|
|
81
|
-
define,
|
|
82
|
-
workerEntryPoints,
|
|
83
|
-
c.esbuild,
|
|
84
|
-
)
|
|
53
|
+
await esbuildWorkers(registry, define, workerEntryPoints)
|
|
85
54
|
}
|
|
86
|
-
await rewriteWorkerUrls(
|
|
55
|
+
await rewriteWorkerUrls(c.dirs, registry)
|
|
87
56
|
|
|
88
57
|
// write out html output with rewritten hrefs
|
|
89
58
|
await Promise.all(
|
|
90
|
-
htmlEntrypoints.map(async html => {
|
|
59
|
+
registry.htmlEntrypoints.map(async html => {
|
|
91
60
|
await writeFile(
|
|
92
|
-
join(
|
|
61
|
+
join(c.dirs.buildDist, html.url, 'index.html'),
|
|
93
62
|
html.output(registry),
|
|
94
63
|
)
|
|
95
64
|
}),
|
|
96
65
|
)
|
|
66
|
+
return registry
|
|
97
67
|
}
|
|
98
68
|
|
|
99
69
|
export async function rewriteWorkerUrls(
|
|
100
|
-
|
|
70
|
+
dirs: DankDirectories,
|
|
101
71
|
registry: WebsiteRegistry,
|
|
102
72
|
) {
|
|
103
|
-
const workers = registry.workers
|
|
73
|
+
const workers = registry.workers
|
|
104
74
|
if (!workers) {
|
|
105
75
|
return
|
|
106
76
|
}
|
|
@@ -113,7 +83,7 @@ export async function rewriteWorkerUrls(
|
|
|
113
83
|
const readingFiles = Promise.all(
|
|
114
84
|
dependentBundlePaths.map(async p => {
|
|
115
85
|
bundleOutputs[p] = await readFile(
|
|
116
|
-
join(
|
|
86
|
+
join(dirs.projectRootAbs, dirs.buildDist, p),
|
|
117
87
|
'utf8',
|
|
118
88
|
)
|
|
119
89
|
}),
|
|
@@ -125,8 +95,8 @@ export async function rewriteWorkerUrls(
|
|
|
125
95
|
for (const w of workers) {
|
|
126
96
|
rewriteChains[registry.mappedHref(w.dependentEntryPoint)].push(s =>
|
|
127
97
|
s.replace(
|
|
128
|
-
createWorkerRegex(w.workerUrlPlaceholder),
|
|
129
|
-
`new
|
|
98
|
+
createWorkerRegex(w.workerCtor, w.workerUrlPlaceholder),
|
|
99
|
+
`new ${w.workerCtor}('${registry.mappedHref(w.workerEntryPoint)}')`,
|
|
130
100
|
),
|
|
131
101
|
)
|
|
132
102
|
}
|
|
@@ -142,16 +112,19 @@ export async function rewriteWorkerUrls(
|
|
|
142
112
|
result = rewriteFn(result)
|
|
143
113
|
}
|
|
144
114
|
await writeFile(
|
|
145
|
-
join(
|
|
115
|
+
join(dirs.projectRootAbs, dirs.buildDist, p),
|
|
146
116
|
result,
|
|
147
117
|
)
|
|
148
118
|
}),
|
|
149
119
|
)
|
|
150
120
|
}
|
|
151
121
|
|
|
152
|
-
export function createWorkerRegex(
|
|
122
|
+
export function createWorkerRegex(
|
|
123
|
+
workerCtor: 'Worker' | 'SharedWorker',
|
|
124
|
+
workerUrl: string,
|
|
125
|
+
): RegExp {
|
|
153
126
|
return new RegExp(
|
|
154
|
-
`new(?:\\s|\\r?\\n)
|
|
127
|
+
`new(?:\\s|\\r?\\n)+${workerCtor}(?:\\s|\\r?\\n)*\\((?:\\s|\\r?\\n)*['"]${workerUrl}['"](?:\\s|\\r?\\n)*\\)`,
|
|
155
128
|
'g',
|
|
156
129
|
)
|
|
157
130
|
}
|
package/lib/build_tag.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { exec } from 'node:child_process'
|
|
2
|
-
import type {
|
|
2
|
+
import type { DankFlags } from './flags.ts'
|
|
3
3
|
|
|
4
|
-
export async function createBuildTag(
|
|
4
|
+
export async function createBuildTag(flags: DankFlags): Promise<string> {
|
|
5
5
|
const now = new Date()
|
|
6
6
|
const ms =
|
|
7
7
|
now.getUTCMilliseconds() +
|
|
@@ -11,7 +11,7 @@ export async function createBuildTag(build: DankBuild): Promise<string> {
|
|
|
11
11
|
const date = now.toISOString().substring(0, 10)
|
|
12
12
|
const time = String(ms).padStart(8, '0')
|
|
13
13
|
const when = `${date}-${time}`
|
|
14
|
-
if (
|
|
14
|
+
if (flags.production) {
|
|
15
15
|
const gitHash = await new Promise((res, rej) =>
|
|
16
16
|
exec('git rev-parse --short HEAD', (err, stdout) => {
|
|
17
17
|
if (err) rej(err)
|
package/lib/config.ts
CHANGED
|
@@ -6,15 +6,45 @@ import type {
|
|
|
6
6
|
PageMapping,
|
|
7
7
|
} from './dank.ts'
|
|
8
8
|
import { LOG } from './developer.ts'
|
|
9
|
-
import {
|
|
9
|
+
import { defaultProjectDirs, type DankDirectories } from './dirs.ts'
|
|
10
|
+
import {
|
|
11
|
+
resolveFlags as lookupDankFlags,
|
|
12
|
+
type DankFlags as DankFlags,
|
|
13
|
+
} from './flags.ts'
|
|
10
14
|
|
|
11
|
-
const
|
|
15
|
+
const DEFAULT_DEV_PORT = 3000
|
|
16
|
+
const DEFAULT_PREVIEW_PORT = 4000
|
|
17
|
+
const DEFAULT_ESBUILD_PORT = 3995
|
|
18
|
+
|
|
19
|
+
const DEFAULT_CONFIG_PATH = './dank.config.ts'
|
|
20
|
+
|
|
21
|
+
export type { DevService } from './dank.ts'
|
|
22
|
+
|
|
23
|
+
export type ResolvedDankConfig = {
|
|
24
|
+
// static from process boot
|
|
25
|
+
get dirs(): Readonly<DankDirectories>
|
|
26
|
+
get flags(): Readonly<Omit<DankFlags, 'dankPort' | 'esbuildPort'>>
|
|
27
|
+
get mode(): 'build' | 'serve'
|
|
28
|
+
|
|
29
|
+
// reloadable from `dank.config.ts` with `reload()`
|
|
30
|
+
get dankPort(): number
|
|
31
|
+
get esbuildPort(): number
|
|
32
|
+
get esbuild(): Readonly<Omit<EsbuildConfig, 'port'>> | undefined
|
|
33
|
+
get pages(): Readonly<Record<`/${string}`, PageMapping>>
|
|
34
|
+
get devPages(): Readonly<DankConfig['devPages']>
|
|
35
|
+
get services(): Readonly<DankConfig['services']>
|
|
36
|
+
|
|
37
|
+
reload(): Promise<void>
|
|
38
|
+
}
|
|
12
39
|
|
|
13
40
|
export async function loadConfig(
|
|
14
41
|
mode: 'build' | 'serve',
|
|
15
|
-
|
|
16
|
-
): Promise<
|
|
17
|
-
|
|
42
|
+
projectRootAbs: string,
|
|
43
|
+
): Promise<ResolvedDankConfig> {
|
|
44
|
+
if (!isAbsolute(projectRootAbs)) {
|
|
45
|
+
throw Error()
|
|
46
|
+
}
|
|
47
|
+
const modulePath = resolve(projectRootAbs, DEFAULT_CONFIG_PATH)
|
|
18
48
|
LOG({
|
|
19
49
|
realm: 'config',
|
|
20
50
|
message: 'loading config module',
|
|
@@ -22,37 +52,119 @@ export async function loadConfig(
|
|
|
22
52
|
modulePath,
|
|
23
53
|
},
|
|
24
54
|
})
|
|
25
|
-
const
|
|
26
|
-
|
|
55
|
+
const dirs = await defaultProjectDirs(projectRootAbs)
|
|
56
|
+
const c = new DankConfigInternal(mode, modulePath, dirs)
|
|
57
|
+
await c.reload()
|
|
27
58
|
return c
|
|
28
59
|
}
|
|
29
60
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
61
|
+
class DankConfigInternal implements ResolvedDankConfig {
|
|
62
|
+
#dirs: Readonly<DankDirectories>
|
|
63
|
+
#flags: Readonly<DankFlags>
|
|
64
|
+
#mode: 'build' | 'serve'
|
|
65
|
+
#modulePath: string
|
|
66
|
+
|
|
67
|
+
#dankPort: number = DEFAULT_DEV_PORT
|
|
68
|
+
#esbuildPort: number = DEFAULT_ESBUILD_PORT
|
|
69
|
+
#esbuild: Readonly<Omit<EsbuildConfig, 'port'>> | undefined
|
|
70
|
+
#pages: Readonly<Record<`/${string}`, PageMapping>> = {}
|
|
71
|
+
#devPages: Readonly<DankConfig['devPages']>
|
|
72
|
+
#services: Readonly<DankConfig['services']>
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
mode: 'build' | 'serve',
|
|
76
|
+
modulePath: string,
|
|
77
|
+
dirs: DankDirectories,
|
|
78
|
+
) {
|
|
79
|
+
this.#dirs = dirs
|
|
80
|
+
this.#flags = lookupDankFlags()
|
|
81
|
+
this.#mode = mode
|
|
82
|
+
this.#modulePath = modulePath
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get dankPort(): number {
|
|
86
|
+
return this.#dankPort
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get esbuildPort(): number {
|
|
90
|
+
return this.#esbuildPort
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
get esbuild(): Omit<EsbuildConfig, 'port'> | undefined {
|
|
94
|
+
return this.#esbuild
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get dirs(): Readonly<DankDirectories> {
|
|
98
|
+
return this.#dirs
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get flags(): Readonly<Omit<DankFlags, 'dankPort' | 'esbuildPort'>> {
|
|
102
|
+
return this.#flags
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get mode(): 'build' | 'serve' {
|
|
106
|
+
return this.#mode
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get pages(): Readonly<Record<`/${string}`, PageMapping>> {
|
|
110
|
+
return this.#pages
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get devPages(): Readonly<DankConfig['devPages']> {
|
|
114
|
+
return this.#devPages
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
get services(): Readonly<DankConfig['services']> {
|
|
118
|
+
return this.#services
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async reload() {
|
|
122
|
+
const userConfig = await resolveConfig(
|
|
123
|
+
this.#modulePath,
|
|
124
|
+
resolveDankDetails(this.#mode, this.#flags),
|
|
125
|
+
)
|
|
126
|
+
this.#dankPort = resolveDankPort(this.#flags, userConfig)
|
|
127
|
+
this.#esbuildPort = resolveEsbuildPort(this.#flags, userConfig)
|
|
128
|
+
this.#esbuild = Object.freeze(userConfig.esbuild)
|
|
129
|
+
this.#pages = Object.freeze(normalizePages(userConfig.pages))
|
|
130
|
+
this.#devPages = Object.freeze(userConfig.devPages)
|
|
131
|
+
this.#services = Object.freeze(userConfig.services)
|
|
35
132
|
}
|
|
36
133
|
}
|
|
37
134
|
|
|
38
|
-
|
|
39
|
-
|
|
135
|
+
function resolveDankPort(flags: DankFlags, userConfig: DankConfig): number {
|
|
136
|
+
return (
|
|
137
|
+
flags.dankPort ||
|
|
138
|
+
(flags.preview
|
|
139
|
+
? userConfig.previewPort || userConfig.port || DEFAULT_PREVIEW_PORT
|
|
140
|
+
: userConfig.port || DEFAULT_DEV_PORT)
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function resolveEsbuildPort(flags: DankFlags, userConfig: DankConfig): number {
|
|
145
|
+
return flags.esbuildPort || userConfig.esbuild?.port || DEFAULT_ESBUILD_PORT
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function resolveConfig(
|
|
40
149
|
modulePath: string,
|
|
150
|
+
details: DankDetails,
|
|
41
151
|
): Promise<DankConfig> {
|
|
42
152
|
const module = await import(`${modulePath}?${Date.now()}`)
|
|
43
153
|
const c: Partial<DankConfig> =
|
|
44
154
|
typeof module.default === 'function'
|
|
45
|
-
? await module.default(
|
|
155
|
+
? await module.default(details)
|
|
46
156
|
: module.default
|
|
47
157
|
validateDankConfig(c)
|
|
48
158
|
return c as DankConfig
|
|
49
159
|
}
|
|
50
160
|
|
|
51
|
-
function resolveDankDetails(
|
|
52
|
-
|
|
161
|
+
function resolveDankDetails(
|
|
162
|
+
mode: 'build' | 'serve',
|
|
163
|
+
flags: DankFlags,
|
|
164
|
+
): DankDetails {
|
|
53
165
|
return {
|
|
54
|
-
dev: !production,
|
|
55
|
-
production,
|
|
166
|
+
dev: !flags.production,
|
|
167
|
+
production: flags.production,
|
|
56
168
|
mode,
|
|
57
169
|
}
|
|
58
170
|
}
|
|
@@ -61,6 +173,7 @@ function validateDankConfig(c: Partial<DankConfig>) {
|
|
|
61
173
|
try {
|
|
62
174
|
validatePorts(c)
|
|
63
175
|
validatePages(c.pages)
|
|
176
|
+
validateDevPages(c.devPages)
|
|
64
177
|
validateDevServices(c.services)
|
|
65
178
|
validateEsbuildConfig(c.esbuild)
|
|
66
179
|
} catch (e: any) {
|
|
@@ -140,6 +253,46 @@ function validatePages(pages?: DankConfig['pages']) {
|
|
|
140
253
|
}
|
|
141
254
|
}
|
|
142
255
|
|
|
256
|
+
function validateDevPages(devPages?: DankConfig['devPages']) {
|
|
257
|
+
if (devPages) {
|
|
258
|
+
for (const [urlPath, mapping] of Object.entries(devPages)) {
|
|
259
|
+
if (!urlPath.startsWith('/__')) {
|
|
260
|
+
throw Error(
|
|
261
|
+
`DankConfig.devPages['${urlPath}'] must start \`${urlPath}\` with a \`/__\` path prefix`,
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
if (typeof mapping === 'string') {
|
|
265
|
+
if (!mapping.endsWith('.html')) {
|
|
266
|
+
throw Error(
|
|
267
|
+
`DankConfig.devPages['${urlPath}'] must configure an html file or DevPageMapping config`,
|
|
268
|
+
)
|
|
269
|
+
}
|
|
270
|
+
} else if (typeof mapping === 'object') {
|
|
271
|
+
if (
|
|
272
|
+
typeof mapping.label !== 'string' ||
|
|
273
|
+
!mapping.label.length
|
|
274
|
+
) {
|
|
275
|
+
throw Error(
|
|
276
|
+
`DankConfig.devPages['${urlPath}'].label must declare a label`,
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
if (
|
|
280
|
+
typeof mapping.webpage !== 'string' ||
|
|
281
|
+
!mapping.webpage.endsWith('.html')
|
|
282
|
+
) {
|
|
283
|
+
throw Error(
|
|
284
|
+
`DankConfig.devPages['${urlPath}'].webpage must configure an html file`,
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
throw Error(
|
|
289
|
+
`DankConfig.devPages['${urlPath}'] must be a DevPageMapping config with \`label\` and \`webpage\` values`,
|
|
290
|
+
)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
143
296
|
function validatePageMapping(urlPath: string, mapping: PageMapping) {
|
|
144
297
|
if (
|
|
145
298
|
mapping.webpage === null ||
|
|
@@ -209,16 +362,18 @@ function validateDevServices(services: DankConfig['services']) {
|
|
|
209
362
|
}
|
|
210
363
|
}
|
|
211
364
|
|
|
212
|
-
function
|
|
365
|
+
function normalizePages(
|
|
366
|
+
pages: DankConfig['pages'],
|
|
367
|
+
): Record<`/${string}`, PageMapping> {
|
|
368
|
+
const result: Record<`/${string}`, PageMapping> = {}
|
|
213
369
|
for (const [pageUrl, mapping] of Object.entries(pages)) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
370
|
+
const mappedMapping =
|
|
371
|
+
typeof mapping === 'string' ? { webpage: mapping } : mapping
|
|
372
|
+
mappedMapping.webpage = mappedMapping.webpage.replace(
|
|
373
|
+
/^\.\//,
|
|
374
|
+
'',
|
|
375
|
+
) as `${string}.html`
|
|
376
|
+
result[pageUrl as `/${string}`] = mappedMapping
|
|
219
377
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
function normalizePagePath(p: `${string}.html`): `${string}.html` {
|
|
223
|
-
return p.replace(/^\.\//, '') as `${string}.html`
|
|
378
|
+
return result
|
|
224
379
|
}
|
package/lib/dank.ts
CHANGED
|
@@ -13,6 +13,8 @@ export type DankConfig = {
|
|
|
13
13
|
// cdn url rewriting can be simulated with PageMapping
|
|
14
14
|
pages: Record<`/${string}`, `${string}.html` | PageMapping>
|
|
15
15
|
|
|
16
|
+
devPages?: Record<`/__${string}`, `${string}.html` | DevPageMapping>
|
|
17
|
+
|
|
16
18
|
// port of `dank serve` frontend dev server
|
|
17
19
|
// used for `dan serve --preview` if previewPort not specified
|
|
18
20
|
port?: number
|
|
@@ -32,6 +34,11 @@ export type PageMapping = {
|
|
|
32
34
|
webpage: `${string}.html`
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
export type DevPageMapping = {
|
|
38
|
+
label: string
|
|
39
|
+
webpage: `${string}.html`
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
export type DevService = {
|
|
36
43
|
command: string
|
|
37
44
|
cwd?: string
|
package/lib/define.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResolvedDankConfig } from './config.ts'
|
|
2
2
|
|
|
3
3
|
export type DankGlobal = {
|
|
4
4
|
IS_DEV: boolean
|
|
@@ -9,9 +9,11 @@ type DefineDankGlobalKey = 'dank.IS_DEV' | 'dank.IS_PROD'
|
|
|
9
9
|
|
|
10
10
|
export type DefineDankGlobal = Record<DefineDankGlobalKey, string>
|
|
11
11
|
|
|
12
|
-
export function createGlobalDefinitions(
|
|
12
|
+
export function createGlobalDefinitions(
|
|
13
|
+
c: ResolvedDankConfig,
|
|
14
|
+
): DefineDankGlobal {
|
|
13
15
|
return {
|
|
14
|
-
'dank.IS_DEV': JSON.stringify(!
|
|
15
|
-
'dank.IS_PROD': JSON.stringify(
|
|
16
|
+
'dank.IS_DEV': JSON.stringify(!c.flags.production),
|
|
17
|
+
'dank.IS_PROD': JSON.stringify(c.flags.production),
|
|
16
18
|
}
|
|
17
19
|
}
|
package/lib/developer.ts
CHANGED
|
@@ -4,6 +4,9 @@ import os from 'node:os'
|
|
|
4
4
|
import { dirname, resolve } from 'node:path'
|
|
5
5
|
import packageJson from '../package.json' with { type: 'json' }
|
|
6
6
|
|
|
7
|
+
const CONSOLE =
|
|
8
|
+
process.env.DANK_LOG_CONSOLE === '1' ||
|
|
9
|
+
process.env.DANK_LOG_CONSOLE === 'true'
|
|
7
10
|
const FILE = process.env.DANK_LOG_FILE
|
|
8
11
|
const ROLLING =
|
|
9
12
|
process.env.DANK_LOG_ROLLING === '1' ||
|
|
@@ -33,7 +36,7 @@ type LogEventData =
|
|
|
33
36
|
| Set<LogEventDatum>
|
|
34
37
|
| Record<string, LogEventDatum>
|
|
35
38
|
|
|
36
|
-
type LogEventDatum = boolean | number | string | null
|
|
39
|
+
type LogEventDatum = boolean | number | string | null | undefined
|
|
37
40
|
|
|
38
41
|
function toStringLogEvent(logEvent: LogEvent): string {
|
|
39
42
|
const when = new Date().toISOString()
|
|
@@ -70,8 +73,17 @@ function toStringData(datum: LogEventData): string {
|
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
|
|
73
|
-
function
|
|
74
|
-
|
|
76
|
+
function logToConsoleAndFile(out: string) {
|
|
77
|
+
logToConsole(out)
|
|
78
|
+
logToFile(out)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function logToConsole(out: string) {
|
|
82
|
+
console.log('\n' + out)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function logToFile(out: string) {
|
|
86
|
+
logs.push(out)
|
|
75
87
|
if (!initialized) {
|
|
76
88
|
initialized = true
|
|
77
89
|
preparing = prepareLogFile().catch(onPrepareLogFileError)
|
|
@@ -114,4 +126,21 @@ function onPrepareLogFileError(e: any) {
|
|
|
114
126
|
process.exit(1)
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
|
|
129
|
+
function makeLogger(
|
|
130
|
+
logDelegate: (out: string) => void,
|
|
131
|
+
): (logEvent: LogEvent) => void {
|
|
132
|
+
return logEvent => logDelegate(toStringLogEvent(logEvent))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const LOG = (function resolveLogFn() {
|
|
136
|
+
if (CONSOLE && FILE?.length) {
|
|
137
|
+
return makeLogger(logToConsoleAndFile)
|
|
138
|
+
}
|
|
139
|
+
if (CONSOLE) {
|
|
140
|
+
return makeLogger(logToConsole)
|
|
141
|
+
}
|
|
142
|
+
if (FILE?.length) {
|
|
143
|
+
return makeLogger(logToFile)
|
|
144
|
+
}
|
|
145
|
+
return () => {}
|
|
146
|
+
})()
|