@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/README.md CHANGED
@@ -17,13 +17,7 @@ github:
17
17
  quickstart:
18
18
 
19
19
  ```bash
20
- bun create spirobel/gucci yournewproject
21
- ```
22
-
23
- barebones quickstart:
24
-
25
- ```bash
26
- bun create spirobel/aldi yournewproject
20
+ bun create spirobel/counter yournewproject
27
21
  ```
28
22
 
29
23
  if you don't have bun installed, run first:
@@ -41,27 +35,19 @@ bun install
41
35
  dev:
42
36
 
43
37
  ```bash
44
- bun run dev
38
+ bun run --hot counter.ts
45
39
  ```
46
40
 
47
41
  production:
48
42
 
49
43
  ```bash
50
- bun run start
51
- ```
52
-
53
- build:
54
-
55
- ```bash
56
- bun run build
44
+ NODE_ENV=production bun run counter.ts
57
45
  ```
58
46
 
59
47
  ## Tutorial
60
48
 
61
- If you understand these 3 basic concepts you can build your own website with mininext:
49
+ If you understand these 3 basic concepts you can build your own webapp with mininext:
62
50
 
63
51
  1. html + css
64
52
  2. templating
65
53
  3. you can use data inside of your html templates
66
-
67
- Tutorial video: [intro to mininext](https://www.youtube.com/watch?v=rz4awKntpzE)
@@ -0,0 +1,46 @@
1
+ import { type CacheAndCursor } from "../minicache";
2
+ import { type Mini, type MiniValue, type ResolvedMiniHtmlString } from "../mininext";
3
+ export declare function newBackendMini(): Mini;
4
+ export type Skeleton = {
5
+ static_routes: BunStaticRoutes;
6
+ _build_result: Bun.BuildOutput;
7
+ _rendered_skeleton: {
8
+ result: string;
9
+ placeholder_ids: string[];
10
+ };
11
+ _resolved_skeleton: ResolvedMiniHtmlString;
12
+ import_paths: string[];
13
+ fill: (...args: MiniValue[]) => Blob;
14
+ mini: () => Mini;
15
+ };
16
+ export type SkeletonBuildParams = {
17
+ stringLiterals: TemplateStringsArray;
18
+ values: MiniValue[];
19
+ root: string;
20
+ mini?: Mini;
21
+ config?: Bun.BuildConfig;
22
+ };
23
+ export declare function buildSkeleton({ stringLiterals, values, root, mini, config, }: SkeletonBuildParams): Promise<Skeleton>;
24
+ export declare function build(params: SkeletonBuildParams): Promise<Skeleton>;
25
+ export declare function curryFill(placeholder_ids: string[], rendered_skeleton: string, mini?: Mini): (...args: MiniValue[]) => Blob;
26
+ export declare function renderBackend(cac: CacheAndCursor): {
27
+ result: string;
28
+ placeholder_ids: string[];
29
+ };
30
+ export type BunStaticRoutes = Record<string, Response>;
31
+ export declare function createStaticRoutes(build_result: Bun.BuildOutput): BunStaticRoutes;
32
+ export declare function getCallerDir(): string;
33
+ export declare function hmr(skel: Skeleton, params: SkeletonBuildParams): Promise<void>;
34
+ declare global {
35
+ interface ReadableStream<R = any> {
36
+ /** consumes the stream and returns the full content as string */
37
+ text(): Promise<string>;
38
+ /** consumes the stream and parses it as JSON */
39
+ json(): Promise<unknown>;
40
+ /** consumes the stream as ArrayBuffer */
41
+ arrayBuffer(): Promise<ArrayBuffer>;
42
+ /** consumes the stream as Uint8Array */
43
+ bytes(): Promise<Uint8Array>;
44
+ }
45
+ var minireload: () => void;
46
+ }
@@ -0,0 +1,233 @@
1
+ import { getResolvedMiniHtmlStringThrows, } from "../minicache";
2
+ import { isBackend, makeNewMini, resolveMiniValue, } from "../mininext";
3
+ import { resolve as backendResolve } from "./miniresolve";
4
+ export function newBackendMini() {
5
+ return makeNewMini({
6
+ cache: new Map(),
7
+ cursor: "1",
8
+ });
9
+ }
10
+ export async function buildSkeleton({ stringLiterals, values, root, mini, config, }) {
11
+ const watchedPaths = [];
12
+ const logPathsPlugin = {
13
+ name: "log-paths",
14
+ setup(build) {
15
+ build.onLoad({ filter: /.*/ }, (args) => {
16
+ watchedPaths.push(args.path);
17
+ return undefined;
18
+ });
19
+ },
20
+ };
21
+ if (!isBackend)
22
+ throw new Error(`build() can only be called on backend, not frontend.
23
+ Use renderRoot() in to mount components in the frontend`);
24
+ if (!mini)
25
+ mini = newBackendMini();
26
+ const _resolved_skeleton = backendResolve(stringLiterals, values, mini);
27
+ const _rendered_skeleton = renderBackend(mini.cacheAndCursor);
28
+ const minify = Bun.env.NODE_ENV === "production";
29
+ const indexpath = root + "/index.html";
30
+ const _build_result = await Bun.build({
31
+ entrypoints: [indexpath],
32
+ files: {
33
+ [indexpath]: _rendered_skeleton.result,
34
+ },
35
+ plugins: [logPathsPlugin],
36
+ root,
37
+ minify,
38
+ ...(config ?? {}),
39
+ });
40
+ if (Bun.env.NODE_ENV === "production") {
41
+ if (!_build_result.success) {
42
+ console.error("Build failed:", _build_result.logs);
43
+ process.exit(1);
44
+ }
45
+ }
46
+ let htmlArtifact = _build_result.outputs.find((output) => output.path === "./index.html");
47
+ if (!htmlArtifact) {
48
+ throw new Error("No index.html found in build outputs");
49
+ }
50
+ let rendered_built_result = await htmlArtifact.text();
51
+ const static_routes = createStaticRoutes(_build_result);
52
+ if (Bun.env.NODE_ENV !== "production") {
53
+ const buildId = crypto.randomUUID();
54
+ const hmrUrl = `/hmr-${buildId}`;
55
+ static_routes[hmrUrl] = new Response(JSON.stringify({ buildId: buildId }), {
56
+ headers: { "Content-Type": "application/json; charset=utf-8" },
57
+ });
58
+ const hmrReloadScript = `<script>
59
+ setInterval(reload, 100);
60
+ async function reload(){
61
+ try {
62
+ const res = await fetch("${hmrUrl}");
63
+ if (!res.ok) return location.reload();
64
+ const { buildId } = await res.json();
65
+ if (buildId !== "${buildId}") location.reload();
66
+ } catch {
67
+ location.reload();
68
+ }
69
+ }
70
+ </script>`;
71
+ const rewriter = new HTMLRewriter().on("body", {
72
+ element(head) {
73
+ head.append(hmrReloadScript, { html: true });
74
+ },
75
+ });
76
+ rendered_built_result = rewriter.transform(rendered_built_result);
77
+ }
78
+ const getMini = () => {
79
+ const newMini = newBackendMini();
80
+ newMini.fill = curryFill(_rendered_skeleton.placeholder_ids, rendered_built_result, newMini);
81
+ return newMini;
82
+ };
83
+ const fill = curryFill(_rendered_skeleton.placeholder_ids, rendered_built_result);
84
+ const result = {
85
+ static_routes,
86
+ _build_result,
87
+ _rendered_skeleton,
88
+ _resolved_skeleton,
89
+ mini: getMini,
90
+ fill,
91
+ import_paths: watchedPaths,
92
+ };
93
+ return result;
94
+ }
95
+ export async function build(params) {
96
+ const result = await buildSkeleton(params);
97
+ if (Bun.env.NODE_ENV !== "production") {
98
+ hmr(result, params);
99
+ }
100
+ return result;
101
+ }
102
+ export function curryFill(placeholder_ids, rendered_skeleton, mini) {
103
+ const fill = (...args) => {
104
+ let localSkeleton = rendered_skeleton;
105
+ const localMini = mini ?? newBackendMini();
106
+ let pointer = 0;
107
+ for (const placeholderID of placeholder_ids) {
108
+ const filler = args[pointer];
109
+ if (typeof filler === undefined || filler === null) {
110
+ localSkeleton = localSkeleton.replace(placeholderID, "");
111
+ }
112
+ else if (typeof filler === "string" || typeof filler === "number") {
113
+ localSkeleton = localSkeleton.replace(placeholderID, String(filler));
114
+ }
115
+ else {
116
+ const slotId = `${pointer}-filled`;
117
+ resolveMiniValue(filler, localMini, slotId);
118
+ const newCac = { ...localMini.cacheAndCursor, cursor: slotId };
119
+ const renderedValue = renderBackend(newCac);
120
+ localSkeleton = localSkeleton.replace(placeholderID, renderedValue.result);
121
+ }
122
+ pointer++;
123
+ }
124
+ return new Blob([localSkeleton], { type: "text/html;charset=utf-8" });
125
+ };
126
+ return fill;
127
+ }
128
+ export function renderBackend(cac) {
129
+ const htmlsnippet = getResolvedMiniHtmlStringThrows(cac);
130
+ if (!htmlsnippet.stringLiterals || !htmlsnippet.values || !htmlsnippet.slots)
131
+ throw new Error("should have literals,values & slots once resolved");
132
+ let result = "";
133
+ const placeholder_ids = [];
134
+ let index = 0;
135
+ for (const literal of htmlsnippet.stringLiterals) {
136
+ const value = index < htmlsnippet.values.length ? htmlsnippet.values[index] : "";
137
+ if (value && typeof value === "object" && "childId" in value) {
138
+ const partRender = renderBackend({
139
+ cache: cac.cache,
140
+ cursor: value.childId,
141
+ });
142
+ result += literal + partRender.result;
143
+ }
144
+ else {
145
+ let primValue = value;
146
+ if (primValue === null) {
147
+ const pl_id = crypto.randomUUID();
148
+ primValue = pl_id;
149
+ placeholder_ids.push(pl_id);
150
+ }
151
+ result += literal + Bun.escapeHTML(primValue);
152
+ }
153
+ index++;
154
+ }
155
+ return { result, placeholder_ids };
156
+ }
157
+ export function createStaticRoutes(build_result) {
158
+ const routes = {};
159
+ for (const output of build_result.outputs) {
160
+ if (output.path === "./index.html")
161
+ continue;
162
+ let urlPath = output.path;
163
+ if (urlPath.startsWith("./")) {
164
+ urlPath = "/" + urlPath.slice(2);
165
+ }
166
+ routes[urlPath] = new Response(output, {
167
+ headers: { "Content-Type": output.type },
168
+ });
169
+ }
170
+ return routes;
171
+ }
172
+ export function getCallerDir() {
173
+ const origPrepareStackTrace = Error.prepareStackTrace;
174
+ Error.prepareStackTrace = (_, stack) => stack; // Return raw CallSite objects
175
+ const err = new Error();
176
+ Error.captureStackTrace(err, getCallerDir); // Skip frames up to this function
177
+ const stack = err.stack; // Type it as CallSite array
178
+ Error.prepareStackTrace = origPrepareStackTrace; // Restore original
179
+ // stack[0] should now be the caller of getCallerDir (inside build)
180
+ // stack[1] is the external caller of build
181
+ const callerCallsite = stack[1];
182
+ if (!callerCallsite) {
183
+ throw new Error("Unable to determine caller directory");
184
+ }
185
+ const callerFile = callerCallsite.getFileName();
186
+ if (!callerFile) {
187
+ throw new Error("Caller file name not available");
188
+ }
189
+ let lastSlash = callerFile.lastIndexOf("/");
190
+ let lastBackslash = callerFile.lastIndexOf("\\");
191
+ let lastSep = Math.max(lastSlash, lastBackslash);
192
+ if (lastSep <= 0) {
193
+ return ".";
194
+ }
195
+ return callerFile.slice(0, lastSep);
196
+ }
197
+ export async function hmr(skel, params) {
198
+ const escapedPaths = skel.import_paths
199
+ .slice(1)
200
+ .map((p) => JSON.stringify(p))
201
+ .join(", ");
202
+ const watcherCode = `
203
+ import fs from "node:fs";
204
+ const files=[${escapedPaths}];
205
+ files.forEach(f=>fs.watchFile(f,{interval:100},()=>{
206
+ console.log("file changed:",f);
207
+ process.exit(0);
208
+ })
209
+ );`;
210
+ const proc = Bun.spawn(["bun", "run", "-"], {
211
+ stdin: "pipe",
212
+ stdout: "pipe",
213
+ stderr: "pipe",
214
+ });
215
+ proc.stdin.write(watcherCode);
216
+ proc.stdin.end();
217
+ const output = await proc.stdout.text();
218
+ console.log("(hmr process)", output);
219
+ if (output.includes("file changed:")) {
220
+ const newSkel = await buildSkeleton(params);
221
+ Object.assign(skel, {
222
+ static_routes: newSkel.static_routes,
223
+ _build_result: newSkel._build_result,
224
+ _rendered_skeleton: newSkel._rendered_skeleton,
225
+ _resolved_skeleton: newSkel._resolved_skeleton,
226
+ fill: newSkel.fill,
227
+ mini: newSkel.mini,
228
+ });
229
+ hmr(skel, params);
230
+ if (globalThis.minireload)
231
+ globalThis.minireload();
232
+ }
233
+ }
@@ -0,0 +1,8 @@
1
+ import { type CacheAndCursor, type ResolvedMiniCacheValue } from "../minicache";
2
+ import { type Mini, type MiniValue, type ResolvedMiniHtmlString, type StringArray } from "../mininext";
3
+ export declare function resolveMiniHtmlString(stringLiterals: StringArray, unresolvedValues: MiniValue[], mini: Mini, slots: string[]): ResolvedMiniHtmlString;
4
+ export declare function resolveValuesForCache(unresolvedValues: MiniValue[], cac: CacheAndCursor, slots?: string[] | null): {
5
+ slots: string[];
6
+ values: ResolvedMiniCacheValue[];
7
+ };
8
+ export declare function resolve(stringLiterals: StringArray, unresolvedValues: MiniValue[], mini: Mini): ResolvedMiniHtmlString;
@@ -0,0 +1,60 @@
1
+ import {} from "../minicache";
2
+ import { resolveMiniValue, } from "../mininext";
3
+ export function resolveMiniHtmlString(stringLiterals, unresolvedValues, mini, slots) {
4
+ const resolvedValues = [];
5
+ let index = 0;
6
+ for (const unresolvedValue of unresolvedValues) {
7
+ const slotId = slots[index];
8
+ if (!slotId)
9
+ throw new Error(`Could not find slot id for ${unresolvedValue}`);
10
+ resolvedValues.push(resolveMiniValue(unresolvedValue, mini, slotId));
11
+ index++;
12
+ }
13
+ return {
14
+ slots,
15
+ stringLiterals,
16
+ values: resolvedValues,
17
+ render: (target, cacheAndCursor) => {
18
+ throw new Error(`not implemented, should not be called on backend.
19
+ Use .build() to make an html skeleton, then use .fill() to fill in the values`);
20
+ },
21
+ };
22
+ }
23
+ export function resolveValuesForCache(unresolvedValues, cac, slots = null) {
24
+ // todo increment number in root cursor instead of using random (1-randromroot-to-avoid-collisions-in-frontend)
25
+ if (!slots)
26
+ slots = unresolvedValues.map(() => crypto.randomUUID());
27
+ const values = unresolvedValues.map((value, index) => {
28
+ const childId = slots[index];
29
+ if (!childId)
30
+ throw new Error("Could not find slot id for value");
31
+ // CASE: primitive
32
+ if (typeof value === "string" ||
33
+ typeof value === "number" ||
34
+ value === null) {
35
+ const cacheEntry = cac.cache.get(childId);
36
+ if (cacheEntry) {
37
+ if (cacheEntry.value === value)
38
+ return value;
39
+ cacheEntry.value = value;
40
+ cacheEntry.dirty = true;
41
+ return value;
42
+ }
43
+ cac.cache.set(childId, { value, dirty: true });
44
+ return value;
45
+ }
46
+ // CASE: child mini htmlstring or component
47
+ return { childId };
48
+ });
49
+ return { slots, values };
50
+ }
51
+ export function resolve(stringLiterals, unresolvedValues, mini) {
52
+ // CASE our cache entry does not exist yet is the only one we need to handle in the backend
53
+ const cac = mini.cacheAndCursor;
54
+ const { slots, values } = resolveValuesForCache(unresolvedValues, cac);
55
+ cac.cache.set(cac.cursor, {
56
+ value: { stringLiterals, values, slots, state: null },
57
+ dirty: true,
58
+ });
59
+ return resolveMiniHtmlString(stringLiterals, unresolvedValues, mini, slots);
60
+ }
@@ -0,0 +1,8 @@
1
+ import type { CacheAndCursor } from "../minicache";
2
+ import { type Mini, type MiniHtmlString } from "../mininext";
3
+ export type RootOptions = {
4
+ component: (mini: Mini) => MiniHtmlString;
5
+ container: HTMLElement;
6
+ cac?: CacheAndCursor;
7
+ };
8
+ export declare function renderRoot(options: RootOptions): void;
@@ -0,0 +1,29 @@
1
+ import { makeNewMini } from "../mininext";
2
+ let roots = [];
3
+ export function renderRoot(options) {
4
+ roots.push(options);
5
+ startRafLoop();
6
+ }
7
+ let rafRunning = false;
8
+ function startRafLoop() {
9
+ if (rafRunning)
10
+ return;
11
+ rafRunning = true;
12
+ function loop() {
13
+ for (const root of roots) {
14
+ let { component, cac } = root;
15
+ if (!cac) {
16
+ cac = {
17
+ cache: new Map(),
18
+ cursor: crypto.randomUUID(),
19
+ };
20
+ }
21
+ const mini = makeNewMini(cac);
22
+ const evaluated = component(mini);
23
+ const resolved = evaluated.resolve(mini);
24
+ root.cac = resolved.render(root.container, cac);
25
+ }
26
+ requestAnimationFrame(loop);
27
+ }
28
+ requestAnimationFrame(loop);
29
+ }
@@ -0,0 +1,10 @@
1
+ import { type CacheAndCursor } from "../minicache";
2
+ export declare function render(target: Element | HTMLElement, cac: CacheAndCursor): CacheAndCursor;
3
+ export type IsInside = {
4
+ element: boolean;
5
+ singleQuotes: boolean;
6
+ doubleQuotes: boolean;
7
+ lastElement: string;
8
+ };
9
+ export declare function isInside(stringLiteral: string, isInside: IsInside): void;
10
+ export declare function makeIdMap(parent: Element): Map<string, HTMLElement>;
@@ -0,0 +1,136 @@
1
+ import { getCacheEntryThrows, getResolvedMiniHtmlStringThrows, } from "../minicache";
2
+ export function render(target, cac) {
3
+ const cacheEntry = getCacheEntryThrows(cac);
4
+ const htmlsnippet = getResolvedMiniHtmlStringThrows(cac);
5
+ if (!htmlsnippet.stringLiterals || !htmlsnippet.values || !htmlsnippet.slots)
6
+ throw new Error("should have literals,values & slots once resolved");
7
+ const children = new Map();
8
+ let new_html_portion = null;
9
+ if (cacheEntry.dirty) {
10
+ let placeholder = "";
11
+ let index = 0;
12
+ const inside = {
13
+ element: false,
14
+ singleQuotes: false,
15
+ doubleQuotes: false,
16
+ lastElement: "",
17
+ };
18
+ for (const literal of htmlsnippet.stringLiterals) {
19
+ isInside(literal, inside);
20
+ const id = htmlsnippet.slots[index];
21
+ if (inside.element || inside.lastElement.trim().endsWith("<style")) {
22
+ placeholder += literal + escapeHtml(htmlsnippet.values[index]);
23
+ }
24
+ else if (!inside.element && index < htmlsnippet.values.length) {
25
+ placeholder += literal + `<span id="${id}"></span>`;
26
+ }
27
+ else {
28
+ placeholder += literal;
29
+ }
30
+ index++;
31
+ }
32
+ new_html_portion = htmlPortion(placeholder);
33
+ const idmap = makeIdMap(new_html_portion);
34
+ for (const childId of htmlsnippet.slots) {
35
+ const cacheEntry = cac.cache.get(childId);
36
+ if (!cacheEntry)
37
+ continue;
38
+ cacheEntry.el = idmap.get(childId);
39
+ children.set(childId, cacheEntry);
40
+ }
41
+ }
42
+ if (!cacheEntry.dirty) {
43
+ for (const childId of htmlsnippet.slots) {
44
+ const cacheEntry = cac.cache.get(childId);
45
+ if (!cacheEntry)
46
+ continue;
47
+ children.set(childId, cacheEntry);
48
+ }
49
+ }
50
+ for (const [childId, child] of children) {
51
+ const value = child.value;
52
+ if (typeof value == "object") {
53
+ const childtarget = child.el;
54
+ if (!childtarget)
55
+ throw new Error(`if not dirty should have el: ${childId}`);
56
+ render(childtarget, { ...cac, cursor: childId });
57
+ }
58
+ else {
59
+ if (!child.dirty)
60
+ continue;
61
+ if (!child.el)
62
+ child.el = document.getElementById(childId) ?? undefined;
63
+ if (!child.el)
64
+ continue; //throw new Error(`${child.value} should have el`);
65
+ child.el.textContent = String(value);
66
+ child.dirty = false;
67
+ }
68
+ }
69
+ if (new_html_portion) {
70
+ const parent = target.parentNode;
71
+ if (parent)
72
+ parent.replaceChild(new_html_portion, target);
73
+ cacheEntry.el = new_html_portion;
74
+ cacheEntry.dirty = false;
75
+ }
76
+ return cac;
77
+ }
78
+ export function isInside(stringLiteral, isInside) {
79
+ for (const char of stringLiteral) {
80
+ // Check for HTML element start (< not in quotes)
81
+ if (char === "<" && !isInside.singleQuotes && !isInside.doubleQuotes) {
82
+ isInside.element = true;
83
+ }
84
+ if (char === ">" && !isInside.singleQuotes && !isInside.doubleQuotes) {
85
+ isInside.element = false;
86
+ }
87
+ if (isInside.element)
88
+ isInside.lastElement += char;
89
+ if (!isInside.element)
90
+ continue;
91
+ // Handle quotes
92
+ if (char === "'" && !isInside.doubleQuotes) {
93
+ isInside.singleQuotes = !isInside.singleQuotes;
94
+ }
95
+ else if (char === '"' && !isInside.singleQuotes) {
96
+ isInside.doubleQuotes = !isInside.doubleQuotes;
97
+ }
98
+ }
99
+ }
100
+ function escapeHtml(string) {
101
+ if (!string)
102
+ return "";
103
+ if (typeof string !== "string" && typeof string !== "number")
104
+ throw new Error(`inside < html > tags or open style tags only use string | number as values.
105
+ ${JSON.stringify(string)}`);
106
+ string = String(string);
107
+ const div = document.createElement("div");
108
+ div.textContent = string;
109
+ return div.innerHTML;
110
+ }
111
+ function htmlPortion(html) {
112
+ const template = document.createElement("template");
113
+ template.innerHTML = html;
114
+ const fragment = template.content;
115
+ const hasManyRoots = fragment.childElementCount > 1;
116
+ if (hasManyRoots)
117
+ throw new Error(`Mini html placeholder template:\n
118
+ ${html}\n
119
+ Root elements: ${fragment.childElementCount}
120
+ Every mini html string should have only one root element.\n`);
121
+ return fragment.firstElementChild;
122
+ }
123
+ // if element is not in DOM yet we cant use getElementById
124
+ export function makeIdMap(parent) {
125
+ const idMap = new Map();
126
+ function recurse(el) {
127
+ if (el.id) {
128
+ idMap.set(el.id, el);
129
+ }
130
+ for (const child of el.children) {
131
+ recurse(child);
132
+ }
133
+ }
134
+ recurse(parent);
135
+ return idMap;
136
+ }
@@ -0,0 +1,9 @@
1
+ import { type CacheAndCursor, type ResolvedMiniCacheValue } from "../minicache";
2
+ import { type Mini, type MiniValue, type ResolvedMiniHtmlString, type StringArray } from "../mininext";
3
+ export declare function resolveMiniHtmlString(stringLiterals: StringArray, unresolvedValues: MiniValue[], mini: Mini, slots: string[]): ResolvedMiniHtmlString;
4
+ export declare function resolveValuesForCache(unresolvedValues: MiniValue[], cac: CacheAndCursor, slots?: string[] | null): {
5
+ slots: string[];
6
+ values: ResolvedMiniCacheValue[];
7
+ };
8
+ export declare function resolve(stringLiterals: StringArray, unresolvedValues: MiniValue[], mini: Mini): ResolvedMiniHtmlString;
9
+ export declare function parametricHtmlChanges(stringLiterals: StringArray, cached_values: ResolvedMiniCacheValue[] | null, values: MiniValue[]): boolean;