@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
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import EventEmitter from 'node:events';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join, resolve } from 'node:path';
|
|
4
|
+
class ResolverImpl {
|
|
5
|
+
#dirs;
|
|
6
|
+
constructor(dirs) {
|
|
7
|
+
this.#dirs = dirs;
|
|
8
|
+
}
|
|
9
|
+
isProjectSubpathInPagesDir(p) {
|
|
10
|
+
return resolve(join(this.#dirs.projectResolved, p)).startsWith(this.#dirs.pagesResolved);
|
|
11
|
+
}
|
|
12
|
+
isPagesSubpathInPagesDir(p) {
|
|
13
|
+
return this.isProjectSubpathInPagesDir(join(this.#dirs.pages, p));
|
|
14
|
+
}
|
|
15
|
+
resolveHrefInPagesDir(from, href) {
|
|
16
|
+
const p = join(dirname(from), href);
|
|
17
|
+
if (this.isProjectSubpathInPagesDir(p)) {
|
|
18
|
+
return p;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return 'outofbounds';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// manages website resources during `dank build` and `dank serve`
|
|
26
|
+
export class WebsiteRegistry extends EventEmitter {
|
|
27
|
+
#build;
|
|
28
|
+
// paths of bundled esbuild outputs
|
|
29
|
+
#bundles = new Set();
|
|
30
|
+
// public dir assets
|
|
31
|
+
#copiedAssets = null;
|
|
32
|
+
// map of entrypoints to their output path
|
|
33
|
+
#entrypointHrefs = {};
|
|
34
|
+
#pageUrls = [];
|
|
35
|
+
#resolver;
|
|
36
|
+
#workers = null;
|
|
37
|
+
constructor(build) {
|
|
38
|
+
super();
|
|
39
|
+
this.#build = build;
|
|
40
|
+
this.#resolver = new ResolverImpl(build.dirs);
|
|
41
|
+
}
|
|
42
|
+
set copiedAssets(copiedAssets) {
|
|
43
|
+
this.#copiedAssets =
|
|
44
|
+
copiedAssets === null ? null : new Set(copiedAssets);
|
|
45
|
+
}
|
|
46
|
+
set pageUrls(pageUrls) {
|
|
47
|
+
this.#pageUrls = pageUrls;
|
|
48
|
+
}
|
|
49
|
+
get resolver() {
|
|
50
|
+
return this.#resolver;
|
|
51
|
+
}
|
|
52
|
+
// bundleOutputs(type?: 'css' | 'js'): Array<string> {
|
|
53
|
+
// if (!type) {
|
|
54
|
+
// return Array.from(this.#bundles)
|
|
55
|
+
// } else {
|
|
56
|
+
// return Array.from(this.#bundles).filter(p => p.endsWith(type))
|
|
57
|
+
// }
|
|
58
|
+
// }
|
|
59
|
+
buildRegistry() {
|
|
60
|
+
return new BuildRegistry(this.#build, this.#onBuildManifest);
|
|
61
|
+
}
|
|
62
|
+
files() {
|
|
63
|
+
const files = new Set();
|
|
64
|
+
for (const pageUrl of this.#pageUrls)
|
|
65
|
+
files.add(pageUrl === '/' ? '/index.html' : `${pageUrl}/index.html`);
|
|
66
|
+
for (const f of this.#bundles)
|
|
67
|
+
files.add(f);
|
|
68
|
+
if (this.#copiedAssets)
|
|
69
|
+
for (const f of this.#copiedAssets)
|
|
70
|
+
files.add(f);
|
|
71
|
+
return files;
|
|
72
|
+
}
|
|
73
|
+
mappedHref(lookup) {
|
|
74
|
+
const found = this.#entrypointHrefs[lookup];
|
|
75
|
+
if (found) {
|
|
76
|
+
return found;
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
throw Error(`mapped href for ${lookup} not found`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
workerEntryPoints() {
|
|
83
|
+
return (this.#workers?.map(({ workerEntryPoint }) => ({
|
|
84
|
+
in: workerEntryPoint,
|
|
85
|
+
out: workerEntryPoint
|
|
86
|
+
.replace(/^pages[\//]/, '')
|
|
87
|
+
.replace(/\.(mj|t)s$/, '.js'),
|
|
88
|
+
})) || null);
|
|
89
|
+
}
|
|
90
|
+
workers() {
|
|
91
|
+
return this.#workers;
|
|
92
|
+
}
|
|
93
|
+
async writeManifest(buildTag) {
|
|
94
|
+
const manifest = this.#manifest(buildTag);
|
|
95
|
+
await writeFile(join(this.#build.dirs.projectRootAbs, this.#build.dirs.buildRoot, 'website.json'), JSON.stringify({
|
|
96
|
+
buildTag,
|
|
97
|
+
files: Array.from(manifest.files),
|
|
98
|
+
pageUrls: Array.from(manifest.pageUrls),
|
|
99
|
+
}, null, 4));
|
|
100
|
+
return manifest;
|
|
101
|
+
}
|
|
102
|
+
#manifest(buildTag) {
|
|
103
|
+
return {
|
|
104
|
+
buildTag,
|
|
105
|
+
files: this.files(),
|
|
106
|
+
pageUrls: new Set(this.#pageUrls),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
#onBuildManifest = (build) => {
|
|
110
|
+
// collect built bundle entrypoint hrefs
|
|
111
|
+
for (const [outPath, entrypoint] of Object.entries(build.bundles)) {
|
|
112
|
+
this.#bundles.add(outPath);
|
|
113
|
+
if (entrypoint) {
|
|
114
|
+
this.#entrypointHrefs[entrypoint] = outPath;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// determine if worker entrypoints have changed
|
|
118
|
+
let updatedWorkerEntrypoints = false;
|
|
119
|
+
const previousWorkers = this.#workers === null
|
|
120
|
+
? null
|
|
121
|
+
: new Set(this.#workers.map(w => w.workerEntryPoint));
|
|
122
|
+
if (build.workers) {
|
|
123
|
+
if (!previousWorkers ||
|
|
124
|
+
previousWorkers.size !==
|
|
125
|
+
new Set(build.workers.map(w => w.workerEntryPoint)).size) {
|
|
126
|
+
updatedWorkerEntrypoints = true;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
updatedWorkerEntrypoints = !build.workers.every(w => previousWorkers.has(w.workerEntryPoint));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (previousWorkers) {
|
|
133
|
+
updatedWorkerEntrypoints = true;
|
|
134
|
+
}
|
|
135
|
+
// merge unique entrypoints from built workers with registry state
|
|
136
|
+
// todo filtering out unique occurrences of clientScript and workerUrl
|
|
137
|
+
// drops reporting/summary/debugging capabilities, but currently
|
|
138
|
+
// this.#workers is used for unique worker/client entrypoints
|
|
139
|
+
if (build.workers) {
|
|
140
|
+
if (!this.#workers) {
|
|
141
|
+
this.#workers = build.workers;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
for (const w of build.workers) {
|
|
145
|
+
const found = this.#workers.find(w2 => {
|
|
146
|
+
return (w.dependentEntryPoint === w2.dependentEntryPoint &&
|
|
147
|
+
w.workerEntryPoint === w2.workerEntryPoint);
|
|
148
|
+
});
|
|
149
|
+
if (!found) {
|
|
150
|
+
this.#workers.push(w);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (updatedWorkerEntrypoints) {
|
|
156
|
+
this.emit('workers');
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// result accumulator of an esbuild `build` or `Context.rebuild`
|
|
161
|
+
export class BuildRegistry {
|
|
162
|
+
#onComplete;
|
|
163
|
+
#resolver;
|
|
164
|
+
#workers = null;
|
|
165
|
+
constructor(build, onComplete) {
|
|
166
|
+
this.#onComplete = onComplete;
|
|
167
|
+
this.#resolver = new ResolverImpl(build.dirs);
|
|
168
|
+
}
|
|
169
|
+
get resolver() {
|
|
170
|
+
return this.#resolver;
|
|
171
|
+
}
|
|
172
|
+
// resolve web worker imported by a webpage module
|
|
173
|
+
addWorker(worker) {
|
|
174
|
+
// todo normalize path
|
|
175
|
+
if (!this.#workers) {
|
|
176
|
+
this.#workers = [worker];
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
this.#workers.push(worker);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
completeBuild(result) {
|
|
183
|
+
const bundles = {};
|
|
184
|
+
for (const [outPath, output] of Object.entries(result.metafile.outputs)) {
|
|
185
|
+
bundles[outPath.replace(/^build[/\\]dist/, '')] =
|
|
186
|
+
output.entryPoint || null;
|
|
187
|
+
}
|
|
188
|
+
let workers = null;
|
|
189
|
+
if (this.#workers) {
|
|
190
|
+
workers = [];
|
|
191
|
+
for (const output of Object.values(result.metafile.outputs)) {
|
|
192
|
+
if (!output.entryPoint)
|
|
193
|
+
continue;
|
|
194
|
+
const inputs = Object.keys(output.inputs);
|
|
195
|
+
for (const worker of this.#workers) {
|
|
196
|
+
if (inputs.includes(worker.clientScript)) {
|
|
197
|
+
workers.push({
|
|
198
|
+
...worker,
|
|
199
|
+
dependentEntryPoint: output.entryPoint,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.#onComplete({
|
|
206
|
+
bundles,
|
|
207
|
+
workers,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
package/lib_js/public.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { copyFile, mkdir, readdir, stat } from 'node:fs/promises';
|
|
2
|
+
import { platform } from 'node:os';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
|
-
export async function copyAssets(
|
|
4
|
+
export async function copyAssets(build) {
|
|
4
5
|
try {
|
|
5
|
-
const stats = await stat(
|
|
6
|
+
const stats = await stat(build.dirs.public);
|
|
6
7
|
if (stats.isDirectory()) {
|
|
7
|
-
await mkdir(
|
|
8
|
-
return await recursiveCopyAssets(
|
|
8
|
+
await mkdir(build.dirs.buildDist, { recursive: true });
|
|
9
|
+
return await recursiveCopyAssets(build);
|
|
9
10
|
}
|
|
10
11
|
else {
|
|
11
12
|
throw Error('./public cannot be a file');
|
|
@@ -15,22 +16,29 @@ export async function copyAssets(outRoot) {
|
|
|
15
16
|
return null;
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
|
-
|
|
19
|
+
const IGNORE = platform() === 'darwin' ? ['.DS_Store'] : [];
|
|
20
|
+
async function recursiveCopyAssets(build, dir = '') {
|
|
19
21
|
const copied = [];
|
|
20
|
-
const to = join(
|
|
22
|
+
const to = join(build.dirs.buildDist, dir);
|
|
21
23
|
let madeDir = dir === '';
|
|
22
|
-
|
|
24
|
+
const listingDir = join(build.dirs.public, dir);
|
|
25
|
+
for (const p of await readdir(listingDir)) {
|
|
26
|
+
if (IGNORE.includes(p)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
23
29
|
try {
|
|
24
|
-
const stats = await stat(join(
|
|
30
|
+
const stats = await stat(join(listingDir, p));
|
|
25
31
|
if (stats.isDirectory()) {
|
|
26
|
-
copied.push(...(await recursiveCopyAssets(
|
|
32
|
+
copied.push(...(await recursiveCopyAssets(build, join(dir, p))));
|
|
27
33
|
}
|
|
28
34
|
else {
|
|
29
35
|
if (!madeDir) {
|
|
30
|
-
await mkdir(join(
|
|
36
|
+
await mkdir(join(build.dirs.buildDist, dir), {
|
|
37
|
+
recursive: true,
|
|
38
|
+
});
|
|
31
39
|
madeDir = true;
|
|
32
40
|
}
|
|
33
|
-
await copyFile(join(
|
|
41
|
+
await copyFile(join(listingDir, p), join(to, p));
|
|
34
42
|
copied.push('/' + join(dir, p).replaceAll('\\', '/'));
|
|
35
43
|
}
|
|
36
44
|
}
|
package/lib_js/serve.js
CHANGED
|
@@ -1,43 +1,57 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, watch as _watch } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, rm, watch as _watch, writeFile, } from 'node:fs/promises';
|
|
2
2
|
import { extname, join, resolve } from 'node:path';
|
|
3
3
|
import { buildWebsite } from "./build.js";
|
|
4
4
|
import { loadConfig } from "./config.js";
|
|
5
5
|
import { createGlobalDefinitions } from "./define.js";
|
|
6
6
|
import { esbuildDevContext } from "./esbuild.js";
|
|
7
|
-
import {
|
|
7
|
+
import { resolveServeFlags } from "./flags.js";
|
|
8
8
|
import { HtmlEntrypoint } from "./html.js";
|
|
9
|
-
import { createBuiltDistFilesFetcher, createDevServeFilesFetcher,
|
|
9
|
+
import { createBuiltDistFilesFetcher, createDevServeFilesFetcher, startWebServer, } from "./http.js";
|
|
10
|
+
import { WebsiteRegistry } from "./metadata.js";
|
|
10
11
|
import { startDevServices, updateDevServices } from "./services.js";
|
|
11
|
-
const isPreview = isPreviewBuild();
|
|
12
|
-
// alternate port for --preview bc of service worker
|
|
13
|
-
const PORT = isPreview ? 4000 : 3000;
|
|
14
|
-
// port for esbuild.serve
|
|
15
|
-
const ESBUILD_PORT = 2999;
|
|
16
12
|
export async function serveWebsite(c) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
const serve = resolveServeFlags(c);
|
|
14
|
+
await rm(serve.dirs.buildRoot, { force: true, recursive: true });
|
|
15
|
+
const abortController = new AbortController();
|
|
16
|
+
process.once('exit', () => abortController.abort());
|
|
17
|
+
if (serve.preview) {
|
|
18
|
+
await startPreviewMode(c, serve, abortController.signal);
|
|
20
19
|
}
|
|
21
20
|
else {
|
|
22
|
-
|
|
23
|
-
await startDevMode(c, abortController.signal);
|
|
21
|
+
await startDevMode(c, serve, abortController.signal);
|
|
24
22
|
}
|
|
25
23
|
return new Promise(() => { });
|
|
26
24
|
}
|
|
27
|
-
async function startPreviewMode(c) {
|
|
28
|
-
const
|
|
29
|
-
const frontend = createBuiltDistFilesFetcher(
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
async function startPreviewMode(c, serve, signal) {
|
|
26
|
+
const manifest = await buildWebsite(c, serve);
|
|
27
|
+
const frontend = createBuiltDistFilesFetcher(serve.dirs.buildDist, manifest);
|
|
28
|
+
const devServices = startDevServices(c, signal);
|
|
29
|
+
startWebServer(serve, frontend, devServices.http, {
|
|
30
|
+
urls: Object.keys(c.pages),
|
|
31
|
+
urlRewrites: collectUrlRewrites(c),
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function collectUrlRewrites(c) {
|
|
35
|
+
return Object.keys(c.pages)
|
|
36
|
+
.sort()
|
|
37
|
+
.map(url => {
|
|
38
|
+
const mapping = c.pages[url];
|
|
39
|
+
return typeof mapping !== 'object' || !mapping.pattern
|
|
40
|
+
? null
|
|
41
|
+
: { url, pattern: mapping.pattern };
|
|
42
|
+
})
|
|
43
|
+
.filter(mapping => mapping !== null);
|
|
32
44
|
}
|
|
33
45
|
// todo changing partials triggers update on html pages
|
|
34
|
-
async function startDevMode(c, signal) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const clientJS = await loadClientJS();
|
|
46
|
+
async function startDevMode(c, serve, signal) {
|
|
47
|
+
await mkdir(serve.dirs.buildWatch, { recursive: true });
|
|
48
|
+
const registry = new WebsiteRegistry(serve);
|
|
49
|
+
const clientJS = await loadClientJS(serve.esbuildPort);
|
|
38
50
|
const pagesByUrlPath = {};
|
|
51
|
+
const partialsByUrlPath = {};
|
|
39
52
|
const entryPointsByUrlPath = {};
|
|
40
53
|
let buildContext = null;
|
|
54
|
+
registry.on('workers', resetBuildContext);
|
|
41
55
|
watch('dank.config.ts', signal, async () => {
|
|
42
56
|
let updated;
|
|
43
57
|
try {
|
|
@@ -47,17 +61,18 @@ async function startDevMode(c, signal) {
|
|
|
47
61
|
return;
|
|
48
62
|
}
|
|
49
63
|
const prevPages = new Set(Object.keys(pagesByUrlPath));
|
|
50
|
-
await Promise.all(Object.entries(updated.pages).map(async ([urlPath,
|
|
51
|
-
c.pages[urlPath] =
|
|
52
|
-
|
|
64
|
+
await Promise.all(Object.entries(updated.pages).map(async ([urlPath, mapping]) => {
|
|
65
|
+
c.pages[urlPath] = mapping;
|
|
66
|
+
const srcPath = typeof mapping === 'string' ? mapping : mapping.webpage;
|
|
67
|
+
if (!pagesByUrlPath[urlPath]) {
|
|
68
|
+
await addPage(urlPath, srcPath);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
53
71
|
prevPages.delete(urlPath);
|
|
54
|
-
if (pagesByUrlPath[urlPath].
|
|
72
|
+
if (pagesByUrlPath[urlPath].fsPath !== srcPath) {
|
|
55
73
|
await updatePage(urlPath);
|
|
56
74
|
}
|
|
57
75
|
}
|
|
58
|
-
else {
|
|
59
|
-
await addPage(urlPath, srcPath);
|
|
60
|
-
}
|
|
61
76
|
}));
|
|
62
77
|
for (const prevPage of Array.from(prevPages)) {
|
|
63
78
|
delete c.pages[prevPage];
|
|
@@ -65,84 +80,101 @@ async function startDevMode(c, signal) {
|
|
|
65
80
|
}
|
|
66
81
|
updateDevServices(updated);
|
|
67
82
|
});
|
|
68
|
-
watch(
|
|
83
|
+
watch(serve.dirs.pages, signal, filename => {
|
|
69
84
|
if (extname(filename) === '.html') {
|
|
70
85
|
for (const [urlPath, srcPath] of Object.entries(c.pages)) {
|
|
71
86
|
if (srcPath === filename) {
|
|
72
87
|
updatePage(urlPath);
|
|
73
88
|
}
|
|
74
89
|
}
|
|
90
|
+
for (const [urlPath, partials] of Object.entries(partialsByUrlPath)) {
|
|
91
|
+
if (partials.includes(filename)) {
|
|
92
|
+
updatePage(urlPath, filename);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
75
95
|
}
|
|
76
96
|
});
|
|
77
|
-
await Promise.all(Object.entries(c.pages).map(([urlPath,
|
|
97
|
+
await Promise.all(Object.entries(c.pages).map(async ([urlPath, mapping]) => {
|
|
98
|
+
const srcPath = typeof mapping === 'string' ? mapping : mapping.webpage;
|
|
99
|
+
await addPage(urlPath, srcPath);
|
|
100
|
+
return new Promise(res => pagesByUrlPath[urlPath].once('entrypoints', res));
|
|
101
|
+
}));
|
|
78
102
|
async function addPage(urlPath, srcPath) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
103
|
+
await mkdir(join(serve.dirs.buildWatch, urlPath), { recursive: true });
|
|
104
|
+
const htmlEntrypoint = (pagesByUrlPath[urlPath] = new HtmlEntrypoint(serve, registry.resolver, urlPath, srcPath, [{ type: 'script', js: clientJS }]));
|
|
105
|
+
htmlEntrypoint.on('entrypoints', entrypoints => {
|
|
106
|
+
const pathsIn = new Set(entrypoints.map(e => e.in));
|
|
107
|
+
if (!entryPointsByUrlPath[urlPath] ||
|
|
108
|
+
!matchingEntrypoints(entryPointsByUrlPath[urlPath].pathsIn, pathsIn)) {
|
|
109
|
+
entryPointsByUrlPath[urlPath] = { entrypoints, pathsIn };
|
|
110
|
+
resetBuildContext();
|
|
111
|
+
}
|
|
85
112
|
});
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
113
|
+
htmlEntrypoint.on('partial', partial => {
|
|
114
|
+
if (!partialsByUrlPath[urlPath]) {
|
|
115
|
+
partialsByUrlPath[urlPath] = [];
|
|
116
|
+
}
|
|
117
|
+
partialsByUrlPath[urlPath].push(partial);
|
|
118
|
+
});
|
|
119
|
+
htmlEntrypoint.on('partials', partials => (partialsByUrlPath[urlPath] = partials));
|
|
120
|
+
htmlEntrypoint.on('output', html => writeFile(join(serve.dirs.buildWatch, urlPath, 'index.html'), html));
|
|
91
121
|
}
|
|
92
122
|
function deletePage(urlPath) {
|
|
123
|
+
pagesByUrlPath[urlPath].removeAllListeners();
|
|
93
124
|
delete pagesByUrlPath[urlPath];
|
|
94
125
|
delete entryPointsByUrlPath[urlPath];
|
|
95
126
|
resetBuildContext();
|
|
96
127
|
}
|
|
97
|
-
async function updatePage(urlPath) {
|
|
98
|
-
|
|
99
|
-
clientJS,
|
|
100
|
-
outDir: watchDir,
|
|
101
|
-
pagesDir: 'pages',
|
|
102
|
-
srcPath: c.pages[urlPath],
|
|
103
|
-
urlPath,
|
|
104
|
-
});
|
|
105
|
-
const entryPointUrls = new Set(update.entryPoints.map(e => e.in));
|
|
106
|
-
if (!hasSameValues(entryPointUrls, entryPointsByUrlPath[urlPath])) {
|
|
107
|
-
entryPointsByUrlPath[urlPath] = entryPointUrls;
|
|
108
|
-
resetBuildContext();
|
|
109
|
-
}
|
|
128
|
+
async function updatePage(urlPath, partial) {
|
|
129
|
+
pagesByUrlPath[urlPath].emit('change', partial);
|
|
110
130
|
}
|
|
111
131
|
function collectEntrypoints() {
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
.flatMap(
|
|
132
|
+
const unique = new Set();
|
|
133
|
+
const pageBundles = Object.values(entryPointsByUrlPath)
|
|
134
|
+
.flatMap(entrypointState => entrypointState.entrypoints)
|
|
115
135
|
.filter(entryPoint => {
|
|
116
|
-
if (
|
|
136
|
+
if (unique.has(entryPoint.in)) {
|
|
117
137
|
return false;
|
|
118
138
|
}
|
|
119
139
|
else {
|
|
120
|
-
|
|
140
|
+
unique.add(entryPoint.in);
|
|
121
141
|
return true;
|
|
122
142
|
}
|
|
123
143
|
});
|
|
144
|
+
const workerBundles = registry.workerEntryPoints();
|
|
145
|
+
if (workerBundles) {
|
|
146
|
+
return [...pageBundles, ...workerBundles];
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return pageBundles;
|
|
150
|
+
}
|
|
124
151
|
}
|
|
125
152
|
function resetBuildContext() {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
153
|
+
switch (buildContext) {
|
|
154
|
+
case 'starting':
|
|
155
|
+
buildContext = 'dirty';
|
|
156
|
+
return;
|
|
157
|
+
case 'dirty':
|
|
158
|
+
case 'disposing':
|
|
159
|
+
return;
|
|
132
160
|
}
|
|
133
161
|
if (buildContext !== null) {
|
|
134
|
-
const
|
|
162
|
+
const disposing = buildContext.dispose();
|
|
135
163
|
buildContext = 'disposing';
|
|
136
|
-
|
|
164
|
+
disposing.then(() => {
|
|
137
165
|
buildContext = null;
|
|
138
166
|
resetBuildContext();
|
|
139
167
|
});
|
|
140
168
|
}
|
|
141
169
|
else {
|
|
142
|
-
|
|
170
|
+
buildContext = 'starting';
|
|
171
|
+
startEsbuildWatch(c, registry, serve, collectEntrypoints()).then(ctx => {
|
|
143
172
|
if (buildContext === 'dirty') {
|
|
144
|
-
buildContext =
|
|
145
|
-
|
|
173
|
+
buildContext = 'disposing';
|
|
174
|
+
ctx.dispose().then(() => {
|
|
175
|
+
buildContext = null;
|
|
176
|
+
resetBuildContext();
|
|
177
|
+
});
|
|
146
178
|
}
|
|
147
179
|
else {
|
|
148
180
|
buildContext = ctx;
|
|
@@ -150,18 +182,32 @@ async function startDevMode(c, signal) {
|
|
|
150
182
|
});
|
|
151
183
|
}
|
|
152
184
|
}
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
// function removePartialFromPage(partial: string, urlPath: string) {
|
|
186
|
+
// const deleteIndex = urlPathsByPartials[partial].indexOf(urlPath)
|
|
187
|
+
// if (deleteIndex !== -1) {
|
|
188
|
+
// if (urlPathsByPartials[partial].length === 1) {
|
|
189
|
+
// delete urlPathsByPartials[partial]
|
|
190
|
+
// } else {
|
|
191
|
+
// urlPathsByPartials[partial].splice(deleteIndex, 1)
|
|
192
|
+
// }
|
|
193
|
+
// }
|
|
194
|
+
// }
|
|
195
|
+
// inital start of esbuild ctx
|
|
196
|
+
resetBuildContext();
|
|
197
|
+
// todo this page route state could be built on change and reused
|
|
198
|
+
const pageRoutes = {
|
|
199
|
+
get urls() {
|
|
200
|
+
return Object.keys(c.pages);
|
|
201
|
+
},
|
|
202
|
+
get urlRewrites() {
|
|
203
|
+
return collectUrlRewrites(c);
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
const frontend = createDevServeFilesFetcher(pageRoutes, serve);
|
|
207
|
+
const devServices = startDevServices(c, signal);
|
|
208
|
+
startWebServer(serve, frontend, devServices.http, pageRoutes);
|
|
163
209
|
}
|
|
164
|
-
function
|
|
210
|
+
function matchingEntrypoints(a, b) {
|
|
165
211
|
if (a.size !== b.size) {
|
|
166
212
|
return false;
|
|
167
213
|
}
|
|
@@ -172,42 +218,21 @@ function hasSameValues(a, b) {
|
|
|
172
218
|
}
|
|
173
219
|
return true;
|
|
174
220
|
}
|
|
175
|
-
async function
|
|
176
|
-
const
|
|
177
|
-
await html.injectPartials();
|
|
178
|
-
if (inputs.urlPath !== '/') {
|
|
179
|
-
await mkdir(join(inputs.outDir, inputs.urlPath), { recursive: true });
|
|
180
|
-
}
|
|
181
|
-
const entryPoints = [];
|
|
182
|
-
html.collectScripts().forEach(scriptImport => {
|
|
183
|
-
entryPoints.push({
|
|
184
|
-
in: scriptImport.in,
|
|
185
|
-
out: scriptImport.out,
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
html.rewriteHrefs();
|
|
189
|
-
html.appendScript(inputs.clientJS);
|
|
190
|
-
await html.writeTo(inputs.outDir);
|
|
191
|
-
return {
|
|
192
|
-
entryPoints,
|
|
193
|
-
srcPath: inputs.srcPath,
|
|
194
|
-
urlPath: inputs.urlPath,
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
async function startEsbuildWatch(entryPoints) {
|
|
198
|
-
const ctx = await esbuildDevContext(createGlobalDefinitions(), entryPoints, 'build/watch');
|
|
221
|
+
async function startEsbuildWatch(c, registry, serve, entryPoints) {
|
|
222
|
+
const ctx = await esbuildDevContext(serve, registry, createGlobalDefinitions(serve), entryPoints, c.esbuild);
|
|
199
223
|
await ctx.watch();
|
|
200
224
|
await ctx.serve({
|
|
201
225
|
host: '127.0.0.1',
|
|
202
|
-
port:
|
|
226
|
+
port: serve.esbuildPort,
|
|
203
227
|
cors: {
|
|
204
|
-
origin: '
|
|
228
|
+
origin: ['127.0.0.1', 'localhost'].map(hostname => `http://${hostname}:${serve.dankPort}`),
|
|
205
229
|
},
|
|
206
230
|
});
|
|
207
231
|
return ctx;
|
|
208
232
|
}
|
|
209
|
-
async function loadClientJS() {
|
|
210
|
-
|
|
233
|
+
async function loadClientJS(esbuildPort) {
|
|
234
|
+
const clientJS = await readFile(resolve(import.meta.dirname, join('..', 'client', 'esbuild.js')), 'utf-8');
|
|
235
|
+
return clientJS.replace('3995', `${esbuildPort}`);
|
|
211
236
|
}
|
|
212
237
|
async function watch(p, signal, fire) {
|
|
213
238
|
const delayFire = 90;
|
package/lib_js/services.js
CHANGED
|
@@ -12,6 +12,13 @@ export function startDevServices(c, _signal) {
|
|
|
12
12
|
running.push({ s, process: startService(s) });
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
+
return {
|
|
16
|
+
http: {
|
|
17
|
+
get running() {
|
|
18
|
+
return running.map(({ s }) => s.http).filter(http => !!http);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
15
22
|
}
|
|
16
23
|
export function updateDevServices(c) {
|
|
17
24
|
if (!c.services?.length) {
|
|
@@ -130,8 +137,12 @@ function startService(s) {
|
|
|
130
137
|
const stderrLabel = logLabel(s.cwd, cmd, args, 31);
|
|
131
138
|
spawned.stderr.on('data', chunk => printChunk(stderrLabel, chunk));
|
|
132
139
|
spawned.on('error', e => {
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
if (e.name !== 'AbortError') {
|
|
141
|
+
const cause = 'code' in e && e.code === 'ENOENT'
|
|
142
|
+
? 'program not found'
|
|
143
|
+
: e.message;
|
|
144
|
+
opPrint(s, 'error: ' + cause);
|
|
145
|
+
}
|
|
135
146
|
removeFromRunning(s);
|
|
136
147
|
});
|
|
137
148
|
spawned.on('exit', () => {
|
package/lib_types/dank.d.ts
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
|
+
import type { Plugin as EsbuildPlugin } from 'esbuild';
|
|
1
2
|
export type DankConfig = {
|
|
2
|
-
|
|
3
|
+
esbuild?: EsbuildConfig;
|
|
4
|
+
pages: Record<`/${string}`, `${string}.html` | PageMapping>;
|
|
5
|
+
port?: number;
|
|
6
|
+
previewPort?: number;
|
|
3
7
|
services?: Array<DevService>;
|
|
4
8
|
};
|
|
9
|
+
export type PageMapping = {
|
|
10
|
+
pattern?: RegExp;
|
|
11
|
+
webpage: `${string}.html`;
|
|
12
|
+
};
|
|
5
13
|
export type DevService = {
|
|
6
14
|
command: string;
|
|
7
15
|
cwd?: string;
|
|
8
16
|
env?: Record<string, string>;
|
|
17
|
+
http?: {
|
|
18
|
+
port: number;
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
export type EsbuildConfig = {
|
|
22
|
+
loaders?: Record<`.${string}`, EsbuildLoader>;
|
|
23
|
+
plugins?: Array<EsbuildPlugin>;
|
|
24
|
+
port?: number;
|
|
9
25
|
};
|
|
26
|
+
export type EsbuildLoader = 'base64' | 'binary' | 'copy' | 'dataurl' | 'empty' | 'file' | 'json' | 'text';
|
|
10
27
|
export declare function defineConfig(c: Partial<DankConfig>): Promise<DankConfig>;
|