pawa-ssr 1.2.2 → 1.2.4
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/CHANGELOG.md +3 -0
- package/README.md +32 -21
- package/index.js +14 -9
- package/package.json +1 -1
- package/power.js +8 -4
- package/utils.js +2 -14
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
- **Full PawaJS Compatibility:** Renders components and templates using the same PawaJS syntax you use on the client.
|
|
11
11
|
- **Server-Side Hooks:** Supports server-side implementations of PawaJS hooks like `$state`, `useInsert`, `setContext`, and `useContext`.
|
|
12
12
|
- **Built-in Directives:** Includes powerful control-flow directives like `if`, `for-each`, `switch`, and `key`.
|
|
13
|
-
- **Automatic Hydration:** Embeds necessary data for the client-side PawaJS to "hydrate" the static HTML and make it interactive without a full re-render using pawajs-continue library.
|
|
13
|
+
- **Automatic Hydration:** Embeds necessary data for the client-side PawaJS to "hydrate" the static HTML and make it interactive without a full re-render using [PawaJS-Continue](https://github.com/Allisboy/pawajs-continue) library.
|
|
14
14
|
- **Client-Only Escape Hatch:** Use the `only-client` attribute to prevent specific components or elements from rendering on the server.
|
|
15
15
|
- **Extensible Plugin System:** Add your own custom directives and rendering lifecycle hooks.
|
|
16
16
|
|
|
@@ -28,6 +28,8 @@ Here's a basic example of how to render a PawaJS component on the server using a
|
|
|
28
28
|
|
|
29
29
|
```javascript
|
|
30
30
|
import { RegisterComponent, $state, useInsert, html } from 'pawajs';
|
|
31
|
+
import {isServer} from 'pawajs/server'
|
|
32
|
+
import { initiateResumer } from "pawajs-continue";
|
|
31
33
|
|
|
32
34
|
const App = () => {
|
|
33
35
|
const message = $state('World');
|
|
@@ -40,8 +42,14 @@ const App = () => {
|
|
|
40
42
|
</div>
|
|
41
43
|
`;
|
|
42
44
|
};
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
RegisterComponent(App);
|
|
46
|
+
```
|
|
47
|
+
**2. A server file (`main.js`)
|
|
48
|
+
```javascript
|
|
49
|
+
import { startApp } from "pawajs-ssr";
|
|
50
|
+
import { App } from "./app.js";
|
|
51
|
+
import {App} from "./app.js"
|
|
52
|
+
const Entry=()=>{
|
|
45
53
|
return html`
|
|
46
54
|
<div>
|
|
47
55
|
<app></app>
|
|
@@ -49,10 +57,17 @@ export const Main=()=>{
|
|
|
49
57
|
`
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
|
|
60
|
+
export const main=async({url})=>{
|
|
61
|
+
const template=Entry()
|
|
62
|
+
const {toString,head}=await startApp(template,{url},true)//template string, context for server , true - for development
|
|
63
|
+
const html=await toString()
|
|
64
|
+
return {html,head}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
53
68
|
```
|
|
54
69
|
|
|
55
|
-
**
|
|
70
|
+
**3. Create your Server Entrypoint (`server.js`)**
|
|
56
71
|
|
|
57
72
|
```javascript
|
|
58
73
|
import express from 'express';
|
|
@@ -65,14 +80,8 @@ const port = 3000;
|
|
|
65
80
|
app.get('/', async (req, res) => {
|
|
66
81
|
try {
|
|
67
82
|
// Get the component's HTML string
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
// Render it with PawaJS SSR
|
|
71
|
-
const { toString, head } = await startApp(
|
|
72
|
-
componentHtml,
|
|
73
|
-
{ /* initial context */ },
|
|
74
|
-
true // development mode
|
|
75
|
-
);
|
|
83
|
+
const {html,head} =async Main({url:req.origin});
|
|
84
|
+
const
|
|
76
85
|
|
|
77
86
|
const renderedHtml = await toString();
|
|
78
87
|
|
|
@@ -86,9 +95,9 @@ app.get('/', async (req, res) => {
|
|
|
86
95
|
${head}
|
|
87
96
|
</head>
|
|
88
97
|
<body>
|
|
89
|
-
<div id="app">${
|
|
98
|
+
<div id="app">${html}</div>
|
|
90
99
|
<!-- Add client-side script for continuity using pawajs-continue library-->
|
|
91
|
-
<script src="/
|
|
100
|
+
<script src="/app.js" type="module"></script>
|
|
92
101
|
</body>
|
|
93
102
|
</html>
|
|
94
103
|
`;
|
|
@@ -118,12 +127,14 @@ import { App } from './src/app.js';
|
|
|
118
127
|
// Register the same components used on the server
|
|
119
128
|
RegisterComponent(App);
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
if(!isServer()){
|
|
131
|
+
// Initialize the resumer for hydration
|
|
132
|
+
initiateResumer();
|
|
133
|
+
|
|
134
|
+
// Start PawaJS on the client to continue the app know as Continue Rendering Model(CRM)
|
|
135
|
+
const appElement = document.getElementById('app');
|
|
136
|
+
pawaStartApp(appElement);
|
|
137
|
+
}
|
|
127
138
|
```
|
|
128
139
|
|
|
129
140
|
## Core Concepts
|
package/index.js
CHANGED
|
@@ -112,6 +112,7 @@ const $state=(initialValue)=>{
|
|
|
112
112
|
if(res instanceof Promise){
|
|
113
113
|
state.async=true
|
|
114
114
|
state.failed=false
|
|
115
|
+
state.retry=()=>{}
|
|
115
116
|
|
|
116
117
|
}else{
|
|
117
118
|
state.value=initialValue()
|
|
@@ -291,24 +292,25 @@ const component=async (el)=>{
|
|
|
291
292
|
fromSerialized:{}
|
|
292
293
|
}
|
|
293
294
|
Object.assign(appContext.transportContext, oldAppContext?.transportContext || {})
|
|
295
|
+
const slotHydrates={}
|
|
294
296
|
Array.from(slot.children).forEach(prop =>{
|
|
295
297
|
if (prop.getAttribute('prop')) {
|
|
296
|
-
|
|
298
|
+
const html=prop.innerHTML
|
|
299
|
+
slots[prop.getAttribute('prop')]=()=>html
|
|
300
|
+
slotHydrates[prop.getAttribute('prop')]=html
|
|
297
301
|
}else{
|
|
298
302
|
console.warn('sloting props must have prop attribute')
|
|
299
303
|
}
|
|
300
304
|
})
|
|
301
305
|
const children=el._componentChildren
|
|
302
306
|
const hydrate={
|
|
303
|
-
children:
|
|
307
|
+
children:children,
|
|
304
308
|
props:{
|
|
305
309
|
...el._hydrateProps,
|
|
306
310
|
},
|
|
307
|
-
slots:{...
|
|
308
|
-
}
|
|
309
|
-
if (isDevelopment) {
|
|
310
|
-
hydrate.children=children
|
|
311
|
+
slots:{...slotHydrates},
|
|
311
312
|
}
|
|
313
|
+
|
|
312
314
|
const id=pawaGenerateId(10)
|
|
313
315
|
const encodeJSON = (obj) => Buffer.from(JSON.stringify(obj)).toString('base64').replace(/\+/g, '-');
|
|
314
316
|
const comment = document.createComment(`component+${id}`);
|
|
@@ -342,7 +344,7 @@ appContext.component._prop={children,...el._props,...slots}
|
|
|
342
344
|
try {
|
|
343
345
|
await fn(stateContext,app)
|
|
344
346
|
} catch (error) {
|
|
345
|
-
console.error(`Error in beforeCall for ${el._componentName}:`, error.message)
|
|
347
|
+
console.error(`Error in beforeCall for ${el._componentName}:`, error.message,error.stack)
|
|
346
348
|
}
|
|
347
349
|
}
|
|
348
350
|
|
|
@@ -421,7 +423,7 @@ appContext.component._prop={children,...el._props,...slots}
|
|
|
421
423
|
})
|
|
422
424
|
store.getStore().stateContext=appContext.formerContext
|
|
423
425
|
} catch (error) {
|
|
424
|
-
console.log(error.message,error.stack);
|
|
426
|
+
console.log(error.message,error.stack,`at ${el.tagName} component`);
|
|
425
427
|
|
|
426
428
|
}
|
|
427
429
|
}
|
|
@@ -481,6 +483,9 @@ const textContentHandler = async(el) => {
|
|
|
481
483
|
el._context,
|
|
482
484
|
`from text interpolation @{} - ${expression} at ${currentHtmlString}`
|
|
483
485
|
);
|
|
486
|
+
if (expression === '') {
|
|
487
|
+
return
|
|
488
|
+
}
|
|
484
489
|
value = value.replace(fullMatch, String(func));
|
|
485
490
|
});
|
|
486
491
|
|
|
@@ -488,7 +493,7 @@ const textContentHandler = async(el) => {
|
|
|
488
493
|
});
|
|
489
494
|
} catch (error) {
|
|
490
495
|
console.warn(`error at ${el._template} textcontent`);
|
|
491
|
-
console.error(error.message, error.stack);
|
|
496
|
+
console.error(error.message, error.stack,`error at ${el._template} textcontent`);
|
|
492
497
|
}
|
|
493
498
|
};
|
|
494
499
|
|
package/package.json
CHANGED
package/power.js
CHANGED
|
@@ -117,7 +117,8 @@ let latestChain
|
|
|
117
117
|
export const Switch = async(el, attr) => {
|
|
118
118
|
if (el._running) return;
|
|
119
119
|
el._running = true;
|
|
120
|
-
|
|
120
|
+
try {
|
|
121
|
+
|
|
121
122
|
const nextSiblings = el.nextElementSibling || null;
|
|
122
123
|
const cases =el.getAttribute('case')
|
|
123
124
|
const chained=[{
|
|
@@ -230,6 +231,9 @@ const switchFunc=evaluateExpr(attr.value,el._context,`at switch directive ${attr
|
|
|
230
231
|
comment.parentElement.insertBefore(template, endComment);
|
|
231
232
|
// No element rendered
|
|
232
233
|
}
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.log(error.message,error.stack,`at switch directive ${el._template}`)
|
|
236
|
+
}
|
|
233
237
|
};
|
|
234
238
|
|
|
235
239
|
export const For=async(el,attr)=>{
|
|
@@ -322,7 +326,7 @@ export const For=async(el,attr)=>{
|
|
|
322
326
|
}
|
|
323
327
|
|
|
324
328
|
} catch (error) {
|
|
325
|
-
console.error(error.message,error.stack)
|
|
329
|
+
console.error(error.message,error.stack,`at for directive ${el._template}`)
|
|
326
330
|
}
|
|
327
331
|
}
|
|
328
332
|
|
|
@@ -339,7 +343,7 @@ export const State=async(el,attr)=>{
|
|
|
339
343
|
el._context[name]={value:result}
|
|
340
344
|
el.removeAttribute(attr.name)
|
|
341
345
|
} catch (error) {
|
|
342
|
-
console.log(error.message,error.stack)
|
|
346
|
+
console.log(error.message,error.stack,`at ${el._template} from state`)
|
|
343
347
|
}
|
|
344
348
|
|
|
345
349
|
}
|
|
@@ -377,6 +381,6 @@ export const Key=async(el,attr)=>{
|
|
|
377
381
|
endComment.parentElement.insertBefore(newElement,endComment)
|
|
378
382
|
await render(newElement,el._context)
|
|
379
383
|
}catch(error){
|
|
380
|
-
console.error(error.message,error.stack)
|
|
384
|
+
console.error(error.message,error.stack,`at key ${el._template}`)
|
|
381
385
|
}
|
|
382
386
|
}
|
package/utils.js
CHANGED
|
@@ -69,18 +69,9 @@ export const processNode = (node, itemContext) => {
|
|
|
69
69
|
if (typeof itemContext === 'number') {
|
|
70
70
|
return
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
if (node.nodeType === 3) { // Text node
|
|
74
|
-
const text = node.textContent
|
|
75
|
-
const newText = text.replace(/{{(.+?)}}/g, (match, exp) => {
|
|
76
|
-
// Use the cached expression evaluator for performance
|
|
77
|
-
const result = evaluateExpr(exp, itemContext, `in processNode for text: ${exp}`);
|
|
78
|
-
return result !== null ? String(result) : match;
|
|
79
|
-
})
|
|
80
|
-
//console.log(newText)
|
|
81
|
-
node.textContent = newText
|
|
82
|
-
} else if (node.attributes) {
|
|
72
|
+
if (node.attributes) {
|
|
83
73
|
Array.from(node.attributes).forEach(attr => {
|
|
74
|
+
if (attr.name !== 'for-key') return
|
|
84
75
|
const newValue = attr.value.replace(/{{(.+?)}}/g, (match, exp) => {
|
|
85
76
|
// Use the cached expression evaluator for performance
|
|
86
77
|
const result = evaluateExpr(exp, itemContext, `in processNode for attribute ${attr.name}: ${exp}`);
|
|
@@ -89,9 +80,6 @@ export const processNode = (node, itemContext) => {
|
|
|
89
80
|
attr.value = newValue
|
|
90
81
|
})
|
|
91
82
|
}
|
|
92
|
-
Array.from(node.childNodes).forEach((n) => {
|
|
93
|
-
processNode(n, itemContext)
|
|
94
|
-
})
|
|
95
83
|
}
|
|
96
84
|
|
|
97
85
|
export const extractAtExpressions=(template) =>{
|