pawajs-continue 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 +21 -0
- package/README.md +58 -0
- package/component.js +250 -0
- package/index.js +154 -0
- package/package.json +28 -0
- package/resume-directive.js +32 -0
- package/resumeFor.js +82 -0
- package/resumeIf.js +70 -0
- package/resumeKey.js +38 -0
- package/resumeSwitch.js +73 -0
- package/utils.js +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 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,58 @@
|
|
|
1
|
+
# pawajs-continue
|
|
2
|
+
|
|
3
|
+
The **Continuity Rendering Model (CRM)** for [Pawajs](https://github.com/Allisboy/pawajs).
|
|
4
|
+
|
|
5
|
+
`pawajs-continue` handles the client-side hydration (resumption) of applications server-rendered with `pawa-ssr`. It picks up where the server left off, attaching event listeners and reactivity to the existing DOM without expensive re-renders.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Resumability**: Hydrates components, state, and directives from server-rendered HTML.
|
|
10
|
+
- **Lightweight**: Only loads what is necessary to make the page interactive.
|
|
11
|
+
- **Seamless Integration**: Designed to work out-of-the-box with `pawajs` and `pawa-ssr`.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install pawajs-continue
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
In your client-side entry point (e.g., `main.js` or `app.js`), import `initiateResumer` and call it before starting the Pawajs app.
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import { isServer } from "pawajs/server";
|
|
25
|
+
import { RegisterComponent, pawaStartApp } from "pawajs";
|
|
26
|
+
import { initiateResumer } from "pawajs-continue";
|
|
27
|
+
import { App } from "./App.js";
|
|
28
|
+
|
|
29
|
+
// Register your root component and others
|
|
30
|
+
RegisterComponent(App);
|
|
31
|
+
|
|
32
|
+
if (!isServer()) {
|
|
33
|
+
const app = document.getElementById('app');
|
|
34
|
+
|
|
35
|
+
// 1. Initialize the continuity engine
|
|
36
|
+
initiateResumer();
|
|
37
|
+
|
|
38
|
+
// 2. Start the app (hydrates the existing DOM)
|
|
39
|
+
pawaStartApp(app);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## How it Works
|
|
44
|
+
|
|
45
|
+
1. **Server-Side**: `pawa-ssr` renders the HTML and embeds serialized data (props, state, context) into comments within the DOM.
|
|
46
|
+
2. **Client-Side**: `pawajs-continue` scans the DOM during the `pawaStartApp` process.
|
|
47
|
+
3. **Continuity**: Instead of creating new DOM elements, it continues by reading he markers or the serialized data, restores the state, and attaches reactive effects to the existing elements from the server rendering.
|
|
48
|
+
|
|
49
|
+
## API
|
|
50
|
+
|
|
51
|
+
### `initiateResumer()`
|
|
52
|
+
|
|
53
|
+
Initializes the hydration logic for attributes, text, loops (`for-each`), conditionals (`if`), and components. This must be called before `pawaStartApp` on the client.
|
|
54
|
+
|
|
55
|
+
## Related Packages
|
|
56
|
+
|
|
57
|
+
- **pawajs**: The core reactive framework.
|
|
58
|
+
- **pawa-ssr**: The server-side rendering engine.
|
package/component.js
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { components,keepContext,render,getCurrentContext } from "pawajs/index.js"
|
|
2
|
+
import { PawaElement, PawaComment } from "pawajs/pawaElement.js"
|
|
3
|
+
import PawaComponent from "pawajs/pawaComponent.js"
|
|
4
|
+
import { createEffect } from "pawajs/reactive.js"
|
|
5
|
+
import { setProps } from "./utils.js"
|
|
6
|
+
import {propsValidator} from 'pawajs/utils.js'
|
|
7
|
+
export const resume_component=(el,attr,setStateContext,mapsPlugin,formerStateContext,
|
|
8
|
+
pawaContext,stateWatch,{comment,endComment,children,name,id,serialized})=>{
|
|
9
|
+
el.removeAttribute(attr.name)
|
|
10
|
+
el._running=true
|
|
11
|
+
let appContext={
|
|
12
|
+
_transportContext:{},
|
|
13
|
+
_formerContext:formerStateContext,
|
|
14
|
+
_reactiveProps:{},
|
|
15
|
+
_template:'',
|
|
16
|
+
_transportContext:{},
|
|
17
|
+
_elementContext:{},
|
|
18
|
+
_hasRun:false,
|
|
19
|
+
_static:[],
|
|
20
|
+
_serializedData:{}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @param {HTMLElement} node
|
|
24
|
+
*/
|
|
25
|
+
try{
|
|
26
|
+
const oldState=getCurrentContext()
|
|
27
|
+
|
|
28
|
+
PawaComment.Element(comment)
|
|
29
|
+
const compo=components.get(name)
|
|
30
|
+
const binary = atob(serialized.replace(/-/g, '+'));
|
|
31
|
+
const bytes = Uint8Array.from(binary, c => c.charCodeAt(0));
|
|
32
|
+
const json = new TextDecoder('utf-8').decode(bytes);
|
|
33
|
+
|
|
34
|
+
const props = JSON.parse(json);
|
|
35
|
+
const prop={
|
|
36
|
+
children:props.children
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* @type {PawaElement | HTMLElement}
|
|
40
|
+
*/
|
|
41
|
+
const element=document.createElement(name)
|
|
42
|
+
PawaElement.Element(element,el._context)
|
|
43
|
+
element._underControl=comment
|
|
44
|
+
comment._componentElement=element
|
|
45
|
+
comment._controlComponent=true
|
|
46
|
+
comment._endComment=endComment
|
|
47
|
+
if(props?.data){
|
|
48
|
+
Object.assign(element._context,props.data)
|
|
49
|
+
}
|
|
50
|
+
comment.data=`<${name}>`
|
|
51
|
+
endComment.data=`</${name}>`
|
|
52
|
+
if(!compo){
|
|
53
|
+
|
|
54
|
+
const fakeComponent=new PawaComponent(()=>null)
|
|
55
|
+
const stateContexts=setStateContext(fakeComponent)
|
|
56
|
+
stateContexts._resume=true
|
|
57
|
+
stateContexts._prop={children:'',...props._props}
|
|
58
|
+
stateContexts._static=[...stateContexts._static,...props.context]
|
|
59
|
+
if(props?.data){
|
|
60
|
+
Object.assign(element._context,props.data)
|
|
61
|
+
}
|
|
62
|
+
stateContexts._elementContext={...element._context}
|
|
63
|
+
const number={notRender:null,index:null}
|
|
64
|
+
children.forEach((value, index) => {
|
|
65
|
+
number.index=index
|
|
66
|
+
if(number.notRender && index <= number.notRender) return
|
|
67
|
+
render(value,element._context,number,attr.name)
|
|
68
|
+
})
|
|
69
|
+
stateContexts._hasRun=true
|
|
70
|
+
keepContext(stateContexts._formerContext)
|
|
71
|
+
return
|
|
72
|
+
}
|
|
73
|
+
// props setter
|
|
74
|
+
let isStatic=false
|
|
75
|
+
for (const [key,value] of Object.entries(props.props)) {
|
|
76
|
+
const attr={
|
|
77
|
+
name:key,
|
|
78
|
+
value:value
|
|
79
|
+
}
|
|
80
|
+
if(!isStatic){
|
|
81
|
+
const resStatic=setProps(el._context,attr,prop,element)
|
|
82
|
+
isStatic=resStatic.static
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if(isStatic){
|
|
86
|
+
const fakeComponent=new PawaComponent(()=>null)
|
|
87
|
+
const stateContexts=setStateContext(fakeComponent)
|
|
88
|
+
stateContexts._resume=true
|
|
89
|
+
if(props?.data){
|
|
90
|
+
Object.assign(element._context,props.data)
|
|
91
|
+
}
|
|
92
|
+
stateContexts._prop={children:'',...props._props}
|
|
93
|
+
stateContexts._elementContext={...element._context}
|
|
94
|
+
stateContexts._static=[...stateContexts._static,...props.context]
|
|
95
|
+
const number={notRender:null,index:null}
|
|
96
|
+
|
|
97
|
+
children.forEach((value, index) => {
|
|
98
|
+
number.index=index
|
|
99
|
+
if(number.notRender && index <= number.notRender) return
|
|
100
|
+
render(value,element._context,number)
|
|
101
|
+
})
|
|
102
|
+
stateContexts._hasRun=true
|
|
103
|
+
keepContext(stateContexts._formerContext)
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
for (const [key,value] of Object.entries(props.slots)) {
|
|
107
|
+
prop[key]=()=>value
|
|
108
|
+
}
|
|
109
|
+
el._isKill=true
|
|
110
|
+
el._kill=()=>{
|
|
111
|
+
pawaWayRemover(comment,endComment)
|
|
112
|
+
comment.remove(),endComment.remove();
|
|
113
|
+
}
|
|
114
|
+
element._props=prop
|
|
115
|
+
let isIndex=0
|
|
116
|
+
//props setter
|
|
117
|
+
const validprops=element._component.validPropRule
|
|
118
|
+
let done=true
|
|
119
|
+
if(validprops && Object.entries(validprops).length > 0){
|
|
120
|
+
done= propsValidator(validprops,{...element._props},element._componentName,element._template,element)
|
|
121
|
+
}
|
|
122
|
+
element._componentTerminate=() => {
|
|
123
|
+
comment._terminateByComponent(endComment)
|
|
124
|
+
}
|
|
125
|
+
const apps={
|
|
126
|
+
children:prop.children,
|
|
127
|
+
...element._props
|
|
128
|
+
}
|
|
129
|
+
const component =element._component
|
|
130
|
+
const stateContexts=setStateContext(component)
|
|
131
|
+
stateContexts._resume=true
|
|
132
|
+
stateContexts._prop={children:props.children,...element._props}
|
|
133
|
+
stateContexts._elementContext={...element._context}
|
|
134
|
+
stateContexts._name=element._componentName
|
|
135
|
+
stateContexts._template=element._template
|
|
136
|
+
stateContexts._recallEffect=()=>{
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
stateContexts._serializedData=props.data
|
|
140
|
+
let isAwait=false
|
|
141
|
+
if(done){
|
|
142
|
+
const compoCall=component.component(apps)
|
|
143
|
+
isAwait=compoCall instanceof Promise
|
|
144
|
+
if( isAwait){
|
|
145
|
+
const storeContext=stateContexts
|
|
146
|
+
compoCall.then((res)=>{
|
|
147
|
+
if (storeContext._hasRun) {
|
|
148
|
+
storeContext._hasRun = false
|
|
149
|
+
}
|
|
150
|
+
keepContext(storeContext)
|
|
151
|
+
if (storeContext?._insert) {
|
|
152
|
+
Object.assign(element._context,storeContext._insert)
|
|
153
|
+
}
|
|
154
|
+
childInsert()
|
|
155
|
+
lifeCircle()
|
|
156
|
+
storeContext._hasRun=true
|
|
157
|
+
})
|
|
158
|
+
}else{
|
|
159
|
+
Object.assign(element._context,stateContexts._insert)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (component?._insert) {
|
|
163
|
+
|
|
164
|
+
Object.assign(element._context,component._insert)
|
|
165
|
+
// console.log(el,el._context)
|
|
166
|
+
}
|
|
167
|
+
const childInsert=()=>{
|
|
168
|
+
element._component?._hook?.beforeMount?.forEach((bfm) => {
|
|
169
|
+
const result= bfm(comment)
|
|
170
|
+
if (typeof result === 'function') {
|
|
171
|
+
element._unMountFunctions.push(result)
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
element._component?._hook?.isMount.forEach((hook) => {
|
|
176
|
+
element._MountFunctions.push(hook)
|
|
177
|
+
})
|
|
178
|
+
element._component?._hook?.isUnMount.forEach((hook) => {
|
|
179
|
+
element._unMountFunctions.push(hook)
|
|
180
|
+
})
|
|
181
|
+
const number={notRender:null,index:null}
|
|
182
|
+
children.forEach((value, index) => {
|
|
183
|
+
isIndex++
|
|
184
|
+
if(value.hasAttribute(attr.name)) value.removeAttribute(attr.name);
|
|
185
|
+
number.index=index
|
|
186
|
+
if(number.notRender && index <= number.notRender){
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
render(value,element._context,number,attr.name)
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
if(!isAwait){
|
|
193
|
+
childInsert()
|
|
194
|
+
}
|
|
195
|
+
const lifeCircle=()=>{
|
|
196
|
+
Promise.resolve().then(()=>{
|
|
197
|
+
element._component?._hook?.effect.forEach((hook) => {
|
|
198
|
+
if(hook?.done) return
|
|
199
|
+
hook.done=true
|
|
200
|
+
const result=stateWatch(hook.effect,hook.deps)
|
|
201
|
+
if (typeof result === 'function') {
|
|
202
|
+
element._unMountFunctions.push(result)
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
if (element._component?._hook?.reactiveEffect) {
|
|
207
|
+
element._component?._hook?.reactiveEffect.forEach((hook) => {
|
|
208
|
+
if(hook?.done) return
|
|
209
|
+
hook.done=true
|
|
210
|
+
const effect=hook.effect(comment)
|
|
211
|
+
if (hook.deps?.component) {
|
|
212
|
+
createEffect(() => {
|
|
213
|
+
return effect()
|
|
214
|
+
},element)
|
|
215
|
+
} else {
|
|
216
|
+
createEffect(() => {
|
|
217
|
+
return effect()
|
|
218
|
+
},hook.deps.value)
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
}
|
|
222
|
+
element._MountFunctions.forEach((func) => {
|
|
223
|
+
func.done=true
|
|
224
|
+
const result=func(comment)
|
|
225
|
+
if (typeof result === 'function') {
|
|
226
|
+
element._unMountFunctions.push(result)
|
|
227
|
+
}
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
if(!isAwait){
|
|
233
|
+
lifeCircle()
|
|
234
|
+
}
|
|
235
|
+
stateContexts._hasRun=true
|
|
236
|
+
|
|
237
|
+
keepContext(stateContexts._formerContext)
|
|
238
|
+
if (stateContexts._transportContext) {
|
|
239
|
+
let contextId = stateContexts._transportContext
|
|
240
|
+
delete pawaContext[contextId]
|
|
241
|
+
}
|
|
242
|
+
__pawaDev.totalComponent++
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
}catch(error){
|
|
246
|
+
console.log(error.message,error.stack)
|
|
247
|
+
console.error(error.message,error.stack)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import {setResumer} from 'pawajs/resumer.js'
|
|
2
|
+
import {resume_state} from './resume-directive.js'
|
|
3
|
+
import {resume_if} from './resumeIf.js'
|
|
4
|
+
import {resume_for} from './resumeFor.js'
|
|
5
|
+
import {resume_key} from './resumeKey.js'
|
|
6
|
+
import {resume_component} from './component.js'
|
|
7
|
+
import {createEffect} from 'pawajs/reactive.js'
|
|
8
|
+
import {checkKeywordsExistence,setPawaDevError} from 'pawajs/utils.js'
|
|
9
|
+
import { resume_switch } from './resumeSwitch.js'
|
|
10
|
+
export const resume_text=(el,attr,isName)=>{
|
|
11
|
+
if (el._running) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
el.removeAttribute('c-t')
|
|
15
|
+
el._checkStatic()
|
|
16
|
+
let textNodes
|
|
17
|
+
el.childNodes.forEach((value, index) => {
|
|
18
|
+
if(value.nodeType === 8 && value.data.startsWith('textEvaluator-')){
|
|
19
|
+
textNodes=value.data.slice(14)
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
const evaluate = () => {
|
|
23
|
+
try {
|
|
24
|
+
// Always use original content from map for evaluation
|
|
25
|
+
let value = textNodes
|
|
26
|
+
const regex = /@{([^}]*)}/g;
|
|
27
|
+
const keys = Object.keys(el._context);
|
|
28
|
+
const resolvePath = (path, obj) => {
|
|
29
|
+
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
30
|
+
};
|
|
31
|
+
const values = keys.map((key) => resolvePath(key, el._context));
|
|
32
|
+
if(!value)return
|
|
33
|
+
value = value.replace(regex, (match, expression) => {
|
|
34
|
+
if (checkKeywordsExistence(el._staticContext,expression)) {
|
|
35
|
+
return ''
|
|
36
|
+
}else{
|
|
37
|
+
|
|
38
|
+
el._textContent[expression]=value
|
|
39
|
+
const func = new Function(...keys, `return ${expression}`);
|
|
40
|
+
return String(func(...values));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
if (el.tagName === 'TEXTAREA') {
|
|
44
|
+
el.value=value
|
|
45
|
+
}else{
|
|
46
|
+
el.textContent=value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
} catch (error) {
|
|
51
|
+
// console.warn(`error at ${el} textcontent`)
|
|
52
|
+
setPawaDevError({
|
|
53
|
+
message:`error at TextContent ${error.message}`,
|
|
54
|
+
error:error,
|
|
55
|
+
template:el._template
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
createEffect(() => {
|
|
61
|
+
evaluate();
|
|
62
|
+
},el);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const resume_attribute=(el,attr,isName)=>{
|
|
66
|
+
if(el._running) return
|
|
67
|
+
// Store original attribute value
|
|
68
|
+
|
|
69
|
+
if (el._componentName) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
el.removeAttribute(attr.name)
|
|
73
|
+
const attrName=attr.name.slice(5)
|
|
74
|
+
const attrValue=attr.value
|
|
75
|
+
el._preRenderAvoid.push(attrName)
|
|
76
|
+
// A set of attributes that are treated as booleans and are best controlled via properties.
|
|
77
|
+
const booleanAttributes = new Set(['checked', 'selected', 'disabled', 'readonly', 'required', 'multiple']);
|
|
78
|
+
el._mainAttribute[attrName]=attr.value
|
|
79
|
+
el._checkStatic()
|
|
80
|
+
el.removeAttribute(attr.name)
|
|
81
|
+
const evaluate = () => {
|
|
82
|
+
|
|
83
|
+
try{
|
|
84
|
+
// Always use original value from map for evaluation
|
|
85
|
+
let value = attrValue;
|
|
86
|
+
let isBoolean
|
|
87
|
+
const regex = /@{([^}]*)}/g;
|
|
88
|
+
const keys = Object.keys(el._context);
|
|
89
|
+
const resolvePath = (path, obj) => {
|
|
90
|
+
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
91
|
+
};
|
|
92
|
+
const values = keys.map((key) => resolvePath(key, el._context));
|
|
93
|
+
|
|
94
|
+
value = value.replace(regex, (match, expression) => {
|
|
95
|
+
if(checkKeywordsExistence(el._staticContext,expression)){
|
|
96
|
+
return ''
|
|
97
|
+
}else{
|
|
98
|
+
const func = new Function(...keys, `return ${expression}`);
|
|
99
|
+
const result = func(...values);
|
|
100
|
+
isBoolean = result; // Assuming one expression for boolean attributes.
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (booleanAttributes.has(attrName)) {
|
|
106
|
+
// If there was no expression, the presence of the attribute means true, unless its value is 'false'.
|
|
107
|
+
const boolValue = regex.test(attrValue) ? !!isBoolean : attrValue.toLowerCase() !== 'false';
|
|
108
|
+
regex.lastIndex = 0; // Reset regex state after .test()
|
|
109
|
+
|
|
110
|
+
// Map attribute name to property name where they differ (e.g., 'readonly' -> 'readOnly')
|
|
111
|
+
const propName = attrName === 'readonly' ? 'readOnly' : attrName;
|
|
112
|
+
|
|
113
|
+
if (propName in el) {
|
|
114
|
+
el[propName] = boolValue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Also update the attribute for consistency and for CSS selectors.
|
|
118
|
+
if (boolValue) {
|
|
119
|
+
el.setAttribute(attrName, '');
|
|
120
|
+
} else {
|
|
121
|
+
el.removeAttribute(attrName);
|
|
122
|
+
}
|
|
123
|
+
} else if (attrName === 'value' && 'value' in el) {
|
|
124
|
+
el.value = value;
|
|
125
|
+
} else {
|
|
126
|
+
el.setAttribute(attrName, value);
|
|
127
|
+
}
|
|
128
|
+
}catch(error){
|
|
129
|
+
console.warn(`failed at attribute ${attrName}`,el)
|
|
130
|
+
setPawaDevError({
|
|
131
|
+
message:`error at attribute ${error.message}`,
|
|
132
|
+
error:error,
|
|
133
|
+
template:el._template
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
createEffect(()=>{
|
|
139
|
+
evaluate()
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const initiateResumer=()=>{
|
|
144
|
+
setResumer({
|
|
145
|
+
resume_attribute,
|
|
146
|
+
resume_text,
|
|
147
|
+
resume_state,
|
|
148
|
+
resume_if,
|
|
149
|
+
resume_for,
|
|
150
|
+
resume_switch,
|
|
151
|
+
resume_component,
|
|
152
|
+
resume_key
|
|
153
|
+
})
|
|
154
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pawajs-continue",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pawajs continuity initializer library for ssr",
|
|
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-continue.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"pawajs",
|
|
15
|
+
"pawa-ssr",
|
|
16
|
+
"continuity",
|
|
17
|
+
"ssr"
|
|
18
|
+
],
|
|
19
|
+
"author": "Allwell oriso-owubo owupele",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/Allisboy/pawajs-continue/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/Allisboy/pawajs-continue#readme",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"pawajs": "^1.3.8"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {$state} from 'pawajs/index.js'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const resume_state=(el,attr,isResume)=>{
|
|
5
|
+
if(el._running)return
|
|
6
|
+
const name=attr.name.split('-')[2]
|
|
7
|
+
el.removeAttribute(attr.name)
|
|
8
|
+
try{
|
|
9
|
+
const keys = Object.keys(el._context);
|
|
10
|
+
const resolvePath = (path, obj) => {
|
|
11
|
+
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
12
|
+
};
|
|
13
|
+
const values = keys.map((key) => resolvePath(key, el._context));
|
|
14
|
+
const val = new Function(...keys, `
|
|
15
|
+
try{
|
|
16
|
+
return ${attr.value}
|
|
17
|
+
}catch(error){
|
|
18
|
+
console.log(error.message,error.stack)
|
|
19
|
+
}
|
|
20
|
+
`)(...values)
|
|
21
|
+
el._context[name] = null
|
|
22
|
+
el._context[name] = $state(val)
|
|
23
|
+
|
|
24
|
+
el.removeAttribute(attr.name)
|
|
25
|
+
} catch (error) {
|
|
26
|
+
setPawaDevError({
|
|
27
|
+
message: `Error from State directive ${error.message}`,
|
|
28
|
+
error: error,
|
|
29
|
+
template: el._template
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
}
|
package/resumeFor.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createEffect } from 'pawajs/reactive.js';
|
|
2
|
+
import { PawaComment, PawaElement } from 'pawajs/pawaElement.js';
|
|
3
|
+
import { processNode, pawaWayRemover, safeEval, getEvalValues, setPawaDevError, getComment,getEndComment, checkKeywordsExistence } from 'pawajs/utils.js';
|
|
4
|
+
import { merger_for } from 'pawajs/merger/for.js';
|
|
5
|
+
/**
|
|
6
|
+
* @param {HTMLElement | PawaElement} el
|
|
7
|
+
* @param {{value:string,name:string}} attr,
|
|
8
|
+
* @param {{...any}} stateContext
|
|
9
|
+
* @param {{comment:Comment,endComment:Comment,id:string,children:[],dataComment:Comment}} arg
|
|
10
|
+
*/
|
|
11
|
+
export const resume_for=(el,attr,stateContext,{comment,endComment,id,children,dataElement})=>{
|
|
12
|
+
const element=dataElement.content.firstElementChild
|
|
13
|
+
if(checkKeywordsExistence(el._staticContext,element.getAttribute('for-each'))) return
|
|
14
|
+
PawaElement.Element(element,el._context)
|
|
15
|
+
element._out = true
|
|
16
|
+
const parent = endComment.parentElement
|
|
17
|
+
element._deCompositionElement = true
|
|
18
|
+
element._isKill = true
|
|
19
|
+
element._kill = () => {
|
|
20
|
+
pawaWayRemover(comment, endComment)
|
|
21
|
+
comment.remove(), endComment.remove();
|
|
22
|
+
}
|
|
23
|
+
const keyOrders=new Map()
|
|
24
|
+
PawaComment.Element(comment)
|
|
25
|
+
comment._setCoveringElement(element)
|
|
26
|
+
element._underControl = comment
|
|
27
|
+
const context = element._context
|
|
28
|
+
const insertIndex = new Map()
|
|
29
|
+
const elementArray = new Set()
|
|
30
|
+
let index=0
|
|
31
|
+
let isCurrentComment
|
|
32
|
+
PawaElement.Element(element,el._context)
|
|
33
|
+
const value=element.getAttribute('for-each')
|
|
34
|
+
const exp = new WeakMap()
|
|
35
|
+
const primitive = { key: value }
|
|
36
|
+
let firstEnter = true
|
|
37
|
+
const split = value.split(' in ')
|
|
38
|
+
const arrayName = split[1]
|
|
39
|
+
const arrayItems = split[0].split(',')
|
|
40
|
+
const arrayItem = arrayItems[0]
|
|
41
|
+
const indexes = arrayItems[1]
|
|
42
|
+
element._underControl = comment
|
|
43
|
+
el._deCompositionElement = true
|
|
44
|
+
element._isKill = true
|
|
45
|
+
element._kill = () => {
|
|
46
|
+
pawaWayRemover(comment, endComment)
|
|
47
|
+
comment.remove(), endComment.remove();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const getKeyComment=(comments)=>{
|
|
51
|
+
if(comments === endComment) return
|
|
52
|
+
const current=comments?.data?.split('@-$@-$@') || []
|
|
53
|
+
if(current[1] === id && current[0] === 'forKey'){
|
|
54
|
+
comments.data=`for-key ${current[2]}`
|
|
55
|
+
PawaComment.Element(comments)
|
|
56
|
+
comments._index = index
|
|
57
|
+
keyOrders.set(index,{comment:comments})
|
|
58
|
+
comments._setKey(current[2])
|
|
59
|
+
isCurrentComment=comments
|
|
60
|
+
elementArray.add(comments)
|
|
61
|
+
insertIndex.set(index, current[2])
|
|
62
|
+
index++
|
|
63
|
+
getKeyComment(comments.nextSibling)
|
|
64
|
+
}else if(current[0] === 'endForKey' && current[1] === id && isCurrentComment){
|
|
65
|
+
isCurrentComment._endComment = comments
|
|
66
|
+
comments.data=`end -for-key ${current[2]}`
|
|
67
|
+
getKeyComment(comments.nextSibling)
|
|
68
|
+
}else{
|
|
69
|
+
getKeyComment(comments.nextSibling)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const unique=id
|
|
74
|
+
let evalFunc
|
|
75
|
+
getKeyComment(comment)
|
|
76
|
+
const att={name:'for-each',value:element.getAttribute('for-each')}
|
|
77
|
+
const evaluate=merger_for(element,stateContext,att,arrayName,arrayItem,indexes,true,
|
|
78
|
+
{comment,endComment,unique,elementArray,insertIndex,keyOrders})
|
|
79
|
+
createEffect(()=>{
|
|
80
|
+
evaluate()
|
|
81
|
+
})
|
|
82
|
+
}
|
package/resumeIf.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createEffect } from 'pawajs/reactive.js';
|
|
2
|
+
import { PawaComment, PawaElement } from 'pawajs/pawaElement.js';
|
|
3
|
+
import { pawaWayRemover, safeEval, getEvalValues, setPawaDevError } from 'pawajs/utils.js';
|
|
4
|
+
import { merger_if} from 'pawajs/merger/if.js'
|
|
5
|
+
export const resume_if=(el,attr,stateContext,{comment,endComment,id,children,number})=>{
|
|
6
|
+
const dataElement=document.querySelector(`[p\\:store-if="${id}"]`);
|
|
7
|
+
dataElement.remove()
|
|
8
|
+
const element=dataElement.content.firstElementChild
|
|
9
|
+
// console.log(id,element,el._attributes)
|
|
10
|
+
const chained=[{
|
|
11
|
+
exp:element.getAttribute('if'),
|
|
12
|
+
condition:'if',
|
|
13
|
+
element:element
|
|
14
|
+
}]
|
|
15
|
+
const nextSiblings=element.nextElementSibling || null
|
|
16
|
+
const chainMap=new Map()
|
|
17
|
+
chainMap.set(element.getAttribute('if'),{condition:'if',element:element})
|
|
18
|
+
const getChained=(nextSibling)=>{
|
|
19
|
+
if (nextSibling !== null) {
|
|
20
|
+
if (nextSibling && nextSibling.getAttribute('else') === '' || nextSibling.getAttribute('else-if')) {
|
|
21
|
+
// console.log(true,'it has',nextSibling.getAttribute('else'))
|
|
22
|
+
if (nextSibling.getAttribute('else-if')) {
|
|
23
|
+
chained.push({
|
|
24
|
+
exp:nextSibling.getAttribute('else-if'),
|
|
25
|
+
condition:'else-if',
|
|
26
|
+
element:nextSibling
|
|
27
|
+
})
|
|
28
|
+
chainMap.set(nextSibling.getAttribute('else-if'),{condition:'else-if',element:nextSibling})
|
|
29
|
+
getChained(nextSibling.nextElementSibling)
|
|
30
|
+
nextSibling.remove()
|
|
31
|
+
}else if (nextSibling.getAttribute('else') === '') {
|
|
32
|
+
chained.push({
|
|
33
|
+
exp:'false',
|
|
34
|
+
condition:'else',
|
|
35
|
+
element:nextSibling
|
|
36
|
+
})
|
|
37
|
+
chainMap.set('else',{condition:'else',element:nextSibling})
|
|
38
|
+
nextSibling.remove()
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
getChained(nextSiblings)
|
|
44
|
+
|
|
45
|
+
PawaElement.Element(element,el._context)
|
|
46
|
+
element._out = true
|
|
47
|
+
const parent = endComment.parentElement
|
|
48
|
+
element._deCompositionElement = true
|
|
49
|
+
element._isKill = true
|
|
50
|
+
element._kill = () => {
|
|
51
|
+
pawaWayRemover(comment, endComment)
|
|
52
|
+
comment.remove(), endComment.remove();
|
|
53
|
+
}
|
|
54
|
+
PawaComment.Element(comment)
|
|
55
|
+
comment._setCoveringElement(element)
|
|
56
|
+
element._underControl = comment
|
|
57
|
+
const context = element._context
|
|
58
|
+
comment._controlComponent = true
|
|
59
|
+
el.removeAttribute(attr.name)
|
|
60
|
+
/**
|
|
61
|
+
* endComment,comment,func(a function to set func),firstEnter,getFirst,stateContext
|
|
62
|
+
*/
|
|
63
|
+
const evaluate=merger_if(element,attr,stateContext,true,
|
|
64
|
+
{comment,endComment,children,chained,chainMap}
|
|
65
|
+
)
|
|
66
|
+
createEffect(()=>{
|
|
67
|
+
evaluate()
|
|
68
|
+
},element)
|
|
69
|
+
|
|
70
|
+
}
|
package/resumeKey.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createEffect } from 'pawajs/reactive.js';
|
|
2
|
+
import { PawaComment, PawaElement } from 'pawajs/pawaElement.js';
|
|
3
|
+
import { pawaWayRemover, safeEval, getEvalValues, setPawaDevError } from 'pawajs/utils.js';
|
|
4
|
+
import { merger_key } from 'pawajs/merger/key.js';
|
|
5
|
+
export const resume_key=(el,attr,stateContext,{comment,endComment,id,children})=>{
|
|
6
|
+
let dataElement
|
|
7
|
+
dataElement=document.querySelector(`[p\\:store-key="${id}"]`);
|
|
8
|
+
const element=dataElement.content.firstElementChild
|
|
9
|
+
dataElement.remove()
|
|
10
|
+
PawaElement.Element(element,el._context)
|
|
11
|
+
element._out = true
|
|
12
|
+
const parent = endComment.parentElement
|
|
13
|
+
element._deCompositionElement = true
|
|
14
|
+
element._isKill = true
|
|
15
|
+
element._kill = () => {
|
|
16
|
+
pawaWayRemover(comment, endComment)
|
|
17
|
+
comment.remove(), endComment.remove();
|
|
18
|
+
}
|
|
19
|
+
PawaComment.Element(comment)
|
|
20
|
+
comment._setCoveringElement(element)
|
|
21
|
+
element._underControl = comment
|
|
22
|
+
const context = element._context
|
|
23
|
+
comment._controlComponent = true
|
|
24
|
+
el.removeAttribute(attr.name)
|
|
25
|
+
const att={name:'key',value:element.getAttribute('key')}
|
|
26
|
+
let key
|
|
27
|
+
try {
|
|
28
|
+
key=new Function(`return ${attr.value}`)()
|
|
29
|
+
} catch (error) {
|
|
30
|
+
key=''
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const evaluate=merger_key(element,att,stateContext,true,
|
|
34
|
+
{comment,endComment,children,old:key})
|
|
35
|
+
createEffect(()=>{
|
|
36
|
+
evaluate()
|
|
37
|
+
},element)
|
|
38
|
+
}
|
package/resumeSwitch.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createEffect } from 'pawajs/reactive.js';
|
|
2
|
+
import { PawaComment, PawaElement } from 'pawajs/pawaElement.js';
|
|
3
|
+
import { pawaWayRemover, safeEval, getEvalValues, setPawaDevError } from 'pawajs/utils.js';
|
|
4
|
+
import { merger_switch } from 'pawajs/merger/switch.js';
|
|
5
|
+
export const resume_switch=(el,attr,stateContext,{comment,endComment,id,children})=>{
|
|
6
|
+
let dataElement
|
|
7
|
+
dataElement=document.querySelector(`[p\\:store-switch="${id}"]`);
|
|
8
|
+
const element=dataElement.content.firstElementChild
|
|
9
|
+
dataElement.remove()
|
|
10
|
+
const att={name:'switch',value:element.getAttribute('switch')}
|
|
11
|
+
element.removeAttribute('switch')
|
|
12
|
+
const chained=[{
|
|
13
|
+
exp:element.getAttribute('case'),
|
|
14
|
+
condition:'case',
|
|
15
|
+
element:element
|
|
16
|
+
}]
|
|
17
|
+
const nextSiblings=element.nextElementSibling || null
|
|
18
|
+
const chainMap=new Map()
|
|
19
|
+
chainMap.set(element.getAttribute('case'),{condition:'case',element:element})
|
|
20
|
+
const getChained=(nextSibling)=>{
|
|
21
|
+
if (nextSibling !== null) {
|
|
22
|
+
if (nextSibling && nextSibling.getAttribute('case') || nextSibling.getAttribute('default') === '') {
|
|
23
|
+
// console.log(true,'it has',nextSibling.getAttribute('else'))
|
|
24
|
+
if (nextSibling.getAttribute('case')) {
|
|
25
|
+
chained.push({
|
|
26
|
+
exp:nextSibling.getAttribute('case'),
|
|
27
|
+
condition:'case',
|
|
28
|
+
element:nextSibling
|
|
29
|
+
})
|
|
30
|
+
chainMap.set(nextSibling.getAttribute('case'),{condition:'case',element:nextSibling})
|
|
31
|
+
getChained(nextSibling.nextElementSibling)
|
|
32
|
+
nextSibling.remove()
|
|
33
|
+
}else if (nextSibling.getAttribute('default') === '') {
|
|
34
|
+
chained.push({
|
|
35
|
+
exp:'false',
|
|
36
|
+
condition:'default',
|
|
37
|
+
element:nextSibling
|
|
38
|
+
})
|
|
39
|
+
chainMap.set('default',{condition:'default',element:nextSibling})
|
|
40
|
+
nextSibling.remove()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
getChained(nextSiblings)
|
|
46
|
+
|
|
47
|
+
PawaElement.Element(element,el._context)
|
|
48
|
+
element._out = true
|
|
49
|
+
const parent = endComment.parentElement
|
|
50
|
+
element._deCompositionElement = true
|
|
51
|
+
element._isKill = true
|
|
52
|
+
element._kill = () => {
|
|
53
|
+
pawaWayRemover(comment, endComment)
|
|
54
|
+
comment.remove(), endComment.remove();
|
|
55
|
+
}
|
|
56
|
+
PawaComment.Element(comment)
|
|
57
|
+
comment._setCoveringElement(element)
|
|
58
|
+
element._underControl = comment
|
|
59
|
+
const context = element._context
|
|
60
|
+
comment._controlComponent = true
|
|
61
|
+
el.removeAttribute(attr.name)
|
|
62
|
+
/**
|
|
63
|
+
* endComment,comment,func(a function to set func),firstEnter,getFirst,stateContext
|
|
64
|
+
*/
|
|
65
|
+
const evaluate=merger_switch(element,att,stateContext,true,
|
|
66
|
+
{comment,endComment,children,chained,chainMap,caseValue:attr}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
createEffect(()=>{
|
|
70
|
+
evaluate()
|
|
71
|
+
},element)
|
|
72
|
+
|
|
73
|
+
}
|
package/utils.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {setPawaDevError,replaceTemplateOperators,checkKeywordsExistence} from 'pawajs/utils.js'
|
|
2
|
+
export const setProps=(context,attr,props,el)=>{
|
|
3
|
+
if(checkKeywordsExistence(el._staticContext,attr.value)){
|
|
4
|
+
return {
|
|
5
|
+
static:true
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const keys = Object.keys(context);
|
|
10
|
+
const resolvePath = (path, obj) => {
|
|
11
|
+
return path.split('.').reduce((acc, key) => acc?.[key], obj);
|
|
12
|
+
};
|
|
13
|
+
const values = keys.map((key) => resolvePath(key, context));
|
|
14
|
+
if(attr.value === '') attr.value=true;
|
|
15
|
+
const value=new Function(...keys,`
|
|
16
|
+
return ()=>{
|
|
17
|
+
try{
|
|
18
|
+
const prop= ${replaceTemplateOperators(attr.value)};
|
|
19
|
+
if(prop === '')return prop
|
|
20
|
+
return prop
|
|
21
|
+
}catch(error){
|
|
22
|
+
console.error(error.message,error.stack)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`)(...values)
|
|
26
|
+
props[attr.name]=value
|
|
27
|
+
return {
|
|
28
|
+
static:false
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
setPawaDevError({
|
|
32
|
+
message:`error from ${el._componentName} prop :${attr.name} ${error.message}`,
|
|
33
|
+
error:error,
|
|
34
|
+
template:this._template
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|