@spirobel/mininext 0.7.5 → 0.8.0

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.js CHANGED
@@ -1,242 +1,140 @@
1
- import { url, Mini, has } from "./url";
2
- import { isError, HtmlString, BasedHtml, head, commonHead, cssReset, basedHtml as html, } from "./html";
3
- import { $ } from "bun";
4
- import { watch } from "fs/promises";
5
- import * as path from "path";
6
- function projectRoot() {
7
- return global.PROJECT_ROOT || import.meta.dir + "/../../../../";
1
+ import { state } from "./minicache";
2
+ import { resolve } from "./frontend/miniresolve";
3
+ import { resolve as backendResolve } from "./backend/miniresolve";
4
+ import { build, getCallerDir, newBackendMini, renderBackend, } from "./backend/html";
5
+ export { renderRoot } from "./frontend/minidom";
6
+ export { createRouter } from "./frontend/minirouter";
7
+ export const isBackend = typeof window === "undefined";
8
+ export function html(stringLiterals, ...values) {
9
+ return constructMiniHtmlString(stringLiterals, values);
8
10
  }
9
- async function build(backendPath = "backend/backend.ts") {
10
- if (Bun.argv[2] === "frontend") {
11
- const newFrontend = await buildFrontend(Bun.argv[3]);
12
- process.stdout.write(JSON.stringify(newFrontend));
13
- return 0;
14
- }
15
- await buildBackend(backendPath);
16
- if (Bun.argv[2] === "dev") {
17
- await devServer();
18
- }
19
- }
20
- const streamPlugin = {
21
- name: "node stream in the frontend",
22
- setup(build) {
23
- build.onResolve({ filter: /^stream$/ }, (args) => {
24
- const path_to_stream_lib = path.resolve(projectRoot(), "node_modules/stream-browserify/index.js");
25
- if (path_to_stream_lib)
26
- return {
27
- path: path_to_stream_lib,
28
- };
29
- });
30
- },
31
- };
32
- const bufferPlugin = {
33
- name: "node buffer in the frontend",
34
- setup(build) {
35
- build.onResolve({ filter: /^buffer$/ }, (args) => {
36
- const path_to_buffer_lib = path.resolve(projectRoot(), "node_modules/buffer/index.js");
37
- if (path_to_buffer_lib)
38
- return {
39
- path: path_to_buffer_lib,
40
- };
41
- });
42
- },
43
- };
44
- const cryptoPlugin = {
45
- name: "node crypto in the frontend",
46
- setup(build) {
47
- build.onResolve({ filter: /^crypto$/ }, (args) => {
48
- const path_to_crypto_lib = path.resolve(projectRoot(), "node_modules/crypto-browserify/index.js");
49
- if (path_to_crypto_lib)
50
- return {
51
- path: path_to_crypto_lib,
52
- };
53
- });
54
- },
55
- };
56
- const nodeHttpsPlugin = {
57
- name: "node https in the frontend",
58
- setup(build) {
59
- build.onResolve({ filter: /^https$/ }, (args) => {
60
- const path_to_node_https_lib = path.resolve(projectRoot(), "node_modules/https-browserify/index.js");
61
- if (path_to_node_https_lib)
62
- return {
63
- path: path_to_node_https_lib,
64
- };
65
- });
66
- },
67
- };
68
- async function buildBackend(backendPath = "backend/backend.ts") {
69
- global.bundledFrontends = {};
70
- global.bundledSVGs = {};
71
- const i = await import(path.resolve(projectRoot(), backendPath));
72
- for (const frontend of url.getFrontends()) {
73
- const firstPlaceToLook = path.resolve(path.dirname(frontend.callerPath), `frontend/${frontend.frontendFilePath}`);
74
- const secondPlaceToLook = path.resolve(projectRoot(), `frontend/${frontend.frontendFilePath}`);
75
- const frontEndPath = (await Bun.file(firstPlaceToLook).exists())
76
- ? firstPlaceToLook
77
- : secondPlaceToLook;
78
- try {
79
- const frontendResult = await $ `bun run build.ts frontend ${frontEndPath}`.json();
80
- bundledFrontends[`/${frontendResult.url}`] = {
81
- frontendContent: frontendResult.script,
82
- frontendFilePath: frontend.frontendFilePath,
83
- position: frontend.position,
84
- };
85
- }
86
- catch (error) {
87
- if (error &&
88
- typeof error === "object" &&
89
- "exitCode" in error &&
90
- "stdout" in error &&
91
- "stderr" in error &&
92
- error.stdout instanceof Buffer &&
93
- error.stderr instanceof Buffer) {
94
- console.error(`Failed with exit code: ${error.exitCode}`);
95
- console.error("Standard Output:", error.stdout.toString());
96
- console.error("Standard Error:", error.stderr.toString());
97
- }
98
- console.log(await $ `bun run build.ts frontend ${frontEndPath}`.text());
99
- }
100
- }
101
- for (const svg of url.getSvgs()) {
102
- const firstPlaceToLook = path.resolve(path.dirname(svg.callerPath), `svgs/${svg.svgFilePath}`);
103
- const secondPlaceToLook = path.resolve(projectRoot(), `${svg.svgFilePath}`);
104
- const svgResolvedFilePath = (await Bun.file(firstPlaceToLook).exists())
105
- ? firstPlaceToLook
106
- : secondPlaceToLook;
107
- const parsedSvgPath = path.parse(svgResolvedFilePath);
108
- const svgContent = Bun.file(svgResolvedFilePath);
109
- const svgHash = Bun.hash(await svgContent.arrayBuffer());
110
- const svgUrl = `/${parsedSvgPath.name}-${svgHash}.svg`;
111
- bundledSVGs[svgUrl] = {
112
- svgContent: await svgContent.text(),
113
- svgFilePath: svg.svgFilePath,
114
- position: svg.position,
115
- options: svg.options,
116
- };
117
- }
118
- const res = await Bun.build({
119
- entrypoints: [path.resolve(projectRoot(), backendPath)],
120
- outdir: path.resolve(projectRoot(), "dist"),
121
- naming: "backend.js",
122
- minify: Bun.argv[2] === "dev" ? false : true, //production
123
- target: "bun",
124
- define: {
125
- bundledFrontends: JSON.stringify(bundledFrontends),
126
- bundledSVGs: JSON.stringify(bundledSVGs),
11
+ export function constructMiniHtmlString(stringLiterals, values) {
12
+ return {
13
+ stringLiterals,
14
+ values,
15
+ resolve: (mini) => {
16
+ if (isBackend)
17
+ return backendResolve(stringLiterals, values, mini);
18
+ return resolve(stringLiterals, values, mini);
127
19
  },
128
- });
129
- }
130
- async function buildFrontend(file) {
131
- const result = await Bun.build({
132
- entrypoints: [file],
133
- outdir: path.resolve(projectRoot(), "dist"),
134
- naming: "[name]-[hash].[ext]",
135
- minify: Bun.argv[2] === "dev" ? false : true, //production
136
- target: "browser",
137
- plugins: [bufferPlugin, streamPlugin, cryptoPlugin, nodeHttpsPlugin],
138
- });
139
- if (!result?.outputs[0]?.path)
140
- console.log(result);
141
- const url = path.basename(result.outputs[0].path);
142
- //results.push({ file, p });
143
- return { url, script: await result.outputs[0].text() };
20
+ async build(mini, root, config) {
21
+ if (!root)
22
+ root = getCallerDir();
23
+ return await build({ stringLiterals, values, root, mini, config });
24
+ },
25
+ renderBackend: (mini) => {
26
+ if (!mini)
27
+ mini = newBackendMini();
28
+ backendResolve(stringLiterals, values, mini);
29
+ return renderBackend(mini.cacheAndCursor).result;
30
+ },
31
+ };
144
32
  }
145
- async function devServer() {
146
- //start the reloader and tell browser to refresh once
147
- await buildBackend();
148
- let refreshed_once = false;
149
- const server = Bun.serve({
150
- port: 3001,
151
- fetch(request) {
152
- const success = server.upgrade(request);
153
- return success
154
- ? new Response("Reloader works!")
155
- : new Response("Reloader WebSocket upgrade error", { status: 400 });
33
+ export function makeNewMini(cac) {
34
+ return {
35
+ html,
36
+ state: (name, value, global) => {
37
+ if (isBackend)
38
+ global = true;
39
+ return state(name, value, cac, global);
156
40
  },
157
- websocket: {
158
- open(ws) {
159
- ws.subscribe("reloader");
160
- if (!refreshed_once) {
161
- ws.send("Reload!");
162
- refreshed_once = true;
163
- }
164
- },
165
- message(ws, message) { }, // a message is received
41
+ flatten,
42
+ cacheAndCursor: cac,
43
+ fill: (...args) => {
44
+ throw new Error("this method can only be used if the mini instance was made from a html skeleton, const skeleton = html`<div></div>`.build(); const mini = skeleton.mini(); const htmlresult = skeleton.fill(...args);");
166
45
  },
167
- });
168
- async function watchAndBuild(dir) {
169
- try {
170
- //start the file watcher that will rebuild frontend on save
171
- const watcher = watch(path.resolve(projectRoot(), dir), {
172
- recursive: true,
173
- });
174
- for await (const event of watcher) {
175
- buildBackend().then(() => {
176
- // tell browser to refresh again because we saw a change
177
- server.publish("reloader", "Reload!");
178
- });
179
- }
46
+ };
47
+ }
48
+ export function standardFlattenRoot(htmlstrings) {
49
+ return html `<div>${htmlstrings}</div>`;
50
+ }
51
+ export function flatten(htmlStringArray, flattenRootFn = standardFlattenRoot) {
52
+ const flattenedArray = combineMiniHtmlStrings(htmlStringArray);
53
+ return flattenValues(flattenRootFn(flattenedArray));
54
+ }
55
+ export function flattenValues(miniHtmlString) {
56
+ const literalsArray = [];
57
+ const values = [];
58
+ let mergeWithPrior = false;
59
+ let index = 0;
60
+ for (const literal of miniHtmlString.stringLiterals) {
61
+ if (mergeWithPrior) {
62
+ mergeWithPrior = false;
63
+ literalsArray[index] += literal;
64
+ }
65
+ else {
66
+ literalsArray.push(literal);
180
67
  }
181
- catch (e) {
182
- console.log(`mini-next dev server has trouble watching "./${dir}", does the directory exist?`);
68
+ const value = miniHtmlString.values[index];
69
+ if (typeof value === "function") {
70
+ throw new Error(`resolve components before passing them into the root element when flattening,
71
+ with const miniHtmlString = component(mini);
72
+
73
+ optimally just have the root element's only value be the htmlstringsarray
74
+ you want to flatten. This is not the place for complex logic,
75
+ it is just to wrap your array in an <ul>, <ol> or <div> element.
76
+
77
+ (every mini html string needs to have only one root element)`);
78
+ }
79
+ else if (value && typeof value === "object" && "resolve" in value) {
80
+ const priorLiteral = literalsArray[index];
81
+ if (!priorLiteral)
82
+ throw new Error("no prior literal, ");
83
+ literalsArray[index] = priorLiteral + value.stringLiterals[0];
84
+ literalsArray.push(...value.stringLiterals.slice(1));
85
+ mergeWithPrior = true;
86
+ values.push(...value.values);
87
+ index += value.stringLiterals.slice(1).length;
88
+ }
89
+ else {
90
+ if (typeof value === "string" || typeof value === "number")
91
+ values.push(value);
92
+ index++;
183
93
  }
184
94
  }
185
- watchAndBuild("frontend");
186
- watchAndBuild("backend");
95
+ const stringLiterals = createTemplateStringsArray(literalsArray);
96
+ return constructMiniHtmlString(stringLiterals, values);
97
+ }
98
+ function combineMiniHtmlStrings(htmlstrings) {
99
+ const stringLiterals = combineTemplateStringsArrays(htmlstrings.map((hs) => hs.stringLiterals));
100
+ const values = htmlstrings.flatMap((hs) => hs.values);
101
+ return constructMiniHtmlString(stringLiterals, values);
187
102
  }
188
- const standardDevReloader = html `
189
- <script>
190
- function reloader() {
191
- let socket = null;
192
-
193
- function connectWebSocket() {
194
- if (socket) {
195
- return;
103
+ function combineTemplateStringsArrays(tsas) {
104
+ const stringlits = [];
105
+ for (const litarray of tsas) {
106
+ const prior = stringlits.at(-1);
107
+ const first = litarray[0];
108
+ if (prior && first) {
109
+ stringlits[stringlits.length - 1] += first;
110
+ stringlits.push(...litarray.slice(1));
111
+ }
112
+ else {
113
+ stringlits.push(...litarray);
196
114
  }
197
- socket = new WebSocket("ws://localhost:3001/reload");
198
-
199
- socket.addEventListener("message", (event) => {
200
- window.location.reload();
201
- });
202
-
203
- socket.addEventListener("close", (event) => {
204
- // Reestablish the connection after 1 second
205
- socket = null;
206
- });
207
-
208
- socket.addEventListener("error", (event) => {
209
- socket = null;
210
- });
211
- }
212
- connectWebSocket(); // connect to reloader, if it does not work:
213
- setInterval(connectWebSocket, 1000); // retry every 1 second
214
- }
215
- reloader();
216
- </script>
217
- `;
218
- async function makeEntrypoint() {
219
- let module;
220
- const backendImportPath = projectRoot() + "/dist/backend.js";
221
- try {
222
- // @ts-ignore
223
- module = await import(backendImportPath);
224
- }
225
- catch (error) {
226
- await build();
227
- // @ts-ignore
228
- module = await import(backendImportPath);
229
115
  }
230
- return module.default();
116
+ return createTemplateStringsArray(stringlits);
231
117
  }
232
- export function getCallerFilePath() {
233
- // const stack = new Error().stack?.split("\n");
234
- // //console.log(stack);
235
- // if (!stack) return "";
236
- // return stack[2].slice(
237
- // stack[2].lastIndexOf("(") + 1,
238
- // stack[2].lastIndexOf(")") + 3
239
- // );
240
- return __dirname;
118
+ function createTemplateStringsArray(strings) {
119
+ const stringsArray = [...strings];
120
+ const frozenRaw = Object.freeze([...strings]);
121
+ Object.defineProperty(stringsArray, "raw", {
122
+ value: frozenRaw,
123
+ writable: false,
124
+ enumerable: false,
125
+ configurable: false,
126
+ });
127
+ return Object.freeze(stringsArray);
128
+ }
129
+ export function resolveMiniValue(value, parentMini, slotId) {
130
+ // make new mini with slotid as cursor
131
+ const mini = makeNewMini({ ...parentMini.cacheAndCursor, cursor: slotId });
132
+ if (typeof value === "function") {
133
+ const component = value(mini);
134
+ // if this happened we need to save the state to the cache
135
+ return component.resolve(mini);
136
+ }
137
+ if (value && typeof value === "object" && "resolve" in value)
138
+ return value.resolve(mini);
139
+ return value;
241
140
  }
242
- export { has, html, url, head, build, makeEntrypoint, isError, BasedHtml, HtmlString, Mini, standardDevReloader, commonHead, cssReset, };
package/package.json CHANGED
@@ -11,9 +11,9 @@
11
11
  "clean": "rm -rf ./dist"
12
12
  },
13
13
  "files": ["dist"],
14
- "version": "0.7.5",
14
+ "version": "0.8.0",
15
15
  "devDependencies": {
16
- "@types/bun": "^1.2.9"
16
+ "@types/bun": "^1.3.9"
17
17
  },
18
18
  "peerDependencies": {
19
19
  "typescript": "^5.0.0"
package/dist/html.d.ts DELETED
@@ -1,73 +0,0 @@
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;