@webqit/webflo 0.10.4 → 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.
Files changed (38) hide show
  1. package/README.md +730 -51
  2. package/package.json +2 -2
  3. package/src/Context.js +1 -1
  4. package/src/config-pi/runtime/Client.js +43 -12
  5. package/src/config-pi/runtime/Server.js +24 -8
  6. package/src/runtime-pi/client/Runtime.js +27 -22
  7. package/src/runtime-pi/client/RuntimeClient.js +28 -15
  8. package/src/runtime-pi/client/generate.js +248 -74
  9. package/src/runtime-pi/client/{generate.oohtml.js → oohtml/full.js} +0 -0
  10. package/src/runtime-pi/client/oohtml/namespacing.js +7 -0
  11. package/src/runtime-pi/client/oohtml/scripting.js +8 -0
  12. package/src/runtime-pi/client/oohtml/templating.js +8 -0
  13. package/src/runtime-pi/client/worker/Worker.js +1 -1
  14. package/src/runtime-pi/server/Router.js +2 -2
  15. package/src/runtime-pi/server/Runtime.js +8 -3
  16. package/src/runtime-pi/server/RuntimeClient.js +21 -11
  17. package/test/site/public/bundle.html +3 -0
  18. package/test/site/public/bundle.html.json +3 -0
  19. package/test/site/public/bundle.js +2 -0
  20. package/test/site/public/bundle.js.gz +0 -0
  21. package/test/site/public/bundle.webflo.js +15 -0
  22. package/test/site/public/bundle.webflo.js.gz +0 -0
  23. package/test/site/public/index.html +30 -0
  24. package/test/site/public/page-2/bundle.html +5 -0
  25. package/test/site/public/page-2/bundle.html.json +1 -0
  26. package/test/site/public/page-2/bundle.js +2 -0
  27. package/test/site/public/page-2/bundle.js.gz +0 -0
  28. package/test/site/public/page-2/index.html +47 -0
  29. package/test/site/public/page-2/logo-130x130.png +0 -0
  30. package/test/site/public/page-2/main.html +3 -0
  31. package/test/site/public/page-4/subpage/bundle.html +0 -0
  32. package/test/site/public/page-4/subpage/bundle.html.json +1 -0
  33. package/test/site/public/page-4/subpage/bundle.js +2 -0
  34. package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
  35. package/test/site/public/page-4/subpage/index.html +31 -0
  36. package/test/site/public/worker.js +3 -0
  37. package/test/site/public/worker.js.gz +0 -0
  38. package/test/site/server/index.js +8 -0
package/README.md CHANGED
@@ -2,76 +2,755 @@
2
2
 
3
3
  <!-- BADGES/ -->
4
4
 
5
- <span class="badge-npmversion"><a href="https://npmjs.org/package/@web-native-js/observables" title="View this project on NPM"><img src="https://img.shields.io/npm/v/@web-native-js/observables.svg" alt="NPM version" /></a></span>
6
- <span class="badge-npmdownloads"><a href="https://npmjs.org/package/@web-native-js/observables" title="View this project on NPM"><img src="https://img.shields.io/npm/dm/@web-native-js/observables.svg" alt="NPM downloads" /></a></span>
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 -->
10
9
 
11
- The execution model of functional programs is fundamentally different from how we like to think in a procedural model.
12
- If I did the following:
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!
13
11
 
14
- let [ valueA, setValueA ] = createSignal(10);
15
- let [ valueB, setValueB ] = createSignal();
16
- createEffect(() => setValueB(valueA() * 2));
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!
17
13
 
18
- createEffect(() => console.log( valueB() ));
14
+ We've put all of that up for a straight read! (Might turn out you already know Webflo 😃)
19
15
 
20
- createEffect(() => {
21
- if (someCondition()) {
22
- setValueA(20);
23
- }
24
- });
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.!
25
17
 
18
+ ## Documentation
26
19
 
27
- createEffect(() => console.log( valueB() ));
20
+ + [Overview](#overview)
21
+ + [Installation](#installation)
22
+ + [Concepts](#concepts)
28
23
 
29
- You'll see now that I have two effect blocks reacting both ABOVE and BELOW the point of mutation.
24
+ ## Overview
30
25
 
31
- Whereas if I did the same procedurally:
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
+ ```
32
40
 
33
- function** fn() {
34
- let a = 10;
35
- let b = a * 2;
41
+ You nest them as *step functions* in a structure that models your application's URL structure.
36
42
 
37
- console.log(b);
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
+ ```
38
48
 
39
- if (someCondition) {
40
- a = 20;
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();
41
58
  }
59
+ return { title: 'Home' };
60
+ }
61
+ ```
42
62
 
43
- console.log(b);
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' };
44
72
  }
45
- fn();
46
- fn.thread( [ 'someCondition' ] );
73
+ ```
47
74
 
48
- I'd expect only the console.log() expression BELOW the point of mutation to react. So, code within Subscript Functions don't just look procedural, but work procedurally.
75
+ ...enabling *all sorts of composition* along the way!
49
76
 
50
- function** fn() {
51
- let result = (a || b) && consequentExpr || alternateExpr;
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' };
52
88
  }
53
- fn();
89
+ ```
54
90
 
55
- @mathias
56
-
57
- @tomayac
58
-
59
- @domenic
60
-
61
- @jaffathecake
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!
62
99
 
63
- @surma
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.
64
118
 
65
- @Paul_Kinlan
66
-
67
- @paul_irish
68
-
69
- @aerotwist
70
-
71
- @jason_mayes
72
-
73
- @sundarpichai
74
-
75
- @ThomasOrTK
76
-
77
- @GrannisWill
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
+