@ijo-elaja/rev.js 0.4.0 → 0.5.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/README.md +4 -1
- package/index.ts +129 -3
- package/package.json +1 -1
- package/test/live/components/Head.html +0 -3
- package/test/live/live.test.ts +0 -9
- package/test/live/pages/404.html +0 -1
- package/test/live/pages/_layout.html +0 -17
- package/test/live/pages/_page.html +0 -3
- package/test/live/pages/whatever/.slug +0 -1
- package/test/live/pages/whatever/[slug]/_page.html +0 -16
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@ A highly opionionated, lightweight, frontend<sup>1</sup> only solution.
|
|
|
23
23
|
- Slugs (dynamic data based routing)
|
|
24
24
|
- Components
|
|
25
25
|
- Inline JavaScript
|
|
26
|
+
- Client-Side Conditional Rendering
|
|
26
27
|
|
|
27
28
|
### Planned/Unplanned
|
|
28
29
|
|
|
@@ -92,7 +93,9 @@ new Rev({
|
|
|
92
93
|
// this is 100% optional, but can be useful
|
|
93
94
|
// when you want more functionality than just
|
|
94
95
|
// what rev.js provides by default
|
|
95
|
-
|
|
96
|
+
// note that typescript will complain without
|
|
97
|
+
// the `as any` if you actually make changes
|
|
98
|
+
elysia: (app) => app as any
|
|
96
99
|
});
|
|
97
100
|
```
|
|
98
101
|
|
package/index.ts
CHANGED
|
@@ -109,12 +109,19 @@ enum EvalstMode {
|
|
|
109
109
|
LAYOUT,
|
|
110
110
|
PAGE,
|
|
111
111
|
SCRIPT,
|
|
112
|
+
CLIENT_CONDITIONAL,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
interface ConditionalBranch {
|
|
116
|
+
condition: string | null;
|
|
117
|
+
content: string;
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
interface EvalstElement {
|
|
115
121
|
match: string;
|
|
116
122
|
mode: EvalstMode;
|
|
117
123
|
content: string;
|
|
124
|
+
branches?: ConditionalBranch[];
|
|
118
125
|
}
|
|
119
126
|
|
|
120
127
|
interface EvalsElement {
|
|
@@ -124,6 +131,100 @@ interface EvalsElement {
|
|
|
124
131
|
isJs: boolean;
|
|
125
132
|
}
|
|
126
133
|
|
|
134
|
+
const parseConditionalBlock = (
|
|
135
|
+
content: string,
|
|
136
|
+
startPos: number,
|
|
137
|
+
): { branches: ConditionalBranch[]; endPos: number; match: string } => {
|
|
138
|
+
const ifMatch = content
|
|
139
|
+
.slice(startPos)
|
|
140
|
+
.match(/^\{\{#\s*if\s+(.+?)\s*#\}\}/);
|
|
141
|
+
if (!ifMatch) throw new Error("Expected {{# if condition #}}");
|
|
142
|
+
|
|
143
|
+
const branches: ConditionalBranch[] = [];
|
|
144
|
+
const fullMatch = [ifMatch[0]];
|
|
145
|
+
let condition: string | null = ifMatch[1].replaceAll("~", "__EVAL_STATE__");
|
|
146
|
+
let currentPos = startPos + ifMatch[0].length;
|
|
147
|
+
let branchContent = "";
|
|
148
|
+
|
|
149
|
+
while (currentPos < content.length) {
|
|
150
|
+
const remaining = content.slice(currentPos);
|
|
151
|
+
|
|
152
|
+
const elseIfMatch = remaining.match(
|
|
153
|
+
/^\{\{#\s*else\s+if\s+(.+?)\s*#\}\}/,
|
|
154
|
+
);
|
|
155
|
+
if (elseIfMatch) {
|
|
156
|
+
branches.push({ condition, content: branchContent });
|
|
157
|
+
fullMatch.push(elseIfMatch[0]);
|
|
158
|
+
condition = elseIfMatch[1].replaceAll("~", "__EVAL_STATE__");
|
|
159
|
+
currentPos += elseIfMatch[0].length;
|
|
160
|
+
branchContent = "";
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const elseMatch = remaining.match(/^\{\{#\s*else\s*#\}\}/);
|
|
165
|
+
if (elseMatch) {
|
|
166
|
+
branches.push({ condition, content: branchContent });
|
|
167
|
+
fullMatch.push(elseMatch[0]);
|
|
168
|
+
condition = null;
|
|
169
|
+
currentPos += elseMatch[0].length;
|
|
170
|
+
branchContent = "";
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const endifMatch = remaining.match(/^\{\{#\s*endif\s*#\}\}/);
|
|
175
|
+
if (endifMatch) {
|
|
176
|
+
branches.push({ condition, content: branchContent });
|
|
177
|
+
fullMatch.push(endifMatch[0]);
|
|
178
|
+
currentPos += endifMatch[0].length;
|
|
179
|
+
return {
|
|
180
|
+
branches,
|
|
181
|
+
endPos: currentPos,
|
|
182
|
+
match: fullMatch.join(""),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
branchContent += remaining[0];
|
|
187
|
+
currentPos++;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
throw new Error("Unclosed {{# if #}} block");
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const generateConditionalHTML = (
|
|
194
|
+
branches: ConditionalBranch[],
|
|
195
|
+
containerId: string,
|
|
196
|
+
state: Record<string, any>,
|
|
197
|
+
): string => {
|
|
198
|
+
let html = `<div id="${containerId}">`;
|
|
199
|
+
|
|
200
|
+
branches.forEach((branch, idx) => {
|
|
201
|
+
html += `<div data-conditional-branch="${idx}" data-condition="${branch.condition || "else"}" style="display: none;">${branch.content}</div>`;
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
html += `</div>`;
|
|
205
|
+
|
|
206
|
+
let conditionScript = "";
|
|
207
|
+
for (let i = 0; i < branches.length; i++) {
|
|
208
|
+
const branch = branches[i];
|
|
209
|
+
if (i === 0) {
|
|
210
|
+
conditionScript += `if (${branch.condition}) { document.querySelector('[data-conditional-branch="${i}"]').style.display = "block"; }`;
|
|
211
|
+
} else if (branch.condition !== null) {
|
|
212
|
+
conditionScript += ` else if (${branch.condition}) { document.querySelector('[data-conditional-branch="${i}"]').style.display = "block"; }`;
|
|
213
|
+
} else {
|
|
214
|
+
conditionScript += ` else { document.querySelector('[data-conditional-branch="${i}"]').style.display = "block"; }`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
html += `<script>
|
|
219
|
+
(function() {
|
|
220
|
+
let __EVAL_STATE__ = ${JSON.stringify(state)};
|
|
221
|
+
${conditionScript}
|
|
222
|
+
})();
|
|
223
|
+
</script>`;
|
|
224
|
+
|
|
225
|
+
return html;
|
|
226
|
+
};
|
|
227
|
+
|
|
127
228
|
const evaluate = async (
|
|
128
229
|
str: string,
|
|
129
230
|
initialState?: Record<string, any>,
|
|
@@ -131,11 +232,12 @@ const evaluate = async (
|
|
|
131
232
|
): Promise<string> => {
|
|
132
233
|
let processedPageContent = str;
|
|
133
234
|
const evalst: EvalstElement[] = [];
|
|
235
|
+
let __EVAL_STATE__ = initialState || {};
|
|
236
|
+
|
|
134
237
|
const regex = /{{ ?(.*?)? ?}}/gims;
|
|
135
238
|
let m;
|
|
136
|
-
let __EVAL_STATE__ = initialState || {};
|
|
137
239
|
|
|
138
|
-
while ((m = regex.exec(
|
|
240
|
+
while ((m = regex.exec(processedPageContent)) !== null) {
|
|
139
241
|
if (m.index === regex.lastIndex) {
|
|
140
242
|
regex.lastIndex++;
|
|
141
243
|
}
|
|
@@ -195,7 +297,7 @@ const evaluate = async (
|
|
|
195
297
|
const code = `let __EVAL_STATE__=${JSON.stringify(
|
|
196
298
|
__EVAL_STATE__,
|
|
197
299
|
)};${toEval}`;
|
|
198
|
-
const data = await Bun.$`bun -e
|
|
300
|
+
const data = await Bun.$`bun -e ${code}`.json();
|
|
199
301
|
// copy result into __EVAL_STATE__
|
|
200
302
|
Object.assign(__EVAL_STATE__, data);
|
|
201
303
|
evalst.push({
|
|
@@ -239,6 +341,30 @@ const evaluate = async (
|
|
|
239
341
|
);
|
|
240
342
|
});
|
|
241
343
|
|
|
344
|
+
let conditionalIndex = 0;
|
|
345
|
+
let pos = 0;
|
|
346
|
+
while (pos < processedPageContent.length) {
|
|
347
|
+
const ifMatch = processedPageContent.slice(pos).match(/\{\{#\s*if\s+/);
|
|
348
|
+
if (!ifMatch || ifMatch.index === undefined) break;
|
|
349
|
+
|
|
350
|
+
const ifPos = pos + ifMatch.index;
|
|
351
|
+
const parsed = parseConditionalBlock(processedPageContent, ifPos);
|
|
352
|
+
|
|
353
|
+
const conditionalHTML = generateConditionalHTML(
|
|
354
|
+
parsed.branches,
|
|
355
|
+
`cond-${conditionalIndex}`,
|
|
356
|
+
__EVAL_STATE__,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
processedPageContent = processedPageContent.replace(
|
|
360
|
+
parsed.match,
|
|
361
|
+
conditionalHTML,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
pos = ifPos + conditionalHTML.length;
|
|
365
|
+
conditionalIndex++;
|
|
366
|
+
}
|
|
367
|
+
|
|
242
368
|
return processedPageContent;
|
|
243
369
|
};
|
|
244
370
|
|
package/package.json
CHANGED
package/test/live/live.test.ts
DELETED
package/test/live/pages/404.html
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<i style="color: red !important">404 Not Found</i>
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<!-- this is a component (very useful for sharing heads between pages) -->
|
|
5
|
-
{{ %Head% }}
|
|
6
|
-
</head>
|
|
7
|
-
<body>
|
|
8
|
-
<header>
|
|
9
|
-
<section>
|
|
10
|
-
<h1><a href="/">test site</a></h1>
|
|
11
|
-
<small class="muted">subheadings are a thing</small>
|
|
12
|
-
</section>
|
|
13
|
-
</header>
|
|
14
|
-
<!-- this is a built-in component -->
|
|
15
|
-
<main>{{ %Outlet% }}</main>
|
|
16
|
-
</body>
|
|
17
|
-
</html>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[slug]
|