@spirobel/mininext 0.3.1 → 0.3.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/dist/mininext/html.d.ts +73 -0
- package/dist/mininext/html.js +288 -0
- package/dist/mininext/mininext.d.ts +15 -0
- package/dist/mininext/mininext.js +185 -0
- package/dist/mininext/url.d.ts +213 -0
- package/dist/mininext/url.js +449 -0
- package/dist/mininext.js +26 -2
- package/dist/tests/html.test.d.ts +1 -0
- package/dist/tests/html.test.js +38 -0
- package/dist/url.d.ts +8 -1
- package/dist/url.js +9 -0
- package/mininext/html.ts +19 -7
- package/mininext/mininext.ts +32 -2
- package/mininext/url.ts +15 -2
- package/package.json +1 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { HandlerReturnType, HtmlHandler, LazyHandlerReturnType, Mini } from "./url";
|
|
2
|
+
export type HtmlStringValues<T = unknown> = HtmlString | HtmlString[] | BasedHtml | BasedHtml[] | (BasedHtml | HtmlString)[] | string | number | HtmlHandler<T> | JsonString | LazyHandlerReturnType | undefined;
|
|
3
|
+
export type JsonStringValues<T = unknown> = HtmlStringValues<T> | {
|
|
4
|
+
[key: string]: any;
|
|
5
|
+
};
|
|
6
|
+
export declare class HtmlString extends Array {
|
|
7
|
+
/**
|
|
8
|
+
* a HtmlString is by default resolved.
|
|
9
|
+
* if we we pass a function as a value to the html`` template string, it will be unresolved.
|
|
10
|
+
* it can also become unresolved if an unresolved HtmlString is passed into it as a value
|
|
11
|
+
*/
|
|
12
|
+
resolved: boolean;
|
|
13
|
+
resolve<T>(mini: Mini<T>): Promise<this>;
|
|
14
|
+
flat(depth?: number): this;
|
|
15
|
+
}
|
|
16
|
+
export declare function html<X = unknown>(strings: TemplateStringsArray, ...values: HtmlStringValues<X>[]): HtmlString;
|
|
17
|
+
export declare class JsonString extends HtmlString {
|
|
18
|
+
}
|
|
19
|
+
export declare class DangerJsonInHtml extends HtmlString {
|
|
20
|
+
}
|
|
21
|
+
export declare const json: <X = unknown>(strings: TemplateStringsArray, ...values: JsonStringValues<X>[]) => JsonString;
|
|
22
|
+
export declare const dangerjson: <X = unknown>(strings: TemplateStringsArray, ...values: JsonStringValues<X>[]) => DangerJsonInHtml;
|
|
23
|
+
export declare const commonHead: HtmlString;
|
|
24
|
+
export declare const cssReset: HtmlString;
|
|
25
|
+
/**
|
|
26
|
+
* Set the default head for all pages. Can still be overwritten on a per page basis
|
|
27
|
+
* @param defaultHead - HtmlString
|
|
28
|
+
*
|
|
29
|
+
* @example Here is what a default head might look like:
|
|
30
|
+
* ```ts
|
|
31
|
+
*head((mini)=>mini.html` <title>hello hello</title> `);
|
|
32
|
+
* url.set([
|
|
33
|
+
* ["/", (mini) => mini.html`<h1>Hello world</h1>`],
|
|
34
|
+
* [
|
|
35
|
+
* "/bye",
|
|
36
|
+
* (mini) =>
|
|
37
|
+
* mini.html`<h1>Goodbye world</h1>${mini.head(
|
|
38
|
+
* mini.html` <title>bye bye</title>`
|
|
39
|
+
* )}`,
|
|
40
|
+
* ],
|
|
41
|
+
* ]);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export declare function head(defaultHead: HtmlHandler): void;
|
|
45
|
+
export declare function htmlResponder(mini: Mini, maybeUnresolved: HandlerReturnType, head?: HtmlHandler, options?: ResponseInit): Promise<Response>;
|
|
46
|
+
/**
|
|
47
|
+
* Generic html error type guard
|
|
48
|
+
* @param submissionResult output of some function
|
|
49
|
+
* @returns boolean - true if the given object has a property called "error" and its value is an instance of HtmlString
|
|
50
|
+
*/
|
|
51
|
+
export declare function isError(submissionResult: any | {
|
|
52
|
+
error: HtmlString;
|
|
53
|
+
}): submissionResult is {
|
|
54
|
+
error: HtmlString;
|
|
55
|
+
};
|
|
56
|
+
declare global {
|
|
57
|
+
var Reloader: BasedHtml | HtmlString | undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The difference between this and HtmlString is that it is fully resolved and only accepts primitive types.
|
|
61
|
+
* In plain english this means:
|
|
62
|
+
* It does not accept functions (that will be resolved at request time with (mini)=>mini.html) like mini.html does.
|
|
63
|
+
*/
|
|
64
|
+
export declare class BasedHtml extends String {
|
|
65
|
+
}
|
|
66
|
+
export type BasedHtmlValues = number | string | undefined | null | boolean | BasedHtml | BasedHtml[];
|
|
67
|
+
/**
|
|
68
|
+
* The difference between this and HtmlString is that it is fully resolved and only accepts primitive types.
|
|
69
|
+
* @param strings - html literals
|
|
70
|
+
* @param values - values will get escaped to prevent xss
|
|
71
|
+
* @returns
|
|
72
|
+
*/
|
|
73
|
+
export declare const basedHtml: (strings: TemplateStringsArray, ...values: BasedHtmlValues[]) => BasedHtml;
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
export class HtmlString extends Array {
|
|
2
|
+
/**
|
|
3
|
+
* a HtmlString is by default resolved.
|
|
4
|
+
* if we we pass a function as a value to the html`` template string, it will be unresolved.
|
|
5
|
+
* it can also become unresolved if an unresolved HtmlString is passed into it as a value
|
|
6
|
+
*/
|
|
7
|
+
resolved = true;
|
|
8
|
+
async resolve(mini) {
|
|
9
|
+
if (this.resolved)
|
|
10
|
+
return this;
|
|
11
|
+
for (const [index, htmlPiece] of this.entries()) {
|
|
12
|
+
if (htmlPiece instanceof HtmlString) {
|
|
13
|
+
let resolvedHtmlPiece = await htmlPiece.resolve(mini);
|
|
14
|
+
if (this instanceof JsonString || this instanceof DangerJsonInHtml) {
|
|
15
|
+
this[index] = JSON.stringify(resolvedHtmlPiece);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
this[index] = resolvedHtmlPiece;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else if (typeof htmlPiece === "function") {
|
|
22
|
+
let resolvedHtmlPiece = await htmlPiece(mini); //passing mini
|
|
23
|
+
//same cases as outer if statement
|
|
24
|
+
if (resolvedHtmlPiece instanceof HtmlString) {
|
|
25
|
+
resolvedHtmlPiece = await resolvedHtmlPiece.resolve(mini);
|
|
26
|
+
}
|
|
27
|
+
else if (htmlPiece instanceof BasedHtml) {
|
|
28
|
+
this[index] = htmlPiece;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
if (this instanceof JsonString || this instanceof DangerJsonInHtml) {
|
|
32
|
+
resolvedHtmlPiece = JSON.stringify(resolvedHtmlPiece);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
const notEmpty = resolvedHtmlPiece || "";
|
|
36
|
+
// values will be escaped by default
|
|
37
|
+
resolvedHtmlPiece = Bun.escapeHTML(notEmpty + "");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Replace the function with the resolved HTML piece in place
|
|
41
|
+
this[index] = resolvedHtmlPiece;
|
|
42
|
+
}
|
|
43
|
+
else if (htmlPiece instanceof BasedHtml) {
|
|
44
|
+
this[index] = htmlPiece;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
this.resolved = true;
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
flat(depth = 1) {
|
|
51
|
+
const flattened = super.flat(depth);
|
|
52
|
+
const newHtmlString = new this.constructor(...flattened);
|
|
53
|
+
newHtmlString.resolved = this.resolved;
|
|
54
|
+
return newHtmlString;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function html(strings, ...values) {
|
|
58
|
+
const htmlStringArray = new HtmlString();
|
|
59
|
+
htmlStringArray.resolved = true;
|
|
60
|
+
// Iterate over strings and values, alternating between them
|
|
61
|
+
for (const [index, string] of strings.entries()) {
|
|
62
|
+
htmlStringArray.push(string);
|
|
63
|
+
if (index < values.length) {
|
|
64
|
+
const value = values[index];
|
|
65
|
+
// we can pass arrays of HtmlString and they will get flattened in the HtmlResponder
|
|
66
|
+
if (Array.isArray(value) &&
|
|
67
|
+
value.every((val) => val instanceof HtmlString || val instanceof BasedHtml)) {
|
|
68
|
+
// If the value is an array of HtmlString objects, add the whole array as a single value
|
|
69
|
+
const notResolved = new HtmlString(...value);
|
|
70
|
+
notResolved.resolved = false;
|
|
71
|
+
values[index] = notResolved;
|
|
72
|
+
htmlStringArray.resolved = false; // we could bother with .find here
|
|
73
|
+
}
|
|
74
|
+
else if (typeof value === "function") {
|
|
75
|
+
htmlStringArray.resolved = false;
|
|
76
|
+
values[index] = value;
|
|
77
|
+
}
|
|
78
|
+
else if (value instanceof JsonString) {
|
|
79
|
+
values[index] = html `<div style="color:red;">
|
|
80
|
+
Please use dangerjson to include json in html. Untrusted input needs
|
|
81
|
+
to pass through a html template function to get escaped. You can do
|
|
82
|
+
html -> dangerjson -> html if you want!
|
|
83
|
+
</div>`;
|
|
84
|
+
}
|
|
85
|
+
else if (!(value instanceof HtmlString || value instanceof BasedHtml)) {
|
|
86
|
+
const notEmpty = value || "";
|
|
87
|
+
// values will be escaped by default
|
|
88
|
+
values[index] = Bun.escapeHTML(notEmpty + "");
|
|
89
|
+
}
|
|
90
|
+
else if (value instanceof HtmlString) {
|
|
91
|
+
if (!value.resolved) {
|
|
92
|
+
htmlStringArray.resolved = false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
htmlStringArray.push(values[index]);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return htmlStringArray;
|
|
99
|
+
}
|
|
100
|
+
export class JsonString extends HtmlString {
|
|
101
|
+
}
|
|
102
|
+
export class DangerJsonInHtml extends HtmlString {
|
|
103
|
+
}
|
|
104
|
+
function JsonTemplateProcessor(danger = false) {
|
|
105
|
+
const constructorr = danger
|
|
106
|
+
? () => new DangerJsonInHtml()
|
|
107
|
+
: () => new JsonString();
|
|
108
|
+
return function (strings, ...values) {
|
|
109
|
+
const jsonStringArray = constructorr();
|
|
110
|
+
jsonStringArray.resolved = true;
|
|
111
|
+
// Iterate over strings and values, alternating between them
|
|
112
|
+
for (const [index, string] of strings.entries()) {
|
|
113
|
+
jsonStringArray.push(string);
|
|
114
|
+
if (index < values.length) {
|
|
115
|
+
const value = values[index];
|
|
116
|
+
// we can pass arrays of HtmlString and they will get flattened in the HtmlResponder
|
|
117
|
+
if (Array.isArray(value) &&
|
|
118
|
+
value.every((val) => val instanceof HtmlString || val instanceof BasedHtml)) {
|
|
119
|
+
// If the value is an array of HtmlString objects, add the whole array as a single value
|
|
120
|
+
const notResolved = new HtmlString(...value);
|
|
121
|
+
notResolved.resolved = false;
|
|
122
|
+
values[index] = notResolved;
|
|
123
|
+
jsonStringArray.resolved = false; // we could bother with .find here
|
|
124
|
+
}
|
|
125
|
+
else if (typeof value === "function") {
|
|
126
|
+
jsonStringArray.resolved = false;
|
|
127
|
+
values[index] = value;
|
|
128
|
+
}
|
|
129
|
+
else if (value instanceof HtmlString || value instanceof BasedHtml) {
|
|
130
|
+
if (value instanceof HtmlString && !value.resolved) {
|
|
131
|
+
jsonStringArray.resolved = false;
|
|
132
|
+
}
|
|
133
|
+
values[index] = value;
|
|
134
|
+
}
|
|
135
|
+
else if (!(value instanceof JsonString)) {
|
|
136
|
+
// values will be turned into a JSON string
|
|
137
|
+
if (value) {
|
|
138
|
+
values[index] = JSON.stringify(value);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
jsonStringArray.push(values[index]);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return jsonStringArray;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
export const json = JsonTemplateProcessor();
|
|
148
|
+
export const dangerjson = JsonTemplateProcessor(true);
|
|
149
|
+
export const commonHead = html ` <meta
|
|
150
|
+
name="viewport"
|
|
151
|
+
content="width=device-width, initial-scale=1.0"
|
|
152
|
+
/>
|
|
153
|
+
<script>
|
|
154
|
+
/* prevent form resubmission */
|
|
155
|
+
if (window.history.replaceState) {
|
|
156
|
+
window.history.replaceState(null, null, window.location.href);
|
|
157
|
+
}
|
|
158
|
+
</script>`;
|
|
159
|
+
export const cssReset = html ` <style>
|
|
160
|
+
/* CSS Reset */
|
|
161
|
+
* {
|
|
162
|
+
margin: 0;
|
|
163
|
+
padding: 0;
|
|
164
|
+
box-sizing: border-box;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Set the background color to black */
|
|
168
|
+
html,
|
|
169
|
+
body {
|
|
170
|
+
background-color: #000;
|
|
171
|
+
color: #fff; /* Set the default text color to white for better contrast */
|
|
172
|
+
}
|
|
173
|
+
</style>`;
|
|
174
|
+
let default_head = (mini) => mini.html `
|
|
175
|
+
<title>mini-next</title>
|
|
176
|
+
${commonHead} ${cssReset}
|
|
177
|
+
`;
|
|
178
|
+
/**
|
|
179
|
+
* Set the default head for all pages. Can still be overwritten on a per page basis
|
|
180
|
+
* @param defaultHead - HtmlString
|
|
181
|
+
*
|
|
182
|
+
* @example Here is what a default head might look like:
|
|
183
|
+
* ```ts
|
|
184
|
+
*head((mini)=>mini.html` <title>hello hello</title> `);
|
|
185
|
+
* url.set([
|
|
186
|
+
* ["/", (mini) => mini.html`<h1>Hello world</h1>`],
|
|
187
|
+
* [
|
|
188
|
+
* "/bye",
|
|
189
|
+
* (mini) =>
|
|
190
|
+
* mini.html`<h1>Goodbye world</h1>${mini.head(
|
|
191
|
+
* mini.html` <title>bye bye</title>`
|
|
192
|
+
* )}`,
|
|
193
|
+
* ],
|
|
194
|
+
* ]);
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export function head(defaultHead) {
|
|
198
|
+
default_head = defaultHead;
|
|
199
|
+
}
|
|
200
|
+
export async function htmlResponder(mini, maybeUnresolved, head = default_head, options = {
|
|
201
|
+
headers: {
|
|
202
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
203
|
+
},
|
|
204
|
+
}) {
|
|
205
|
+
if (!(maybeUnresolved instanceof HtmlString)) {
|
|
206
|
+
maybeUnresolved = html `${maybeUnresolved + ""}`;
|
|
207
|
+
}
|
|
208
|
+
if (maybeUnresolved instanceof DangerJsonInHtml) {
|
|
209
|
+
maybeUnresolved = html `<div style="color:red;">
|
|
210
|
+
Use json and not dangerjson. The purpose of dangerjson is to be explicit
|
|
211
|
+
when you embed unescaped json elements in an html document.
|
|
212
|
+
</div>`;
|
|
213
|
+
}
|
|
214
|
+
if (!(maybeUnresolved instanceof JsonString)) {
|
|
215
|
+
const reloader = new HtmlString();
|
|
216
|
+
reloader.push(global.Reloader || "");
|
|
217
|
+
maybeUnresolved = html `<!DOCTYPE html>
|
|
218
|
+
<html>
|
|
219
|
+
<head>
|
|
220
|
+
${reloader} ${head}
|
|
221
|
+
</head>
|
|
222
|
+
<body>
|
|
223
|
+
${maybeUnresolved}
|
|
224
|
+
</body>
|
|
225
|
+
</html> `;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
const headers = {
|
|
229
|
+
...options.headers,
|
|
230
|
+
...{ "Content-Type": "application/json; charset=utf-8" },
|
|
231
|
+
};
|
|
232
|
+
options.headers = headers;
|
|
233
|
+
}
|
|
234
|
+
const definitelyResolved = await maybeUnresolved.resolve(mini);
|
|
235
|
+
const flattend = definitelyResolved.flat(Infinity);
|
|
236
|
+
async function* stepGen() {
|
|
237
|
+
let index = 0;
|
|
238
|
+
while (index < flattend.length) {
|
|
239
|
+
const step = flattend[index++];
|
|
240
|
+
if (step)
|
|
241
|
+
yield String(step);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
function Stream(a) {
|
|
245
|
+
return a;
|
|
246
|
+
}
|
|
247
|
+
return new Response(Stream(stepGen), options);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Generic html error type guard
|
|
251
|
+
* @param submissionResult output of some function
|
|
252
|
+
* @returns boolean - true if the given object has a property called "error" and its value is an instance of HtmlString
|
|
253
|
+
*/
|
|
254
|
+
export function isError(submissionResult) {
|
|
255
|
+
return ("error" in submissionResult && submissionResult.error instanceof HtmlString);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* The difference between this and HtmlString is that it is fully resolved and only accepts primitive types.
|
|
259
|
+
* In plain english this means:
|
|
260
|
+
* It does not accept functions (that will be resolved at request time with (mini)=>mini.html) like mini.html does.
|
|
261
|
+
*/
|
|
262
|
+
export class BasedHtml extends String {
|
|
263
|
+
}
|
|
264
|
+
//TODO make it so we can embed BasedHtml into mini.html partially resolved html strings
|
|
265
|
+
/**
|
|
266
|
+
* The difference between this and HtmlString is that it is fully resolved and only accepts primitive types.
|
|
267
|
+
* @param strings - html literals
|
|
268
|
+
* @param values - values will get escaped to prevent xss
|
|
269
|
+
* @returns
|
|
270
|
+
*/
|
|
271
|
+
export const basedHtml = (strings, ...values) => {
|
|
272
|
+
// Apply escapeHtml to each value before using them in the template string
|
|
273
|
+
// In case it didn't already get escaped
|
|
274
|
+
for (const [index, value] of values.entries()) {
|
|
275
|
+
// we can pass arrays of BasedHtml and they will get flattened automatically
|
|
276
|
+
if (Array.isArray(value) &&
|
|
277
|
+
value.every((val) => val instanceof BasedHtml)) {
|
|
278
|
+
// If the value is an array of BasedHtml objects, flatten it and add to ...values
|
|
279
|
+
values[index] = value.join("");
|
|
280
|
+
}
|
|
281
|
+
else if (!(value instanceof BasedHtml)) {
|
|
282
|
+
const notEmpty = value || "";
|
|
283
|
+
// values will be escaped by default
|
|
284
|
+
values[index] = Bun.escapeHTML(notEmpty + "");
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return new BasedHtml(String.raw({ raw: strings }, ...values));
|
|
288
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
/// <reference types="bun-types" />
|
|
3
|
+
import { url, Mini, type HtmlHandler } from "./url";
|
|
4
|
+
import { isError, HtmlString, BasedHtml, head, commonHead, cssReset, basedHtml as html } from "./html";
|
|
5
|
+
import type { Server, WebSocketHandler } from "bun";
|
|
6
|
+
declare global {
|
|
7
|
+
var PROJECT_ROOT: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
declare function build(backendPath?: string): Promise<void>;
|
|
10
|
+
declare const standardDevReloader: BasedHtml;
|
|
11
|
+
declare function makeEntrypoint(): Promise<{
|
|
12
|
+
fetch: (req: Request, server: Server) => Promise<Response>;
|
|
13
|
+
websocket: WebSocketHandler;
|
|
14
|
+
}>;
|
|
15
|
+
export { html, url, head, build, makeEntrypoint, isError, BasedHtml, HtmlString, type HtmlHandler, Mini, standardDevReloader, commonHead, cssReset, };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { url, Mini } from "./url";
|
|
2
|
+
import { isError, HtmlString, BasedHtml, head, commonHead, cssReset, basedHtml as html, } from "./html";
|
|
3
|
+
import { watch } from "fs/promises";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
function projectRoot() {
|
|
6
|
+
return global.PROJECT_ROOT || import.meta.dir + "/../../../../";
|
|
7
|
+
}
|
|
8
|
+
async function build(backendPath = "backend/backend.ts") {
|
|
9
|
+
await buildBackend(backendPath);
|
|
10
|
+
if (Bun.argv[2] === "dev") {
|
|
11
|
+
await devServer();
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const streamPlugin = {
|
|
15
|
+
name: "node stream in the frontend",
|
|
16
|
+
setup(build) {
|
|
17
|
+
build.onResolve({ filter: /^stream$/ }, (args) => {
|
|
18
|
+
const path_to_stream_lib = path.resolve(projectRoot(), "node_modules/stream-browserify/index.js");
|
|
19
|
+
if (path_to_stream_lib)
|
|
20
|
+
return {
|
|
21
|
+
path: path_to_stream_lib,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const bufferPlugin = {
|
|
27
|
+
name: "node buffer in the frontend",
|
|
28
|
+
setup(build) {
|
|
29
|
+
build.onResolve({ filter: /^buffer$/ }, (args) => {
|
|
30
|
+
const path_to_buffer_lib = path.resolve(projectRoot(), "node_modules/buffer/index.js");
|
|
31
|
+
if (path_to_buffer_lib)
|
|
32
|
+
return {
|
|
33
|
+
path: path_to_buffer_lib,
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const cryptoPlugin = {
|
|
39
|
+
name: "node crypto in the frontend",
|
|
40
|
+
setup(build) {
|
|
41
|
+
build.onResolve({ filter: /^crypto$/ }, (args) => {
|
|
42
|
+
const path_to_crypto_lib = path.resolve(projectRoot(), "node_modules/crypto-browserify/index.js");
|
|
43
|
+
if (path_to_crypto_lib)
|
|
44
|
+
return {
|
|
45
|
+
path: path_to_crypto_lib,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
async function buildBackend(backendPath = "backend/backend.ts") {
|
|
51
|
+
global.FrontendScriptUrls = [];
|
|
52
|
+
global.FrontendScripts = [];
|
|
53
|
+
global.bundledSVGs = {};
|
|
54
|
+
const i = await import(path.resolve(projectRoot(), backendPath));
|
|
55
|
+
for (const frontend of url.getFrontends()) {
|
|
56
|
+
const f = await buildFrontend(frontend);
|
|
57
|
+
FrontendScriptUrls.push("/" + f.url);
|
|
58
|
+
FrontendScripts.push(f.script);
|
|
59
|
+
}
|
|
60
|
+
for (const svgPath of url.getSvgPaths()) {
|
|
61
|
+
const parsedSvgPath = path.parse(svgPath);
|
|
62
|
+
const svgContent = Bun.file(path.join(projectRoot() + "/backend/", svgPath));
|
|
63
|
+
const svgHash = Bun.hash(await svgContent.arrayBuffer());
|
|
64
|
+
const svgUrl = `/${parsedSvgPath.name}-${svgHash}.svg`;
|
|
65
|
+
bundledSVGs[svgUrl] = {
|
|
66
|
+
svgContent: await svgContent.text(),
|
|
67
|
+
svgPath,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
const res = await Bun.build({
|
|
71
|
+
entrypoints: [path.resolve(projectRoot(), backendPath)],
|
|
72
|
+
outdir: path.resolve(projectRoot(), "dist"),
|
|
73
|
+
naming: "backend.js",
|
|
74
|
+
minify: Bun.argv[2] === "dev" ? false : true, //production
|
|
75
|
+
target: "bun",
|
|
76
|
+
define: {
|
|
77
|
+
FrontendScripts: JSON.stringify(FrontendScripts),
|
|
78
|
+
FrontendScriptUrls: JSON.stringify(FrontendScriptUrls),
|
|
79
|
+
bundledSVGs: JSON.stringify(bundledSVGs),
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function buildFrontend(file) {
|
|
84
|
+
const result = await Bun.build({
|
|
85
|
+
entrypoints: [path.resolve(projectRoot(), `frontend/${file}`)],
|
|
86
|
+
outdir: path.resolve(projectRoot(), "dist"),
|
|
87
|
+
naming: "[name]-[hash].[ext]",
|
|
88
|
+
minify: Bun.argv[2] === "dev" ? false : true, //production
|
|
89
|
+
target: "browser",
|
|
90
|
+
plugins: [bufferPlugin, streamPlugin, cryptoPlugin],
|
|
91
|
+
});
|
|
92
|
+
if (!result?.outputs[0]?.path)
|
|
93
|
+
console.log(result);
|
|
94
|
+
const url = path.basename(result.outputs[0].path);
|
|
95
|
+
//results.push({ file, p });
|
|
96
|
+
return { url, script: await result.outputs[0].text() };
|
|
97
|
+
}
|
|
98
|
+
async function devServer() {
|
|
99
|
+
//start the reloader and tell browser to refresh once
|
|
100
|
+
await buildBackend();
|
|
101
|
+
let refreshed_once = false;
|
|
102
|
+
const server = Bun.serve({
|
|
103
|
+
port: 3001,
|
|
104
|
+
fetch(request) {
|
|
105
|
+
const success = server.upgrade(request);
|
|
106
|
+
return success
|
|
107
|
+
? new Response("Reloader works!")
|
|
108
|
+
: new Response("Reloader WebSocket upgrade error", { status: 400 });
|
|
109
|
+
},
|
|
110
|
+
websocket: {
|
|
111
|
+
open(ws) {
|
|
112
|
+
ws.subscribe("reloader");
|
|
113
|
+
if (!refreshed_once) {
|
|
114
|
+
ws.send("Reload!");
|
|
115
|
+
refreshed_once = true;
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
message(ws, message) { }, // a message is received
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
async function watchAndBuild(dir) {
|
|
122
|
+
try {
|
|
123
|
+
//start the file watcher that will rebuild frontend on save
|
|
124
|
+
const watcher = watch(path.resolve(projectRoot(), dir), {
|
|
125
|
+
recursive: true,
|
|
126
|
+
});
|
|
127
|
+
for await (const event of watcher) {
|
|
128
|
+
buildBackend().then(() => {
|
|
129
|
+
// tell browser to refresh again because we saw a change
|
|
130
|
+
server.publish("reloader", "Reload!");
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
console.log(`mini-next dev server has trouble watching "./${dir}", does the directory exist?`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
watchAndBuild("frontend");
|
|
139
|
+
watchAndBuild("backend");
|
|
140
|
+
}
|
|
141
|
+
const standardDevReloader = html `
|
|
142
|
+
<script>
|
|
143
|
+
function reloader() {
|
|
144
|
+
let socket = null;
|
|
145
|
+
|
|
146
|
+
function connectWebSocket() {
|
|
147
|
+
if (socket) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
socket = new WebSocket("ws://localhost:3001/reload");
|
|
151
|
+
|
|
152
|
+
socket.addEventListener("message", (event) => {
|
|
153
|
+
window.location.reload();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
socket.addEventListener("close", (event) => {
|
|
157
|
+
// Reestablish the connection after 1 second
|
|
158
|
+
socket = null;
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
socket.addEventListener("error", (event) => {
|
|
162
|
+
socket = null;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
connectWebSocket(); // connect to reloader, if it does not work:
|
|
166
|
+
setInterval(connectWebSocket, 1000); // retry every 1 second
|
|
167
|
+
}
|
|
168
|
+
reloader();
|
|
169
|
+
</script>
|
|
170
|
+
`;
|
|
171
|
+
async function makeEntrypoint() {
|
|
172
|
+
let module;
|
|
173
|
+
const backendImportPath = projectRoot() + "/dist/backend.js";
|
|
174
|
+
try {
|
|
175
|
+
// @ts-ignore
|
|
176
|
+
module = await import(backendImportPath);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
await build();
|
|
180
|
+
// @ts-ignore
|
|
181
|
+
module = await import(backendImportPath);
|
|
182
|
+
}
|
|
183
|
+
return module.default();
|
|
184
|
+
}
|
|
185
|
+
export { html, url, head, build, makeEntrypoint, isError, BasedHtml, HtmlString, Mini, standardDevReloader, commonHead, cssReset, };
|