@webqit/webflo 0.10.2 → 0.10.5
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 +750 -3
- package/package.json +3 -3
- package/src/Context.js +8 -4
- package/src/config-pi/deployment/Env.js +2 -2
- package/src/config-pi/deployment/Layout.js +2 -2
- package/src/config-pi/deployment/Origins.js +2 -2
- package/src/config-pi/deployment/Virtualization.js +2 -2
- package/src/config-pi/runtime/Client.js +45 -14
- package/src/config-pi/runtime/Server.js +26 -10
- package/src/config-pi/runtime/client/Worker.js +2 -2
- package/src/config-pi/runtime/server/Headers.js +2 -2
- package/src/config-pi/runtime/server/Redirects.js +2 -2
- package/src/config-pi/static/Manifest.js +2 -2
- package/src/config-pi/static/Ssg.js +2 -2
- package/src/runtime-pi/client/Runtime.js +28 -23
- package/src/runtime-pi/client/RuntimeClient.js +28 -15
- package/src/runtime-pi/client/generate.js +251 -77
- package/src/runtime-pi/client/{generate.oohtml.js → oohtml/full.js} +0 -0
- package/src/runtime-pi/client/oohtml/namespacing.js +7 -0
- package/src/runtime-pi/client/oohtml/scripting.js +8 -0
- package/src/runtime-pi/client/oohtml/templating.js +8 -0
- package/src/runtime-pi/client/worker/Worker.js +21 -23
- package/src/runtime-pi/server/Router.js +2 -2
- package/src/runtime-pi/server/Runtime.js +8 -3
- package/src/runtime-pi/server/RuntimeClient.js +21 -11
- package/src/webflo.js +7 -9
- package/test/site/public/bundle.html +3 -0
- package/test/site/public/bundle.html.json +3 -0
- package/test/site/public/bundle.js +2 -0
- package/test/site/public/bundle.js.gz +0 -0
- package/test/site/public/bundle.webflo.js +15 -0
- package/test/site/public/bundle.webflo.js.gz +0 -0
- package/test/site/public/index.html +30 -0
- package/test/site/public/page-2/bundle.html +5 -0
- package/test/site/public/page-2/bundle.html.json +1 -0
- package/test/site/public/page-2/bundle.js +2 -0
- package/test/site/public/page-2/bundle.js.gz +0 -0
- package/test/site/public/page-2/index.html +47 -0
- package/test/site/public/page-2/logo-130x130.png +0 -0
- package/test/site/public/page-2/main.html +3 -0
- package/test/site/public/page-4/subpage/bundle.html +0 -0
- package/test/site/public/page-4/subpage/bundle.html.json +1 -0
- package/test/site/public/page-4/subpage/bundle.js +2 -0
- package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
- package/test/site/public/page-4/subpage/index.html +31 -0
- package/test/site/public/worker.js +3 -0
- package/test/site/public/worker.js.gz +0 -0
- package/test/site/server/index.js +8 -0
- package/src/Cli.js +0 -131
- package/src/Configurator.js +0 -97
package/README.md
CHANGED
|
@@ -2,8 +2,755 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- BADGES/ -->
|
|
4
4
|
|
|
5
|
-
<span class="badge-npmversion"><a href="https://npmjs.org/package/@
|
|
6
|
-
<span class="badge-npmdownloads"><a href="https://npmjs.org/package/@
|
|
7
|
-
<span class="badge-patreon"><a href="https://patreon.com/ox_harris" title="Donate to this project using Patreon"><img src="https://img.shields.io/badge/patreon-donate-yellow.svg" alt="Patreon donate button" /></a></span>
|
|
5
|
+
<span class="badge-npmversion"><a href="https://npmjs.org/package/@webqit/webflo" title="View this project on NPM"><img src="https://img.shields.io/npm/v/@webqit/webflo.svg" alt="NPM version" /></a></span>
|
|
6
|
+
<span class="badge-npmdownloads"><a href="https://npmjs.org/package/@webqit/webflo" title="View this project on NPM"><img src="https://img.shields.io/npm/dm/@webqit/webflo.svg" alt="NPM downloads" /></a></span>
|
|
8
7
|
|
|
9
8
|
<!-- /BADGES -->
|
|
9
|
+
|
|
10
|
+
Webflo is a universal *web*, *mobile*, and *API backend* framework built to solve for the underrated `.html` + `.css` + `.js` stack! You'll be delighted at how it is crafted to keep your *tooling budget* low and your *application performance* high!
|
|
11
|
+
|
|
12
|
+
Webflo lets you build all things web, mobile, and API backends - anything from as basic as a static `index.html` page to as rich as a universal app capable of MPA, SPA, or hybrid routing, SSG, SSR, CSR, or hybrid rendering, offline functionalities and PWA installability, etc - without *loosing* your *vanilla web* stack!
|
|
13
|
+
|
|
14
|
+
We've put all of that up for a straight read! (Might turn out you already know Webflo 😃)
|
|
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.!
|
|
17
|
+
|
|
18
|
+
## Documentation
|
|
19
|
+
|
|
20
|
+
+ [Overview](#overview)
|
|
21
|
+
+ [Installation](#installation)
|
|
22
|
+
+ [Concepts](#concepts)
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
|
|
26
|
+
<details>
|
|
27
|
+
<summary><b>Build <i>scalable</i> anything</b> using a <i>Divide-and-Conquer Algorithm<a href="https://en.wikipedia.org/wiki/Divide-and-conquer_algorithm"><small><sup>[i]</sup></small></a></i>! Webflo gives you a <i>workflow</i>-based design pattern for laying out your routes; and this is new!</summary>
|
|
28
|
+
<br>
|
|
29
|
+
|
|
30
|
+
Webflo lets you layout your application routes using *handler functions* as the building block, each defined in an `index.js` file.
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
/**
|
|
34
|
+
├── index.js
|
|
35
|
+
*/
|
|
36
|
+
export default function(event, context, next) {
|
|
37
|
+
return { title: 'Home' };
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You nest them as *step functions* in a structure that models your application's URL structure.
|
|
42
|
+
|
|
43
|
+
```shell
|
|
44
|
+
├── index.js --------------------------------- http://localhost:3000
|
|
45
|
+
└── products/index.js ------------------------ http://localhost:3000/products
|
|
46
|
+
└── stickers/index.js ------------------ http://localhost:3000/products/stickers
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
They form a step-based workflow for your routes, with each step controlling the next...
|
|
50
|
+
|
|
51
|
+
```js
|
|
52
|
+
/**
|
|
53
|
+
├── index.js
|
|
54
|
+
*/
|
|
55
|
+
export default function(event, context, next) {
|
|
56
|
+
if (next.stepname) {
|
|
57
|
+
return next();
|
|
58
|
+
}
|
|
59
|
+
return { title: 'Home' };
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
/**
|
|
65
|
+
├── products/index.js
|
|
66
|
+
*/
|
|
67
|
+
export default function(event, context, next) {
|
|
68
|
+
if (next.stepname) {
|
|
69
|
+
return next();
|
|
70
|
+
}
|
|
71
|
+
return { title: 'Products' };
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
...enabling *all sorts of composition* along the way!
|
|
76
|
+
|
|
77
|
+
```js
|
|
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
|
+
```
|
|
90
|
+
|
|
91
|
+
Now, all of this gives you new way to break work down on each of your application routes!
|
|
92
|
+
</details>
|
|
93
|
+
|
|
94
|
+
<details>
|
|
95
|
+
<summary><b>Build <i>future-proof</i> anything</b> by banking more on the 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>
|
|
96
|
+
<br>
|
|
97
|
+
|
|
98
|
+
All parts of a Webflo app speak the same language and seamlessly talk to each other and the web platform itself without the usual compiler!
|
|
99
|
+
|
|
100
|
+
For when your application involves routing:
|
|
101
|
+
+ [The Fetch Standard](https://fetch.spec.whatwg.org/) (across client, server, and Service Worker environments) - 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 - for all things *requests and responses*. ([Details ahead](#))
|
|
102
|
+
|
|
103
|
+
> Your Request and Response objects are also able to *seamlessly exchange* - in addition to JSON - other standard objects like [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), and [ReadableStream](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
|
|
104
|
+
|
|
105
|
+
+ [WHATWG URL](https://url.spec.whatwg.org/) and [WHATWG URLPattern](https://developer.mozilla.org/en-US/docs/Web/API/URLPattern) (across client, server, and Service Worker environments) for all things *URL* and *URL pattern matching*. ([Details ahead](#))
|
|
106
|
+
|
|
107
|
+
For when your application involves pages and a UI:
|
|
108
|
+
+ [The HTML Standard](https://html.spec.whatwg.org/) (across client, server, and Service Worker environments) for all things *markup* - conventional `.html`-based pages and templates, valid HTML syntax, etc. You go with a "zero-JavaScript" proposition or with *Progressive Enhancement* that makes do with "just-enough JavaScript"!
|
|
109
|
+
|
|
110
|
+
> Your markup is also easily extendable with the [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) templating system, [Reactive Scripts (`<script type="subscript"></script>`)](https://github.com/webqit/oohtml#subscript), and whatever else is possible with HTML.
|
|
111
|
+
|
|
112
|
+
+ [WHATWG DOM](https://dom.spec.whatwg.org/) (across client and server environments) for all things *programmatic pages* - rendering, manipulation, interactivity, etc.
|
|
113
|
+
|
|
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), [The State API (`document.state` and `element.state`)](https://github.com/webqit/oohtml#state-api), and whatever else is possible with the DOM.
|
|
115
|
+
|
|
116
|
+
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) - with full support for route handlers - for [Progressive Web Apps (PWA)](https://web.dev/progressive-web-apps/) functionalities.
|
|
118
|
+
|
|
119
|
+
> 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 standards-based approach lets you work the way the web works now, and the way it'll work in the future!
|
|
122
|
+
</details>
|
|
123
|
+
|
|
124
|
+
*This and more - ahead!*
|
|
125
|
+
|
|
126
|
+
## Installation
|
|
127
|
+
|
|
128
|
+
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.
|
|
129
|
+
|
|
130
|
+
```shell
|
|
131
|
+
mkdir my-app
|
|
132
|
+
cd my-app
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
With [npm available on your terminal](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm), the following command will install Webflo to your project:
|
|
136
|
+
|
|
137
|
+
> System Requirements: Node.js 12.0 or later
|
|
138
|
+
|
|
139
|
+
```shell
|
|
140
|
+
$ npm i @webqit/webflo
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The installation automatically creates a `package.json` file at project root, containing `@webqit/webflo` as a project dependency.
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"dependencies": {
|
|
148
|
+
"@webqit/webflo": "..."
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Other important definitions like project `name`, package `type`, and *aliases* for common Webflo commands will now also belong here.
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"name": "my-app",
|
|
158
|
+
"type": "module",
|
|
159
|
+
"scripts": {
|
|
160
|
+
"start": "webflo start::server --mode=dev",
|
|
161
|
+
"generate": "webflo generate::client --compress=gz --auto-embeds"
|
|
162
|
+
},
|
|
163
|
+
"dependencies": {
|
|
164
|
+
"@webqit/webflo": "..."
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
All is now set! The commands `npm start` and `npm run generate` will be coming in often during development.
|
|
170
|
+
|
|
171
|
+
### "Hello World!"
|
|
172
|
+
|
|
173
|
+
To be sure that Webflo is listening, run `npx webflo help` on the terminal. An overview of available commands will be shown.
|
|
174
|
+
|
|
175
|
+
If you can't wait to say *Hello World!* 😅, you can have an HTML page say that right now!
|
|
176
|
+
+ Create an `index.html` file in a new subdirectory `public`.
|
|
177
|
+
|
|
178
|
+
```shell
|
|
179
|
+
public
|
|
180
|
+
└── index.html
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```html
|
|
184
|
+
<!DOCTYPE html>
|
|
185
|
+
<html>
|
|
186
|
+
<head>
|
|
187
|
+
<title>My App</title>
|
|
188
|
+
</head>
|
|
189
|
+
<body>
|
|
190
|
+
<h1>Hello World!</h1>
|
|
191
|
+
<p>This is <b>My App</b></p>
|
|
192
|
+
</body>
|
|
193
|
+
</html>
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
+ Start the Webflo server and visit `http://localhost:3000` on your browser to see your page. 😃
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
$ npm start
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Concepts
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
+ [Handler Functions and Layout](#handler-functions-and-layout)
|
|
206
|
+
+ [Step Functions and Workflows](#step-functions-and-workflows)
|
|
207
|
+
+ [Requests and Responses](#requests-and-responses)
|
|
208
|
+
+ [Rendering and Templating](#rendering-and-templating)
|
|
209
|
+
+ [Web Standards](#web-standards)
|
|
210
|
+
|
|
211
|
+
### Handler Functions and Layout
|
|
212
|
+
|
|
213
|
+
Whether building a *server-based*, *browser-based*, or *universal* application, Webflo gives us one consistent way to handle routing and navigation: using *handler functions*!
|
|
214
|
+
|
|
215
|
+
```js
|
|
216
|
+
/**
|
|
217
|
+
[server|client|worker]
|
|
218
|
+
├── index.js
|
|
219
|
+
*/
|
|
220
|
+
export default function(event, context, next) {
|
|
221
|
+
}
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Each function receives an `event` object representing the current flow.
|
|
225
|
+
|
|
226
|
+
For *server-based* applications (e.g. traditional web apps, API backends), server-side handlers go into a directory named `server`.
|
|
227
|
+
|
|
228
|
+
```js
|
|
229
|
+
/**
|
|
230
|
+
server
|
|
231
|
+
├── index.js
|
|
232
|
+
*/
|
|
233
|
+
export default function(event, context, next) {
|
|
234
|
+
return {
|
|
235
|
+
title: 'Home | FluffyPets',
|
|
236
|
+
source: 'server',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
> **Note**
|
|
242
|
+
> <br>The above function runs on calling `npm start` on your terminal and visiting http://localhost:3000.
|
|
243
|
+
|
|
244
|
+
For *browser-based* applications (e.g. Single Page Apps), client-side handlers go into a directory named `client`.
|
|
245
|
+
|
|
246
|
+
```js
|
|
247
|
+
/**
|
|
248
|
+
client
|
|
249
|
+
├── index.js
|
|
250
|
+
*/
|
|
251
|
+
export default function(event, context, next) {
|
|
252
|
+
return {
|
|
253
|
+
title: 'Home | FluffyPets',
|
|
254
|
+
source: 'in-browser',
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
> **Note**
|
|
260
|
+
> <br>The above function is built as part of your application's JS bundle on calling `npm run generate` on your terminal. (It is typically bundled to the file `./public/bundle.js`. And the `--auto-embeds` flag in that command gets this automatically embeded into 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
|
+
|
|
262
|
+
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
|
+
|
|
264
|
+
```js
|
|
265
|
+
/**
|
|
266
|
+
worker
|
|
267
|
+
├── index.js
|
|
268
|
+
*/
|
|
269
|
+
export default function(event, context, next) {
|
|
270
|
+
return {
|
|
271
|
+
title: 'Home | FluffyPets',
|
|
272
|
+
source: 'service-worker',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
> **Note**
|
|
278
|
+
> <br>The above function is built as part of your application's Service Worker JS bundle on calling `npm run generate` on your terminal. (This 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.
|
|
279
|
+
|
|
280
|
+
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
|
+
|
|
282
|
+
```shell
|
|
283
|
+
client
|
|
284
|
+
├── index.js
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
```shell
|
|
288
|
+
worker
|
|
289
|
+
├── index.js
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
```shell
|
|
293
|
+
server
|
|
294
|
+
├── index.js
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Static files, e.g. images, stylesheets, etc, have their place in a files directory named `public`.
|
|
298
|
+
|
|
299
|
+
```shell
|
|
300
|
+
public
|
|
301
|
+
├── logo.png
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Step Functions and Workflows
|
|
305
|
+
|
|
306
|
+
Whether routing in the `/client`, `/worker`, or `/server` directory above, nested URLs follow the concept of Step Functions! As seen earlier, these are parent-child arrangements of handlers that model an URL strucuture.
|
|
307
|
+
|
|
308
|
+
```shell
|
|
309
|
+
server
|
|
310
|
+
├── index.js --------------------------------- http://localhost:3000
|
|
311
|
+
└── products/index.js ------------------------ http://localhost:3000/products
|
|
312
|
+
└── stickers/index.js ------------------ http://localhost:3000/products/stickers
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Each handler calls a `next()` function to propagate flow to a child step, if any; is able to pass a `context` object along, and can *recompose* the child step's return value.
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
/**
|
|
319
|
+
server
|
|
320
|
+
├── index.js
|
|
321
|
+
*/
|
|
322
|
+
export default async function(event, context, next) {
|
|
323
|
+
if (next.stepname) {
|
|
324
|
+
let childContext = { user: { id: 2 }, };
|
|
325
|
+
let childResponse = await next( childContext );
|
|
326
|
+
return { ...childResponse, title: childResponse.title + ' | FluffyPets' };
|
|
327
|
+
}
|
|
328
|
+
return { title: 'Home | FluffyPets' };
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
```js
|
|
333
|
+
/**
|
|
334
|
+
server
|
|
335
|
+
├── products/index.js
|
|
336
|
+
*/
|
|
337
|
+
export default function(event, context, next) {
|
|
338
|
+
if (next.stepname) {
|
|
339
|
+
return next();
|
|
340
|
+
}
|
|
341
|
+
return { title: 'Products' };
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
This step-based workflow helps to decomplicate routing and navigation, and gets us scaling horizontally as our application grows larger.
|
|
346
|
+
|
|
347
|
+
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's steps that have no corresponding step functions.
|
|
348
|
+
|
|
349
|
+
This means that we could even handle all URLs from the root handler alone.
|
|
350
|
+
|
|
351
|
+
```js
|
|
352
|
+
/**
|
|
353
|
+
server
|
|
354
|
+
├── index.js
|
|
355
|
+
*/
|
|
356
|
+
export default function(event, context, next) {
|
|
357
|
+
// For http://localhost:3000/products
|
|
358
|
+
if (next.pathname === 'products') {
|
|
359
|
+
return { title: 'Products' };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// For http://localhost:3000/products/stickers
|
|
363
|
+
if (next.pathname === 'products/stickers') {
|
|
364
|
+
return { title: 'Stickers' };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Should we later support other URLs like static assets http://localhost:3000/logo.png
|
|
368
|
+
if (next.pathname) {
|
|
369
|
+
return next();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// For the root URL http://localhost:3000
|
|
373
|
+
return { title: 'Home' };
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Something interesting happens on calling `next()` at the *edge* of the workflow - the point where there are no more child steps - as in the case above: Webflo takes the *default action*!
|
|
378
|
+
|
|
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.
|
|
380
|
+
|
|
381
|
+
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
|
+
|
|
383
|
+
```shell
|
|
384
|
+
my-app
|
|
385
|
+
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/prodcuts, http://localhost:3000/prodcuts/stickers, etc
|
|
386
|
+
└── public/logo.png ------------------------- http://localhost:3000/logo.png
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
> **Note**
|
|
390
|
+
> <br>The root handler effectively becomes the single point of entry to the application - being that it sees even static requests!
|
|
391
|
+
|
|
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 options built into the Service Worker.)
|
|
393
|
+
|
|
394
|
+
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
|
+
|
|
396
|
+
```js
|
|
397
|
+
/**
|
|
398
|
+
worker
|
|
399
|
+
├── index.js
|
|
400
|
+
*/
|
|
401
|
+
export default async function(event, context, next) {
|
|
402
|
+
// For http://localhost:3000/about
|
|
403
|
+
if (next.pathname === 'about') {
|
|
404
|
+
return {
|
|
405
|
+
name: 'FluffyPets',
|
|
406
|
+
version: '1.0',
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// For http://localhost:3000/logo.png
|
|
411
|
+
if (next.pathname === 'logo.png') {
|
|
412
|
+
let response = await next();
|
|
413
|
+
console.log( 'Logo file size:', response.headers.get('Content-Length') );
|
|
414
|
+
return response;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// For every other URL
|
|
418
|
+
return next();
|
|
419
|
+
}
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Now we get the following layout-to-URL mapping for our application:
|
|
423
|
+
|
|
424
|
+
```shell
|
|
425
|
+
my-app
|
|
426
|
+
├── worker/index.js ------------------------- http://localhost:3000/about, http://localhost:3000/logo.png
|
|
427
|
+
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/prodcuts, http://localhost:3000/prodcuts/stickers, etc
|
|
428
|
+
└── public/logo.png ------------------------- http://localhost:3000/logo.png
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
> **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 options built into the Service Worker.
|
|
433
|
+
|
|
434
|
+
Lastly, for workflows in **the `/client` directory**, the *default action* of `next()` 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
|
+
|
|
436
|
+
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
|
+
|
|
438
|
+
```js
|
|
439
|
+
/**
|
|
440
|
+
client
|
|
441
|
+
├── index.js
|
|
442
|
+
*/
|
|
443
|
+
export default async function(event, context, next) {
|
|
444
|
+
// For http://localhost:3000/login
|
|
445
|
+
if (next.pathname === 'login') {
|
|
446
|
+
return {
|
|
447
|
+
name: 'John Doe',
|
|
448
|
+
role: 'owner',
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// For every other URL
|
|
453
|
+
return next();
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Our overall layout-to-URL mapping for this application now becomes:
|
|
458
|
+
|
|
459
|
+
```shell
|
|
460
|
+
my-app
|
|
461
|
+
├── client/index.js ------------------------- http://localhost:3000/login
|
|
462
|
+
├── worker/index.js ------------------------- http://localhost:3000/about, http://localhost:3000/logo.png
|
|
463
|
+
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/prodcuts, http://localhost:3000/prodcuts/stickers, etc
|
|
464
|
+
└── public/logo.png ------------------------- http://localhost:3000/logo.png
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
If there's anything we have now, it is the ability to break work down, optionally across step functions, optionally between layers!
|
|
468
|
+
|
|
469
|
+
### Requests and Responses
|
|
470
|
+
|
|
471
|
+
Routes in Webflo can be designed for different types of request/response scenarios.
|
|
472
|
+
|
|
473
|
+
Generally, handler functions can return any type of jsonfyable data (`string`, `number`, `boolean`, `object`, `array`), or other primitive types like `ArrayBuffer`, `Blob`, etc, or an instance of `event.Response` containing the same. (Here `event.Response` is essentially the [WHATWG Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object, available in all environments - `/client`, `/worker`, and `/server`.)
|
|
474
|
+
|
|
475
|
+
A nested handler's return value goes as-is to its parent handler, where it gets a chance to be recomposed. Whatever is obtained from the root handler is sent:
|
|
476
|
+
+ either into the response stream (with jsonfyable data automatically translating to a proper JSON response),
|
|
477
|
+
+ or into an HTML document for rendering (where applicable), as detailed ahead.
|
|
478
|
+
|
|
479
|
+
But, where workflows return `undefined`, a `404` HTTP response is returned. In the case of client-side workflows - in `/client`, the already running HTML page in the browser receives empty data, and, at the same time, set to an error state. (Details in [Rendering and Templating](#rendering-and-templating).)
|
|
480
|
+
|
|
481
|
+
#### Server-Side: API and Page Responses
|
|
482
|
+
|
|
483
|
+
On the server, jsonfyable response effectively becomes a *JSON API response*! (So, we get an API backend this way by default.)
|
|
484
|
+
|
|
485
|
+
```js
|
|
486
|
+
/**
|
|
487
|
+
server
|
|
488
|
+
├── index.js
|
|
489
|
+
*/
|
|
490
|
+
export default async function(event, context, next) {
|
|
491
|
+
return { title: 'Home | FluffyPets' };
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
But, for a route that is intended to *also* be accessed as a web page, data obtained as JSON objects (as in above) can get automatically rendered to HTML as a *page* response. Incoming requests are identified as *page requests* when they indicate in their `Accept` header that HTML responses are acceptable - `Accept: text/html,etc`. (Browsers automatically do this on navigation requests.) Next, it should be either that a custom `render` callback has been defined on the route, or that an HTML file that pairs with the route exists in the `/public` directory - for automatic rendering by Webflo.
|
|
496
|
+
|
|
497
|
+
+ **Case 1: Custom `render` callbacks**. These are functions exported as `render` (`export function render() {}`) from the route.
|
|
498
|
+
|
|
499
|
+
```js
|
|
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.
|
|
546
|
+
|
|
547
|
+
+ **Case 2: Automatically-paired HTML files**. These are valid HTML documents named `index.html` in the `/public` directory, or a subdirectory that corresponds with the route.
|
|
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>
|
|
569
|
+
|
|
570
|
+
<script type="subscript">
|
|
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).)
|
|
578
|
+
|
|
579
|
+
> **Note**
|
|
580
|
+
> <br>Nested routes may not always need to have an equivalent `index.html` file; Webflo makes do with one from parent or ancestor.
|
|
581
|
+
|
|
582
|
+
#### Client-Side: Navigation Responses
|
|
583
|
+
|
|
584
|
+
On the client (the browser), every navigation event (page-to-page navigation, history back and forward navigation, and form submissions) initiates a request/response flow. The request object Webflo generates for these navigations is assigned an `Accept: application/json` header, so that data can be obtained as a JSON object. This request goes through the route's workflow (whether in the `/client`, `/worker`, or `/server` layer), and the JSON data obtained is simply sent into the already running HTML document as `document.state.page`. This makes it globally accessible to embedded scripts and rendering logic! (Details in [Rendering and Templating](#rendering-and-templating).)
|
|
585
|
+
|
|
586
|
+
### Rendering and Templating
|
|
587
|
+
|
|
588
|
+
As covered just above, routes that are intended to be accessed as a web page are expected to *first* be accessible as a JSON endpoint (returning an object). On the server, rendering happens *after* data is obtained from the workflow, but only when the browser explicitly asks for a `text/html` response! On the client, rendering happens *after* data is obtained from the workflow on each navigation event, but right into the same loaded document in the browser. In both cases, the concept of *templating* with HTML documents makes it possible to get pages to be as unique, or as generic, as needed on each navigation.
|
|
589
|
+
|
|
590
|
+
Every rendering and templating concept in Webflo is DOM-based - both with Client-Side Rendering and Server-Side Rendering (going by the default Webflo-native rendering). On the server, Webflo makes this so by making a DOM instance off of your `index.html` file. So, we get the same familiar `document` object and DOM elements everywhere! Webflo simply makes sure that the data obtained on each navigation is available as part of the `document` object - exposed at `document.state.page`.
|
|
591
|
+
|
|
592
|
+
You can access the `document` object (and its `document.state.page` property) both from a custom `render` callback and from a script that you can directly embed on the page.
|
|
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
|
+
```
|
|
648
|
+
|
|
649
|
+
From here, even the most-rudimentary form of rendering and templating becomes possible (using vanilla HTML and native DOM methods), and this is a good thing: you get away with less tooling until you absolutely need to add up on tooling!
|
|
650
|
+
|
|
651
|
+
However, the `document` objects in Webflo can be a lot fun to work with: they support Object-Oriented HTML (OOHTML) natively, and this gives us a lot of (optional) syntax sugars on top of vanilla HTML and the DOM!
|
|
652
|
+
|
|
653
|
+
> **Note**
|
|
654
|
+
> <br>You can learn more about OOHTML [here](https://github.com/webqit/oohtml).
|
|
655
|
+
|
|
656
|
+
> **Note**
|
|
657
|
+
> <br>You can disable OOHTML in config where you do not need to use its features in HTML and the DOM.
|
|
658
|
+
|
|
659
|
+
#### Rendering
|
|
660
|
+
|
|
661
|
+
Getting your application data `document.state.page` rendered into HTML can be a trival thing for applications that do not have much going on in the UI. Webflo allows your tooling budget to be as low as just using vanilla DOM APIs! (You could even disable support for OOHTML in config.)
|
|
662
|
+
|
|
663
|
+
```html
|
|
664
|
+
<!--
|
|
665
|
+
public
|
|
666
|
+
├── index.html
|
|
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>
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
> **Note**
|
|
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](#)*.
|
|
690
|
+
|
|
691
|
+
> **Note**
|
|
692
|
+
> <br>Considering the vanilla approach for your baisc UI? You probbably should! Low tooling budgets are a win in this case, and bare DOM manipulations are nothing to feel guilty of! (You may want to keep all of that JS in an external JS file to make your HTML tidy.)
|
|
693
|
+
|
|
694
|
+
Where your application UI is more than basic, you would benefit from using OOHTML features in HTML and on the DOM! (Documents created by Webflo are OOHTML-ready by default.) Here, you are able to write reactive UI logic, namespace-based HTML, HTML modules and imports, etc - without the usual framework thinking.
|
|
695
|
+
|
|
696
|
+
To write **reactive UI logic**, OOHTML makes it possible to define `<script>` elements right along with your HTML elements - where you get to write as much or as little JavaScript as needed for a behaviour!
|
|
697
|
+
|
|
698
|
+
```html
|
|
699
|
+
<!--
|
|
700
|
+
public
|
|
701
|
+
├── index.html
|
|
702
|
+
-->
|
|
703
|
+
<!DOCTYPE html>
|
|
704
|
+
<html>
|
|
705
|
+
<head>
|
|
706
|
+
<title>FluffyPets</title>
|
|
707
|
+
<script type="subscript">
|
|
708
|
+
let app = document.state;
|
|
709
|
+
let titleElement = this.querySelector('title');
|
|
710
|
+
titleElement.innerHTML = app.page.title;
|
|
711
|
+
</script>
|
|
712
|
+
</head>
|
|
713
|
+
<body>
|
|
714
|
+
<h1></h1>
|
|
715
|
+
<script type="subscript">
|
|
716
|
+
let app = document.state;
|
|
717
|
+
let h1Element = this.querySelector('h1');
|
|
718
|
+
h1Element.innerHTML = app.page.title;
|
|
719
|
+
</script>
|
|
720
|
+
</body>
|
|
721
|
+
</html>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
> **Note**
|
|
725
|
+
> <br>You'll find it logical that UI logic is the whole essence of the HTML `<script>` element, after all! OOHTML just extends the regular `<script>` element with the `subscript` type to give them *reactivity* and keep them scoped to their host element! (You can learn more [here](https://github.com/webqit/oohtml#subscript).)
|
|
726
|
+
|
|
727
|
+
From here, you can go on to use any DOM manipulation library of your choice; e.g jQuery, or even better, the jQuery-like [Play UI](https://github.com/webqit/play-ui) library.
|
|
728
|
+
|
|
729
|
+
```html
|
|
730
|
+
<!--
|
|
731
|
+
public
|
|
732
|
+
├── index.html
|
|
733
|
+
-->
|
|
734
|
+
<!DOCTYPE html>
|
|
735
|
+
<html>
|
|
736
|
+
<head>
|
|
737
|
+
<title>FluffyPets</title>
|
|
738
|
+
<script type="subscript">
|
|
739
|
+
let app = document.state;
|
|
740
|
+
$('title').html(app.page.title);
|
|
741
|
+
</script>
|
|
742
|
+
</head>
|
|
743
|
+
<body>
|
|
744
|
+
<h1></h1>
|
|
745
|
+
<script type="subscript">
|
|
746
|
+
let app = document.state;
|
|
747
|
+
$('h1').html(app.page.title);
|
|
748
|
+
</script>
|
|
749
|
+
</body>
|
|
750
|
+
</html>
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
You'll find many other OOHTML features that let you write the most enjoyable HTML. And when you need to write class-based components, you'll fall in love with [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)!
|
|
754
|
+
|
|
755
|
+
#### Templating
|
|
756
|
+
|