@webqit/webflo 0.11.12 β 0.11.15
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 +548 -295
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,6 @@ Ok, we've put all of that up for a straight read!
|
|
|
20
20
|
+ [Installation](#installation)
|
|
21
21
|
+ [Concepts](#concepts)
|
|
22
22
|
+ [Webflo Applications](#webflo-applications)
|
|
23
|
-
+ [Workflow API](#workflow-api)
|
|
24
23
|
+ [Webflo Config](#webflo-config)
|
|
25
24
|
+ [Technology Stack](#technology-stack)
|
|
26
25
|
+ [Getting Started](#getting-started)
|
|
@@ -124,7 +123,7 @@ This and much more - ahead!
|
|
|
124
123
|
</details>
|
|
125
124
|
|
|
126
125
|
<details>
|
|
127
|
-
<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>
|
|
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>already works</i>!</summary>
|
|
128
127
|
|
|
129
128
|
Here's a glimpse of the standards-based stack you get of Webflo!
|
|
130
129
|
|
|
@@ -195,13 +194,13 @@ Other important definitions like project `name`, package `type`, and *aliases* f
|
|
|
195
194
|
}
|
|
196
195
|
```
|
|
197
196
|
|
|
198
|
-
|
|
197
|
+
And that gets it all ready! The commands `npm start` and `npm run generate` will be coming in often during development.
|
|
199
198
|
|
|
200
199
|
### "Hello World!"
|
|
201
200
|
|
|
202
201
|
To be sure that Webflo is listening, run `npx webflo help` on the terminal. An overview of available commands should be shown.
|
|
203
202
|
|
|
204
|
-
If you can't wait to say *Hello World!* π
, you can have an HTML page say that right
|
|
203
|
+
If you can't wait to say *Hello World!* π
, you can have an HTML page say that right away!
|
|
205
204
|
+ Create an `index.html` file in a new subdirectory `public`.
|
|
206
205
|
|
|
207
206
|
```shell
|
|
@@ -225,7 +224,7 @@ If you can't wait to say *Hello World!* π
, you can have an HTML page say that
|
|
|
225
224
|
+ Start the Webflo server and visit `http://localhost:3000` on your browser to see your page. π
|
|
226
225
|
|
|
227
226
|
```bash
|
|
228
|
-
|
|
227
|
+
npm start
|
|
229
228
|
```
|
|
230
229
|
|
|
231
230
|
## Concepts
|
|
@@ -241,6 +240,8 @@ If you can't wait to say *Hello World!* π
, you can have an HTML page say that
|
|
|
241
240
|
|
|
242
241
|
Whether building a *server-based*, *browser-based*, or *universal* application, Webflo gives you one consistent way to handle routing and navigation: using *handler functions*!
|
|
243
242
|
|
|
243
|
+
You just define an `index.js` file with a function that gets called to handle a request! No setup!
|
|
244
|
+
|
|
244
245
|
```js
|
|
245
246
|
/**
|
|
246
247
|
[server|client|worker]
|
|
@@ -250,10 +251,13 @@ export default function(event, context, next) {
|
|
|
250
251
|
}
|
|
251
252
|
```
|
|
252
253
|
|
|
253
|
-
>
|
|
254
|
-
>
|
|
254
|
+
<details>
|
|
255
|
+
<summary>More details...</summary>
|
|
255
256
|
|
|
256
|
-
|
|
257
|
+
> Function name may also be specific to a [*HTTP method*](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods): `get`, `post`, `put`, `patch`, `del` (for *delete*), `options`, `head`, etc.
|
|
258
|
+
</details>
|
|
259
|
+
|
|
260
|
+
Each function receives an `event` object representing details about the request - e.g. `event.request`, `event.url`, `event.session`. ([Details ahead](#workflow-api).)
|
|
257
261
|
|
|
258
262
|
For *server-based* applications (e.g. traditional web apps and API backends), server-side handlers go into a directory named `server`.
|
|
259
263
|
|
|
@@ -270,8 +274,11 @@ export default function(event, context, next) {
|
|
|
270
274
|
}
|
|
271
275
|
```
|
|
272
276
|
|
|
273
|
-
>
|
|
274
|
-
|
|
277
|
+
<details>
|
|
278
|
+
<summary>How it works...</summary>
|
|
279
|
+
|
|
280
|
+
> The above function responds on starting the server - `npm start` on your terminal - and visiting http://localhost:3000.
|
|
281
|
+
</details>
|
|
275
282
|
|
|
276
283
|
For *browser-based* applications (e.g. Single Page Apps), client-side handlers go into a directory named `client`.
|
|
277
284
|
|
|
@@ -288,8 +295,11 @@ export default function(event, context, next) {
|
|
|
288
295
|
}
|
|
289
296
|
```
|
|
290
297
|
|
|
291
|
-
>
|
|
292
|
-
|
|
298
|
+
<details>
|
|
299
|
+
<summary>How it works...</summary>
|
|
300
|
+
|
|
301
|
+
> 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.
|
|
302
|
+
</details>
|
|
293
303
|
|
|
294
304
|
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`.
|
|
295
305
|
|
|
@@ -306,10 +316,13 @@ export default function(event, context, next) {
|
|
|
306
316
|
}
|
|
307
317
|
```
|
|
308
318
|
|
|
309
|
-
>
|
|
310
|
-
|
|
319
|
+
<details>
|
|
320
|
+
<summary>How it works...</summary>
|
|
321
|
+
|
|
322
|
+
> 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).)
|
|
323
|
+
</details>
|
|
311
324
|
|
|
312
|
-
So, depending on what's being built, an application's handler functions may take the following form (in part or in whole
|
|
325
|
+
So, depending on what's being built, an application's handler functions may take the following form (in part or in whole):
|
|
313
326
|
|
|
314
327
|
```shell
|
|
315
328
|
client
|
|
@@ -344,7 +357,38 @@ server
|
|
|
344
357
|
βββ stickers/index.js ------------------ http://localhost:3000/products/stickers
|
|
345
358
|
```
|
|
346
359
|
|
|
347
|
-
Each step calls a `next()` function to forward the current request to the next step
|
|
360
|
+
Each step calls a `next()` function to forward the current request to the next step.
|
|
361
|
+
|
|
362
|
+
```js
|
|
363
|
+
/**
|
|
364
|
+
server
|
|
365
|
+
βββ index.js
|
|
366
|
+
*/
|
|
367
|
+
export default async function(event, context, next) {
|
|
368
|
+
if (next.stepname) {
|
|
369
|
+
return next();
|
|
370
|
+
}
|
|
371
|
+
return { title: 'Home | FluffyPets' };
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
/**
|
|
377
|
+
server
|
|
378
|
+
βββ products/index.js
|
|
379
|
+
*/
|
|
380
|
+
export default function(event, context, next) {
|
|
381
|
+
if (next.stepname) {
|
|
382
|
+
return next();
|
|
383
|
+
}
|
|
384
|
+
return { title: 'Products' };
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
<details>
|
|
389
|
+
<summary>More details...</summary>
|
|
390
|
+
|
|
391
|
+
Each step can pass a `context` object to a child step, and can *recompose* its return value.
|
|
348
392
|
|
|
349
393
|
```js
|
|
350
394
|
/**
|
|
@@ -361,18 +405,55 @@ export default async function(event, context, next) {
|
|
|
361
405
|
}
|
|
362
406
|
```
|
|
363
407
|
|
|
408
|
+
<details>
|
|
409
|
+
<summary>Even more details...</summary>
|
|
410
|
+
|
|
411
|
+
The `next()` function can be used to re-direct the current request to a different route - using a relative or absolute URL.
|
|
412
|
+
|
|
413
|
+
```js
|
|
414
|
+
/**
|
|
415
|
+
server
|
|
416
|
+
βββ index.js
|
|
417
|
+
*/
|
|
418
|
+
export default async function(event, context, next) {
|
|
419
|
+
if (next.stepname === 'products') {
|
|
420
|
+
return next( context, '/api/products?params=allowed' ); // With an absolute URL
|
|
421
|
+
}
|
|
422
|
+
return { title: 'Home | FluffyPets' };
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
364
426
|
```js
|
|
365
427
|
/**
|
|
366
428
|
server
|
|
367
429
|
βββ products/index.js
|
|
368
430
|
*/
|
|
369
|
-
export default function(event, context, next) {
|
|
431
|
+
export default async function(event, context, next) {
|
|
370
432
|
if (next.stepname) {
|
|
371
433
|
return next();
|
|
372
434
|
}
|
|
373
|
-
return
|
|
435
|
+
return next( context, '../api/products?params=allowed' ); // With a relative URL
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
The `next()` function can also run as an independent request - using [the same parameters as with the WHATWG Request constructor](https://developer.mozilla.org/en-US/docs/Web/API/Request/Request#parameters).
|
|
440
|
+
|
|
441
|
+
```js
|
|
442
|
+
/**
|
|
443
|
+
server
|
|
444
|
+
βββ index.js
|
|
445
|
+
*/
|
|
446
|
+
export default async function(event, context, next) {
|
|
447
|
+
if (next.stepname === 'products') {
|
|
448
|
+
return next( context, '/api/products?params=allowed', {
|
|
449
|
+
method: 'get', { headers: { Authorization: 'djjdd' } }
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
return { title: 'Home | FluffyPets' };
|
|
374
453
|
}
|
|
375
454
|
```
|
|
455
|
+
</details>
|
|
456
|
+
</details>
|
|
376
457
|
|
|
377
458
|
This step-based workflow helps to decomplicate routing and gets us scaling horizontally as our application grows larger.
|
|
378
459
|
|
|
@@ -394,6 +475,12 @@ export default function(event, context, next) {
|
|
|
394
475
|
}
|
|
395
476
|
```
|
|
396
477
|
|
|
478
|
+
<details>
|
|
479
|
+
<summary>More details...</summary>
|
|
480
|
+
|
|
481
|
+
> Every handler function has a `this.stepname` and `this.pathname` property. Server-side handlers have an extra `this.dirname` property.
|
|
482
|
+
</details>
|
|
483
|
+
|
|
397
484
|
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.
|
|
398
485
|
|
|
399
486
|
For example, it is possible to handle all URLs from the root handler alone.
|
|
@@ -469,7 +556,7 @@ export default async function(event, context, next) {
|
|
|
469
556
|
}
|
|
470
557
|
```
|
|
471
558
|
|
|
472
|
-
Now we get the following
|
|
559
|
+
Now we get the following handler-to-URL mapping for our application:
|
|
473
560
|
|
|
474
561
|
```shell
|
|
475
562
|
my-app
|
|
@@ -504,7 +591,7 @@ export default async function(event, context, next) {
|
|
|
504
591
|
}
|
|
505
592
|
```
|
|
506
593
|
|
|
507
|
-
Our overall
|
|
594
|
+
Our overall handler-to-URL mapping for this application now becomes:
|
|
508
595
|
|
|
509
596
|
```shell
|
|
510
597
|
my-app
|
|
@@ -560,18 +647,27 @@ my-app
|
|
|
560
647
|
|
|
561
648
|
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.)
|
|
562
649
|
|
|
563
|
-
>
|
|
564
|
-
|
|
650
|
+
<details>
|
|
651
|
+
<summary>How it works...</summary>
|
|
652
|
+
|
|
653
|
+
> The `Accept` header hint is already how browsers make requests on every page load. So, it just works!
|
|
654
|
+
</details>
|
|
565
655
|
|
|
566
656
|
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!
|
|
567
657
|
|
|
568
|
-
>
|
|
569
|
-
|
|
658
|
+
<details>
|
|
659
|
+
<summary>How it works...</summary>
|
|
660
|
+
|
|
661
|
+
> Unless disabled, [SPA Routing](#spa-routing) is automatically built into your app's JS bundle from the `npm run generate` command. So, it just works!
|
|
662
|
+
</details>
|
|
570
663
|
|
|
571
664
|
With no extra work, your application can function as either a *Multi Page App (MPA)* or a *Single Page App (SPA)*!
|
|
572
665
|
|
|
573
|
-
>
|
|
574
|
-
>
|
|
666
|
+
<details>
|
|
667
|
+
<summary>Some disambiguation...</summary>
|
|
668
|
+
|
|
669
|
+
> 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.
|
|
670
|
+
</details>
|
|
575
671
|
|
|
576
672
|
#### Layout and Templating Overview
|
|
577
673
|
|
|
@@ -587,7 +683,7 @@ my-app
|
|
|
587
683
|
βββ footer.html ------------------------------ <footer></footer> <!-- To appear at bottom of each index.html page -->
|
|
588
684
|
```
|
|
589
685
|
|
|
590
|
-
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
|
|
686
|
+
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 route.
|
|
591
687
|
|
|
592
688
|
```html
|
|
593
689
|
my-app
|
|
@@ -600,7 +696,7 @@ my-app
|
|
|
600
696
|
|
|
601
697
|
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!
|
|
602
698
|
|
|
603
|
-
Now, with pages in Webflo being [DOM-based](#overview) (both client-side and [server-side](#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](#oohtml) - unless disabled in config. 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 -
|
|
699
|
+
Now, with pages in Webflo being [DOM-based](#overview) (both client-side and [server-side](#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](#oohtml) - unless disabled in config. 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 - all in a *module*, *export* and *import* paradigm.
|
|
604
700
|
|
|
605
701
|
Here, you are able to define reusable contents in a `<template>` element...
|
|
606
702
|
|
|
@@ -622,7 +718,7 @@ Here, you are able to define reusable contents in a `<template>` element...
|
|
|
622
718
|
</body>
|
|
623
719
|
```
|
|
624
720
|
|
|
625
|
-
The *module* element - `<template
|
|
721
|
+
The *module* element - `<template>` - is able to load its contents from a remote `.html` file that serves as a bundle:
|
|
626
722
|
|
|
627
723
|
```html
|
|
628
724
|
<!--
|
|
@@ -634,6 +730,10 @@ public
|
|
|
634
730
|
```
|
|
635
731
|
|
|
636
732
|
```html
|
|
733
|
+
<!--
|
|
734
|
+
public
|
|
735
|
+
βββ index.html
|
|
736
|
+
-->
|
|
637
737
|
<head>
|
|
638
738
|
<template name="routes" src="/bundle.html"></template>
|
|
639
739
|
</head>
|
|
@@ -643,7 +743,7 @@ What [we'll see shortly](#bundling) is how multiple standalone `.html` files - e
|
|
|
643
743
|
|
|
644
744
|
#### In a Multi Page Layout
|
|
645
745
|
|
|
646
|
-
In a Multi Page layout (as [
|
|
746
|
+
In a Multi Page layout (as seen [earlier](#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.
|
|
647
747
|
|
|
648
748
|
```html
|
|
649
749
|
<!--
|
|
@@ -702,12 +802,15 @@ public/products
|
|
|
702
802
|
</html>
|
|
703
803
|
```
|
|
704
804
|
|
|
705
|
-
>
|
|
706
|
-
|
|
805
|
+
<details>
|
|
806
|
+
<summary>How it works...</summary>
|
|
807
|
+
|
|
808
|
+
> 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.
|
|
809
|
+
</details>
|
|
707
810
|
|
|
708
811
|
#### In a Single Page Layout
|
|
709
812
|
|
|
710
|
-
In a Single Page layout (as seen [earlier](#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 that form the equivalent of
|
|
813
|
+
In a Single Page layout (as seen [earlier](#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 that form the equivalent of the application's URL structure.
|
|
711
814
|
|
|
712
815
|
```html
|
|
713
816
|
<!--
|
|
@@ -723,7 +826,7 @@ public
|
|
|
723
826
|
<main exportgroup="main.html">Welcome to our Home Page</main>
|
|
724
827
|
```
|
|
725
828
|
|
|
726
|
-
Now, the `<main>` elements are each imported on navigating to their respective
|
|
829
|
+
Now, the `<main>` elements are each imported on navigating to their respective routes. 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.
|
|
727
830
|
|
|
728
831
|
```html
|
|
729
832
|
<!--
|
|
@@ -744,8 +847,11 @@ public
|
|
|
744
847
|
</html>
|
|
745
848
|
```
|
|
746
849
|
|
|
747
|
-
>
|
|
748
|
-
|
|
850
|
+
<details>
|
|
851
|
+
<summary>How it works...</summary>
|
|
852
|
+
|
|
853
|
+
> 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.
|
|
854
|
+
</details>
|
|
749
855
|
|
|
750
856
|
#### In a Multi SPA Layout
|
|
751
857
|
|
|
@@ -765,7 +871,11 @@ my-app
|
|
|
765
871
|
βββ footer.html ------------------------------ <footer></footer> <!-- To appear at bottom of each document root -->
|
|
766
872
|
```
|
|
767
873
|
|
|
768
|
-
|
|
874
|
+
<details>
|
|
875
|
+
<summary>How it works...</summary>
|
|
876
|
+
|
|
877
|
+
> 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.
|
|
878
|
+
</details>
|
|
769
879
|
|
|
770
880
|
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.
|
|
771
881
|
|
|
@@ -777,7 +887,7 @@ public
|
|
|
777
887
|
<!DOCTYPE html>
|
|
778
888
|
<html>
|
|
779
889
|
<head>
|
|
780
|
-
<script type="module" src="webflo.bundle.js"></script>
|
|
890
|
+
<script type="module" src="/webflo.bundle.js"></script>
|
|
781
891
|
<script type="module" src="/products/bundle.js"></script>
|
|
782
892
|
<template name="pages" src="/bundle.html"></template>
|
|
783
893
|
</head>
|
|
@@ -793,7 +903,7 @@ public
|
|
|
793
903
|
<!DOCTYPE html>
|
|
794
904
|
<html>
|
|
795
905
|
<head>
|
|
796
|
-
<script type="module" src="webflo.bundle.js"></script>
|
|
906
|
+
<script type="module" src="/webflo.bundle.js"></script>
|
|
797
907
|
<script type="module" src="/about/bundle.js"></script>
|
|
798
908
|
<template name="pages" src="/bundle.html"></template>
|
|
799
909
|
</head>
|
|
@@ -809,7 +919,7 @@ public
|
|
|
809
919
|
<!DOCTYPE html>
|
|
810
920
|
<html>
|
|
811
921
|
<head>
|
|
812
|
-
<script type="module" src="webflo.bundle.js"></script>
|
|
922
|
+
<script type="module" src="/webflo.bundle.js"></script>
|
|
813
923
|
<script type="module" src="/bundle.js"></script>
|
|
814
924
|
<template name="pages" src="/bundle.html"></template>
|
|
815
925
|
</head>
|
|
@@ -908,8 +1018,11 @@ However, since the `document` objects in Webflo natively support [OOHTML](#oohtm
|
|
|
908
1018
|
</html>
|
|
909
1019
|
```
|
|
910
1020
|
|
|
911
|
-
>
|
|
912
|
-
|
|
1021
|
+
<details>
|
|
1022
|
+
<summary>Re-introducing logic in the actual language for logic - JavaScript...</summary>
|
|
1023
|
+
|
|
1024
|
+
> Now, this comes logical being that 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*!
|
|
1025
|
+
</details>
|
|
913
1026
|
|
|
914
1027
|
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.
|
|
915
1028
|
|
|
@@ -1064,15 +1177,22 @@ Observer.observe(state, propertyName, change => {
|
|
|
1064
1177
|
|
|
1065
1178
|
This way, all the moving parts of your application remain coordinated, and can easily be rendered to reflect them on the UI!
|
|
1066
1179
|
|
|
1067
|
-
|
|
1180
|
+
Now, 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](#the-observer-api). It comes off as the simplest approach to state and reactivity!
|
|
1068
1181
|
|
|
1069
1182
|
> **Note**
|
|
1070
|
-
> <br>The State API is
|
|
1183
|
+
> <br>The State API is available as long as the [OOHTML support level](#oohtml) in config is left as `full`, or set to `scripting`.
|
|
1071
1184
|
|
|
1072
1185
|
#### The `document.state.data` Object
|
|
1073
1186
|
|
|
1074
1187
|
This property reperesents the application data at any point in time - obtained from route handers on each navigation. Webflo simply updates this property and lets the page's [rendering logic](#client-and-server-side-rendering), or other parts of the application, take over.
|
|
1075
1188
|
|
|
1189
|
+
```js
|
|
1190
|
+
console.log(document.state.data) // { title: 'Home | FluffyPets' }
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
<details>
|
|
1194
|
+
<summary>More examples...</summary>
|
|
1195
|
+
|
|
1076
1196
|
```js
|
|
1077
1197
|
Observer.observe(document.state, 'data', e => {
|
|
1078
1198
|
console.log('Current page data is: ', e.value);
|
|
@@ -1085,6 +1205,7 @@ Observer.observe(document.state, 'data', e => {
|
|
|
1085
1205
|
document.title = title;
|
|
1086
1206
|
</script>
|
|
1087
1207
|
```
|
|
1208
|
+
</details>
|
|
1088
1209
|
|
|
1089
1210
|
#### The `document.state.url` Object
|
|
1090
1211
|
|
|
@@ -1094,6 +1215,9 @@ This is a *live* object that reperesents the properties of the application URL a
|
|
|
1094
1215
|
console.log(document.state.url) // { hash, host, hostname, href, origin, password, pathname, port, protocol, search, searchParams, username }
|
|
1095
1216
|
```
|
|
1096
1217
|
|
|
1218
|
+
<details>
|
|
1219
|
+
<summary>More examples...</summary>
|
|
1220
|
+
|
|
1097
1221
|
```js
|
|
1098
1222
|
Observer.observe(document.state.url, 'hash', e => {
|
|
1099
1223
|
console.log(document.state.url.hash === e.value); // true
|
|
@@ -1132,6 +1256,7 @@ document.addEventListener('synthetic-navigation', e => {
|
|
|
1132
1256
|
document.title = 'Login as ' + role;
|
|
1133
1257
|
</script>
|
|
1134
1258
|
```
|
|
1259
|
+
</details>
|
|
1135
1260
|
|
|
1136
1261
|
### Requests and Responses
|
|
1137
1262
|
|
|
@@ -1199,6 +1324,9 @@ Where workflows throw an exception, an *error* status is implied.
|
|
|
1199
1324
|
|
|
1200
1325
|
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.
|
|
1201
1326
|
|
|
1327
|
+
<details>
|
|
1328
|
+
<summary>Examples...</summary>
|
|
1329
|
+
|
|
1202
1330
|
```js
|
|
1203
1331
|
let response = event.Response(data, { headers: { 'Set-Cookie': cookieString }});
|
|
1204
1332
|
|
|
@@ -1220,19 +1348,20 @@ response.headers.cookies = { 'Cookie-1': cookieObject };
|
|
|
1220
1348
|
response.headers.cookies = { 'Cookie-2': cookie2Object };
|
|
1221
1349
|
|
|
1222
1350
|
console.log(response.headers.cookies); // { 'Cookie-1': cookieObject, 'Cookie-2': cookie2Object };
|
|
1223
|
-
|
|
1351
|
+
```
|
|
1224
1352
|
|
|
1225
1353
|
Set cookies are [accessed](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cookie) on the next request via request headers.
|
|
1226
1354
|
|
|
1227
1355
|
```js
|
|
1228
1356
|
console.log(event.request.headers.get('Cookie')); // Cookie-1=cookie-val&Cookie-2=cookie2-val;
|
|
1229
|
-
|
|
1357
|
+
```
|
|
1230
1358
|
|
|
1231
1359
|
Webflo also offers a *convenience* method.
|
|
1232
1360
|
|
|
1233
1361
|
```js
|
|
1234
1362
|
console.log(event.request.headers.cookies); // { 'Cookie-1': 'cookie-val', 'Cookie-2': 'cookie2-val' };
|
|
1235
1363
|
```
|
|
1364
|
+
</details>
|
|
1236
1365
|
|
|
1237
1366
|
### Webflo Applications
|
|
1238
1367
|
|
|
@@ -1251,11 +1380,18 @@ On being loaded, the state of the application is initialized, or is restored thr
|
|
|
1251
1380
|
|
|
1252
1381
|
##### SPA Navigation
|
|
1253
1382
|
|
|
1254
|
-
Unless disabled in config, 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.
|
|
1383
|
+
Unless disabled in config, 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.
|
|
1384
|
+
|
|
1385
|
+
<details>
|
|
1386
|
+
<summary>How it works...</summary>
|
|
1387
|
+
|
|
1388
|
+
SPA Navigation follows the following rules:
|
|
1389
|
+
|
|
1255
1390
|
+ 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.
|
|
1256
1391
|
+ 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.
|
|
1257
1392
|
+ 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.
|
|
1258
1393
|
+ 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.
|
|
1394
|
+
</details>
|
|
1259
1395
|
|
|
1260
1396
|
<details>
|
|
1261
1397
|
<summary>Config (Default)</summary>
|
|
@@ -1264,7 +1400,9 @@ Unless disabled in config, it is factored-in at build time for the application c
|
|
|
1264
1400
|
{ "spa_navigation": true }
|
|
1265
1401
|
```
|
|
1266
1402
|
|
|
1267
|
-
> File: `.webqit/webflo/client.json
|
|
1403
|
+
> **File: `.webqit/webflo/client.json`**
|
|
1404
|
+
|
|
1405
|
+
> **Command: `webflo config client spa_navigation=TRUE`**
|
|
1268
1406
|
</details>
|
|
1269
1407
|
|
|
1270
1408
|
##### SPA State
|
|
@@ -1279,75 +1417,98 @@ This is a *live* object that exposes the network activity and network state of t
|
|
|
1279
1417
|
console.log(document.state.network) // { requesting, remote, error, redirecting, connectivity, }
|
|
1280
1418
|
```
|
|
1281
1419
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1420
|
+
<details>
|
|
1421
|
+
<summary>Property: <code>.network.requesting: null|Object</code></summary>
|
|
1422
|
+
|
|
1423
|
+
This property tells when a request is ongoing, in which case it exposes the `params` object used to initiate the request.
|
|
1424
|
+
|
|
1425
|
+
On the UI, this could be used to hide a menu drawer that may have been open.
|
|
1426
|
+
|
|
1427
|
+
```html
|
|
1428
|
+
<menu-drawer>
|
|
1429
|
+
<script type="subscript">
|
|
1430
|
+
let { network: { requesting } } = document.state;
|
|
1431
|
+
if (requesting) {
|
|
1432
|
+
$(this).attr('open', false);
|
|
1433
|
+
}
|
|
1434
|
+
</script>
|
|
1435
|
+
</menu-drawer>
|
|
1436
|
+
```
|
|
1437
|
+
</details>
|
|
1438
|
+
|
|
1439
|
+
<details>
|
|
1440
|
+
<summary>Property: <code>.network.remote: null|String</code></summary>
|
|
1441
|
+
|
|
1442
|
+
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.
|
|
1443
|
+
|
|
1444
|
+
On the UI, this could be used to show/hide a spinner, or progress bar, to provide a visual cue.
|
|
1445
|
+
|
|
1446
|
+
```html
|
|
1447
|
+
<progress-bar>
|
|
1448
|
+
<script type="subscript">
|
|
1449
|
+
let { network: { remote } } = document.state;
|
|
1450
|
+
$(this).attr('hidden', !remote);
|
|
1451
|
+
</script>
|
|
1452
|
+
</progress-bar>
|
|
1453
|
+
```
|
|
1454
|
+
</details>
|
|
1455
|
+
|
|
1456
|
+
<details>
|
|
1457
|
+
<summary>Property: <code>.network.error: null|Error</code></summary>
|
|
1458
|
+
|
|
1459
|
+
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.
|
|
1460
|
+
|
|
1461
|
+
On the UI, this could be used to show/hide cute error elements.
|
|
1462
|
+
|
|
1463
|
+
```html
|
|
1464
|
+
<nice-error>
|
|
1465
|
+
<script type="subscript">
|
|
1466
|
+
let { network: { error } } = document.state;
|
|
1467
|
+
$(this).attr('hidden', !error);
|
|
1468
|
+
</script>
|
|
1469
|
+
</nice-error>
|
|
1470
|
+
```
|
|
1471
|
+
</details>
|
|
1472
|
+
|
|
1473
|
+
<details>
|
|
1474
|
+
<summary>Property: <code>.network.redirecting: null|String</code></summary>
|
|
1475
|
+
|
|
1476
|
+
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.
|
|
1477
|
+
|
|
1478
|
+
On the UI, this could be used to prevent further interactions with the outgoing page.
|
|
1479
|
+
|
|
1480
|
+
```html
|
|
1481
|
+
<body>
|
|
1482
|
+
<script type="subscript">
|
|
1483
|
+
let { network: { redirecting } } = document.state;
|
|
1484
|
+
$(this).css(redirecting ? { pointerEvents: 'none', filter: 'blur(2)' } : { pointerEvents: 'auto', filter: 'blur(0)' });
|
|
1485
|
+
</script>
|
|
1486
|
+
</body>
|
|
1487
|
+
```
|
|
1488
|
+
</details>
|
|
1489
|
+
|
|
1490
|
+
<details>
|
|
1491
|
+
<summary>Property: <code>.network.connectivity: String</code></summary>
|
|
1492
|
+
|
|
1493
|
+
This property tells of [the browser's ability to connect to the network](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine): `online`, `offline`.
|
|
1494
|
+
|
|
1495
|
+
On the UI, this could be used to show/hide a connectivity status.
|
|
1496
|
+
|
|
1497
|
+
```html
|
|
1498
|
+
<body>
|
|
1499
|
+
<script type="subscript">
|
|
1500
|
+
let { network: { connectivity } } = document.state;
|
|
1501
|
+
$(this).attr( 'connectivity', connectivity });
|
|
1502
|
+
</script>
|
|
1503
|
+
</body>
|
|
1504
|
+
```
|
|
1505
|
+
</details>
|
|
1348
1506
|
|
|
1349
1507
|
Here are some additional examples with the [Observer API](#the-observer-api).
|
|
1350
1508
|
|
|
1509
|
+
<details>
|
|
1510
|
+
<summary>Visualize the network state...</summary>
|
|
1511
|
+
|
|
1351
1512
|
```js
|
|
1352
1513
|
// Visualize the network state
|
|
1353
1514
|
let onlineVisualizer = changes => {
|
|
@@ -1358,6 +1519,10 @@ let onlineVisualizer = changes => {
|
|
|
1358
1519
|
Observer.observe(document.state.network, onlineVisualizer);
|
|
1359
1520
|
// Or: Observer.observe(document, [ ['state', 'network'] ], onlineVisualizer, { subtree: true });
|
|
1360
1521
|
```
|
|
1522
|
+
</details>
|
|
1523
|
+
|
|
1524
|
+
<details>
|
|
1525
|
+
<summary>Visualize "connectivity"...</summary>
|
|
1361
1526
|
|
|
1362
1527
|
```js
|
|
1363
1528
|
// Visualize the 'connectivity' property
|
|
@@ -1367,6 +1532,10 @@ let connectivityVisualizer = e => {
|
|
|
1367
1532
|
Observer.observe(document.state.network, 'connectivity', connectivityVisualizer);
|
|
1368
1533
|
// Or: Observer.observe(document.state, [ ['network', 'connectivity'] ], connectivityeVisualizer);
|
|
1369
1534
|
```
|
|
1535
|
+
</details>
|
|
1536
|
+
|
|
1537
|
+
<details>
|
|
1538
|
+
<summary>Catch request errors; attempt a retry...</summary>
|
|
1370
1539
|
|
|
1371
1540
|
```js
|
|
1372
1541
|
// Catch request errors; attempt a retry
|
|
@@ -1379,6 +1548,7 @@ Observer.observe(document.state.network, 'error', e => {
|
|
|
1379
1548
|
}
|
|
1380
1549
|
});
|
|
1381
1550
|
```
|
|
1551
|
+
</details>
|
|
1382
1552
|
|
|
1383
1553
|
##### Form Actions
|
|
1384
1554
|
|
|
@@ -1412,216 +1582,298 @@ Webflo client-side applications are intended to provide an app-like-first experi
|
|
|
1412
1582
|
{ "service_worker_support": true }
|
|
1413
1583
|
```
|
|
1414
1584
|
|
|
1415
|
-
> File: `.webqit/webflo/client.json
|
|
1585
|
+
> **File: `.webqit/webflo/client.json`**
|
|
1586
|
+
|
|
1587
|
+
> **Command: `webflo config client service_worker_support=TRUE`**
|
|
1416
1588
|
</details>
|
|
1417
1589
|
|
|
1418
1590
|
##### Fetching Strategy
|
|
1419
1591
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1592
|
+
<details>
|
|
1593
|
+
<summary>Network First</summary>
|
|
1594
|
+
|
|
1595
|
+
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, 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.
|
|
1596
|
+
|
|
1597
|
+
<details>
|
|
1598
|
+
<summary>Config (Default)</summary>
|
|
1424
1599
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1600
|
+
```json
|
|
1601
|
+
{ "default_fetching_strategy": "network-first" }
|
|
1602
|
+
```
|
|
1428
1603
|
|
|
1429
|
-
|
|
1604
|
+
*To list specific URLs...*
|
|
1430
1605
|
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1606
|
+
```json
|
|
1607
|
+
{ "network_first_urls": [ "/logo.png" ] }
|
|
1608
|
+
```
|
|
1434
1609
|
|
|
1435
|
-
|
|
1436
|
-
</details>
|
|
1610
|
+
> **File: `.webqit/webflo/worker.json`**
|
|
1437
1611
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
<summary>Config (Alternative)</summary>
|
|
1612
|
+
> **Command: `webflo config worker default_fetching_strategy=network-first`**
|
|
1613
|
+
</details>
|
|
1614
|
+
</details>
|
|
1442
1615
|
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
```
|
|
1616
|
+
<details>
|
|
1617
|
+
<summary>Cache First</summary>
|
|
1446
1618
|
|
|
1447
|
-
|
|
1619
|
+
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.
|
|
1448
1620
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
```
|
|
1621
|
+
<details>
|
|
1622
|
+
<summary>Config</summary>
|
|
1452
1623
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1624
|
+
```json
|
|
1625
|
+
{ "default_fetching_strategy": "cache-first" }
|
|
1626
|
+
```
|
|
1455
1627
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
<details>
|
|
1459
|
-
<summary>Config (Alternative)</summary>
|
|
1628
|
+
*To list specific URLs...*
|
|
1460
1629
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1630
|
+
```json
|
|
1631
|
+
{ "cache_first_urls": [ "/logo.png" ] }
|
|
1632
|
+
```
|
|
1633
|
+
|
|
1634
|
+
> **File: `.webqit/webflo/worker.json`**
|
|
1635
|
+
|
|
1636
|
+
> **Command: `webflo config worker default_fetching_strategy=cache-first`**
|
|
1637
|
+
</details>
|
|
1638
|
+
</details>
|
|
1639
|
+
|
|
1640
|
+
<details>
|
|
1641
|
+
<summary>Network Only</summary>
|
|
1642
|
+
|
|
1643
|
+
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.
|
|
1644
|
+
|
|
1645
|
+
<details>
|
|
1646
|
+
<summary>Config</summary>
|
|
1647
|
+
|
|
1648
|
+
```json
|
|
1649
|
+
{ "default_fetching_strategy": "network-only" }
|
|
1650
|
+
```
|
|
1464
1651
|
|
|
1465
|
-
|
|
1652
|
+
*To list specific URLs...*
|
|
1466
1653
|
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1654
|
+
```json
|
|
1655
|
+
{ "network_only_urls": [ "/logo.png" ] }
|
|
1656
|
+
```
|
|
1470
1657
|
|
|
1471
|
-
|
|
1472
|
-
</details>
|
|
1658
|
+
> **File: `.webqit/webflo/worker.json`**
|
|
1473
1659
|
|
|
1474
|
-
|
|
1660
|
+
> **Command: `webflo config worker default_fetching_strategy=network-only`**
|
|
1661
|
+
</details>
|
|
1662
|
+
</details>
|
|
1663
|
+
|
|
1664
|
+
<details>
|
|
1665
|
+
<summary>Cache Only</summary>
|
|
1666
|
+
|
|
1667
|
+
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. 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.)
|
|
1668
|
+
|
|
1669
|
+
<details>
|
|
1670
|
+
<summary>Config</summary>
|
|
1671
|
+
|
|
1672
|
+
```json
|
|
1673
|
+
{ "default_fetching_strategy": "cache-only" }
|
|
1674
|
+
```
|
|
1475
1675
|
|
|
1476
|
-
|
|
1477
|
-
<summary>Config (Alternative)</summary>
|
|
1676
|
+
*To list specific URLs...*
|
|
1478
1677
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1678
|
+
```json
|
|
1679
|
+
{ "cache_only_urls": [ "/logo.png" ] }
|
|
1680
|
+
```
|
|
1482
1681
|
|
|
1483
|
-
|
|
1682
|
+
> **File: `.webqit/webflo/worker.json`**
|
|
1484
1683
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1684
|
+
> **Command: `webflo config worker default_fetching_strategy=cache-only`**
|
|
1685
|
+
</details>
|
|
1686
|
+
</details>
|
|
1488
1687
|
|
|
1489
|
-
|
|
1490
|
-
</details>
|
|
1688
|
+
In all cases above, the convention for specifying URLs for a strategy accepts an [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.)
|
|
1491
1689
|
|
|
1492
|
-
|
|
1690
|
+
<details>
|
|
1691
|
+
<summary>Example...</summary>
|
|
1493
1692
|
|
|
1494
1693
|
```json
|
|
1495
1694
|
{ "cache_only_urls": [ "/icons/*.svg" ] }
|
|
1496
1695
|
```
|
|
1696
|
+
</details>
|
|
1497
1697
|
|
|
1498
1698
|
##### Cross-Thread Communications
|
|
1499
1699
|
|
|
1500
1700
|
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:
|
|
1501
1701
|
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
On both the client and worker side of your application, the `workport` object is accessible from route handlers as `this.runtime.workport`.
|
|
1505
|
-
|
|
1506
|
-
```js
|
|
1507
|
-
/**
|
|
1508
|
-
[client|worker]
|
|
1509
|
-
βββ index.js
|
|
1510
|
-
*/
|
|
1511
|
-
export default async function(event, context, next) {
|
|
1512
|
-
let { workport } = this.runtime;
|
|
1513
|
-
workport.messaging.post({ ... });
|
|
1514
|
-
return { ... };
|
|
1515
|
-
}
|
|
1516
|
-
```
|
|
1702
|
+
###### The `workport` API
|
|
1517
1703
|
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
+ **`.messaging.post()`** - for sending arbitrary data to the other side. E.g. `workport.messaging.post({ type: 'TEST' })`.
|
|
1521
|
-
+ **`.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).)
|
|
1522
|
-
+ **`.messaging.request()`** - for sending *replyable* messages to the other side, using the [MessageChannel](https://developer.mozilla.org/docs/Web/API/MessageChannel/MessageChannel) API.
|
|
1523
|
-
|
|
1524
|
-
```js
|
|
1525
|
-
// On the worker side
|
|
1526
|
-
workport.messaging.listen(event => {
|
|
1527
|
-
console.log(event.data);
|
|
1528
|
-
if (event.ports[0]) {
|
|
1529
|
-
event.ports[0].postMessage({ type: 'WORKS' });
|
|
1530
|
-
}
|
|
1531
|
-
});
|
|
1532
|
-
```
|
|
1533
|
-
|
|
1534
|
-
```js
|
|
1535
|
-
// On the client side
|
|
1536
|
-
let response = await workport.messaging.request({ type: 'TEST' });
|
|
1537
|
-
console.log(response); // { type: 'WORKS' }
|
|
1538
|
-
```
|
|
1539
|
-
|
|
1540
|
-
+ **`.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.
|
|
1541
|
-
|
|
1542
|
-
```js
|
|
1543
|
-
// On the worker side
|
|
1544
|
-
let channelId = 'channel-1';
|
|
1545
|
-
workport.messaging.channel(channelId).listen(event => {
|
|
1546
|
-
console.log(event.data);
|
|
1547
|
-
});
|
|
1548
|
-
```
|
|
1549
|
-
|
|
1550
|
-
```js
|
|
1551
|
-
// On the client side
|
|
1552
|
-
let channelId = 'channel-1';
|
|
1553
|
-
workport.messaging.channel(channelId).broadcast({ type: 'TEST' });
|
|
1554
|
-
```
|
|
1555
|
-
|
|
1556
|
-
For [UI Nofitications](https://developer.mozilla.org/en-US/docs/Web/API/notification), both sides of the API exposes the following methods:
|
|
1557
|
-
|
|
1558
|
-
+ **`.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).
|
|
1559
|
-
|
|
1560
|
-
```js
|
|
1561
|
-
let title = 'Test Nofitication';
|
|
1562
|
-
let options = { body: '...', icon: '...', actions: [ ... ] };
|
|
1563
|
-
workport.nofitications.fire(title, options).then(event => {
|
|
1564
|
-
console.log(event.action);
|
|
1565
|
-
});
|
|
1566
|
-
```
|
|
1567
|
-
|
|
1568
|
-
+ **`.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.)
|
|
1569
|
-
|
|
1570
|
-
```js
|
|
1571
|
-
workport.nofitications.listen(event => {
|
|
1572
|
-
console.log(event.action);
|
|
1573
|
-
});
|
|
1574
|
-
```
|
|
1575
|
-
|
|
1576
|
-
For [Push Nofitications](https://developer.mozilla.org/en-US/docs/Web/API/Push_API), the client-side of the API exposes the following methods:
|
|
1577
|
-
|
|
1578
|
-
+ **`.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.)
|
|
1579
|
-
+ **`.push.unsubscribe()`** - the equivalent of the [`PushSubscription.unsubscribe()`](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription/unsubscribe) method.
|
|
1580
|
-
+ **`.push.getSubscription()`** - the equivalent of the [`PushManager.getSubscription()`](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/getSubscription) method.
|
|
1704
|
+
This is an object with simple methods for working with *cross-thread* messages, UI and Push Notifications.
|
|
1581
1705
|
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
+ **`.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))`.
|
|
1706
|
+
On both the client and worker side of your application, the `workport` object is accessible from route handlers as `this.runtime.workport`.
|
|
1585
1707
|
|
|
1586
|
-
|
|
1708
|
+
```js
|
|
1709
|
+
/**
|
|
1710
|
+
[client|worker]
|
|
1711
|
+
βββ index.js
|
|
1712
|
+
*/
|
|
1713
|
+
export default async function(event, context, next) {
|
|
1714
|
+
let { workport } = this.runtime;
|
|
1715
|
+
workport.messaging.post({ ... });
|
|
1716
|
+
return { ... };
|
|
1717
|
+
}
|
|
1718
|
+
```
|
|
1719
|
+
|
|
1720
|
+
For cross-thread messaging, both sides of the API exposes the following methods:
|
|
1721
|
+
|
|
1722
|
+
<details>
|
|
1723
|
+
<summary>Method: <code>.messaging.post()</code></summary>
|
|
1724
|
+
|
|
1725
|
+
The `.messaging.post()` method is used for sending any arbitrary data to the other side. E.g. `workport.messaging.post({ type: 'TEST' })`.
|
|
1726
|
+
</details>
|
|
1727
|
+
|
|
1728
|
+
<details>
|
|
1729
|
+
<summary>Method: <code>.messaging.listen()</code></summary>
|
|
1730
|
+
|
|
1731
|
+
The `.messaging.listen()` method is used for registering a listener to the `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).)
|
|
1732
|
+
</details>
|
|
1733
|
+
|
|
1734
|
+
<details>
|
|
1735
|
+
<summary>Method: <code>.messaging.request()</code></summary>
|
|
1736
|
+
|
|
1737
|
+
The `.messaging.request()` method is used for sending a message to the other side and obtaing a response, using the [MessageChannel](https://developer.mozilla.org/docs/Web/API/MessageChannel/MessageChannel) API.
|
|
1738
|
+
|
|
1739
|
+
```js
|
|
1740
|
+
// On the worker side
|
|
1741
|
+
workport.messaging.listen(event => {
|
|
1742
|
+
console.log(event.data);
|
|
1743
|
+
if (event.ports[0]) {
|
|
1744
|
+
event.ports[0].postMessage({ type: 'WORKS' });
|
|
1745
|
+
}
|
|
1746
|
+
});
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
```js
|
|
1750
|
+
// On the client side
|
|
1751
|
+
let response = await workport.messaging.request({ type: 'TEST' });
|
|
1752
|
+
console.log(response); // { type: 'WORKS' }
|
|
1753
|
+
```
|
|
1754
|
+
</details>
|
|
1755
|
+
|
|
1756
|
+
<details>
|
|
1757
|
+
<summary>Method: <code>.messaging.channel()</code></summary>
|
|
1758
|
+
|
|
1759
|
+
The `.messaging.channel()` method is used 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.
|
|
1760
|
+
|
|
1761
|
+
```js
|
|
1762
|
+
// On the worker side
|
|
1763
|
+
let channelId = 'channel-1';
|
|
1764
|
+
workport.messaging.channel(channelId).listen(event => {
|
|
1765
|
+
console.log(event.data);
|
|
1766
|
+
});
|
|
1767
|
+
```
|
|
1768
|
+
|
|
1769
|
+
```js
|
|
1770
|
+
// On the client side
|
|
1771
|
+
let channelId = 'channel-1';
|
|
1772
|
+
workport.messaging.channel(channelId).broadcast({ type: 'TEST' });
|
|
1773
|
+
```
|
|
1774
|
+
</details>
|
|
1775
|
+
|
|
1776
|
+
For [UI Nofitications](https://developer.mozilla.org/en-US/docs/Web/API/notification), both sides of the API exposes the following methods:
|
|
1777
|
+
|
|
1778
|
+
<details>
|
|
1779
|
+
<summary>Method: <code>.nofitications.fire()</code></summary>
|
|
1780
|
+
|
|
1781
|
+
The `.nofitications.fire()` method is used 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).
|
|
1782
|
+
|
|
1783
|
+
```js
|
|
1784
|
+
let title = 'Test Nofitication';
|
|
1785
|
+
let options = { body: '...', icon: '...', actions: [ ... ] };
|
|
1786
|
+
workport.nofitications.fire(title, options).then(event => {
|
|
1787
|
+
console.log(event.action);
|
|
1788
|
+
});
|
|
1789
|
+
```
|
|
1790
|
+
</details>
|
|
1791
|
+
|
|
1792
|
+
<details>
|
|
1793
|
+
<summary>Method: <code>.nofitications.listen()</code></summary>
|
|
1794
|
+
|
|
1795
|
+
The `.nofitications.listen()` method (in Service-Workers) is used 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.)
|
|
1796
|
+
|
|
1797
|
+
```js
|
|
1798
|
+
workport.nofitications.listen(event => {
|
|
1799
|
+
console.log(event.action);
|
|
1800
|
+
});
|
|
1801
|
+
```
|
|
1802
|
+
</details>
|
|
1803
|
+
|
|
1804
|
+
For [Push Nofitications](https://developer.mozilla.org/en-US/docs/Web/API/Push_API), the client-side of the API exposes the following methods:
|
|
1805
|
+
|
|
1806
|
+
<details>
|
|
1807
|
+
<summary>Method: <code>.push.subscribe()</code></summary>
|
|
1808
|
+
|
|
1809
|
+
The `.push.subscribe()` method is 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.)
|
|
1810
|
+
</details>
|
|
1811
|
+
|
|
1812
|
+
<details>
|
|
1813
|
+
<summary>Method: <code>.push.unsubscribe()</code></summary>
|
|
1814
|
+
|
|
1815
|
+
The `.push.unsubscribe()` method is the equivalent of the [`PushSubscription.unsubscribe()`](https://developer.mozilla.org/en-US/docs/Web/API/PushSubscription/unsubscribe) method.
|
|
1816
|
+
</details>
|
|
1817
|
+
|
|
1818
|
+
<details>
|
|
1819
|
+
<summary>Method: <code>.push.getSubscription()</code></summary>
|
|
1820
|
+
|
|
1821
|
+
The `.push.getSubscription()` method is the equivalent of the [`PushManager.getSubscription()`](https://developer.mozilla.org/en-US/docs/Web/API/PushManager/getSubscription) method.
|
|
1822
|
+
</details>
|
|
1823
|
+
|
|
1824
|
+
The worker-side of the API exposes the following methods:
|
|
1825
|
+
|
|
1826
|
+
<details>
|
|
1827
|
+
<summary>Method: <code>.push.listen()</code></summary>
|
|
1828
|
+
|
|
1829
|
+
The `.push.listen()` method is 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))`.
|
|
1830
|
+
</details>
|
|
1831
|
+
|
|
1832
|
+
###### Route *events*
|
|
1833
|
+
|
|
1834
|
+
These are simple route events that fire when messaging and notification events happen.
|
|
1835
|
+
|
|
1836
|
+
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.
|
|
1837
|
+
|
|
1838
|
+
```js
|
|
1839
|
+
/**
|
|
1840
|
+
[client|worker]
|
|
1841
|
+
βββ index.js
|
|
1842
|
+
*/
|
|
1843
|
+
export default async function(event, context, next) {
|
|
1844
|
+
return { ... };
|
|
1845
|
+
}
|
|
1846
|
+
export async function alert(event, context, next) {
|
|
1847
|
+
return { ... };
|
|
1848
|
+
}
|
|
1849
|
+
```
|
|
1850
|
+
|
|
1851
|
+
The event type is given in the `event.type` property. This could be:
|
|
1852
|
+
|
|
1853
|
+
+ **`message`** - both client and worker side. For *replyable* messages, the event handler's return value is automatically sent back as response.
|
|
1854
|
+
+ **`notificationclick`** - worker side.
|
|
1855
|
+
+ **`push`** - worker side.
|
|
1856
|
+
|
|
1857
|
+
<details>
|
|
1858
|
+
<summary>Advanced...</summary>
|
|
1859
|
+
|
|
1860
|
+
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.
|
|
1861
|
+
|
|
1862
|
+
```js
|
|
1863
|
+
/**
|
|
1864
|
+
worker
|
|
1865
|
+
βββ index.js
|
|
1866
|
+
*/
|
|
1867
|
+
export async function alert(event, context, next) {
|
|
1868
|
+
if (event.type === 'push') {
|
|
1869
|
+
await next(context, '/services/push');
|
|
1870
|
+
return;
|
|
1871
|
+
}
|
|
1872
|
+
console.log(event.type);
|
|
1873
|
+
}
|
|
1874
|
+
```
|
|
1875
|
+
</details>
|
|
1587
1876
|
|
|
1588
|
-
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.
|
|
1589
|
-
|
|
1590
|
-
```js
|
|
1591
|
-
/**
|
|
1592
|
-
[client|worker]
|
|
1593
|
-
βββ index.js
|
|
1594
|
-
*/
|
|
1595
|
-
export default async function(event, context, next) {
|
|
1596
|
-
return { ... };
|
|
1597
|
-
}
|
|
1598
|
-
export async function alert(event, context, next) {
|
|
1599
|
-
return { ... };
|
|
1600
|
-
}
|
|
1601
|
-
```
|
|
1602
|
-
|
|
1603
|
-
The event type is given in the `event.type` property. This could be:
|
|
1604
|
-
|
|
1605
|
-
+ **`message`** - both client and worker side. For *replyable* messages, the event handler's return value is automatically sent back as response.
|
|
1606
|
-
+ **`notificationclick`** - worker side.
|
|
1607
|
-
+ **`push`** - worker side.
|
|
1608
|
-
|
|
1609
|
-
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.
|
|
1610
|
-
|
|
1611
|
-
```js
|
|
1612
|
-
/**
|
|
1613
|
-
worker
|
|
1614
|
-
βββ index.js
|
|
1615
|
-
*/
|
|
1616
|
-
export async function alert(event, context, next) {
|
|
1617
|
-
if (event.type === 'push') {
|
|
1618
|
-
await next(context, '/services/push');
|
|
1619
|
-
return;
|
|
1620
|
-
}
|
|
1621
|
-
console.log(event.type);
|
|
1622
|
-
}
|
|
1623
|
-
```
|
|
1624
|
-
|
|
1625
1877
|
#### API Backends
|
|
1626
1878
|
|
|
1627
1879
|
In Webflo, an API backend is what you, in essence, come off with with your server-side routes.
|
|
@@ -1694,16 +1946,15 @@ A simple tool, like [`staticgen`](https://github.com/tj/staticgen), or the basic
|
|
|
1694
1946
|
}
|
|
1695
1947
|
```
|
|
1696
1948
|
|
|
1697
|
-
>
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
You have a static site!
|
|
1949
|
+
<details>
|
|
1950
|
+
<summary>How it works...</summary>
|
|
1701
1951
|
|
|
1702
|
-
|
|
1952
|
+
> 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.
|
|
1953
|
+
</details>
|
|
1703
1954
|
|
|
1704
|
-
|
|
1955
|
+
*Happy static!*
|
|
1705
1956
|
|
|
1706
|
-
|
|
1957
|
+
## Webflo Config
|
|
1707
1958
|
|
|
1708
1959
|
Webflo comes *convention-first*! But it is entirely configurable for when you need it! The easiest way to do this is to run the command `webflo config` and follow the walkthrough. To simply get an overview, use the command `webflo config help`, and all commands and their description are shown.
|
|
1709
1960
|
|
|
@@ -1713,7 +1964,7 @@ Webflo applications are often built on/with the following technologies.
|
|
|
1713
1964
|
|
|
1714
1965
|
### OOHTML
|
|
1715
1966
|
|
|
1716
|
-
[OOHTML](https://github.com/webqit/oohtml) is a proposed set of new features for HTML that makes it fun to hand-author your
|
|
1967
|
+
[OOHTML](https://github.com/webqit/oohtml) is a proposed set of new features for HTML that makes it fun to hand-author your HTML documents! Within OOHTML are [HTML Modules](https://github.com/webqit/oohtml#html-modules) and [HTML Imports](https://github.com/webqit/oohtml#html-imports), [Reactive Scripts](https://github.com/webqit/oohtml#subscript) and more!
|
|
1717
1968
|
|
|
1718
1969
|
Webflo natively supports OOHTML in full! But it is also possible to switch this to none, or to partial support - when specific features aren't needed anywhere in your application. Server-side and client-side support for OOHTML exist independently. This is good when, for example, your application places more importance on SSR, and less on CSR, in which case a reduced support for OOHTML can reduce the overall client JS bundle size.
|
|
1719
1970
|
|
|
@@ -1726,7 +1977,9 @@ Webflo natively supports OOHTML in full! But it is also possible to switch this
|
|
|
1726
1977
|
|
|
1727
1978
|
*Values: `full`, `namespacing`, `scripting`, `templating`, `none` - See [details at OOHTML SSR](https://github.com/webqit/oohtml-ssr#options)*
|
|
1728
1979
|
|
|
1729
|
-
> File: `.webqit/webflo/client.json
|
|
1980
|
+
> **File: `.webqit/webflo/client.json`**
|
|
1981
|
+
|
|
1982
|
+
> **Command: `webflo config client oohtml_support=full`**
|
|
1730
1983
|
|
|
1731
1984
|
> File: `.webqit/webflo/server.json` | Command: `webflo config server oohtml_support=full`
|
|
1732
1985
|
</details>
|