@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_js/serve.js
CHANGED
|
@@ -1,268 +1,250 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, watch as _watch, writeFile
|
|
2
|
-
import { extname, join, resolve } from
|
|
1
|
+
import { mkdir, readFile, rm, watch as _watch, writeFile } from "node:fs/promises";
|
|
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
7
|
import { resolveServeFlags } from "./flags.js";
|
|
8
8
|
import { HtmlEntrypoint } from "./html.js";
|
|
9
|
-
import { createBuiltDistFilesFetcher, createDevServeFilesFetcher, startWebServer
|
|
9
|
+
import { createBuiltDistFilesFetcher, createDevServeFilesFetcher, startWebServer } from "./http.js";
|
|
10
10
|
import { WebsiteRegistry } from "./metadata.js";
|
|
11
11
|
import { startDevServices, updateDevServices } from "./services.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
async function serveWebsite(c) {
|
|
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);
|
|
19
|
+
} else {
|
|
20
|
+
await startDevMode(c, serve, abortController.signal);
|
|
21
|
+
}
|
|
22
|
+
return new Promise(() => {
|
|
23
|
+
});
|
|
24
24
|
}
|
|
25
25
|
async function startPreviewMode(c, serve, signal) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
const manifest = await buildWebsite(c);
|
|
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
33
|
}
|
|
34
34
|
function collectUrlRewrites(c) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return typeof mapping !== 'object' || !mapping.pattern
|
|
40
|
-
? null
|
|
41
|
-
: { url, pattern: mapping.pattern };
|
|
42
|
-
})
|
|
43
|
-
.filter(mapping => mapping !== null);
|
|
35
|
+
return Object.keys(c.pages).sort().map((url) => {
|
|
36
|
+
const mapping = c.pages[url];
|
|
37
|
+
return typeof mapping !== "object" || !mapping.pattern ? null : { url, pattern: mapping.pattern };
|
|
38
|
+
}).filter((mapping) => mapping !== null);
|
|
44
39
|
}
|
|
45
|
-
// todo changing partials triggers update on html pages
|
|
46
40
|
async function startDevMode(c, serve, signal) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
41
|
+
await mkdir(serve.dirs.buildWatch, { recursive: true });
|
|
42
|
+
const registry = new WebsiteRegistry(serve);
|
|
43
|
+
const clientJS = await loadClientJS(serve.esbuildPort);
|
|
44
|
+
const pagesByUrlPath = {};
|
|
45
|
+
const partialsByUrlPath = {};
|
|
46
|
+
const entryPointsByUrlPath = {};
|
|
47
|
+
let buildContext = null;
|
|
48
|
+
registry.on("workers", () => {
|
|
49
|
+
resetBuildContext();
|
|
50
|
+
});
|
|
51
|
+
watch("dank.config.ts", signal, async () => {
|
|
52
|
+
let updated;
|
|
53
|
+
try {
|
|
54
|
+
updated = await loadConfig("serve");
|
|
55
|
+
} catch (ignore) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const prevPages = new Set(Object.keys(pagesByUrlPath));
|
|
59
|
+
await Promise.all(Object.entries(updated.pages).map(async ([urlPath, mapping]) => {
|
|
60
|
+
c.pages[urlPath] = mapping;
|
|
61
|
+
const srcPath = typeof mapping === "string" ? mapping : mapping.webpage;
|
|
62
|
+
if (!pagesByUrlPath[urlPath]) {
|
|
63
|
+
await addPage(urlPath, srcPath);
|
|
64
|
+
} else {
|
|
65
|
+
prevPages.delete(urlPath);
|
|
66
|
+
if (pagesByUrlPath[urlPath].fsPath !== srcPath) {
|
|
67
|
+
await updatePage(urlPath);
|
|
62
68
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}));
|
|
77
|
-
for (const prevPage of Array.from(prevPages)) {
|
|
78
|
-
delete c.pages[prevPage];
|
|
79
|
-
deletePage(prevPage);
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
for (const prevPage of Array.from(prevPages)) {
|
|
72
|
+
delete c.pages[prevPage];
|
|
73
|
+
deletePage(prevPage);
|
|
74
|
+
}
|
|
75
|
+
updateDevServices(updated);
|
|
76
|
+
});
|
|
77
|
+
watch(serve.dirs.pages, signal, (filename) => {
|
|
78
|
+
if (extname(filename) === ".html") {
|
|
79
|
+
for (const [urlPath, srcPath] of Object.entries(c.pages)) {
|
|
80
|
+
if (srcPath === filename) {
|
|
81
|
+
updatePage(urlPath);
|
|
80
82
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
for (const [urlPath, srcPath] of Object.entries(c.pages)) {
|
|
86
|
-
if (srcPath === filename) {
|
|
87
|
-
updatePage(urlPath);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
for (const [urlPath, partials] of Object.entries(partialsByUrlPath)) {
|
|
91
|
-
if (partials.includes(filename)) {
|
|
92
|
-
updatePage(urlPath, filename);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
83
|
+
}
|
|
84
|
+
for (const [urlPath, partials] of Object.entries(partialsByUrlPath)) {
|
|
85
|
+
if (partials.includes(filename)) {
|
|
86
|
+
updatePage(urlPath, filename);
|
|
95
87
|
}
|
|
96
|
-
|
|
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
|
-
}));
|
|
102
|
-
async function addPage(urlPath, srcPath) {
|
|
103
|
-
await mkdir(join(serve.dirs.buildWatch, urlPath), { recursive: true });
|
|
104
|
-
const htmlEntrypoint = (pagesByUrlPath[urlPath] = new HtmlEntrypoint(serve, 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
|
-
}
|
|
112
|
-
});
|
|
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));
|
|
88
|
+
}
|
|
121
89
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
90
|
+
});
|
|
91
|
+
await Promise.all(Object.entries(c.pages).map(async ([urlPath, mapping]) => {
|
|
92
|
+
const srcPath = typeof mapping === "string" ? mapping : mapping.webpage;
|
|
93
|
+
await addPage(urlPath, srcPath);
|
|
94
|
+
return new Promise((res) => pagesByUrlPath[urlPath].once("entrypoints", res));
|
|
95
|
+
}));
|
|
96
|
+
async function addPage(urlPath, srcPath) {
|
|
97
|
+
await mkdir(join(serve.dirs.buildWatch, urlPath), { recursive: true });
|
|
98
|
+
const htmlEntrypoint = pagesByUrlPath[urlPath] = new HtmlEntrypoint(serve, registry.resolver, urlPath, srcPath, [{ type: "script", js: clientJS }]);
|
|
99
|
+
htmlEntrypoint.on("entrypoints", (entrypoints) => {
|
|
100
|
+
const pathsIn = new Set(entrypoints.map((e) => e.in));
|
|
101
|
+
if (!entryPointsByUrlPath[urlPath] || !matchingEntrypoints(entryPointsByUrlPath[urlPath].pathsIn, pathsIn)) {
|
|
102
|
+
entryPointsByUrlPath[urlPath] = { entrypoints, pathsIn };
|
|
126
103
|
resetBuildContext();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
htmlEntrypoint.on("partial", (partial) => {
|
|
107
|
+
if (!partialsByUrlPath[urlPath]) {
|
|
108
|
+
partialsByUrlPath[urlPath] = [];
|
|
109
|
+
}
|
|
110
|
+
partialsByUrlPath[urlPath].push(partial);
|
|
111
|
+
});
|
|
112
|
+
htmlEntrypoint.on("partials", (partials) => {
|
|
113
|
+
partialsByUrlPath[urlPath] = partials;
|
|
114
|
+
});
|
|
115
|
+
htmlEntrypoint.on("output", (html) => {
|
|
116
|
+
const path = join(serve.dirs.buildWatch, urlPath, "index.html");
|
|
117
|
+
writeFile(path, html);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
function deletePage(urlPath) {
|
|
121
|
+
pagesByUrlPath[urlPath].removeAllListeners();
|
|
122
|
+
delete pagesByUrlPath[urlPath];
|
|
123
|
+
delete entryPointsByUrlPath[urlPath];
|
|
124
|
+
resetBuildContext();
|
|
125
|
+
}
|
|
126
|
+
async function updatePage(urlPath, partial) {
|
|
127
|
+
pagesByUrlPath[urlPath].emit("change", partial);
|
|
128
|
+
}
|
|
129
|
+
function collectEntrypoints() {
|
|
130
|
+
const unique = /* @__PURE__ */ new Set();
|
|
131
|
+
const pageBundles = Object.values(entryPointsByUrlPath).flatMap((entrypointState) => entrypointState.entrypoints).filter((entryPoint) => {
|
|
132
|
+
if (unique.has(entryPoint.in)) {
|
|
133
|
+
return false;
|
|
134
|
+
} else {
|
|
135
|
+
unique.add(entryPoint.in);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const workerBundles = registry.workerEntryPoints();
|
|
140
|
+
if (workerBundles) {
|
|
141
|
+
return [...pageBundles, ...workerBundles];
|
|
142
|
+
} else {
|
|
143
|
+
return pageBundles;
|
|
127
144
|
}
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
}
|
|
146
|
+
function resetBuildContext() {
|
|
147
|
+
switch (buildContext) {
|
|
148
|
+
case "starting":
|
|
149
|
+
buildContext = "dirty";
|
|
150
|
+
return;
|
|
151
|
+
case "dirty":
|
|
152
|
+
case "disposing":
|
|
153
|
+
return;
|
|
130
154
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return pageBundles;
|
|
155
|
+
if (buildContext !== null) {
|
|
156
|
+
const disposing = buildContext.dispose();
|
|
157
|
+
buildContext = "disposing";
|
|
158
|
+
disposing.then(() => {
|
|
159
|
+
buildContext = null;
|
|
160
|
+
resetBuildContext();
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
buildContext = "starting";
|
|
164
|
+
startEsbuildWatch(c, registry, serve, collectEntrypoints()).then((ctx) => {
|
|
165
|
+
if (buildContext === "dirty") {
|
|
166
|
+
buildContext = "disposing";
|
|
167
|
+
ctx.dispose().then(() => {
|
|
168
|
+
buildContext = null;
|
|
169
|
+
resetBuildContext();
|
|
170
|
+
});
|
|
171
|
+
} else {
|
|
172
|
+
buildContext = ctx;
|
|
150
173
|
}
|
|
174
|
+
});
|
|
151
175
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
if (buildContext !== null) {
|
|
162
|
-
const disposing = buildContext.dispose();
|
|
163
|
-
buildContext = 'disposing';
|
|
164
|
-
disposing.then(() => {
|
|
165
|
-
buildContext = null;
|
|
166
|
-
resetBuildContext();
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
buildContext = 'starting';
|
|
171
|
-
startEsbuildWatch(c, registry, serve, collectEntrypoints()).then(ctx => {
|
|
172
|
-
if (buildContext === 'dirty') {
|
|
173
|
-
buildContext = 'disposing';
|
|
174
|
-
ctx.dispose().then(() => {
|
|
175
|
-
buildContext = null;
|
|
176
|
-
resetBuildContext();
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
buildContext = ctx;
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
}
|
|
176
|
+
}
|
|
177
|
+
resetBuildContext();
|
|
178
|
+
const pageRoutes = {
|
|
179
|
+
get urls() {
|
|
180
|
+
return Object.keys(c.pages);
|
|
181
|
+
},
|
|
182
|
+
get urlRewrites() {
|
|
183
|
+
return collectUrlRewrites(c);
|
|
184
184
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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);
|
|
185
|
+
};
|
|
186
|
+
const frontend = createDevServeFilesFetcher(pageRoutes, serve);
|
|
187
|
+
const devServices = startDevServices(c, signal);
|
|
188
|
+
startWebServer(serve, frontend, devServices.http, pageRoutes);
|
|
209
189
|
}
|
|
210
190
|
function matchingEntrypoints(a, b) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
191
|
+
if (a.size !== b.size) {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
for (const v in a) {
|
|
195
|
+
if (!b.has(v)) {
|
|
196
|
+
return false;
|
|
218
197
|
}
|
|
219
|
-
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
220
200
|
}
|
|
221
201
|
async function startEsbuildWatch(c, registry, serve, entryPoints) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
202
|
+
const ctx = await esbuildDevContext(serve, registry, createGlobalDefinitions(serve), entryPoints, c.esbuild);
|
|
203
|
+
await ctx.watch();
|
|
204
|
+
await ctx.serve({
|
|
205
|
+
host: "127.0.0.1",
|
|
206
|
+
port: serve.esbuildPort,
|
|
207
|
+
cors: {
|
|
208
|
+
origin: ["127.0.0.1", "localhost"].map((hostname) => `http://${hostname}:${serve.dankPort}`)
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
return ctx;
|
|
232
212
|
}
|
|
233
213
|
async function loadClientJS(esbuildPort) {
|
|
234
|
-
|
|
235
|
-
|
|
214
|
+
const clientJS = await readFile(resolve(import.meta.dirname, join("..", "client", "esbuild.js")), "utf-8");
|
|
215
|
+
return clientJS.replace("3995", `${esbuildPort}`);
|
|
236
216
|
}
|
|
237
217
|
async function watch(p, signal, fire) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
258
|
-
}, timeout);
|
|
259
|
-
}
|
|
218
|
+
const delayFire = 90;
|
|
219
|
+
const timeout = 100;
|
|
220
|
+
let changes = {};
|
|
221
|
+
try {
|
|
222
|
+
for await (const { filename } of _watch(p, {
|
|
223
|
+
recursive: true,
|
|
224
|
+
signal
|
|
225
|
+
})) {
|
|
226
|
+
if (filename) {
|
|
227
|
+
if (!changes[filename]) {
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
changes[filename] = now + delayFire;
|
|
230
|
+
setTimeout(() => {
|
|
231
|
+
const now2 = Date.now();
|
|
232
|
+
for (const [filename2, then] of Object.entries(changes)) {
|
|
233
|
+
if (then <= now2) {
|
|
234
|
+
fire(filename2);
|
|
235
|
+
delete changes[filename2];
|
|
236
|
+
}
|
|
260
237
|
}
|
|
238
|
+
}, timeout);
|
|
261
239
|
}
|
|
240
|
+
}
|
|
262
241
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
242
|
+
} catch (e) {
|
|
243
|
+
if (e.name !== "AbortError") {
|
|
244
|
+
throw e;
|
|
267
245
|
}
|
|
246
|
+
}
|
|
268
247
|
}
|
|
248
|
+
export {
|
|
249
|
+
serveWebsite
|
|
250
|
+
};
|