@webqit/webflo 0.10.5 β 0.11.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 +1082 -323
- package/bundle.html.json +1665 -0
- package/package.json +2 -2
- package/src/config-pi/runtime/Client.js +7 -10
- package/src/config-pi/runtime/client/Worker.js +30 -12
- package/src/runtime-pi/Router.js +1 -1
- package/src/runtime-pi/client/Runtime.js +98 -49
- package/src/runtime-pi/client/RuntimeClient.js +12 -40
- package/src/runtime-pi/client/Workport.js +163 -0
- package/src/runtime-pi/client/generate.js +71 -37
- package/src/runtime-pi/client/worker/Worker.js +57 -23
- package/src/runtime-pi/client/worker/Workport.js +80 -0
- package/src/runtime-pi/server/Runtime.js +22 -8
- package/src/runtime-pi/server/RuntimeClient.js +6 -6
- package/src/runtime-pi/util.js +2 -2
- package/test/site/package.json +9 -0
- package/test/site/public/bundle.html +3 -0
- package/test/site/public/bundle.html.json +1 -0
- package/test/site/public/bundle.js +1 -1
- package/test/site/public/bundle.js.gz +0 -0
- package/test/site/public/bundle.webflo.js +8 -8
- package/test/site/public/bundle.webflo.js.gz +0 -0
- package/test/site/public/index.html +5 -5
- package/test/site/public/index1.html +35 -0
- package/test/site/public/page-2/bundle.js +1 -1
- package/test/site/public/page-2/bundle.js.gz +0 -0
- package/test/site/public/page-2/index.html +3 -4
- package/test/site/public/page-3/logo-130x130.png +0 -0
- package/test/site/public/page-4/subpage/bundle.js +1 -1
- package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
- package/test/site/public/sparoots.json +5 -0
- package/test/site/public/worker.js +1 -1
- package/test/site/public/worker.js.gz +0 -0
- package/test/site/server/index.js +14 -6
- package/src/runtime-pi/client/WorkerComm.js +0 -102
package/README.md
CHANGED
|
@@ -7,122 +7,150 @@
|
|
|
7
7
|
|
|
8
8
|
<!-- /BADGES -->
|
|
9
9
|
|
|
10
|
-
Webflo is a universal *web*, *mobile*, and *API backend* framework built to solve for the underrated `.html` + `.css` + `.js` stack!
|
|
10
|
+
Webflo is a universal *web*, *mobile*, and *API backend* framework built to solve for the underrated `.html` + `.css` + `.js` stack! This has been written specifically to draw directly on the plain old stack at the language level - to facilitate building web-native applications!
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Ok, we've put all of that up for a straight read!
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
> Depending on your current framework background, the hardest part of Webflo might be having break ties with something that isn't conventional to the `.html` + `.css` + `.js` stack: all of that JSX, CSS-in-JS, etc.!
|
|
14
|
+
> **Note**
|
|
15
|
+
> <br>Depending on your current framework background, the hardest part of Webflo might be having to break ties with something that isn't conventional to the `.html` + `.css` + `.js` stack: all of that JSX, CSS-in-JS, etc.!
|
|
17
16
|
|
|
18
17
|
## Documentation
|
|
19
18
|
|
|
20
19
|
+ [Overview](#overview)
|
|
21
20
|
+ [Installation](#installation)
|
|
22
21
|
+ [Concepts](#concepts)
|
|
22
|
+
+ [Webflo Applications](#webflo-applications)
|
|
23
|
+
+ [Workflow API](#workflow-api)
|
|
24
|
+
+ [Webflo Config](#webflo-config)
|
|
25
|
+
+ [Getting Started](#getting-started)
|
|
26
|
+
+ [Getting Involved](#getting-involved)
|
|
23
27
|
|
|
24
28
|
## Overview
|
|
25
29
|
|
|
26
30
|
<details>
|
|
27
|
-
<summary><b>Build <i>
|
|
28
|
-
|
|
31
|
+
<summary><b>Build <i>anything</i></b> - from as basic as a static <code>index.html</code> page to as rich as a universal app that's either a <i>Multi Page Application (MPA)</i>, <i>Single Page Application (SPA)</i>, or a hybrid of these, implementing <i>Server Side Rendering (SSR)</i>, <i>Client Side Rendering (CSR)</i>, or a hybrid of these, offline and <i>PWA</i> capabilities, etc. - this time, <i>without loosing the vanilla advantage</i>!
|
|
32
|
+
</summary>
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
Here's a glimpse of your Webflo app.
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
*/
|
|
36
|
-
export default function(event, context, next) {
|
|
37
|
-
return { title: 'Home' };
|
|
38
|
-
}
|
|
39
|
-
```
|
|
36
|
+
For when your application has a Server side.
|
|
37
|
+
+ The `public` directory for static files.
|
|
38
|
+
+ The `server` directory for server-side routing. (i.e. dynamic request handling on the server - in the case of Multi Page Applications, API backends, etc.)
|
|
40
39
|
|
|
41
|
-
|
|
40
|
+
```shell
|
|
41
|
+
my-app
|
|
42
|
+
βββ server/index.js
|
|
43
|
+
βββ public/logo.png
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
And a typical `index.js` route handler has the following anatomy.
|
|
42
47
|
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
```js
|
|
49
|
+
/**
|
|
50
|
+
server
|
|
51
|
+
βββ index.js
|
|
52
|
+
*/
|
|
53
|
+
export default function(event, context, next) {
|
|
54
|
+
if (next.pathname) {
|
|
55
|
+
return next(); // <--------------------------------- http://localhost:3000/logo.png (or other non-root URLs)
|
|
56
|
+
}
|
|
57
|
+
return { title: 'Hello from Server' }; // <------------- http://localhost:3000/ (root URL)
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> Above, you are handling requests for the root URL and allowing others to flow through to step handlers or to the `public` directory. (Details ahead.)
|
|
62
|
+
|
|
63
|
+
Response is a JSON (API) response when handler return value is jsonfyable. (As above for the root URL.)
|
|
64
|
+
|
|
65
|
+
Or it ends up being rendered as a page response when there is an `index.html` file in the `public` directory that pairs with the route (and when the incoming request matches `text/html` in its `Accept` header).
|
|
48
66
|
|
|
49
|
-
|
|
67
|
+
```shell
|
|
68
|
+
my-app
|
|
69
|
+
βββ server/index.js
|
|
70
|
+
βββ public/index.html
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
And a typical `index.html` page has the following anatomy.
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<!--
|
|
77
|
+
public
|
|
78
|
+
βββ index.html
|
|
79
|
+
-->
|
|
80
|
+
<!DOCTYPE html>
|
|
81
|
+
<html>
|
|
82
|
+
<head>
|
|
83
|
+
<link rel="stylesheet" href="/style.css" /> <!-- ---------------------- Application CSS -->
|
|
84
|
+
<script type="module" src="/bundle.js"></script> <!-- ----------------- Application JS bundle -->
|
|
85
|
+
<template name="routes" src="/bundle.html"></template> <!-- ------------- Reusable HTML Templates and partials (Details ahead) -->
|
|
86
|
+
</head>
|
|
87
|
+
<body>...</body>
|
|
88
|
+
</html>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> These are regular HTML markup! And above, you're also leveraging HTML includes! (Details ahead.)
|
|
50
92
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
*/
|
|
55
|
-
export default function(event, context, next) {
|
|
56
|
-
if (next.stepname) {
|
|
57
|
-
return next();
|
|
58
|
-
}
|
|
59
|
-
return { title: 'Home' };
|
|
60
|
-
}
|
|
61
|
-
```
|
|
93
|
+
For when your application has a Client side.
|
|
94
|
+
+ The `client` directory for client-side routing. (i.e. dynamic request handling right in the browser - in the case of Single Page Applications, etc.)
|
|
95
|
+
+ The `worker` directory for, heck, Service Worker based routing! (i.e. dynamic request handling in the application Service Worker - in the case of Progressive Web Apps, etc.)
|
|
62
96
|
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
return { title: 'Products' };
|
|
72
|
-
}
|
|
73
|
-
```
|
|
97
|
+
```shell
|
|
98
|
+
my-app
|
|
99
|
+
βββ client/index.js
|
|
100
|
+
βββ worker/index.js
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
And in both cases, a typical `index.js` route handler has the following anatomy.
|
|
74
104
|
|
|
75
|
-
|
|
105
|
+
```js
|
|
106
|
+
/**
|
|
107
|
+
[client|worker]
|
|
108
|
+
βββ index.js
|
|
109
|
+
*/
|
|
110
|
+
export default function(event, context, next) {
|
|
111
|
+
if (next.pathname) {
|
|
112
|
+
return next(); // <--------------------------------- http://localhost:3000/logo.png (or other non-root URLs)
|
|
113
|
+
}
|
|
114
|
+
return { title: 'Hello from [Browser|Worker]' }; // <--- http://localhost:3000/ (root URL)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
76
117
|
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
βββ index.js
|
|
80
|
-
*/
|
|
81
|
-
export default async function(event, context, next) {
|
|
82
|
-
if (next.stepname) {
|
|
83
|
-
let childContext = { user: { id: 2 }, };
|
|
84
|
-
let childResponse = await next( childContext );
|
|
85
|
-
return { ...childResponse, title: childResponse.title + ' | FluffyPets' };
|
|
86
|
-
}
|
|
87
|
-
return { title: 'Home | FluffyPets' };
|
|
88
|
-
}
|
|
89
|
-
```
|
|
118
|
+
> Above, you are handling requests for the root URL and allowing others to flow through to nested handlers or to the network. (Details ahead.)
|
|
90
119
|
|
|
91
|
-
|
|
120
|
+
Responses for *navigation* requests are rendered back into the current running page in the browser.
|
|
121
|
+
|
|
122
|
+
This and much more - ahead!
|
|
92
123
|
</details>
|
|
93
124
|
|
|
94
125
|
<details>
|
|
95
|
-
<summary><b>Build <i>future-proof
|
|
96
|
-
<br>
|
|
126
|
+
<summary><b>Build <i>future-proof anything</i></b> by banking more on web standards and less on abstractions! Webflo <i>just follows</i> where a native feature, standard, or conventional HTML, CSS or JS <i>just works</i>!</summary>
|
|
97
127
|
|
|
98
|
-
|
|
128
|
+
Here's a glimpse of the standards-based stack you get of Webflo!
|
|
99
129
|
|
|
100
130
|
For when your application involves routing:
|
|
101
|
-
+ [The Fetch Standard](https://fetch.spec.whatwg.org/)
|
|
131
|
+
+ [The Fetch Standard](https://fetch.spec.whatwg.org/), comprising of the [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) interfaces, is used for all things *requests and responses* - across client, server, and Service Worker environments. ([Details ahead](#requests-and-responses))
|
|
102
132
|
|
|
103
|
-
>
|
|
133
|
+
> This paves the way to using other native APIs as-is, when handling requests and responses. For example, if you sent an instance of the native [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData), [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob), [File](https://developer.mozilla.org/en-US/docs/Web/API/File), or [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) object from the browser side of your application, you'd be getting the same instance on the server side!
|
|
104
134
|
|
|
105
|
-
+ [WHATWG URL](https://url.spec.whatwg.org/) and [WHATWG URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern)
|
|
135
|
+
+ [WHATWG URL](https://url.spec.whatwg.org/) and [WHATWG URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) are used for all things *URL* and *URL pattern matching*, respectively - across client, server, and Service Worker environments. ([Details ahead](#))
|
|
106
136
|
|
|
107
137
|
For when your application involves pages and a UI:
|
|
108
|
-
+ [The HTML Standard](https://html.spec.whatwg.org/)
|
|
138
|
+
+ [The HTML Standard](https://html.spec.whatwg.org/) is held for all things *markup* - across client, server, and Service Worker environments! Webflo is all about using conventional `.html`-based pages and templates, valid HTML syntax, etc. You are able to get away with a "zero-JavaScript" proposition or with *Progressive Enhancement* that makes do with "just-enough JavaScript"!
|
|
109
139
|
|
|
110
|
-
> Your markup is also easily extendable with
|
|
140
|
+
> Your markup is also easily extendable with [OOHTML](https://github.com/webqit/oohtml) - a set of new features for HTML that makes it fun to hand-author your UI! Within OOHTML are [HTML Modules (`<template name="partials"></template>`)](https://github.com/webqit/oohtml#html-modules) and [HTML Imports (`<import template="partials"></import>`)](https://github.com/webqit/oohtml#html-imports), [Reactive Scripts (`<script type="subscript"></script>`)](https://github.com/webqit/oohtml#subscript) and more!
|
|
111
141
|
|
|
112
|
-
+ [WHATWG DOM](https://dom.spec.whatwg.org/)
|
|
142
|
+
+ [WHATWG DOM](https://dom.spec.whatwg.org/) is universally available - not only on the client-side, but also on the server-side via [OOHTML-SSR](https://github.com/webqit/oohtml-ssr) - for all things *dynamic pages*: rendering, manipulation, interactivity, etc.
|
|
113
143
|
|
|
114
|
-
> Your DOM is also easily enrichable with [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements), [Subscript Elements](https://github.com/webqit/oohtml#subscript)
|
|
144
|
+
> Your DOM is also easily enrichable with [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements), plus [Subscript Elements](https://github.com/webqit/oohtml#subscript) and [The State API (`document.state` and `element.state`)](https://github.com/webqit/oohtml#state-api) from OOHTML.
|
|
115
145
|
|
|
116
146
|
For when your application needs to give an app-like experience:
|
|
117
|
-
+ [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API)
|
|
147
|
+
+ [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API), extended with full support for routing, come into play for offline and [Progressive Web Apps (PWA)](https://web.dev/progressive-web-apps/) capabilities.
|
|
118
148
|
|
|
119
149
|
> You are also able to easily make your web app installable by complementing this with a [Web App Manifest](https://developer.mozilla.org/en-US/docs/Web/Manifest).
|
|
120
|
-
|
|
121
|
-
This
|
|
150
|
+
|
|
151
|
+
This and more - ahead! For building web-native apps!
|
|
122
152
|
</details>
|
|
123
153
|
|
|
124
|
-
*This and more - ahead!*
|
|
125
|
-
|
|
126
154
|
## Installation
|
|
127
155
|
|
|
128
156
|
Every Webflo project starts on an empty directory that you can create on your machine. The command below will make a new directory `my-app` from the terminal and navigate into it.
|
|
@@ -132,9 +160,9 @@ mkdir my-app
|
|
|
132
160
|
cd my-app
|
|
133
161
|
```
|
|
134
162
|
|
|
135
|
-
With [npm available on your terminal](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), the following command
|
|
163
|
+
With [npm available on your terminal](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), run the following command to install Webflo to your project:
|
|
136
164
|
|
|
137
|
-
> System Requirements: Node.js
|
|
165
|
+
> System Requirements: Node.js 14.0 or later
|
|
138
166
|
|
|
139
167
|
```shell
|
|
140
168
|
$ npm i @webqit/webflo
|
|
@@ -158,7 +186,7 @@ Other important definitions like project `name`, package `type`, and *aliases* f
|
|
|
158
186
|
"type": "module",
|
|
159
187
|
"scripts": {
|
|
160
188
|
"start": "webflo start::server --mode=dev",
|
|
161
|
-
"generate": "webflo generate::client --
|
|
189
|
+
"generate": "webflo generate::client --compression=gz --auto-embed"
|
|
162
190
|
},
|
|
163
191
|
"dependencies": {
|
|
164
192
|
"@webqit/webflo": "..."
|
|
@@ -204,9 +232,9 @@ If you can't wait to say *Hello World!* π
, you can have an HTML page say that
|
|
|
204
232
|
|
|
205
233
|
+ [Handler Functions and Layout](#handler-functions-and-layout)
|
|
206
234
|
+ [Step Functions and Workflows](#step-functions-and-workflows)
|
|
235
|
+
+ [Pages, Layout and Templating](#pages-layout-and-templating)
|
|
236
|
+
+ [Client and Server-Side Rendering](#client-and-server-side-rendering)
|
|
207
237
|
+ [Requests and Responses](#requests-and-responses)
|
|
208
|
-
+ [Rendering and Templating](#rendering-and-templating)
|
|
209
|
-
+ [Web Standards](#web-standards)
|
|
210
238
|
|
|
211
239
|
### Handler Functions and Layout
|
|
212
240
|
|
|
@@ -221,9 +249,9 @@ export default function(event, context, next) {
|
|
|
221
249
|
}
|
|
222
250
|
```
|
|
223
251
|
|
|
224
|
-
Each function receives an `event` object representing the current flow.
|
|
252
|
+
Each function receives an `event` object representing details - e.g. `event.request`, `event.url`, `event.session` - about the current flow. (Details ahead.)
|
|
225
253
|
|
|
226
|
-
For *server-based* applications (e.g. traditional web apps
|
|
254
|
+
For *server-based* applications (e.g. traditional web apps and API backends), server-side handlers go into a directory named `server`.
|
|
227
255
|
|
|
228
256
|
```js
|
|
229
257
|
/**
|
|
@@ -239,7 +267,7 @@ export default function(event, context, next) {
|
|
|
239
267
|
```
|
|
240
268
|
|
|
241
269
|
> **Note**
|
|
242
|
-
> <br>The above function
|
|
270
|
+
> <br>The above function responds on starting the server - `npm start` on your terminal - and visiting http://localhost:3000.
|
|
243
271
|
|
|
244
272
|
For *browser-based* applications (e.g. Single Page Apps), client-side handlers go into a directory named `client`.
|
|
245
273
|
|
|
@@ -257,7 +285,7 @@ export default function(event, context, next) {
|
|
|
257
285
|
```
|
|
258
286
|
|
|
259
287
|
> **Note**
|
|
260
|
-
> <br>The above function is built as part of your application's JS bundle on
|
|
288
|
+
> <br>The above function is built as part of your application's JS bundle on running the `npm run generate` command. (It is typically bundled to the file `./public/bundle.js`. And the `--auto-embed` flag in that command gets it automatically embedded on your `./public/index.html` page as `<script type="module" src="/bundle.js"></script>`.) Then it responds from right in the browser on visiting http://localhost:3000.
|
|
261
289
|
|
|
262
290
|
For *browser-based* applications that want to support offline usage via Service-Workers (e.g Progressive Web Apps), Webflo allows us to define equivalent handlers for requests hitting the Service Worker. These worker-based handlers go into a directory named `worker`.
|
|
263
291
|
|
|
@@ -275,7 +303,7 @@ export default function(event, context, next) {
|
|
|
275
303
|
```
|
|
276
304
|
|
|
277
305
|
> **Note**
|
|
278
|
-
> <br>The above function is built as part of your application's Service Worker JS bundle on
|
|
306
|
+
> <br>The above function is built as part of your application's Service Worker JS bundle on running the `npm run generate` command. (It is typically bundled to the file `./public/worker.js`, and the main application bundle automatically connects to it.) Then it responds from within the Service Worker on visiting http://localhost:3000. (More details [ahead](#service-workers).)
|
|
279
307
|
|
|
280
308
|
So, depending on what's being built, an application's handler functions may take the following form (in part or in whole as with universal applications):
|
|
281
309
|
|
|
@@ -303,7 +331,7 @@ public
|
|
|
303
331
|
|
|
304
332
|
### Step Functions and Workflows
|
|
305
333
|
|
|
306
|
-
Whether routing in the `/client`, `/worker`, or `/server` directory above, nested URLs follow the concept of Step Functions!
|
|
334
|
+
Whether routing in the `/client`, `/worker`, or `/server` directory above, nested URLs follow the concept of Step Functions! These are parent-child layout of handlers that model an URL strucuture.
|
|
307
335
|
|
|
308
336
|
```shell
|
|
309
337
|
server
|
|
@@ -312,7 +340,7 @@ server
|
|
|
312
340
|
βββ stickers/index.js ------------------ http://localhost:3000/products/stickers
|
|
313
341
|
```
|
|
314
342
|
|
|
315
|
-
Each
|
|
343
|
+
Each step calls a `next()` function to forward the current request to the next step (if any), is able to pass a `context` object along, and can *recompose* the return value.
|
|
316
344
|
|
|
317
345
|
```js
|
|
318
346
|
/**
|
|
@@ -342,11 +370,29 @@ export default function(event, context, next) {
|
|
|
342
370
|
}
|
|
343
371
|
```
|
|
344
372
|
|
|
345
|
-
This step-based workflow helps to decomplicate routing and
|
|
373
|
+
This step-based workflow helps to decomplicate routing and gets us scaling horizontally as our application grows larger.
|
|
374
|
+
|
|
375
|
+
Workflows may be designed with *wildcard* steps using a hyphen `-` as step name. Wildcard steps match all paths at the given level of the route! A `this.stepname` property can always be used to see the current URL step that matched.
|
|
376
|
+
|
|
377
|
+
```js
|
|
378
|
+
/**
|
|
379
|
+
server
|
|
380
|
+
βββ -/index.js
|
|
381
|
+
*/
|
|
382
|
+
export default function(event, context, next) {
|
|
383
|
+
if (next.stepname) {
|
|
384
|
+
return next();
|
|
385
|
+
}
|
|
386
|
+
if (this.stepname === 'products') {
|
|
387
|
+
return { title: 'Products' };
|
|
388
|
+
}
|
|
389
|
+
return { title: 'Untitled' };
|
|
390
|
+
}
|
|
391
|
+
```
|
|
346
392
|
|
|
347
|
-
|
|
393
|
+
Additionally, workflows may be designed with as many or as few step functions as necessary; the flow control parameters `next.stepname` and `next.pathname` can be used at any point to handle the rest of an URL that have no corresponding step functions.
|
|
348
394
|
|
|
349
|
-
|
|
395
|
+
For example, it is possible to handle all URLs from the root handler alone.
|
|
350
396
|
|
|
351
397
|
```js
|
|
352
398
|
/**
|
|
@@ -374,9 +420,9 @@ export default function(event, context, next) {
|
|
|
374
420
|
}
|
|
375
421
|
```
|
|
376
422
|
|
|
377
|
-
|
|
423
|
+
Webflo takes a *default action* when `next()` is called at the *edge* of the workflow - the point where there are no more child steps - as in the `return next()` statement above!
|
|
378
424
|
|
|
379
|
-
For workflows in **the `/server` directory**, the *default action* of `next()` at the edge is to go match and return a static file in the `public` directory.
|
|
425
|
+
For workflows in **the `/server` directory**, the *default action* of `next()`ing at the edge is to go match and return a static file in the `public` directory.
|
|
380
426
|
|
|
381
427
|
So, above, should our handler receive static file requests like `http://localhost:3000/logo.png`, the expression `return next()` would get Webflo to match and return the logo at `public/logo.png`, if any; a `404` response otherwise.
|
|
382
428
|
|
|
@@ -389,7 +435,7 @@ my-app
|
|
|
389
435
|
> **Note**
|
|
390
436
|
> <br>The root handler effectively becomes the single point of entry to the application - being that it sees even static requests!
|
|
391
437
|
|
|
392
|
-
Now, for workflows in **the `/worker` directory**, the *default action* of `next()` at the edge is to send the request through the network to the server. (But Webflo will know to attempt resolving the request from the application's caching
|
|
438
|
+
Now, for workflows in **the `/worker` directory**, the *default action* of `next()`ing at the edge is to send the request through the network to the server. (But Webflo will know to attempt resolving the request from the application's caching system built into the Service Worker.)
|
|
393
439
|
|
|
394
440
|
So, above, if we defined handler functions in the `/worker` directory, we could decide to either handle the received requests or just `next()` them to the server.
|
|
395
441
|
|
|
@@ -429,9 +475,9 @@ my-app
|
|
|
429
475
|
```
|
|
430
476
|
|
|
431
477
|
> **Note**
|
|
432
|
-
> <br>Handlers in the `/worker` directory are only designed to see Same-Origin requests since Cross-Origin URLs like `https://auth.example.com/oauth` do not belong in the application's layout! These external URLs, however, benefit from the application's caching
|
|
478
|
+
> <br>Handlers in the `/worker` directory are only designed to see Same-Origin requests since Cross-Origin URLs like `https://auth.example.com/oauth` do not belong in the application's layout! These external URLs, however, benefit from the application's caching system built into the Service Worker.
|
|
433
479
|
|
|
434
|
-
|
|
480
|
+
For workflows in **the `/client` directory**, the *default action* of `next()`ing at the edge is to send the request through the network to the server. But where there is a Service Worker layer, then that becomes the next destination.
|
|
435
481
|
|
|
436
482
|
So, above, if we defined handler functions in the `/client` directory, we could decide to either handle the navigation requests in-browser or just `next()` them, this time, to the Service Worker layer.
|
|
437
483
|
|
|
@@ -464,23 +510,26 @@ my-app
|
|
|
464
510
|
βββ public/logo.png ------------------------- http://localhost:3000/logo.png
|
|
465
511
|
```
|
|
466
512
|
|
|
467
|
-
If there's anything we have now, it is the ability to break work down
|
|
513
|
+
If there's anything we have now, it is the ability to break work down<a href="https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm"><small><sup>[i]</sup></small></a>, optionally across step functions, optionally between layers!
|
|
468
514
|
|
|
469
|
-
###
|
|
470
|
-
|
|
471
|
-
Routes in Webflo can be designed for different types of request/response scenarios.
|
|
515
|
+
### Pages, Layout and Templating
|
|
472
516
|
|
|
473
|
-
|
|
517
|
+
HTML files in the `public` directory, just like every other *public* file, are served statically when accessed directly - e.g. `http://localhost:3000/index.html`. But `index.html` files, specifically, are treated as *pages* by Webflo. They are, therefore, also accessible with path URLs like `http://localhost:3000`.
|
|
474
518
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
519
|
+
```shell
|
|
520
|
+
my-app
|
|
521
|
+
βββ public/index.html ----------------------- http://localhost:3000/index.html, http://localhost:3000
|
|
522
|
+
```
|
|
478
523
|
|
|
479
|
-
But, where
|
|
524
|
+
But, where an `index.html` file pairs with a route...
|
|
480
525
|
|
|
481
|
-
|
|
526
|
+
```shell
|
|
527
|
+
my-app
|
|
528
|
+
βββ server/index.js
|
|
529
|
+
βββ public/index.html
|
|
530
|
+
```
|
|
482
531
|
|
|
483
|
-
|
|
532
|
+
...the route handler determines what happens.
|
|
484
533
|
|
|
485
534
|
```js
|
|
486
535
|
/**
|
|
@@ -488,269 +537,979 @@ server
|
|
|
488
537
|
βββ index.js
|
|
489
538
|
*/
|
|
490
539
|
export default async function(event, context, next) {
|
|
491
|
-
|
|
540
|
+
// For http://localhost:3000/index.html, etc
|
|
541
|
+
if (next.pathname) {
|
|
542
|
+
return next();
|
|
543
|
+
}
|
|
544
|
+
// For http://localhost:3000 specifically
|
|
545
|
+
return { ... };
|
|
492
546
|
}
|
|
493
547
|
```
|
|
494
548
|
|
|
495
|
-
|
|
549
|
+
Now, we are able to access the data component of a route differently from its HTML component!
|
|
496
550
|
|
|
497
|
-
|
|
551
|
+
```shell
|
|
552
|
+
my-app
|
|
553
|
+
βββ server/index.js ------------------------- http://localhost:3000 -------------------- application/json
|
|
554
|
+
βββ public/index.html ----------------------- http://localhost:3000/index.html --------- text/html
|
|
555
|
+
```
|
|
498
556
|
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
server
|
|
502
|
-
βββ index.js
|
|
503
|
-
*/
|
|
504
|
-
export default async function(event, context, next) {
|
|
505
|
-
return { title: 'Home | FluffyPets' };
|
|
506
|
-
}
|
|
507
|
-
export async function render(event, data, next) {
|
|
508
|
-
return `
|
|
509
|
-
<!DOCTYPE html>
|
|
510
|
-
<html>
|
|
511
|
-
<head><title>FluffyPets</title></head>
|
|
512
|
-
<body>
|
|
513
|
-
<h1>${ data.title }</h1>
|
|
514
|
-
</body>
|
|
515
|
-
</html>
|
|
516
|
-
`;
|
|
517
|
-
}
|
|
518
|
-
```
|
|
519
|
-
|
|
520
|
-
But custom `render` callbacks are step functions too that may be nested as necessary to form a *render* workflow.
|
|
521
|
-
|
|
522
|
-
```js
|
|
523
|
-
/**
|
|
524
|
-
server
|
|
525
|
-
βββ index.js
|
|
526
|
-
*/
|
|
527
|
-
export async function render(event, data, next) {
|
|
528
|
-
// For render callbacks at child step
|
|
529
|
-
if (next.stepname) {
|
|
530
|
-
return next();
|
|
531
|
-
}
|
|
532
|
-
return `
|
|
533
|
-
<!DOCTYPE html>
|
|
534
|
-
<html>
|
|
535
|
-
<head><title>FluffyPets</title></head>
|
|
536
|
-
<body>
|
|
537
|
-
<h1>${ data.title }</h1>
|
|
538
|
-
</body>
|
|
539
|
-
</html>
|
|
540
|
-
`;
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
> **Note**
|
|
545
|
-
> <br>Typically, though, child steps do not always need to have an equivalent`render` callback being that they automatically inherit rendering from their parent or ancestor.
|
|
557
|
+
But, we can also access the route in a way that gets the data rendered into the automatically-paired `index.html` file for a dynamic page response. We'd simply set the [`Accept`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) header of the request to match `text/html` - e.g. `text/html`, `text/*`, `*/html`, `*/*`, and Webflo will automatically perform [Server-Side Rendering](#client-and-server-side-rendering) to give a page response. (Automatic pairing works the same for nested routes! But top-level `index.html` files are implicitly inherited down the hierarchy.)
|
|
546
558
|
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
```js
|
|
550
|
-
/**
|
|
551
|
-
server
|
|
552
|
-
βββ index.js
|
|
553
|
-
*/
|
|
554
|
-
export default async function(event, context, next) {
|
|
555
|
-
return { title: 'Home | FluffyPets' };
|
|
556
|
-
}
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
```html
|
|
560
|
-
<!--
|
|
561
|
-
public
|
|
562
|
-
βββ index.html
|
|
563
|
-
-->
|
|
564
|
-
<!DOCTYPE html>
|
|
565
|
-
<html>
|
|
566
|
-
<head><title>FluffyPets</title></head>
|
|
567
|
-
<body namespace>
|
|
568
|
-
<h1 data-id="headline"></h1>
|
|
559
|
+
> **Note**
|
|
560
|
+
> <br>The `Accept` header hint is already how browsers make requests on every page load. So, it just works!
|
|
569
561
|
|
|
570
|
-
|
|
571
|
-
this.namespace.headline = document.state.page.title;
|
|
572
|
-
</script>
|
|
573
|
-
</body>
|
|
574
|
-
</html>
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
The data obtained above is simply sent into the loaded HTML document instance as `document.state.page`. This makes it globally accessible to embedded scripts and rendering logic! (Details in [Rendering and Templating](#rendering-and-templating).)
|
|
562
|
+
Now, for Single Page Applications, subsequent navigations, after the initial page load, just ask for the data on destination URLs and perform [Client-Side Rendering](#client-and-server-side-rendering) on the same running document. Navigation is sleek and instant!
|
|
578
563
|
|
|
579
|
-
|
|
580
|
-
|
|
564
|
+
> **Note**
|
|
565
|
+
> <br>Unless disabled in [config](#spa_routing), SPA routing is automatically built into your app's JS bundle from the `npm run generate` command. So, it just works!
|
|
581
566
|
|
|
582
|
-
|
|
567
|
+
With no extra work, your application can function as either a *Multi Page App (MPA)* or a *Single Page App (SPA)*!
|
|
583
568
|
|
|
584
|
-
|
|
569
|
+
> **Note**
|
|
570
|
+
> <br>In a Single Page Application, all pages are based off a single `index.html` document. In a Multi Page Application, pages are individual `index.html` documents - ideally. But, Server-Side Rendering makes it possible to serve the same, but dynamically-rendered, `index.html` document across page loads - essentially an SPA architecture hiding on the server. But, here, lets take Multi Page Applications for an individual-page architecture.
|
|
585
571
|
|
|
586
|
-
|
|
572
|
+
#### Layout and Templating Overview
|
|
587
573
|
|
|
588
|
-
|
|
574
|
+
In a Multi Page Application (with an individual-page architecture), each page is its own `index.html` document, and it is often necessary to have certain page sections - e.g. site header, footer, and sidebar, etc. - stay consistent across pages. These sections can be defined once and *imported* on every page.
|
|
589
575
|
|
|
590
|
-
|
|
576
|
+
```html
|
|
577
|
+
my-app
|
|
578
|
+
βββ public
|
|
579
|
+
βββ about/index.html ------------------------- <!DOCTYPE html>
|
|
580
|
+
βββ prodcuts/index.html ---------------------- <!DOCTYPE html>
|
|
581
|
+
βββ index.html ------------------------------- <!DOCTYPE html>
|
|
582
|
+
βββ header.html ------------------------------ <header></header> <!-- To appear at top of each index.html page -->
|
|
583
|
+
βββ footer.html ------------------------------ <footer></footer> <!-- To appear at bottom of each index.html page -->
|
|
584
|
+
```
|
|
591
585
|
|
|
592
|
-
|
|
593
|
-
+ **Case 1: From within a `render` callback**. If you defined a custom `render` callback on your route, you could call the `next()` function to advance the *render workflow* into Webflo's default rendering mode. The created `window` instance is returned.
|
|
594
|
-
|
|
595
|
-
```js
|
|
596
|
-
/**
|
|
597
|
-
server
|
|
598
|
-
βββ index.js
|
|
599
|
-
*/
|
|
600
|
-
export default async function(event, context, next) {
|
|
601
|
-
return { title: 'Home | FluffyPets' };
|
|
602
|
-
}
|
|
603
|
-
export async function render(event, data, next) {
|
|
604
|
-
let window = await next( data );
|
|
605
|
-
let { document } = window;
|
|
606
|
-
console.log( document.state.page ); // { title: 'Home | FluffyPets' }
|
|
607
|
-
return window;
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
+ **Case 2: From within an embedded script**. If you embedded a script on your HTML page, you would have access to the same `document.state.page` data.
|
|
612
|
-
|
|
613
|
-
```html
|
|
614
|
-
<!--
|
|
615
|
-
public
|
|
616
|
-
βββ index.html
|
|
617
|
-
-->
|
|
618
|
-
<!DOCTYPE html>
|
|
619
|
-
<html>
|
|
620
|
-
<head>
|
|
621
|
-
<title>FluffyPets</title>
|
|
622
|
-
<script>
|
|
623
|
-
setTimeout(() => {
|
|
624
|
-
console.log( document.state.page ); // { title: 'Home | FluffyPets' }
|
|
625
|
-
}, 0);
|
|
626
|
-
</script>
|
|
627
|
-
</head>
|
|
628
|
-
<body></body>
|
|
629
|
-
</html>
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
But you could have that as an external resource - as in below. (But notice the `ssr` attribute on the `<script>` element. It tells Webflo to allow the script to be fetched and executed in a server-side context.)
|
|
633
|
-
|
|
634
|
-
```html
|
|
635
|
-
<!--
|
|
636
|
-
public
|
|
637
|
-
βββ index.html
|
|
638
|
-
-->
|
|
639
|
-
<!DOCTYPE html>
|
|
640
|
-
<html>
|
|
641
|
-
<head>
|
|
642
|
-
<title>FluffyPets</title>
|
|
643
|
-
<script src="app.js" ssr></script>
|
|
644
|
-
</head>
|
|
645
|
-
<body></body>
|
|
646
|
-
</html>
|
|
647
|
-
```
|
|
586
|
+
In a Single Page Application, each page is the same `index.html` document, and it is often necessary to have the main page sections change on each route. These sections can be defined per-route and *imported* to the document on navigating to their respective URLs.
|
|
648
587
|
|
|
649
|
-
|
|
588
|
+
```html
|
|
589
|
+
my-app
|
|
590
|
+
βββ public
|
|
591
|
+
βββ about/main.html -------------------------- <main></main> <!-- To appear at main area of index.html -->
|
|
592
|
+
βββ prodcuts/main.html ----------------------- <main></main> <!-- To appear at main area of index.html -->
|
|
593
|
+
βββ main.html -------------------------------- <main></main> <!-- To appear at main area of index.html -->
|
|
594
|
+
βββ index.html ------------------------------- <!DOCTYPE html>
|
|
595
|
+
```
|
|
650
596
|
|
|
651
|
-
|
|
597
|
+
This, in both cases, is templating - the ability to define HTML *partials* once, and have them reused multiple times. Webflo just concerns itself with templating, and the choice of a Multi Page Application or Single Page Application becomes yours! And heck, you can even have the best of both worlds in the same application - with an architecture we'll call [Multi SPA](#in-a-multi-spa-layout)! It's all a *layout* thing!
|
|
652
598
|
|
|
653
|
-
|
|
654
|
-
> <br>You can learn more about OOHTML [here](https://github.com/webqit/oohtml).
|
|
599
|
+
Now, with pages in Webflo being [DOM-based](#overview) (both client-side and [server-side](https://github.com/webqit/oohtml-ssr)), documents can be manipulated directly with DOM APIs, e.g. to replace or insert nodes, attributes, etc. But even better, templating in Webflo is based on the [HTML Modules](https://github.com/webqit/oohtml#html-modules) and [HTML Imports](https://github.com/webqit/oohtml#html-imports) features in [OOHTML](https://github.com/webqit/oohtml) - unless disabled in [config](#oohtml_support). These features provide a powerful declarative templating system on top of the standard [HTML `<template>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template) element - with a *module*, *export* and *import* paradigm.
|
|
655
600
|
|
|
656
|
-
|
|
657
|
-
> <br>You can disable OOHTML in config where you do not need to use its features in HTML and the DOM.
|
|
601
|
+
Here, you are able to define reusable contents in a `<template>` element...
|
|
658
602
|
|
|
659
|
-
|
|
603
|
+
```html
|
|
604
|
+
<head>
|
|
605
|
+
<template name="routes">
|
|
606
|
+
<header exportgroup="header.html">Header Area</header>
|
|
607
|
+
<main exportgroup="main.html">Main Area</main>
|
|
608
|
+
</template>
|
|
609
|
+
</head>
|
|
610
|
+
```
|
|
660
611
|
|
|
661
|
-
|
|
612
|
+
...and have them imported anywhere on the root document using an `<import>` element:
|
|
662
613
|
|
|
663
614
|
```html
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
<!DOCTYPE html>
|
|
669
|
-
<html>
|
|
670
|
-
<head>
|
|
671
|
-
<title>FluffyPets</title>
|
|
672
|
-
<script>
|
|
673
|
-
let app = document.state;
|
|
674
|
-
setTimeout(() => {
|
|
675
|
-
let titleElement = querySelector('title');
|
|
676
|
-
let h1Element = querySelector('h1');
|
|
677
|
-
titleElement.innerHTML = app.page.title;
|
|
678
|
-
h1Element.innerHTML = app.page.title;
|
|
679
|
-
}, 0);
|
|
680
|
-
</script>
|
|
681
|
-
</head>
|
|
682
|
-
<body>
|
|
683
|
-
<h1></h1>
|
|
684
|
-
</body>
|
|
685
|
-
</html>
|
|
615
|
+
<body>
|
|
616
|
+
<import template="routes" name="header.html"></import>
|
|
617
|
+
<import template="routes" name="main.html"></import>
|
|
618
|
+
</body>
|
|
686
619
|
```
|
|
687
620
|
|
|
688
|
-
|
|
689
|
-
> <br>We've used a *quick* `setTimeout()` strategy there to wait until the DOM is fully ready to be accessed; also, to wait for data to be available at `document.state.data`. In practice, the assumed delay of `0` may be too small. But, for when you can afford it, a better strategy is to actually *observe* for *[DOM readiness](https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event)* and *[data availability](#)*.
|
|
621
|
+
The *module* element - `<template name>` - is able to load its contents from a remote `.html` file that serves as a bundle:
|
|
690
622
|
|
|
691
|
-
|
|
692
|
-
|
|
623
|
+
```html
|
|
624
|
+
<!--
|
|
625
|
+
public
|
|
626
|
+
βββ bundle.html
|
|
627
|
+
-->
|
|
628
|
+
<header exportgroup="header.html">Header Area</header>
|
|
629
|
+
<main exportgroup="main.html">Main Area</main>
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
```html
|
|
633
|
+
<head>
|
|
634
|
+
<template name="routes" src="/bundle.html"></template>
|
|
635
|
+
</head>
|
|
636
|
+
```
|
|
693
637
|
|
|
694
|
-
|
|
638
|
+
What [we'll see shortly](#bundling) is how multiple standalone `.html` files - e.g. those `header.html`, `footer.html`, `main.html` files above - come together into one `bundle.html` file for an application.
|
|
695
639
|
|
|
696
|
-
|
|
640
|
+
#### In a Multi Page Layout
|
|
641
|
+
|
|
642
|
+
In a Multi Page layout (as [above](#layout-and-templating-overview)), generic contents - e.g. header and footer sections, etc. - are typically bundled into one `bundle.html` file that can be embedded on each page of the application.
|
|
697
643
|
|
|
698
644
|
```html
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
645
|
+
<!--
|
|
646
|
+
public
|
|
647
|
+
βββ index.html
|
|
648
|
+
-->
|
|
649
|
+
<!DOCTYPE html>
|
|
650
|
+
<html>
|
|
651
|
+
<head>
|
|
652
|
+
<script type="module" src="/bundle.js"></script>
|
|
653
|
+
<template name="routes" src="/bundle.html"></template>
|
|
654
|
+
</head>
|
|
655
|
+
<body>
|
|
656
|
+
<import template="routes" name="header.html"></import>
|
|
657
|
+
<main>Welcome to our Home Page</main>
|
|
658
|
+
<import template="routes" name="footer.html"></import>
|
|
659
|
+
</body>
|
|
660
|
+
</html>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
```html
|
|
664
|
+
<!--
|
|
665
|
+
public/about
|
|
666
|
+
βββ index.html
|
|
667
|
+
-->
|
|
668
|
+
<!DOCTYPE html>
|
|
669
|
+
<html>
|
|
670
|
+
<head>
|
|
671
|
+
<script type="module" src="/bundle.js"></script>
|
|
672
|
+
<template name="routes" src="/bundle.html"></template>
|
|
673
|
+
</head>
|
|
674
|
+
<body>
|
|
675
|
+
<import template="routes" name="header.html"></import>
|
|
676
|
+
<main>Welcome to our About Page</main>
|
|
677
|
+
<import template="routes" name="footer.html"></import>
|
|
678
|
+
</body>
|
|
679
|
+
</html>
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
```html
|
|
683
|
+
<!--
|
|
684
|
+
public/products
|
|
685
|
+
βββ index.html
|
|
686
|
+
-->
|
|
687
|
+
<!DOCTYPE html>
|
|
688
|
+
<html>
|
|
689
|
+
<head>
|
|
690
|
+
<script type="module" src="/bundle.js"></script>
|
|
691
|
+
<template name="routes" src="/bundle.html"></template>
|
|
692
|
+
</head>
|
|
693
|
+
<body>
|
|
694
|
+
<import template="routes" name="header.html"></import>
|
|
695
|
+
<main>Welcome to our Products Page</main>
|
|
696
|
+
<import template="routes" name="footer.html"></import>
|
|
697
|
+
</body>
|
|
698
|
+
</html>
|
|
722
699
|
```
|
|
723
700
|
|
|
724
701
|
> **Note**
|
|
725
|
-
> <br>
|
|
702
|
+
> <br>In this architecture, navigation is traditional - a new page loads each time. The `bundle.js` script comes with the appropriate OOHTML support level required for the imports to function.
|
|
703
|
+
|
|
704
|
+
#### In a Single Page Layout
|
|
726
705
|
|
|
727
|
-
|
|
706
|
+
In a Single Page layout (as [above](#layout-and-templating-overview)), page-specific contents - e.g. main sections - are typically bundled together into one `bundle.html` file that can be embedded on the document root. Nested routes end up as nested `<template>` elements to form the equivalent of their URL structure.
|
|
728
707
|
|
|
729
708
|
```html
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
709
|
+
<!--
|
|
710
|
+
public
|
|
711
|
+
βββ bundle.html
|
|
712
|
+
-->
|
|
713
|
+
<template name="about">
|
|
714
|
+
<main exportgroup="main.html">Welcome to our About Page</main>
|
|
715
|
+
</template>
|
|
716
|
+
<template name="products">
|
|
717
|
+
<main exportgroup="main.html">Welcome to our Products Page</main>
|
|
718
|
+
</template>
|
|
719
|
+
<main exportgroup="main.html">Welcome to our Home Page</main>
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
Now, the `<main>` elements are each imported on navigating to their respective URLs. This time, Webflo takes care of setting the URL path as a global `template` attribute on the `<body>` element such that `<import>` elements that inherit this global attribute are resolved from its current value.
|
|
723
|
+
|
|
724
|
+
```html
|
|
725
|
+
<!--
|
|
726
|
+
public
|
|
727
|
+
βββ index.html
|
|
728
|
+
-->
|
|
729
|
+
<!DOCTYPE html>
|
|
730
|
+
<html>
|
|
731
|
+
<head>
|
|
732
|
+
<script type="module" src="/bundle.js"></script>
|
|
733
|
+
<template name="routes" src="/bundle.html"></template>
|
|
734
|
+
</head>
|
|
735
|
+
<body template="routes/"> <!-- This "template" attribute automatically changes to routes/about or routes/products as we navigate to http://localhost:3000/about and http://localhost:3000/products respectively -->
|
|
736
|
+
<header></header>
|
|
737
|
+
<import name="main.html"></import> <!-- This import element omits its "template" attribute so as to inherit the global one -->
|
|
738
|
+
<footer></footer>
|
|
739
|
+
</body>
|
|
740
|
+
</html>
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
> **Note**
|
|
744
|
+
> <br>In this architecture, navigation is instant and sleek - Webflo prevents a full page reload, obtains and sets data at `document.state.data` for the new URL, then sets the `template` attribute on the `<body>` element to the new URL path. The `bundle.js` script comes with the appropriate OOHTML support level required for the imports to function.
|
|
745
|
+
|
|
746
|
+
#### In a Multi SPA Layout
|
|
747
|
+
|
|
748
|
+
It's all a *layout* thing, so a hybrid of the two architectures above is possible in one application, to take advantage of the unique benefits of each! Here, you are able to have routes that are standalone `index.html` documents (MPA), which in turn, are able to act as a single document root for their subroutes (SPA).
|
|
749
|
+
|
|
750
|
+
```html
|
|
751
|
+
my-app
|
|
752
|
+
βββ public
|
|
753
|
+
βββ about/index.html ------------------------- <!DOCTYPE html> <!-- Document root 1 -->
|
|
754
|
+
βββ prodcuts
|
|
755
|
+
β βββ free/main.html --------------------------- <main></main> <!-- To appear at main area of document root 2 -->
|
|
756
|
+
β βββ paid/main.html --------------------------- <main></main> <!-- To appear at main area of document root 2 -->
|
|
757
|
+
β βββ main.html -------------------------------- <main></main> <!-- To appear at main area of document root 2 -->
|
|
758
|
+
β βββ index.html ------------------------------- <!DOCTYPE html> <!-- Document root 2, (doubles as an SPA) -->
|
|
759
|
+
βββ index.html ------------------------------- <!DOCTYPE html> <!-- Document root 0 -->
|
|
760
|
+
βββ header.html ------------------------------ <header></header> <!-- To appear at top of each document root -->
|
|
761
|
+
βββ footer.html ------------------------------ <footer></footer> <!-- To appear at bottom of each document root -->
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
The above gives us three document roots: `/index.html`, `/about/index.html`, `/prodcuts/index.html`. The `/prodcuts` route doubles as a Single Page Application such that visiting the `/prodcuts` route loads the document root `/prodcuts/index.html` and lets Webflo SPA routing determine which of `/prodcuts/main.html`, `/prodcuts/free/main.html`, `/prodcuts/paid/main.html` is imported on a given URL.
|
|
765
|
+
|
|
766
|
+
Webflo ensures that only the amount of JavaScript for a document root is actually loaded! So, above, a common JavaScript build is shared across the three document roots alongside an often tiny root-specific build.
|
|
767
|
+
|
|
768
|
+
```html
|
|
769
|
+
<!--
|
|
770
|
+
public
|
|
771
|
+
βββ products/index.html
|
|
772
|
+
-->
|
|
773
|
+
<!DOCTYPE html>
|
|
774
|
+
<html>
|
|
775
|
+
<head>
|
|
776
|
+
<script type="module" src="webflo.bundle.js"></script>
|
|
777
|
+
<script type="module" src="/products/bundle.js"></script>
|
|
778
|
+
<template name="pages" src="/bundle.html"></template>
|
|
779
|
+
</head>
|
|
780
|
+
<body>...</body>
|
|
781
|
+
</html>
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
```html
|
|
785
|
+
<!--
|
|
786
|
+
public
|
|
787
|
+
βββ about/index.html
|
|
788
|
+
-->
|
|
789
|
+
<!DOCTYPE html>
|
|
790
|
+
<html>
|
|
791
|
+
<head>
|
|
792
|
+
<script type="module" src="webflo.bundle.js"></script>
|
|
793
|
+
<script type="module" src="/about/bundle.js"></script>
|
|
794
|
+
<template name="pages" src="/bundle.html"></template>
|
|
795
|
+
</head>
|
|
796
|
+
<body>...</body>
|
|
797
|
+
</html>
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
```html
|
|
801
|
+
<!--
|
|
802
|
+
public
|
|
803
|
+
βββ index.html
|
|
804
|
+
-->
|
|
805
|
+
<!DOCTYPE html>
|
|
806
|
+
<html>
|
|
807
|
+
<head>
|
|
808
|
+
<script type="module" src="webflo.bundle.js"></script>
|
|
809
|
+
<script type="module" src="/bundle.js"></script>
|
|
810
|
+
<template name="pages" src="/bundle.html"></template>
|
|
811
|
+
</head>
|
|
812
|
+
<body>...</body>
|
|
813
|
+
</html>
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
The Webflo `generate` command automatically figures out a given architecture and generates the appropriate scripts for the application! It also factors into the generated scripts the location of each document root so that [all navigations to these roots are handled as a regular page load](#spa-navigation).
|
|
817
|
+
|
|
818
|
+
#### Bundling
|
|
819
|
+
|
|
820
|
+
Template `.html` files are bundled from the filesystem into a single file using the [OOHTML CLI](https://github.com/webqit/oohtml-cli) utility. On installing this utility, you may want to add the following to your npm scripts in `package.json`.
|
|
821
|
+
|
|
822
|
+
```json
|
|
823
|
+
"scripts": {
|
|
824
|
+
"generate:templates": "oohtml bundle --recursive --auto-embed=routes"
|
|
825
|
+
}
|
|
826
|
+
```
|
|
827
|
+
|
|
828
|
+
The `--recursive` flag gets the bundler to recursively bundle *subroots* in a [Multi SPA](#in-a-multi-spa-layout) layout - where subdirectories with their own `index.html` document. (Subroots are ignored by default.)
|
|
829
|
+
|
|
830
|
+
The `--auto-embed` flag gets the bundler to automatically embed the generated `bundle.html` file on the matched `index.html` document. A value of `routes` for the flag ends up as the name of the *embed* template: `<template name="routes" src="/bundle.html"></template>`.
|
|
831
|
+
|
|
832
|
+
> **Note**
|
|
833
|
+
> <br>If your HTML files are actually based off the `public` directory, you'll need to tell the above command to run in the `public` directory, either by [configuring the bundler](https://github.com/webqit/oohtml-cli#other-options), or by rewriting the command with a prefix: `cd public && oohtml bundle --recursive --auto-embed=routes`.
|
|
834
|
+
|
|
835
|
+
### Client and Server-Side Rendering
|
|
836
|
+
|
|
837
|
+
With pages in Webflo being [DOM-based](#overview) (both client-side and [server-side](https://github.com/webqit/oohtml-ssr)), we are able to access and manipulate documents and elements using familiar DOM APIs - e.g. to replace or insert contents, attributes, etc. Rendering in Webflo is based on this concept!
|
|
838
|
+
|
|
839
|
+
Here, Webflo simply makes sure that the data obtained from each route is available as part of the `document` object, such that it is accessible to our rendering logic as a `data` property on the `document.state` object - `document.state.data`. (The `document.state` object is always available unless disabled in config.)
|
|
840
|
+
|
|
841
|
+
So, we could embed a script on our page and render this data on the relevant parts of the document.
|
|
842
|
+
|
|
843
|
+
```html
|
|
844
|
+
<!--
|
|
845
|
+
public
|
|
846
|
+
βββ index.html
|
|
847
|
+
-->
|
|
848
|
+
<!DOCTYPE html>
|
|
849
|
+
<html>
|
|
850
|
+
<head>
|
|
851
|
+
<title></title>
|
|
852
|
+
<script>
|
|
853
|
+
setTimeout(() => {
|
|
854
|
+
console.log( document.state.data ); // { title: 'Home | FluffyPets' }
|
|
855
|
+
let { title } = document.state.data;
|
|
856
|
+
document.title = title;
|
|
857
|
+
}, 0);
|
|
858
|
+
</script>
|
|
859
|
+
</head>
|
|
860
|
+
<body></body>
|
|
861
|
+
</html>
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
Where your rendering logic is an external script, your `<script>` element would need to have an `ssr` Boolean attribute to get the rendering engine to fetch and run your script on the server.
|
|
865
|
+
|
|
866
|
+
```html
|
|
867
|
+
<!--
|
|
868
|
+
public
|
|
869
|
+
βββ index.html
|
|
870
|
+
-->
|
|
871
|
+
<!DOCTYPE html>
|
|
872
|
+
<html>
|
|
873
|
+
<head>
|
|
874
|
+
<title></title>
|
|
875
|
+
<script src="app.js" ssr></script>
|
|
876
|
+
</head>
|
|
877
|
+
<body></body>
|
|
878
|
+
</html>
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
From here, even the most-rudimentary form of rendering (using vanilla HTML and native DOM methods) becomes possible, and this is a good thing: you get away with less tooling until you absolutely need to add up on tooling!
|
|
882
|
+
|
|
883
|
+
However, since the `document` objects in Webflo natively support [OOHTML](https://github.com/webqit/oohtml) - unless disabled in [config](#oohtml_support), we are able to write reactive UI logic! Here, OOHTML makes it possible to embed reactive `<script>` elements (called [Subscript](https://github.com/webqit/oohtml#subscript)) right within HTML elements - where each expression automatically self-updates whenever references to data, or its properties, get an update!
|
|
884
|
+
|
|
885
|
+
```html
|
|
886
|
+
<!--
|
|
887
|
+
public
|
|
888
|
+
βββ index.html
|
|
889
|
+
-->
|
|
734
890
|
<!DOCTYPE html>
|
|
735
891
|
<html>
|
|
736
892
|
<head>
|
|
737
|
-
<title
|
|
738
|
-
<script type="subscript">
|
|
739
|
-
let app = document.state;
|
|
740
|
-
$('title').html(app.page.title);
|
|
741
|
-
</script>
|
|
893
|
+
<title></title>
|
|
742
894
|
</head>
|
|
743
895
|
<body>
|
|
744
896
|
<h1></h1>
|
|
745
897
|
<script type="subscript">
|
|
746
|
-
let
|
|
747
|
-
|
|
898
|
+
let { title } = document.state.data;
|
|
899
|
+
document.title = title;
|
|
900
|
+
let h1Element = this.querySelector('h1');
|
|
901
|
+
h1Element.innerHTML = title;
|
|
902
|
+
</script>
|
|
903
|
+
</body>
|
|
904
|
+
</html>
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
> **Note**
|
|
908
|
+
> <br>Now, this comes logical since logic is the whole essence of the HTML `<script>` element, after all! Compared to other syntax alternatives, this uniquely enables us to do all things logic in the actual language for logic - JavaScript. Then, OOHTML gives us more by extending the regular `<script>` element with the `subscript` type which gets any JavaScript code to be *reactive*!
|
|
909
|
+
|
|
910
|
+
Note that because these scripts are naturally reactive, we do not require any `setTimeout()` construct like we required earlier in the case of the classic `<script>` element. These expressions self-update as the values they depend on become available, removed, or updated - i.e. as `document.state` gets updated.
|
|
911
|
+
|
|
912
|
+
Going forward, we can get to write more succinct code! Using the [Namespaced HTML](https://github.com/webqit/oohtml#namespaced-html) feature in OOHTML, we could do without those `querySelector()` calls up there. Also, we could go on to use any DOM manipulation library of our choice; e.g jQuery, or even better, the jQuery-like [Play UI](https://github.com/webqit/play-ui) library.
|
|
913
|
+
|
|
914
|
+
```html
|
|
915
|
+
<!--
|
|
916
|
+
public
|
|
917
|
+
βββ index.html
|
|
918
|
+
-->
|
|
919
|
+
<!DOCTYPE html>
|
|
920
|
+
<html>
|
|
921
|
+
<head>
|
|
922
|
+
<title></title>
|
|
923
|
+
<script src="/jquery.js"></script>
|
|
924
|
+
</head>
|
|
925
|
+
<body namespace>
|
|
926
|
+
<h1 id="headline1"></h1>
|
|
927
|
+
<script type="subscript">
|
|
928
|
+
let { title } = document.state.data;
|
|
929
|
+
document.title = title;
|
|
930
|
+
let { headline1, headline2 } = this.namespace;
|
|
931
|
+
$(headline1).html(title);
|
|
932
|
+
$(headline2).html(title);
|
|
748
933
|
</script>
|
|
749
934
|
</body>
|
|
750
935
|
</html>
|
|
751
936
|
```
|
|
752
937
|
|
|
753
|
-
|
|
938
|
+
Above, we've also referenced some currently non-existent element `headline2` - ahead of when it becomes added in the DOM! This should give a glimpse of the powerful reactivity we get with having OOHTML around on our document!
|
|
939
|
+
|
|
940
|
+
```js
|
|
941
|
+
setTimeout(() => {
|
|
942
|
+
let headline2 = document.createElement('h2');
|
|
943
|
+
headline2.id = 'headline2';
|
|
944
|
+
document.body.append(headline2);
|
|
945
|
+
}, 1000);
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
Taking things further, it is possible to write class-based components that abstract away all logic! You can find a friend in [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)! Plus, your Custom Elements can function reactively using [SubscriptElement](https://github.com/webqit/oohtml#subscript) as base class!
|
|
949
|
+
|
|
950
|
+
#### Custom Render Functions
|
|
951
|
+
|
|
952
|
+
Custom `render` functions can be defined on a route (`export function render() {}`) to entirely handle, or extend, rendering.
|
|
953
|
+
|
|
954
|
+
```js
|
|
955
|
+
/**
|
|
956
|
+
server
|
|
957
|
+
βββ index.js
|
|
958
|
+
*/
|
|
959
|
+
export default async function(event, context, next) {
|
|
960
|
+
return { title: 'Home | FluffyPets' };
|
|
961
|
+
}
|
|
962
|
+
export async function render(event, data, next) {
|
|
963
|
+
return `
|
|
964
|
+
<!DOCTYPE html>
|
|
965
|
+
<html>
|
|
966
|
+
<head><title>FluffyPets</title></head>
|
|
967
|
+
<body>
|
|
968
|
+
<h1>${ data.title }</h1>
|
|
969
|
+
</body>
|
|
970
|
+
</html>
|
|
971
|
+
`;
|
|
972
|
+
}
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
<details>
|
|
976
|
+
<summary>And, custom <code>render</code> functions can be step functions too, nested as necessary to form a <i>render</i> workflow.</summary>
|
|
977
|
+
|
|
978
|
+
```js
|
|
979
|
+
/**
|
|
980
|
+
server
|
|
981
|
+
βββ index.js
|
|
982
|
+
*/
|
|
983
|
+
export async function render(event, data, next) {
|
|
984
|
+
// For render callbacks at child step
|
|
985
|
+
if (next.stepname) {
|
|
986
|
+
return next();
|
|
987
|
+
}
|
|
988
|
+
return `
|
|
989
|
+
<!DOCTYPE html>
|
|
990
|
+
<html>
|
|
991
|
+
<head><title>FluffyPets</title></head>
|
|
992
|
+
<body>
|
|
993
|
+
<h1>${ data.title }</h1>
|
|
994
|
+
</body>
|
|
995
|
+
</html>
|
|
996
|
+
`;
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
> **Note**
|
|
1001
|
+
> <br>Typically, though, child steps do not always need to have an equivalent`render` callback being that they automatically inherit rendering from their parent or ancestor.
|
|
1002
|
+
</details>
|
|
1003
|
+
|
|
1004
|
+
But, custom render functions do not always need to do as much as entirely handle rendering. It is possible to get them to trigger Webflo's native rendering and simply modify the documents being rendered. Here, you would simply call the `next()` function to advance the *render workflow* into Webflo's default rendering. A `window` instance is returned containing the document being rendered.
|
|
1005
|
+
|
|
1006
|
+
```js
|
|
1007
|
+
/**
|
|
1008
|
+
server
|
|
1009
|
+
βββ index.js
|
|
1010
|
+
*/
|
|
1011
|
+
export default async function(event, context, next) {
|
|
1012
|
+
return { title: 'Home | FluffyPets' };
|
|
1013
|
+
}
|
|
1014
|
+
export async function render(event, data, next) {
|
|
1015
|
+
let window = await next( data );
|
|
1016
|
+
let { document } = window;
|
|
1017
|
+
console.log( document.state.data ); // { title: 'Home | FluffyPets' }
|
|
1018
|
+
return window;
|
|
1019
|
+
}
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
Custom render functions must return a value, and `window` objects are accepted. (Actually, any object that has a `toString()` method can be returned.)
|
|
1023
|
+
|
|
1024
|
+
### Requests and Responses
|
|
1025
|
+
|
|
1026
|
+
On each request, the event object passed to route handlers exposes the incoming request as `event.request`. This is an instance of `event.Request` - an extension of the [WHATWG Request](https://developer.mozilla.org/en-US/docs/Web/API/Request) class. The event object also exposes `event.Response` - an extension of the [WHATWG Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) class, for returning instance-based responses.
|
|
1027
|
+
|
|
1028
|
+
Now, routes in Webflo can be designed for different types of request/response scenarios. Webflo does the heavy lifting on each request/response flow!
|
|
1029
|
+
|
|
1030
|
+
#### Scenario 1: Static File Requests and Responses
|
|
1031
|
+
|
|
1032
|
+
Static file requests like `http://localhost:3000/logo.png` are expected to get a file response. These requests are automatically handled by Webflo when `next()`ed forward by route handlers, or where there are no route handlers.
|
|
1033
|
+
+ On the server, Webflo serves files from the `public` directory. File conents along with the appropriate headers like [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type), [`Content-Length`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Length), etc. are returned as an instance of `event.Response`. Where a request has an [`Accept-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) header set (e.g. `gzip`, `br`) and there exists a matching *compressed version* of the said file on the file system (e.g. `./public/logo.png.gz`, `./public/logo.png.br`), the compressed version is served and the appropriate [`Content-Encoding`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding) response header is set.
|
|
1034
|
+
+ On the client, Webflo serves static files from the network, or from the application cache, where available.
|
|
1035
|
+
|
|
1036
|
+
#### Scenario 2: API Requests and Responses
|
|
1037
|
+
|
|
1038
|
+
JSON (API) requests (requests made with an [`Accept`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) header that matches `application/json`) are expected to get a JSON (API) response (responses with a [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) header of `application/json`). Webflo automatically responds by simply jsonfying workflow return values which are usually plain objects, or other jsonfyable types - `string`, `number`, `boolean`, `array`.
|
|
1039
|
+
+ Routes intended to be accessed this way are expected to return a jsonfyable value (or an instance of `event.Response` containing same) from the workflow.
|
|
1040
|
+
+ Workflow responses that are an instance of `event.Response` with a `Content-Type` header already set are sent as-is.
|
|
1041
|
+
|
|
1042
|
+
#### Scenario 3: Page Requests and Responses
|
|
1043
|
+
|
|
1044
|
+
HTML page requests (requests made to the server with an [`Accept`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept) header that matches `text/html`) are expected to get a HTML response (responses with a [`Content-Type`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type) header of `text/html`). Webflo automatically responds by rendering the workflow return value into an HTML response - via [Server-Side Rendering](#client-and-server-side-rendering).
|
|
1045
|
+
+ Routes intended to be accessed this way are expected to return a plain object (or an instance of `event.Response` containing same) from the workflow in order to be renderable.
|
|
1046
|
+
+ Workflow responses that are an instance of `event.Response` with a `Content-Type` header already set are sent as-is, and not rendered.
|
|
1047
|
+
|
|
1048
|
+
#### Scenario 4: Single Page Navigation Requests and Responses
|
|
1049
|
+
|
|
1050
|
+
In a Single Page Application layout, every navigation event (page-to-page navigation, history back and forward navigation, and form submissions) is expected to initiate a request/response flow without a full page reload, since the destination URLs are often based off the already loaded document. The Webflo client JS intercepts these navigation events and generates the equivalent request object with an `Accept` header of `application/json`, so that data can be obtained as a JSON object ([scenerio 2 above](#scenario-2-api-requests-and-responses)) for [Client-Side Rendering](#client-and-server-side-rendering).
|
|
1051
|
+
|
|
1052
|
+
The generated request also [hints the server](#custom-redirect-responses) on how to return cross-SPA redirects (redirects that will point to another origin, or to another SPA root (in a [Multi SPA](#in-a-multi-spa-layout) layout)) so that it can be handled manually by the client. The following headers are set: `X-Redirect-Policy: manual-when-cross-spa`, `X-Redirect-Code: 200`.
|
|
1053
|
+
+ Same-SPA redirects are sent as-is, and the Webflo client JS receives and renders the final data and updates the address bar with the final URL.
|
|
1054
|
+
+ Cross-SPA/cross-origin redirects are communicated back, as hinted, and the destination URL is opened as a fresh page load.
|
|
1055
|
+
|
|
1056
|
+
#### Scenario 5: Range Requests and Responses
|
|
1057
|
+
|
|
1058
|
+
In all cases, where a request specifies a [`Range`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range) header, Webflo automatically slices the response body to satisfy the range, and the appropriate [`Content-Range`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) response header is set.
|
|
1059
|
+
+ Workflow responses that are an instance of `event.Response` with a `Content-Range` header already set are sent as-is.
|
|
1060
|
+
|
|
1061
|
+
#### Other Requests and Responses
|
|
1062
|
+
|
|
1063
|
+
Workflows may return any other data type, e.g. an instance of the native [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData), [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob), [File](https://developer.mozilla.org/en-US/docs/Web/API/File), or [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream), etc., or an instance of `event.Response` containing same - usually on routes that do not double as a page route. Webflo tries to send these along with the appropriate response headers.
|
|
1064
|
+
|
|
1065
|
+
> **Note**
|
|
1066
|
+
> <br>The fact that all requests, even static file requests, are seen by route handlers, where defined, means that they get a chance to dynamically generate the responses that the client sees!
|
|
1067
|
+
|
|
1068
|
+
#### Custom Redirect Responses
|
|
1069
|
+
|
|
1070
|
+
It is possible to hint the server on how to serve redirect responses. The response code for these redirects could be substituted with a non-rediret status code so that it can be recieved as a normal response and handled manually. The following pair of headers make this possible: `X-Redirect-Code`, `X-Redirect-Policy`.
|
|
1071
|
+
+ The `X-Redirect-Code` can be any valid (but preferably, 2xx) HTTP status code. This is the response code that you want Webflo to substitute the actual redirect code with.
|
|
1072
|
+
+ The `X-Redirect-Policy` header can be any of `manual` - treat all redirects as manual, `manual-if-cross-origin` - treat cross-origin redirects as manual, `manual-if-cross-spa` - treat cross-SPA redirects (including cross-origin redirects) as manual.
|
|
1073
|
+
|
|
1074
|
+
Re-coded redirects have the standard `Location` header, and its own `X-Redirect-Code` response header containing the original redirect status code.
|
|
1075
|
+
|
|
1076
|
+
#### Failure Responses
|
|
1077
|
+
|
|
1078
|
+
Where workflows return `undefined`, a `Not Found` status is implied.
|
|
1079
|
+
+ On the server side, a `404` HTTP response is returned.
|
|
1080
|
+
+ On the client-side, the initiating document in the browser has its `document.state.data` emptied. The error is also exposed on the [`document.state.network.error`](#the-documentstatenetwork-object) property.
|
|
1081
|
+
|
|
1082
|
+
Where workflows throw an exception, an *error* status is implied.
|
|
1083
|
+
+ On the server side, the error is logged and a `500` HTTP response is returned.
|
|
1084
|
+
+ On the client-side, the initiating document in the browser has its `document.state.data` emptied. The error is also exposed on the [`document.state.network.error`](#the-documentstatenetwork-object) property.
|
|
1085
|
+
|
|
1086
|
+
#### Cookie Responses
|
|
1087
|
+
|
|
1088
|
+
Handlers can set [response cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) via the standard `Response` constructor, or using the standard `Headers.set()` method.
|
|
1089
|
+
|
|
1090
|
+
```js
|
|
1091
|
+
let response = event.Response(data, { headers: { 'Set-Cookie': cookieString }});
|
|
1092
|
+
|
|
1093
|
+
response.headers.set('Set-Cookie', cookieString);
|
|
1094
|
+
```
|
|
1095
|
+
|
|
1096
|
+
Webflo also offers a *convenience* method.
|
|
1097
|
+
|
|
1098
|
+
```js
|
|
1099
|
+
let response = event.Response(data, { headers: { cookies: cookieString }});
|
|
1100
|
+
|
|
1101
|
+
response.headers.cookies = { 'Cookie-1': cookieString, 'Cookie-2': cookie2String };
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
```js
|
|
1105
|
+
let cookieObject = { value: 'cookie-val', expires, maxAge, domain, path, secure, HttpOnly, sameSite };
|
|
1106
|
+
let cookie2Object = { value: 'cookie2-val' };
|
|
1107
|
+
response.headers.cookies = { 'Cookie-1': cookieObject };
|
|
1108
|
+
response.headers.cookies = { 'Cookie-2': cookie2Object };
|
|
1109
|
+
|
|
1110
|
+
console.log(response.headers.cookies); // { 'Cookie-1': cookieObject, 'Cookie-2': cookie2Object };
|
|
1111
|
+
````
|
|
1112
|
+
|
|
1113
|
+
Set cookies are [accessed](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie) on the next request via request headers.
|
|
1114
|
+
|
|
1115
|
+
```js
|
|
1116
|
+
console.log(event.request.headers.get('Cookie')); // Cookie-1=cookie-val&Cookie-2=cookie2-val;
|
|
1117
|
+
````
|
|
1118
|
+
|
|
1119
|
+
Webflo also offers a *convenience* method.
|
|
1120
|
+
|
|
1121
|
+
```js
|
|
1122
|
+
console.log(event.request.headers.cookies); // { 'Cookie-1': 'cookie-val', 'Cookie-2': 'cookie2-val' };
|
|
1123
|
+
````
|
|
1124
|
+
|
|
1125
|
+
### Webflo Applications
|
|
1126
|
+
|
|
1127
|
+
In just a few concepts, Webflo comes ready for any type of application! Now, additional details of a Webflo app - depending on the type - are covered in the following sections.
|
|
1128
|
+
|
|
1129
|
+
+ [Application State](#application-state)
|
|
1130
|
+
+ [Client-Side Applications](#client-side-applications)
|
|
1131
|
+
+ [API Backends](#api-backends)
|
|
1132
|
+
+ [Static Sites](#static-sites)
|
|
1133
|
+
|
|
1134
|
+
#### Application State
|
|
1135
|
+
|
|
1136
|
+
For all things application state, Webflo leverages the [State API](https://github.com/webqit/oohtml#state-api) that's natively available in OOHTML-based documents - both client-side and server-side. This API exposes an application-wide `document.state` object and a per-element `element.state` object. And these are *live* read/write objects that can be observed for property changes using the [Observer API](https://github.com/webqit/observer). It comes off as the simplest approach to state and reactivity!
|
|
1137
|
+
|
|
1138
|
+
> **Note**
|
|
1139
|
+
> <br>The State API is not available when the OOHTML support level in config is switched away from `full` and `scripting`.
|
|
1140
|
+
|
|
1141
|
+
##### The `document.state.data` Object
|
|
1142
|
+
|
|
1143
|
+
This property represents the data obtained from route handers on each navigation. Webflo simply exposes this data and lets the page's [rendering logic](#client-and-server-side-rendering), or other parts of the application, take over.
|
|
1144
|
+
|
|
1145
|
+
```js
|
|
1146
|
+
Observer.observe(document.state, 'data', e => {
|
|
1147
|
+
console.log('Current page data is: ', e.value);
|
|
1148
|
+
});
|
|
1149
|
+
```
|
|
1150
|
+
|
|
1151
|
+
##### The `document.state.url` Object
|
|
1152
|
+
|
|
1153
|
+
This is a *live* object that reperesents the properties of the application URL at any point in time. The object exposes the same URL properties as with the [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) API, but as *live* properties that can be observed as navigation happens, and modified to initiate navigation - all using the [Observer API](https://github.com/webqit/observer).
|
|
1154
|
+
|
|
1155
|
+
```js
|
|
1156
|
+
console.log(document.state.url) // { hash, host, hostname, href, origin, password, pathname, port, protocol, search, searchParams, username }
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
```js
|
|
1160
|
+
Observer.observe(document.state.url, 'hash', e => {
|
|
1161
|
+
console.log(document.state.url.hash === e.value); // true
|
|
1162
|
+
});
|
|
1163
|
+
```
|
|
1164
|
+
|
|
1165
|
+
```js
|
|
1166
|
+
// Navigates to "/login#form" as if a link was clicked
|
|
1167
|
+
document.addEventListener('synthetic-navigation', e => {
|
|
1168
|
+
Observer.set(document.state.url, 'href', '/login#form');
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
// Or...
|
|
1172
|
+
document.addEventListener('synthetic-navigation', e => {
|
|
1173
|
+
Observer.set(document.state.url, { pathname: '/login', hash: '#form' });
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
console.log(document.state.url.hash); // #form
|
|
1177
|
+
```
|
|
1178
|
+
|
|
1179
|
+
There is also the *convenience* `query` property that offers the URL parameters as a *live* object.
|
|
1180
|
+
|
|
1181
|
+
```js
|
|
1182
|
+
// For URL: http://localhost:3000/login?as=student
|
|
1183
|
+
console.log(document.state.url.query.as) // student
|
|
1184
|
+
|
|
1185
|
+
// Re-rewrite the URL and initiate navigation by simply modifying a query parameter
|
|
1186
|
+
document.addEventListener('synthetic-navigation', e => {
|
|
1187
|
+
Observer.set(document.state.url.query, 'as', 'business');
|
|
1188
|
+
});
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1191
|
+
#### Client-Side Applications
|
|
1192
|
+
|
|
1193
|
+
Web pages that embed the Webflo client JS bundle deliver a great user experience.
|
|
1194
|
+
+ **First-paint-ready.** On the first page request, you get a [server-rendered](#client-and-server-side-rendering) HTML page that's optimized for the first paint of your application.
|
|
1195
|
+
+ **Fluid and app-like.** On being loaded, the state of the application is restored through hydration, and [subsequent navigations](#spa-navigation) are sleek and instant, while performing [Client-Side Rendering](#client-and-server-side-rendering).
|
|
1196
|
+
|
|
1197
|
+
For these client-side applications, the `npm run generate` command does both the building and embedding of the script for each document root in the application.
|
|
1198
|
+
|
|
1199
|
+
##### SPA Navigation
|
|
1200
|
+
|
|
1201
|
+
Unless disabled in [config](#spa_navigation), it is factored-in at build time for the application client JS to be able to automatially figure out when to intercept a navigation event and prevent a full page reload, and when not to. It follows the following rules:
|
|
1202
|
+
+ When it ascertains that the destination URL is based on the current running `index.html` document in the browser (an SPA architecture), a full page reload is prevented for *soft* navigation. But where the destination URL points out of the current document root (a [Multi SPA](#in-a-multi-spa-layout) architecture), navigation is allowed as a normal page load, and a new page root is loaded.
|
|
1203
|
+
+ If navigation is initiated with any of the following keys pressed: Meta Key, Alt Key, Shift Key, Ctrl Key, navigation is allowed to work the default way - regardless of the first rule above.
|
|
1204
|
+
+ If navigation is initiated from a link element that has the `target` attribute, or the `download` attribute, navigation is allowed to work the default way - regardless of the first rule above.
|
|
1205
|
+
+ If navigation is initiated from a form element that has the `target` attribute, navigation is allowed to work the default way - regardless of the first rule above.
|
|
1206
|
+
|
|
1207
|
+
> To entirely disable SPA navigation in config where necessary, run `webflo config client` and follow the prompt.
|
|
1208
|
+
|
|
1209
|
+
##### SPA State
|
|
1210
|
+
|
|
1211
|
+
In addition to [the universal concept of state](#application-state) of a Webflo application, state on the client side also includes the following aspects of the client-side lifecycle that can be used to provide visual cues on the UI.
|
|
1212
|
+
|
|
1213
|
+
###### The `document.state.network` Object
|
|
1214
|
+
|
|
1215
|
+
This is a *live* object that exposes the network activity and network state of the application.
|
|
1216
|
+
|
|
1217
|
+
```js
|
|
1218
|
+
console.log(document.state.network) // { requesting, remote, error, redirecting, online, }
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
+ **`network.requesting`: `null|Object`** - This property tells when a request is ongoing, in which case it exposes the `params` object used to initiate the request.
|
|
1222
|
+
+ **`network.remote`: `null|String`** - This property tells when a remote request is ongoing - usually the same navigation requests as at `network.requesting`, but when not handled by any client-side route handlers, or when `next()`ed to this point by route handlers. The `remote` property also goes live when a route handler calls the special `fetch()` function that they recieve on their fourth parameter.
|
|
1223
|
+
+ **`network.error`: `null|Error`** - This property tells when a request is *errored* in which case it contains an `Error` instance of the error. For requests that can be retried, the `Error` instance also has a custom `retry()` method.
|
|
1224
|
+
+ **`network.redirecting`: `null|String`** - This property tells when a client-side redirect is ongoing - see [Scenario 4: Single Page Navigation Requests and Responses](#scenario-4-single-page-navigation-requests-and-responses) - in which case it exposes the destination URL.
|
|
1225
|
+
+ **`network.online`: `Boolean`** - This property tells of [the browser's ability to connect to the network](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine).
|
|
1226
|
+
|
|
1227
|
+
Now, being a *live* object means that `document.state.network` can be observed using the [Observer API](https://github.com/webqit/observer).
|
|
1228
|
+
|
|
1229
|
+
```js
|
|
1230
|
+
// Visualize the network state
|
|
1231
|
+
let onlineVisualizer = changes => {
|
|
1232
|
+
changes.forEach(e => {
|
|
1233
|
+
console.log(e.name, ':', e.value);
|
|
1234
|
+
});
|
|
1235
|
+
};
|
|
1236
|
+
Observer.observe(document.state.network, onlineVisualizer);
|
|
1237
|
+
// Or: Observer.observe(document, [ ['state', 'network'] ], onlineVisualizer, { subtree: true });
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
```js
|
|
1241
|
+
// Visualize the 'online' property
|
|
1242
|
+
let onlineVisualizer = e => {
|
|
1243
|
+
console.log('You are ', e.value ? 'online' : 'offline');
|
|
1244
|
+
};
|
|
1245
|
+
Observer.observe(document.state.network, 'online', onlineVisualizer);
|
|
1246
|
+
// Or: Observer.observe(document.state, [ ['network', 'online'] ], onlineVisualizer);
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
```js
|
|
1250
|
+
// Catch request errors; attempt a retry
|
|
1251
|
+
Observer.observe(document.state.network, 'error', e => {
|
|
1252
|
+
if (!e.value) return;
|
|
1253
|
+
console.error(e.value.message);
|
|
1254
|
+
if (e.value.retry) {
|
|
1255
|
+
console.error('Retrying...');
|
|
1256
|
+
e.value.retry();
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
###### Form Actions
|
|
1262
|
+
|
|
1263
|
+
When navigation occurs [via form submissions](#scenario-4-single-page-navigation-requests-and-responses), the form element and the submit button are made to go on the *active* state while the request is processed. For both of these elements, the Webflo client simply sets the `element.state.active` to `true` on submission, then `false`, on completion.
|
|
1264
|
+
|
|
1265
|
+
##### Service Workers
|
|
1266
|
+
|
|
1267
|
+
Webflo client-side applications are intended to provide an app-like-first experience. So unless disabled in [config](#enable_service_worker), a [Service Worker](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) is built as part of your application on running the `npm run generate` command. You may define [route handlers in the `/worker` directory](#handler-functions-and-layout) of your application, and these will be built into the service worker to handle Same-Origin requests of the application. Where there are no *worker* handlers, or where they forward these requests, the request is fetched, either from the cache, or from the network, depending on the fetching strategy built into the Service Worker.
|
|
1268
|
+
|
|
1269
|
+
###### Fetching Strategy
|
|
1270
|
+
|
|
1271
|
+
+ **Network First** - This strategy tells the Service Worker to always attempt fetching from the network first for given resources, before fetching from the cache. On every successful network fetch, a copy of the response is saved to the cache for next time. (This is good for resources that need to be fresh to the user on a "best effort" basis.) Unless [changed](#default_fetching_strategy), this is Webflo's default fetching strategy. When not the default strategy, a list of specific URLs that should be fetched this way can be [configured](#network_first_urls).
|
|
1272
|
+
+ **Cache First** - This strategy tells the Service Worker to always attempt fetching from the cache first for given resources, before fetching from the network. After serving a cached response, or where not found in cache, a network fetch happens and a copy of the response is saved to the cache for next time. (This is good for resources that do not critially need to be fresh to the user.) When not the default strategy, a list of specific URLs that should be fetched this way can be [configured](#cache_first_urls).
|
|
1273
|
+
+ **Network Only** - This strategy tells the Service Worker to always fetch given resources from the network only. They are simply not available when offline. (This is good for resources that critially need to be fresh to the user.) When not the default strategy, a list of specific URLs that should be fetched this way can be [configured](#network_only_urls).
|
|
1274
|
+
+ **Cache Only** - This strategy tells the Service Worker to always fetch given resources from the cache only. (This is good for resources that do not change often.) When not the default strategy, a list of specific URLs that should be fetched this way can be [configured](#cache_only_urls). The listed resources are pre-cached ahead of when they'll be needed - and are served from the cache each time. (Pre-caching happens on the one-time `install` event of the Service Worker.)
|
|
1275
|
+
|
|
1276
|
+
In all cases above, the convention for specifying URLs for a strategy accepts [URL patterns](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) - against which URLs can be matched on the fly. For example, to place all files in an `/image` directory (and subdirectories) on the *Cache First* strategy, the pattern `/image/*` can be used. To place all `.svg` files in an `/icons` directory (including subdirectories) on the *Cache Only* strategy, the pattern `/icons/*.svg` can be used. (Specifically for the *Cache Only* strategy, patterns are resolved at Service Worker build-time, and each pattern must match, at least, a file.)
|
|
1277
|
+
|
|
1278
|
+
###### Cross-Thread Communications
|
|
1279
|
+
|
|
1280
|
+
A couple APIs exists in browsers for establishing a two-way communication channel between a page and its service worker, for firing UI Notifications from either ends, and for implementing Push Notifications. Webflo offers to simply this with a unifying set of conventions:
|
|
1281
|
+
|
|
1282
|
+
+ The `workport` API - an object with simple methods for working with *cross-thread* messages, UI and Push Notifications.
|
|
1283
|
+
|
|
1284
|
+
On both the client and worker side of your application, the `workport` object is accessible from route handlers as `this.runtime.workport`.
|
|
1285
|
+
|
|
1286
|
+
```js
|
|
1287
|
+
/**
|
|
1288
|
+
[client|worker]
|
|
1289
|
+
βββ index.js
|
|
1290
|
+
*/
|
|
1291
|
+
export default async function(event, context, next) {
|
|
1292
|
+
let { workport } = this.runtime;
|
|
1293
|
+
workport.messaging.post({ ... });
|
|
1294
|
+
return { ... };
|
|
1295
|
+
}
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
For cross-thread messaging, both sides of the API exposes the following methods:
|
|
1299
|
+
|
|
1300
|
+
+ **`.messaging.post()`** - for sending arbitrary data to the other side. E.g. `workport.messaging.post({ type: 'TEST' })`.
|
|
1301
|
+
+ **`.messaging.listen()`** - for listening to `message` event from the other side. E.g. `workport.messaging.listen(event => console.log(event.data.type))`. (See [`window: onmessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/message_event), [`worker: onmessage`](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/message_event).)
|
|
1302
|
+
+ **`.messaging.request()`** - for sending *replyable* messages to the other side, using the [MessageChannel](https://developer.mozilla.org/docs/Web/API/MessageChannel/MessageChannel) API.
|
|
1303
|
+
|
|
1304
|
+
```js
|
|
1305
|
+
// On the worker side
|
|
1306
|
+
workport.messaging.listen(event => {
|
|
1307
|
+
console.log(event.data);
|
|
1308
|
+
if (event.ports[0]) {
|
|
1309
|
+
event.ports[0].postMessage({ type: 'WORKS' });
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
```js
|
|
1315
|
+
// On the client side
|
|
1316
|
+
let response = await workport.messaging.request({ type: 'TEST' });
|
|
1317
|
+
console.log(response); // { type: 'WORKS' }
|
|
1318
|
+
```
|
|
1319
|
+
|
|
1320
|
+
+ **`.messaging.channel()`** - for sending *broadcast* messages to the other side - including all other browsing contents that live on the same origin, using the [Broadcast Channel](https://developer.mozilla.org/docs/Web/API/Broadcast_Channel_API) API.
|
|
1321
|
+
|
|
1322
|
+
```js
|
|
1323
|
+
// On the worker side
|
|
1324
|
+
let channelId = 'channel-1';
|
|
1325
|
+
workport.messaging.channel(channelId).listen(event => {
|
|
1326
|
+
console.log(event.data);
|
|
1327
|
+
});
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
```js
|
|
1331
|
+
// On the client side
|
|
1332
|
+
let channelId = 'channel-1';
|
|
1333
|
+
workport.messaging.channel(channelId).broadcast({ type: 'TEST' });
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
For [UI Nofitications](https://developer.mozilla.org/en-US/docs/Web/API/notification), both sides of the API exposes the following methods:
|
|
1337
|
+
|
|
1338
|
+
+ **`.nofitications.fire()`** - for firing up a UI notification. This uses the [`Nofitications constructor`](https://developer.mozilla.org/en-US/docs/Web/API/Notification/Notification), and thus, accepts the same arguments as the constructor. But it returns a `Promise` that resolves when the notification is *clicked* or *closed*, but rejects when the notification encounters an error, or when the application isn't granted the [notification permission](https://developer.mozilla.org/en-US/docs/Web/API/Notification/requestPermission).
|
|
1339
|
+
|
|
1340
|
+
```js
|
|
1341
|
+
let title = 'Test Nofitication';
|
|
1342
|
+
let options = { body: '...', icon: '...', actions: [ ... ] };
|
|
1343
|
+
workport.nofitications.fire(title, options).then(event => {
|
|
1344
|
+
console.log(event.action);
|
|
1345
|
+
});
|
|
1346
|
+
```
|
|
1347
|
+
|
|
1348
|
+
+ **`.nofitications.listen()`** - (in Service-Workers) for listening to [`notificationclick`](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event) events. (Handlers are called each time a notification is clicked.)
|
|
1349
|
+
|
|
1350
|
+
```js
|
|
1351
|
+
workport.nofitications.listen(event => {
|
|
1352
|
+
console.log(event.action);
|
|
1353
|
+
});
|
|
1354
|
+
```
|
|
1355
|
+
|
|
1356
|
+
For [Push Nofitications](https://developer.mozilla.org/en-US/docs/Web/API/Push_API), the client-side of the API exposes the following methods:
|
|
1357
|
+
|
|
1358
|
+
+ **`.push.subscribe()`** - the equivalent of the [`PushManager.subscribe()`](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe) method. (But this can also take the *applicationServerKey* as a first argument, and other options as a second argument, in which case it automatically runs the key through an `urlBase64ToUint8Array()` function.)
|
|
1359
|
+
+ **`.push.unsubscribe()`** - the equivalent of the [`PushSubscription.unsubscribe()`](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription/unsubscribe) method.
|
|
1360
|
+
+ **`.push.getSubscription()`** - the equivalent of the [`PushManager.getSubscription()`](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/getSubscription) method.
|
|
1361
|
+
|
|
1362
|
+
The worker-side of the API exposes the following methods:
|
|
1363
|
+
|
|
1364
|
+
+ **`.push.listen()`** - for listening to the [`push`](https://developer.mozilla.org/en-US/docs/Web/API/PushEvent) from within Service Workers. E.g. `workport.push.listen(event => console.log(event.data.type))`.
|
|
1365
|
+
|
|
1366
|
+
+ Route *events* - simple route events that fire when messaging and notification events happen.
|
|
1367
|
+
|
|
1368
|
+
On both the client and worker side of your application, you can define an event listener alongside your *root* route handler. The event listener is called to handle all messaging and notification events that happen.
|
|
1369
|
+
|
|
1370
|
+
```js
|
|
1371
|
+
/**
|
|
1372
|
+
[client|worker]
|
|
1373
|
+
βββ index.js
|
|
1374
|
+
*/
|
|
1375
|
+
export default async function(event, context, next) {
|
|
1376
|
+
return { ... };
|
|
1377
|
+
}
|
|
1378
|
+
export async function alert(event, context, next) {
|
|
1379
|
+
return { ... };
|
|
1380
|
+
}
|
|
1381
|
+
```
|
|
1382
|
+
|
|
1383
|
+
The event type is given in the `event.type` property. This could be:
|
|
1384
|
+
|
|
1385
|
+
+ **`message`** - both client and worker side. For *replyable* messages, the event handler's return value is automatically sent back as response.
|
|
1386
|
+
+ **`notificationclick`** - worker side.
|
|
1387
|
+
+ **`push`** - worker side.
|
|
1388
|
+
|
|
1389
|
+
The `next()` function could be used to delegate the handling of an event to step handlers where defined. This time, the path name must be given as a second argument to the call.
|
|
1390
|
+
|
|
1391
|
+
```js
|
|
1392
|
+
/**
|
|
1393
|
+
worker
|
|
1394
|
+
βββ index.js
|
|
1395
|
+
*/
|
|
1396
|
+
export async function alert(event, context, next) {
|
|
1397
|
+
if (event.type === 'push') {
|
|
1398
|
+
await next(context, '/services/push');
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
console.log(event.type);
|
|
1402
|
+
}
|
|
1403
|
+
```
|
|
1404
|
+
|
|
1405
|
+
#### API Backends
|
|
1406
|
+
|
|
1407
|
+
In Webflo, an API backend is what you, in essence, come off with with your server-side routes.
|
|
1408
|
+
|
|
1409
|
+
```js
|
|
1410
|
+
/**
|
|
1411
|
+
server
|
|
1412
|
+
βββ index.js
|
|
1413
|
+
*/
|
|
1414
|
+
export default function(event, context, next) {
|
|
1415
|
+
if (next.pathname) return next();
|
|
1416
|
+
return { ... };
|
|
1417
|
+
}
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
You are always able to lay out your route handlers in the structure for a formalized REST API.
|
|
1421
|
+
|
|
1422
|
+
```shell
|
|
1423
|
+
server
|
|
1424
|
+
βββ index.js
|
|
1425
|
+
βββ api/v1/index.js
|
|
1426
|
+
βββ api/v1/products/index.js
|
|
1427
|
+
```
|
|
1428
|
+
|
|
1429
|
+
And if you will partition your backend for both page routes and a formalized REST API...
|
|
1430
|
+
|
|
1431
|
+
```shell
|
|
1432
|
+
server
|
|
1433
|
+
βββ index.js βββ
|
|
1434
|
+
βββ cart/index.js βββ Page Routes
|
|
1435
|
+
βββ products/index.js βββ
|
|
1436
|
+
βββ api/v1/index.js βββ
|
|
1437
|
+
βββ api/v1/orders/index.js βββ REST API
|
|
1438
|
+
βββ api/v1/products/index.js βββ
|
|
1439
|
+
```
|
|
1440
|
+
|
|
1441
|
+
...you could get your page routes to run off your REST API by re-routing your `next()` calls to consume the appropriate API route.
|
|
1442
|
+
|
|
1443
|
+
```js
|
|
1444
|
+
/**
|
|
1445
|
+
server
|
|
1446
|
+
βββ cart/index.js
|
|
1447
|
+
*/
|
|
1448
|
+
export default async function(event, context, next) {
|
|
1449
|
+
if (next.pathname) {
|
|
1450
|
+
return next();
|
|
1451
|
+
}
|
|
1452
|
+
// Items to display in cart are in the "/api/v1/orders" route
|
|
1453
|
+
let cartItems = await next(context, `/api/v1/orders?user_id=1`);
|
|
1454
|
+
return { title: 'Your Cart', ...cartItems };
|
|
1455
|
+
}
|
|
1456
|
+
```
|
|
1457
|
+
|
|
1458
|
+
This way, there is one source of truth for your application - both when visiting from a page and from a REST API.
|
|
1459
|
+
|
|
1460
|
+
#### Static Sites
|
|
1461
|
+
|
|
1462
|
+
You can build an entire static site from off the `/public` directory alone! It's all about placing files and HTML pages there to be served statically!
|
|
1463
|
+
|
|
1464
|
+
Here, static pages means pages that are not server-rendered during the request/response cycle, but served directly from files. You are free to hand-author each of them - either as standalone `index.html` files, or as a combination of `index.html` *roots* plus *templates* that can all get resolved client-side. The [Pages, Layout and Templating](https://github.com/webqit/webflo/blob/master/README.md#pages-layout-and-templating) section covers layout patterns.
|
|
1465
|
+
|
|
1466
|
+
On the other hand, if you have a dynamic site, you can make a static site off it! The idea is to turn on your server and crawl your dynamic site via HTTP requests, outputting static HTML representations of each page. This is called *Pre-Rendering* or *Static-Site Generation* (SSG)!
|
|
1467
|
+
|
|
1468
|
+
A simple tool, like [`staticgen`](https://github.com/tj/staticgen), or the basic [`wget`](https://www.gnu.org/software/wget/) command (similar to `curl`), can get this done in an instant. On figuring out the command that works best for you, you may want to add an alias of the command to your npm scripts in `package.json`.
|
|
1469
|
+
|
|
1470
|
+
|
|
1471
|
+
```json
|
|
1472
|
+
"scripts": {
|
|
1473
|
+
"generate:site": "wget -P public -nv -nH -r -E localhost:3000"
|
|
1474
|
+
}
|
|
1475
|
+
```
|
|
1476
|
+
|
|
1477
|
+
> **Note**
|
|
1478
|
+
> <br>Above, we used the `-P` flag to specify the output directory as `public`, the `-nv` flag to opt into βnon-verboseβ mode which outputs less information, the `-r` flag to get it to crawl and download recursively, and the `-E` flag to get it to add the `.html` extension to generated files.
|
|
1479
|
+
|
|
1480
|
+
You have a static site!
|
|
1481
|
+
|
|
1482
|
+
### Workflow API
|
|
1483
|
+
|
|
1484
|
+
> TODO
|
|
1485
|
+
|
|
1486
|
+
### Webflo Config
|
|
1487
|
+
|
|
1488
|
+
> TODO
|
|
1489
|
+
|
|
1490
|
+
## Getting Started
|
|
1491
|
+
|
|
1492
|
+
Your baby steps with Webflo could be a ["Hello World" `index.html` file](#installation), optionally, next with a route handler that returns an equivalent `{ title: 'Hello World'}` object!
|
|
1493
|
+
|
|
1494
|
+
You could soon be taking all your ideas to Webflo! π
|
|
1495
|
+
|
|
1496
|
+
> **Warning**
|
|
1497
|
+
> <br>Webflo is still evolving and some things may change quickly! Let's not go to production just yet!
|
|
1498
|
+
|
|
1499
|
+
## Getting Involved
|
|
1500
|
+
|
|
1501
|
+
All forms of contributions and PR are welcome! To report bugs or request features, please submit an [issue](https://github.com/webqit/webflo/issues). For general discussions, ideation or community help, please join our github [Discussions](https://github.com/webqit/webflo/discussions).
|
|
1502
|
+
|
|
1503
|
+
## License
|
|
1504
|
+
|
|
1505
|
+
MIT.
|
|
1506
|
+
|
|
1507
|
+
...
|
|
1508
|
+
|
|
1509
|
+
## Getting Involved
|
|
1510
|
+
|
|
1511
|
+
All forms of contributions and PR are welcome! To report bugs or request features, please submit an [issue](https://github.com/webqit/webflo/issues).
|
|
754
1512
|
|
|
755
|
-
|
|
1513
|
+
## License
|
|
756
1514
|
|
|
1515
|
+
MIT.
|