pawa-ssr 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Allwell Oriso-owubo (Allisboy)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # pawajs-ssr
2
+ pawajs ssr (server side rendering) for javascript
3
+
4
+ # Directives
5
+ server-for, server-if,server-else,server-else-if
6
+
7
+ >>>html
8
+ <div server-if="user.value.name">
9
+ <h1>@(user.value.name)</h1>
10
+ </div>
11
+ >>>
12
+
13
+ * Notice pawajs ssr uses @() instead of @{} and pawajs ssr doesn't use client's hooks
package/classes.js ADDED
@@ -0,0 +1,56 @@
1
+
2
+ export const ComponentProps=(some,message)=>{
3
+
4
+ return({
5
+ Array:()=>{
6
+
7
+ if (Array.isArray(some)) {
8
+ return true
9
+ }else{
10
+ throw new Error(message ?message + ' / Not type of an Array ': `${some} must be an array`);
11
+
12
+ }
13
+ },
14
+ String:()=>{
15
+ if (typeof some === 'string') {
16
+ return true
17
+ }else{
18
+ throw new Error(message? message + ' / Not type of a String' :`${some} must be a string`);
19
+
20
+ }
21
+ },
22
+ Number:()=>{
23
+ if (typeof some === 'number') {
24
+ return true
25
+ }else{
26
+ throw new Error(message? message+' / Not type of a Number ': `${some} must be a number`);
27
+
28
+ }
29
+ },
30
+ Object:()=>{
31
+ if (typeof some === 'object') {
32
+ return true
33
+ }else{
34
+ throw new Error(message? message+' / Not type of an Object ' :`${some} must be an object`);
35
+
36
+ }
37
+ },
38
+ Function:()=>{
39
+ if (typeof some === 'function') {
40
+ return true
41
+ }else{
42
+ throw new Error(message? message+' / Not type of a Function ': `${some} must be a function`);
43
+
44
+ }
45
+ },
46
+ Boolean:()=>{
47
+ if (typeof some === 'boolean') {
48
+ return true
49
+ }else{
50
+ throw new Error(message? message+' / Not type of a Boolean ' :`${some} must be a Boolean`);
51
+
52
+ }
53
+ },
54
+ })
55
+
56
+ }
package/index.js ADDED
@@ -0,0 +1,448 @@
1
+ import {Attr, DOMParser,parseHTML,Node, HTMLElement} from 'linkedom'
2
+ import PawaElement from './pawaElement.js'
3
+ import { If,Else,ElseIf,For } from './power.js';
4
+ import PawaComponent from './pawaComponent.js';
5
+ import { sanitizeTemplate,propsValidator, evaluateExpr } from './utils.js';
6
+
7
+ /**
8
+ * @type{null|{_formerContext:stateContext,_hasRun:boolean,_prop:object,_name:string,_insert:object,_transportContext}}
9
+ */
10
+ // export let stateContext=null;
11
+ // let formerContext=null;
12
+ export const components=new Map()
13
+
14
+
15
+
16
+ export const $state=(arg)=>{
17
+ return {
18
+ value:arg
19
+ }
20
+ }
21
+
22
+ // under consideration
23
+
24
+ /**
25
+ * @type{string}
26
+ */
27
+ export const allServerAttr=['server-if','server-else','server-else-if','server-for']
28
+
29
+ export const RegisterComponent=(...arg)=>{
30
+
31
+ arg.forEach(func=>{
32
+ if (typeof func === 'function') {
33
+ components.set(func.name.toUpperCase(),func)
34
+ }else{
35
+ console.warn('must be a function')
36
+ }
37
+ })
38
+
39
+ }
40
+
41
+ const compoBeforeCall = new Set()
42
+ const compoAfterCall=new Set()
43
+ const renderBeforePawa=new Set()
44
+ const renderAfterPawa=new Set()
45
+ const renderBeforeChild=new Set()
46
+ const attrPlugin=new Set()
47
+ /**
48
+ * @typedef {{
49
+ * attribute?:{register:Array<string>,plugin:(el:HTMLElement,attr:object)=>void},
50
+ * component?:{
51
+ * beforeCall?:(stateContext:PawaComponent,app:object)=>void,
52
+ * afterCall?:(stateContext:PawaComponent,el:HTMLElement)=>void
53
+ * },
54
+ * renderSystem?:{
55
+ * beforePawa?:(el:HTMLElement,context:object)=>void,
56
+ * afterPawa?:(el:PawaElement)=>void,
57
+ * beforeChildRender?:(el:PawaElement)=>void
58
+ * }
59
+ * }} PluginObject
60
+ */
61
+ /**
62
+ * @param {Array<()=>PluginObject>} func
63
+ */
64
+ export const PluginSystem=(...func)=>{
65
+ func.forEach(fn=>{
66
+ /**
67
+ * @type {PluginObject}
68
+ */
69
+ const getPlugin=fn()
70
+ // attributes plugin or extension
71
+ if (getPlugin?.attribute) {
72
+ const attr=getPlugin.attribute
73
+ if(attr.register === null){
74
+ console.error('attribute register must be giving is an array of attributes to add into pawajs attribute rendering')
75
+ }
76
+ if(Array.isArray(attr.register)){
77
+ attr.register.forEach(attr=>{
78
+ if(pawaAttributes.has(attr)){
79
+ console.warn('attribute already exist in pawajs Attributes',attr)
80
+ throw Error('attribute already exist ',attr)
81
+ }else{
82
+ pawaAttributes.add(attr)
83
+ }
84
+ })
85
+ }else{
86
+ console.warn('pawa attribute plugin register must be an array')
87
+ }
88
+ if(attr.plugin === null){
89
+ console.error('attribute plugin function must be giving, is a function of attributes to run the plugin pawajs attribute rendering')
90
+ }else{
91
+ if(attr.plugin instanceof Function){
92
+ attrPlugin.add(attr.plugin)
93
+ }
94
+ }
95
+
96
+
97
+ }
98
+ if (getPlugin?.component) {
99
+ if (getPlugin.component?.beforeCall && typeof getPlugin.component?.beforeCall === 'function') {
100
+ compoBeforeCall.add(getPlugin.component.beforeCall)
101
+ }
102
+ if (getPlugin.component?.afterCall && typeof getPlugin.component?.afterCall === 'function') {
103
+ compoAfterCall.add(getPlugin.component.afterCall)
104
+ }
105
+ }
106
+ if (getPlugin?.renderSystem) {
107
+ if (getPlugin.renderSystem?.beforePawa && typeof getPlugin.renderSystem?.beforePawa === 'function') {
108
+ renderBeforePawa.add(getPlugin.renderSystem?.beforePawa)
109
+ }
110
+ if (getPlugin.renderSystem?.afterPawa && typeof getPlugin.renderSystem?.afterPawa === 'function') {
111
+ renderAfterPawa.add(getPlugin.renderSystem?.afterPawa)
112
+ }
113
+ if (getPlugin.renderSystem?.beforeChildRender && typeof getPlugin.renderSystem?.beforeChildRender === 'function') {
114
+ renderAfterPawa.add(getPlugin.renderSystem?.beforeChildRender)
115
+ }
116
+ }
117
+ })
118
+ }
119
+
120
+
121
+ /**
122
+ *
123
+ * @param {PawaElement|HTMLElement} el
124
+ * @returns
125
+ */
126
+ const component=(el)=>{
127
+ if(el._running){
128
+ return
129
+ }
130
+ try {
131
+ const slot=el._slots
132
+ const slots={}
133
+ let stateContext=null
134
+ Array.from(slot.children).forEach(prop =>{
135
+ if (prop.getAttribute('prop')) {
136
+ slots[prop.getAttribute('prop')]=prop.innerHTML
137
+ }else{
138
+ console.warn('sloting props must have prop attribute')
139
+ }
140
+ })
141
+ const children=el._componentChildren
142
+ const component =el._component
143
+ stateContext=component
144
+ const insert=(arg={})=>{
145
+ Object.assign(stateContext.context,arg)
146
+ }
147
+ /**
148
+ *
149
+ * @param {object} props
150
+ * @returns {object}
151
+ */
152
+ stateContext._prop={children,...el._props}
153
+ stateContext._name=el._componentName
154
+ const useValidateProps=(props={}) => {
155
+ if (!stateContext) {
156
+ console.warn('must be used inside of a component')
157
+ return
158
+ }
159
+
160
+ return propsValidator(props,stateContext._prop,stateContext._name)
161
+ }
162
+ const app = {
163
+ children,
164
+ app:{
165
+ insert,
166
+ useValidateProps
167
+ },
168
+ ...slots,
169
+ ...el._props
170
+ }
171
+ for (const fn of compoBeforeCall) {
172
+ try {
173
+ fn(stateContext,app)
174
+ } catch (error) {
175
+ console.error(error.message,error.stack)
176
+ }
177
+ }
178
+ const {document}=parseHTML()
179
+ const comment=document.createComment('componet')
180
+ el.replaceWith(comment)
181
+ const div=document.createElement('div')
182
+ let compo
183
+ try{
184
+ compo=sanitizeTemplate(component.component(app))
185
+ }catch(error){
186
+ console.error(error.message,error.stack)
187
+ }
188
+ if (component?._insert) {
189
+ Object.assign(el._context,component._insert)
190
+ }
191
+
192
+ div.innerHTML=compo
193
+ if(Object.entries(el._restProps).length > 0){
194
+ const findElement=div.querySelector('[--]') || div.querySelector('[rest]')
195
+ if (findElement) {
196
+ for (const [key,value] of Object.entries(el._restProps)) {
197
+ findElement.setAttribute(value.name,value.value)
198
+ findElement.removeAttribute('--')
199
+ findElement.removeAttribute('rest')
200
+ }
201
+ }
202
+ }
203
+ for (const fn of compoAfterCall) {
204
+ try {
205
+ fn(stateContext,div?.firstElementChild)
206
+ } catch (error) {
207
+ console.error(error.message,error.stack)
208
+ }
209
+ }
210
+ const newElement=div.firstElementChild
211
+ if (newElement) {
212
+ comment.replaceWith(newElement)
213
+ render(newElement,el._context)
214
+ }
215
+ comment.remove()
216
+ } catch (error) {
217
+ console.log(error.message,error.stack);
218
+
219
+ }
220
+ }
221
+ const textContentHandler=(el)=>{
222
+ if (el._running) {
223
+ return
224
+ }
225
+ const nodesMap = new Map();
226
+
227
+ // Get all text nodes and store their original content
228
+ const textNodes = el.childNodes.filter(node => node.nodeType === 3);
229
+ textNodes.forEach(node => {
230
+ nodesMap.set(node, node.nodeValue);
231
+ });
232
+ const evaluate = () => {
233
+ try {
234
+ textNodes.forEach(textNode => {
235
+ // Always use original content from map for evaluation
236
+ let value = nodesMap.get(textNode);
237
+ const regex = /@\((.*?)\)/g;
238
+
239
+ value = value.replace(regex, (match, expression) => {
240
+ const func = evaluateExpr(expression,el._context)
241
+ return String(func);
242
+ });
243
+ textNode.nodeValue = value;
244
+
245
+
246
+ });
247
+ } catch (error) {
248
+ console.warn(`error at ${el} textcontent`)
249
+ }
250
+ };
251
+ evaluate()
252
+ }
253
+ const attributeHandler=(el,attr)=>{
254
+ if (el._hasForOrIf()) {
255
+ return
256
+ }
257
+ if(el._running){
258
+ return
259
+ }
260
+ const removableAttributes=new Set()
261
+ removableAttributes.add('disabled')
262
+ const evaluate=()=>{
263
+ try {
264
+ const regex = /@\((.*?)\)/g;
265
+
266
+ let value = attr.value;
267
+ const keys = Object.keys(el._context);
268
+ const resolvePath = (path, obj) => {
269
+ return path.split('.').reduce((acc, key) => acc?.[key], obj);
270
+ };
271
+ const values = keys.map((key) => resolvePath(key, el._context));
272
+
273
+ value = value.replace(regex, (match, expression) => {
274
+ return evaluateExpr(expression,el._context)
275
+ });
276
+
277
+ if (removableAttributes.has(attr.name)) {
278
+ if (value) {
279
+ el.setAttribute(attr.name, '');
280
+ } else {
281
+ el.removeAttribute(attr.name)
282
+ }
283
+ } else {
284
+ el.setAttribute(attr.name, value);
285
+ }
286
+ } catch (error) {
287
+ console.log(error.message,error.stack)
288
+ }
289
+ }
290
+ evaluate()
291
+ }
292
+ /**
293
+ * @param {HTMLElement} el
294
+ */
295
+ const innerHtml = (el,context) => {
296
+ if (el.getAttribute('client')) {
297
+ return
298
+ }
299
+ const {document}=parseHTML()
300
+ const nodesMap = new Map();
301
+
302
+ // Get all text nodes and store original value
303
+ const textNodes = Array.from(el.childNodes).filter(
304
+ (node) => node.nodeType === 3
305
+ );
306
+
307
+ textNodes.forEach((node) => {
308
+ nodesMap.set(node, node.nodeValue);
309
+ });
310
+
311
+ const evaluate = () => {
312
+ try {
313
+
314
+ textNodes.forEach((textNode) => {
315
+ const originalValue = nodesMap.get(textNode);
316
+ const regex = /@html\((.*?)\)/g;
317
+ let match;
318
+ let hasHtml = false;
319
+ const fragments = [];
320
+
321
+ let lastIndex = 0;
322
+
323
+ while ((match = regex.exec(originalValue))) {
324
+ const before = originalValue.slice(lastIndex, match.index);
325
+ if (before) fragments.push(document.createTextNode(before));
326
+
327
+ let expression = match[1];
328
+ let htmlString = '';
329
+
330
+
331
+ try {
332
+
333
+ htmlString = evaluateExpr(expression,context)
334
+ } catch (e) {
335
+ htmlString = `<span style="color:red;">[Invalid Expression]</span>`;
336
+ }
337
+
338
+ const temp = document.createElement('div');
339
+ temp.innerHTML = sanitizeTemplate(htmlString);
340
+ fragments.push(...temp.childNodes);
341
+ hasHtml = true;
342
+
343
+ lastIndex = regex.lastIndex;
344
+ }
345
+
346
+ const after = originalValue.slice(lastIndex);
347
+ if (after) fragments.push(document.createTextNode(after));
348
+
349
+ if (hasHtml) {
350
+ const parent = textNode.parentNode;
351
+ parent.insertBefore(document.createDocumentFragment(), textNode);
352
+ fragments.forEach((frag) => parent.insertBefore(frag, textNode));
353
+ parent.removeChild(textNode);
354
+ }
355
+ });
356
+ } catch (error) {
357
+ console.warn(`Error while evaluating innerHTML for`, el, error);
358
+ }
359
+ };
360
+
361
+ // Helper to resolve nested properties
362
+ const resolvePath = (path, obj) => {
363
+ return path.split('.').reduce((acc, key) => acc?.[key], obj);
364
+ };
365
+
366
+ evaluate();
367
+ };
368
+ const directives={
369
+ 'server-if':If,
370
+ 'server-else':Else,
371
+ 'server-else-if':ElseIf,
372
+ 'server-for':For
373
+ }
374
+
375
+ /**
376
+ *
377
+ * @param {PawaElement | HTMLElement} el
378
+ * @param {object} contexts
379
+ */
380
+ export const render=(el,contexts={})=>{
381
+ const context={
382
+ ...contexts
383
+ }
384
+ for (const fn of renderBeforePawa) {
385
+ try {
386
+ fn(el,context)
387
+ } catch (error) {
388
+ console.error(error.message,error.stack)
389
+ }
390
+ }
391
+ innerHtml(el,context)
392
+ PawaElement.Element(el,context)
393
+
394
+ if(el.childNodes.some(node=>node.nodeType === 3 && node.nodeValue.includes('@('))){
395
+ textContentHandler(el)
396
+ }
397
+ for (const fn of renderAfterPawa) {
398
+ try {
399
+ fn(el)
400
+ } catch (error) {
401
+ console.error(error.message,error.stack)
402
+ }
403
+ }
404
+ el.attributes.forEach(attr=>{
405
+ if (directives[attr.name]) {
406
+ directives[attr.name](el,attr)
407
+ }else if(attr.value.includes('@(')){
408
+ attributeHandler(el,attr)
409
+ }else {
410
+ attrPlugin.forEach((plugins) => {
411
+ plugins(el,attr)
412
+ })
413
+ }
414
+
415
+ })
416
+ if (el._componentName) {
417
+ component(el)
418
+ return
419
+ }
420
+ for (const fn of renderBeforeChild) {
421
+ try {
422
+ fn(el)
423
+ } catch (error) {
424
+ console.error(error.message,error.stack)
425
+ }
426
+ }
427
+ if(!el._running){
428
+ Array.from(el.children).forEach(child=>{
429
+ render(child,el._context)
430
+ })
431
+ }
432
+ }
433
+
434
+ export const startApp=(html,context={})=>{
435
+ const app=new DOMParser()
436
+ const {document}=parseHTML()
437
+ const body= app.parseFromString(html,'text/html')
438
+ const div=body.firstElementChild
439
+ const element=document.createElement('div')
440
+ element.appendChild(div)
441
+ render(div,context);
442
+
443
+
444
+ return {
445
+ element:div,
446
+ toString:()=>element.innerHTML
447
+ }
448
+ }
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "pawa-ssr",
3
+ "version": "1.0.0",
4
+ "description": "pawajs ssr libary",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/Allisboy/pawajs-ssr.git"
12
+ },
13
+ "keywords": [
14
+ "ssr",
15
+ "html",
16
+ "js",
17
+ "pawajs"
18
+ ],
19
+ "author": "Allwell Oriso-owubo",
20
+ "license": "MIT",
21
+ "bugs": {
22
+ "url": "https://github.com/Allisboy/pawajs-ssr/issues"
23
+ },
24
+ "homepage": "https://github.com/Allisboy/pawajs-ssr#readme",
25
+ "dependencies": {
26
+ "linkedom": "^0.18.11",
27
+ "vm2": "^3.9.19"
28
+ }
29
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Represents a Pawa component instance.
3
+ * @class
4
+ */
5
+ class PawaComponent {
6
+ /**
7
+ * @param {Function} func - The component function that defines rendering logic.
8
+ */
9
+ constructor(func) {
10
+ /**
11
+ * Default props for the component (currently unused).
12
+ * @type {Object}
13
+ */
14
+ this.prop = {};
15
+ /**
16
+ * The component function.
17
+ * @type {Function}
18
+ */
19
+ this.component = func;
20
+ /**
21
+ * Tracks whether the component has been rendered.
22
+ * @type {boolean}
23
+ */
24
+ this._hasRun = false;
25
+ /**
26
+ * Data to inject into the rendering context.
27
+ * @type {Object}
28
+ */
29
+ this._insert = {};
30
+ /**
31
+ * Lifecycle hooks for mount, unmount, and effects.
32
+ * @type {{effect: Array, isMount: Array, isUnMount: Array}}
33
+ */
34
+ this._hook = {
35
+ effect: [],
36
+ isMount: [],
37
+ isUnMount: [],
38
+ };
39
+ /**
40
+ * Map for non-reactive internal state (currently unused).
41
+ * @type {Map}
42
+ */
43
+ this._innerState = new Map();
44
+ /**
45
+ * Map for reactive state created via $state.
46
+ * @type {Map}
47
+ */
48
+ this._stateMap = new Map();
49
+ /**
50
+ * Stores the previous context for scoping.
51
+ * @type {Object|null}
52
+ */
53
+ this._formerContext = null;
54
+ /**
55
+ * @type {boolean}
56
+ */
57
+ this._isAction=false
58
+ /**
59
+ * @type{object}
60
+ */
61
+ this._action={}
62
+ }
63
+ /**
64
+ * Returns the component function.
65
+ * @returns {Function}
66
+ */
67
+ getComponent() {
68
+ return this.component;
69
+ }
70
+ }
71
+ export default PawaComponent
package/pawaElement.js ADDED
@@ -0,0 +1,103 @@
1
+ import { HTMLElement, parseHTML } from "linkedom"
2
+ import { allServerAttr, components } from "./index.js"
3
+ import PawaComponent from "./pawaComponent.js"
4
+ import { evaluateExpr, splitAndAdd } from "./utils.js"
5
+
6
+ class PawaElement {
7
+ /**
8
+ *
9
+ * @param {HTMLElement} element
10
+ * @param {object} context
11
+ */
12
+ constructor(element,context) {
13
+ const {document}=parseHTML()
14
+ /**
15
+ * @type{PawaElement|HTMLElement}
16
+ */
17
+ this._el=element
18
+ this._slots=document.createDocumentFragment()
19
+ this._context=context
20
+ this._props={}
21
+ this._component=null
22
+ this._componentName=''
23
+ this._running=false
24
+ this._hasForOrIf=this.hasForOrIf
25
+ /**
26
+ * @typedef{object}
27
+ * @property{any}
28
+ * Object of Html Attributes for Rest Attributes
29
+ */
30
+ this._restProps={}
31
+ this._componentChildren=null
32
+ this.getComponent()
33
+ this.setProps()
34
+ }
35
+ /**
36
+ *
37
+ * @param {HTMLElement} el
38
+ * @param {object} context
39
+ * @returns {PawaElement}
40
+ */
41
+ static Element(el,context){
42
+ const pawa=new PawaElement(el,context)
43
+ Object.assign(el,pawa)
44
+ return el
45
+ }
46
+ hasForOrIf(){
47
+ if (this._el.getAttribute('server-if') || this._el.getAttribute('server-for') || this._el.getAttribute('server-else') || this._el.getAttribute('server-else-if')) {
48
+ return true
49
+ }else{
50
+ return false
51
+ }
52
+ }
53
+
54
+ getComponent(){
55
+ if (components.has(splitAndAdd(this._el.tagName.toUpperCase())) && this._el.getAttribute('client') === null) {
56
+ this._componentName=splitAndAdd(this._el.tagName.toUpperCase())
57
+ this._component=new PawaComponent(components.get(splitAndAdd(this._el.tagName.toUpperCase())))
58
+ Array.from(this._el.children).forEach(slot =>{
59
+
60
+ if (slot.tagName === 'TEMPLATE' && slot.getAttribute('prop')) {
61
+
62
+ this._slots.appendChild(slot)
63
+ }
64
+ })
65
+ this._componentChildren=this._el.innerHTML
66
+ }else{
67
+ if(this._el.getAttribute('client')){
68
+ this._el.removeAttribute('client')
69
+ }
70
+ }
71
+ }
72
+
73
+ //set Component props
74
+ setProps(){
75
+ if (this._componentName) {
76
+
77
+ this._el.attributes.forEach(attr=>{
78
+ if(!allServerAttr.includes(attr.name)){
79
+ if (attr.name.startsWith('-') || attr.name.startsWith('r-')) {
80
+ let name=''
81
+ if (attr.name.startsWith('r-')) {
82
+ name=attr.name.slice(2)
83
+ } else {
84
+ name=attr.name.slice(1)
85
+ }
86
+
87
+ this._restProps[name]={name:name,value:attr.value}
88
+
89
+ } else {
90
+ try {
91
+ const func = evaluateExpr(attr.value,this._context)
92
+ const name=attr.name
93
+ this._props[name]=func
94
+ } catch (error) {
95
+ console.log(error.message,error.stack)
96
+ }
97
+ }
98
+ }
99
+ })
100
+ }
101
+ }
102
+ }
103
+ export default PawaElement
package/power.js ADDED
@@ -0,0 +1,108 @@
1
+ import { render } from "./index.js";
2
+ import { convertToNumber,evaluateExpr } from "./utils.js";
3
+
4
+
5
+
6
+ export const If = (el, attr) => {
7
+ if (el._running) return;
8
+ el._running = true;
9
+
10
+ const nextSibling = el.nextElementSibling || null;
11
+ if (nextSibling && (nextSibling.getAttribute('server-else') !== null || nextSibling.getAttribute('server-else-if'))) {
12
+
13
+ nextSibling.setAttribute('data-if', attr.value);
14
+ }
15
+
16
+ const result = evaluateExpr(attr.value, el._context);
17
+ if (result) {
18
+ const newElement = el.cloneNode(true);
19
+ newElement.removeAttribute('server-if');
20
+ newElement.setAttribute('s-data-if', convertToNumber(attr.value));
21
+ el.replaceWith(newElement);
22
+ render(newElement, el._context);
23
+ } else {
24
+ el.remove();
25
+ }
26
+ };
27
+
28
+ export const Else = (el, attr) => {
29
+ if (el._running) return;
30
+ el._running = true;
31
+
32
+ const value = el.getAttribute('data-if') || '';
33
+ el.removeAttribute('data-if');
34
+ const result = evaluateExpr(value, el._context);
35
+ if (result) {
36
+ el.remove();
37
+ } else {
38
+ const newElement = el.cloneNode(true);
39
+ newElement.removeAttribute('server-else');
40
+ newElement.setAttribute('s-data-else',convertToNumber(value));
41
+ el.replaceWith(newElement);
42
+ render(newElement, el._context);
43
+ }
44
+ };
45
+
46
+ export const ElseIf = (el, attr) => {
47
+ if (el._running) return;
48
+ el._running = true;
49
+
50
+ const nextSibling = el.nextElementSibling || null;
51
+ const prevCondition = el.getAttribute('data-if') || '';
52
+ el.removeAttribute('data-if');
53
+
54
+ if (nextSibling && (nextSibling.getAttribute('server-else') !== null || nextSibling.getAttribute('server-else-if'))) {
55
+
56
+ nextSibling.setAttribute('data-if', attr.value);
57
+ }
58
+
59
+ const prevResult = evaluateExpr(prevCondition, el._context);
60
+ const currentResult = evaluateExpr(attr.value, el._context);
61
+
62
+ if (prevResult) {
63
+ el.remove();
64
+ } else if (currentResult) {
65
+ const newElement = el.cloneNode(true);
66
+ newElement.removeAttribute('server-else-if');
67
+ newElement.setAttribute('s-data-else-if', convertToNumber(attr.value));
68
+ el.replaceWith(newElement);
69
+ render(newElement, el._context);
70
+ } else {
71
+ el.remove();
72
+ }
73
+ };
74
+
75
+ export const For=(el,attr)=>{
76
+ if(el._running){
77
+ return
78
+ }
79
+ el._running=true
80
+ const value=attr.value
81
+ const split=value.split(' in ')
82
+ const arrayName=split[1]
83
+ const arrayItems=split[0].split(',')
84
+ const arrayItem=arrayItems[0]
85
+ const indexes=arrayItems[1]
86
+ const array=evaluateExpr(arrayName,el._context)
87
+ if(Array.isArray(array)){
88
+ array.forEach((item,index)=>{
89
+ const context=el._context
90
+ const itemContext = {
91
+ [arrayItem]: item,
92
+ [indexes]: index,
93
+ ...context
94
+ }
95
+ const newElement=el.cloneNode(true)
96
+ newElement.removeAttribute('server-for')
97
+ newElement.setAttribute('s-data-for',convertToNumber(attr.value))
98
+
99
+ el.parentElement.insertBefore(newElement,el)
100
+ render(newElement,itemContext)
101
+ if (newElement.getAttribute('server-key')) {
102
+ newElement.setAttribute('s-data-loop-key',convertToNumber(newElement.getAttribute('server-key')))
103
+ newElement.removeAttribute('server-key')
104
+ }
105
+ })
106
+ }
107
+ el.remove()
108
+ }
package/utils.js ADDED
@@ -0,0 +1,151 @@
1
+ import {VM} from 'vm2'
2
+ export const splitAndAdd=(string) => {
3
+ const strings=string.split('-')
4
+ let newString=''
5
+ strings.forEach(str=>{
6
+ newString+=str
7
+ })
8
+ return newString.toUpperCase()
9
+ }
10
+
11
+ export const matchRoute = (pattern, path) => {
12
+ // Remove trailing slashes for consistency
13
+ const cleanPattern = pattern.replace(/\/$/, '');
14
+ const cleanPath = path.replace(/\/$/, '');
15
+
16
+ const patternParts = cleanPattern.split('/');
17
+ const pathParts = cleanPath.split('/');
18
+
19
+ if (patternParts.length !== pathParts.length) {
20
+
21
+ return [false, {}];
22
+ }
23
+
24
+ const params = {};
25
+
26
+ const match = patternParts.every((part, index) => {
27
+ if (part.startsWith(':')) {
28
+ // This is a parameter
29
+ const paramName = part.slice(1);
30
+ params[paramName] = pathParts[index];
31
+ return true;
32
+ }
33
+ return part === pathParts[index];
34
+ });
35
+
36
+ return [match, params];
37
+ }
38
+ export const sanitizeTemplate = (temp) => {
39
+ if (typeof temp !== 'string') return '';
40
+ return temp.replace(/<script\b[^>]*>([\s\S]*?)<\/script>/gi, '');
41
+ };
42
+ /**
43
+ * Safely evaluates a JavaScript expression in a sandbox.
44
+ *
45
+ * @param {string} expr - The expression to evaluate.
46
+ * @param {object} context - The context to expose inside the sandbox.
47
+ * @returns {any} - The result of the evaluated expression or null on error.
48
+ */
49
+ export const evaluateExpr = (expr, context = {}) => {
50
+ try {
51
+ const vm = new VM({
52
+ timeout: 50,
53
+ sandbox: { ...context },
54
+ require:false
55
+ });
56
+
57
+ return vm.run(expr);
58
+ } catch (err) {
59
+ console.warn(`Evaluation failed for: ${expr}`, err.message);
60
+ return null;
61
+ }
62
+ };
63
+ export const propsValidator=(obj={},propsAttri,name)=>{
64
+ let newObj={}
65
+
66
+ const jsTypes=['Array','String','Number']
67
+ for (const[key,value] of Object.entries(obj)) {
68
+ const propsValue=propsAttri[key]
69
+ if(typeof value === 'object'){
70
+ if(propsAttri[key] || propsAttri[key] === 0){
71
+ const checker=ComponentProps(propsAttri[key],value?.err,name)
72
+ if (value.type) {
73
+ checker[value.type.name]()
74
+ }
75
+ }else{
76
+ if (value.strict) {
77
+ console.warn(`props ${key} at ${name} component props is needed`)
78
+ throw new Error(` ${key} props is needed`|| `${key} props undefined ${name}`);
79
+ }else{
80
+ if (value.default || value.default === 0) {
81
+ propsAttri[key]=value.default
82
+ }
83
+ }
84
+
85
+ }
86
+ }
87
+ }
88
+ return {...propsAttri}
89
+ }
90
+
91
+ export const convertToNumber=(str)=>{
92
+ let hash = 0;
93
+ for (let i = 0; i < str.length; i++) {
94
+ hash=(hash * 31 + str.charCodeAt(i)) | 0
95
+ }
96
+ return hash
97
+ }
98
+ export const ComponentProps=(some,message,name)=>{
99
+
100
+ return({
101
+ Array:()=>{
102
+
103
+ if (Array.isArray(some)) {
104
+ return true
105
+ }else{
106
+ throw new Error(message ?message + ' / Not type of an Array ': `${some} must be an array at ${name} component`);
107
+
108
+ }
109
+ },
110
+ String:()=>{
111
+ if (typeof some === 'string') {
112
+ return true
113
+ }else{
114
+ throw new Error(message? message + ' / Not type of a String' :`${some} must be a string at ${name} component`);
115
+
116
+ }
117
+ },
118
+ Number:()=>{
119
+ if (typeof some === 'number') {
120
+ return true
121
+ }else{
122
+ throw new Error(message? message+' / Not type of a Number ': `${some} must be a number at ${name} component`);
123
+
124
+ }
125
+ },
126
+ Object:()=>{
127
+ if (typeof some === 'object') {
128
+ return true
129
+ }else{
130
+ throw new Error(message? message+' / Not type of an Object ' :`${some} must be an object at ${name} component`);
131
+
132
+ }
133
+ },
134
+ Function:()=>{
135
+ if (typeof some === 'function') {
136
+ return true
137
+ }else{
138
+ throw new Error(message? message+' / Not type of a Function ': `${some} must be a function at ${name} component`);
139
+ }
140
+ },
141
+ Boolean:()=>{
142
+ if (typeof some === 'boolean') {
143
+ return true
144
+ }else{
145
+ throw new Error(message? message+' / Not type of a Boolean ' :`${some} must be a Boolean at ${name} component`);
146
+
147
+ }
148
+ },
149
+ })
150
+
151
+ }