@webqit/webflo 0.11.20 → 0.11.23
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 +68 -23
- package/package.json +1 -1
- package/src/runtime-pi/client/Runtime.js +31 -15
- package/test/index.test.js +6 -5
package/README.md
CHANGED
|
@@ -7,41 +7,81 @@
|
|
|
7
7
|
|
|
8
8
|
<!-- /BADGES -->
|
|
9
9
|
|
|
10
|
-
Webflo is a universal *web*, *mobile*, and *API backend* framework
|
|
10
|
+
Webflo is a universal *web*, *mobile*, and *API backend* framework that gets it all done in vanilla HTML, CSS, and JavaScript! It's a powerful little thing written to facilitate building more authentic, web-native applications!
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Here, we've put all of that up for a 10min straight read!
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
TL;DR: here's what's ahead...
|
|
15
|
+
|
|
16
|
+
+ web-native development and the vanilla advantage!
|
|
17
|
+
+ the path of least engineering!
|
|
18
|
+
|
|
19
|
+
## The Catch...
|
|
20
|
+
|
|
21
|
+
Webflo is a framework on its own track - working and thinking in vanilla HTML, CSS and JavaScript! Instead of trying to follow certain norms, it takes a plunge to draw on native web platform features - plus some more futurisric, fascinating stuffs we're proposing as standards! This means that you also have to be excited about taking a plunge to happily meet Webflo!
|
|
22
|
+
|
|
23
|
+
## The Wins...
|
|
24
|
+
|
|
25
|
+
Much of what eludes the web today...
|
|
16
26
|
|
|
17
|
-
|
|
27
|
+
+ the long-missing framework design and architecture for a HTML-first thinking!
|
|
28
|
+
+ a focused standards-based philosophy that breeds more authentic applications!
|
|
29
|
+
|
|
30
|
+
Plus native support for how you want to work...
|
|
31
|
+
|
|
32
|
+
+ a new approach to reactivity that's based on no syntax at all but plain old JavaScript!
|
|
33
|
+
+ a new "imports" feature for HTML that makes HTML more reusable!
|
|
34
|
+
+ and much more.
|
|
35
|
+
|
|
36
|
+
## Documentation
|
|
37
|
+
|
|
38
|
+
All of Webflo in a 10-min read!
|
|
18
39
|
|
|
19
40
|
+ [Overview](#overview)
|
|
20
41
|
+ [Installation](#installation)
|
|
21
42
|
+ [Concepts](#concepts)
|
|
43
|
+
+ [Handler Functions and Layout](#handler-functions-and-layout)
|
|
44
|
+
+ [Step Functions and Workflows](#step-functions-and-workflows)
|
|
45
|
+
+ [Pages, Layout and Templating](#pages-layout-and-templating)
|
|
46
|
+
+ [Client and Server-Side Rendering](#client-and-server-side-rendering)
|
|
47
|
+
+ [Requests and Responses](#requests-and-responses)
|
|
22
48
|
+ [Webflo Applications](#webflo-applications)
|
|
49
|
+
+ [Client-Side Applications](#client-side-applications)
|
|
50
|
+
+ [Progressive Web Apps](#progressive-web-apps)
|
|
51
|
+
+ [API Backends](#api-backends)
|
|
52
|
+
+ [Static Sites](#static-sites)
|
|
23
53
|
+ [Webflo Config](#webflo-config)
|
|
24
|
-
+ [
|
|
54
|
+
+ [Webflo Tooling](#webflo-tooling)
|
|
55
|
+
+ [OOHTML](#oohtml)
|
|
56
|
+
+ [OOHTML SSR](#oohtml-ssr)
|
|
57
|
+
+ [OOHTML CLI](#oohtml-cli)
|
|
58
|
+
+ [The Observer API](#the-observer-api)
|
|
25
59
|
+ [Getting Started](#getting-started)
|
|
26
60
|
+ [Getting Involved](#getting-involved)
|
|
27
61
|
|
|
28
62
|
## Overview
|
|
29
63
|
|
|
30
64
|
<details>
|
|
31
|
-
<summary><b>Build <i>anything</i></b> - from as basic as a static <code>index.html</code> page to as rich as a universal app that's either a <i>Multi Page Application (MPA)</i>, <i>Single Page Application (SPA)</i>, or a hybrid of
|
|
65
|
+
<summary><b>Build <i>anything</i></b> - from as basic as a static <code>index.html</code> page to as rich as a universal app that's either a <i>Multi Page Application (MPA)</i>, <i>Single Page Application (SPA)</i>, or a hybrid of both, implementing <i>Server Side Rendering (SSR)</i>, <i>Client Side Rendering (CSR)</i>, or a hybrid of both, offline and <i>PWA</i> capabilities, etc. - this time, <i>without loosing the vanilla advantage</i>!
|
|
32
66
|
</summary>
|
|
33
67
|
|
|
34
68
|
Here's a glimpse of your Webflo app.
|
|
35
69
|
|
|
36
|
-
For when your application
|
|
70
|
+
For when your application is a static site, or has static files to serve.
|
|
37
71
|
+ The `public` directory for static files.
|
|
38
|
-
+ The `server` directory for server-side routing. (i.e. dynamic request handling on the server - in the case of Multi Page Applications, API backends, etc.)
|
|
39
72
|
|
|
40
73
|
```shell
|
|
41
74
|
my-app
|
|
42
|
-
├── server/index.js
|
|
43
75
|
└── public/logo.png
|
|
44
76
|
```
|
|
77
|
+
|
|
78
|
+
For when your application has a Server side.
|
|
79
|
+
+ The `server` directory for server-side routing. (i.e. dynamic request handling on the server - in the case of Multi Page Applications, API backends, etc.)
|
|
80
|
+
|
|
81
|
+
```shell
|
|
82
|
+
my-app
|
|
83
|
+
└── server/index.js
|
|
84
|
+
```
|
|
45
85
|
|
|
46
86
|
And a typical `index.js` route handler has the following anatomy.
|
|
47
87
|
|
|
@@ -100,7 +140,7 @@ For when your application has a Client side.
|
|
|
100
140
|
└── worker/index.js
|
|
101
141
|
```
|
|
102
142
|
|
|
103
|
-
And in both cases, a typical `index.js` route handler has the following anatomy.
|
|
143
|
+
And in both cases, a typical `index.js` route handler has the following anatomy. (Same with server-side handlers.)
|
|
104
144
|
|
|
105
145
|
```js
|
|
106
146
|
/**
|
|
@@ -385,7 +425,7 @@ export default function(event, context, next) {
|
|
|
385
425
|
}
|
|
386
426
|
```
|
|
387
427
|
|
|
388
|
-
|
|
428
|
+
We get a step-based workflow that helps to decomplicate routing and lets us scale horizontally as our application grows larger.
|
|
389
429
|
|
|
390
430
|
<details>
|
|
391
431
|
<summary>More details...</summary>
|
|
@@ -525,7 +565,7 @@ So, above, should our handler receive static file requests like `http://localhos
|
|
|
525
565
|
|
|
526
566
|
```shell
|
|
527
567
|
my-app
|
|
528
|
-
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/
|
|
568
|
+
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/products, http://localhost:3000/products/stickers, etc
|
|
529
569
|
└── public/logo.png ------------------------- http://localhost:3000/logo.png
|
|
530
570
|
```
|
|
531
571
|
|
|
@@ -567,7 +607,7 @@ Now we get the following handler-to-URL mapping for our application:
|
|
|
567
607
|
```shell
|
|
568
608
|
my-app
|
|
569
609
|
├── worker/index.js ------------------------- http://localhost:3000/about, http://localhost:3000/logo.png
|
|
570
|
-
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/
|
|
610
|
+
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/products, http://localhost:3000/products/stickers, etc
|
|
571
611
|
└── public/logo.png ------------------------- http://localhost:3000/logo.png
|
|
572
612
|
```
|
|
573
613
|
|
|
@@ -606,7 +646,7 @@ Our overall handler-to-URL mapping for this application now becomes:
|
|
|
606
646
|
my-app
|
|
607
647
|
├── client/index.js ------------------------- http://localhost:3000/login
|
|
608
648
|
├── worker/index.js ------------------------- http://localhost:3000/about, http://localhost:3000/logo.png
|
|
609
|
-
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/
|
|
649
|
+
├── server/index.js ------------------------- http://localhost:3000, http://localhost:3000/products, http://localhost:3000/products/stickers, etc
|
|
610
650
|
└── public/logo.png ------------------------- http://localhost:3000/logo.png
|
|
611
651
|
```
|
|
612
652
|
|
|
@@ -692,7 +732,7 @@ In a Multi Page Application (with an individual-page architecture), each page is
|
|
|
692
732
|
my-app
|
|
693
733
|
└── public
|
|
694
734
|
├── about/index.html ------------------------- <!DOCTYPE html>
|
|
695
|
-
├──
|
|
735
|
+
├── products/index.html ---------------------- <!DOCTYPE html>
|
|
696
736
|
├── index.html ------------------------------- <!DOCTYPE html>
|
|
697
737
|
├── header.html ------------------------------ <header></header> <!-- To appear at top of each index.html page -->
|
|
698
738
|
└── footer.html ------------------------------ <footer></footer> <!-- To appear at bottom of each index.html page -->
|
|
@@ -704,7 +744,7 @@ In a Single Page Application, each page is the same `index.html` document, and i
|
|
|
704
744
|
my-app
|
|
705
745
|
└── public
|
|
706
746
|
├── about/main.html -------------------------- <main></main> <!-- To appear at main area of index.html -->
|
|
707
|
-
├──
|
|
747
|
+
├── products/main.html ----------------------- <main></main> <!-- To appear at main area of index.html -->
|
|
708
748
|
├── main.html -------------------------------- <main></main> <!-- To appear at main area of index.html -->
|
|
709
749
|
└── index.html ------------------------------- <!DOCTYPE html>
|
|
710
750
|
```
|
|
@@ -733,7 +773,7 @@ Here, you are able to define reusable contents in a `<template>` element...
|
|
|
733
773
|
</body>
|
|
734
774
|
```
|
|
735
775
|
|
|
736
|
-
The *module* element - `<template>` - is able to load its contents from a remote `.html` file that serves as a bundle:
|
|
776
|
+
The *module* element - `<template>` - is also able to load its contents from a remote `.html` file that serves as a bundle:
|
|
737
777
|
|
|
738
778
|
```html
|
|
739
779
|
<!--
|
|
@@ -825,7 +865,7 @@ public/products
|
|
|
825
865
|
|
|
826
866
|
#### In a Single Page Layout
|
|
827
867
|
|
|
828
|
-
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.
|
|
868
|
+
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. Notice how nested routes end up as nested `<template>` elements that form the equivalent of the application's URL structure.
|
|
829
869
|
|
|
830
870
|
```html
|
|
831
871
|
<!--
|
|
@@ -876,7 +916,7 @@ It's all a *layout* thing, so a hybrid of the two architectures above is possibl
|
|
|
876
916
|
my-app
|
|
877
917
|
└── public
|
|
878
918
|
├── about/index.html ------------------------- <!DOCTYPE html> <!-- Document root 1 -->
|
|
879
|
-
├──
|
|
919
|
+
├── products
|
|
880
920
|
│ ├── free/main.html --------------------------- <main></main> <!-- To appear at main area of document root 2 -->
|
|
881
921
|
│ ├── paid/main.html --------------------------- <main></main> <!-- To appear at main area of document root 2 -->
|
|
882
922
|
│ ├── main.html -------------------------------- <main></main> <!-- To appear at main area of document root 2 -->
|
|
@@ -886,7 +926,7 @@ my-app
|
|
|
886
926
|
└── footer.html ------------------------------ <footer></footer> <!-- To appear at bottom of each document root -->
|
|
887
927
|
```
|
|
888
928
|
|
|
889
|
-
The above gives us three document roots: `/index.html`, `/about/index.html`, `/
|
|
929
|
+
The above gives us three document roots: `/index.html`, `/about/index.html`, `/products/index.html`. The `/products` route doubles as a Single Page Application such that visiting the `/products` route loads the document root `/products/index.html` and lets Webflo SPA routing determine which of `/products/main.html`, `/products/free/main.html`, `/products/paid/main.html` is imported on a given URL.
|
|
890
930
|
|
|
891
931
|
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.
|
|
892
932
|
|
|
@@ -1224,7 +1264,7 @@ Observer.observe(document.state, 'data', e => {
|
|
|
1224
1264
|
|
|
1225
1265
|
#### The `document.state.url` Object
|
|
1226
1266
|
|
|
1227
|
-
This is a *live* object that reperesents the properties of the application URL at any point in time. The object exposes the same URL properties as
|
|
1267
|
+
This is a *live* object that reperesents the properties of the application URL at any point in time. The object exposes the same URL properties as of a standard [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL) object, but, here, as *live* properties that can be observed as navigation happens, and modified to initiate navigation - all using the [Observer API](#the-observer-api).
|
|
1228
1268
|
|
|
1229
1269
|
```js
|
|
1230
1270
|
console.log(document.state.url) // { hash, host, hostname, href, origin, password, pathname, port, protocol, search, searchParams, username }
|
|
@@ -1973,10 +2013,15 @@ A simple tool, like [`staticgen`](https://github.com/tj/staticgen), or the basic
|
|
|
1973
2013
|
|
|
1974
2014
|
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.
|
|
1975
2015
|
|
|
1976
|
-
##
|
|
2016
|
+
## Webflo Tooling
|
|
1977
2017
|
|
|
1978
2018
|
Webflo applications are often built on/with the following technologies.
|
|
1979
2019
|
|
|
2020
|
+
+ [OOHTML](#oohtml)
|
|
2021
|
+
+ [OOHTML SSR](#oohtml-ssr)
|
|
2022
|
+
+ [OOHTML CLI](#oohtml-cli)
|
|
2023
|
+
+ [The Observer API](#the-observer-api)
|
|
2024
|
+
|
|
1980
2025
|
### OOHTML
|
|
1981
2026
|
|
|
1982
2027
|
[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!
|
package/package.json
CHANGED
|
@@ -52,7 +52,6 @@ export default class Runtime {
|
|
|
52
52
|
* @return void
|
|
53
53
|
*/
|
|
54
54
|
constructor(cx, clientCallback) {
|
|
55
|
-
|
|
56
55
|
// ---------------
|
|
57
56
|
this.cx = cx;
|
|
58
57
|
this.clients = new Map;
|
|
@@ -97,12 +96,12 @@ export default class Runtime {
|
|
|
97
96
|
// Capture all link-clicks
|
|
98
97
|
// and fire to this router.
|
|
99
98
|
window.addEventListener('click', e => {
|
|
100
|
-
var
|
|
101
|
-
if (!
|
|
102
|
-
if (!
|
|
99
|
+
var anchorEl = e.target.closest('a');
|
|
100
|
+
if (!anchorEl || !anchorEl.href) return;
|
|
101
|
+
if (!anchorEl.target && !anchorEl.download && this.isSpaRoute(anchorEl, e)) {
|
|
103
102
|
// Publish everything, including hash
|
|
104
|
-
this.go(Url.copy(
|
|
105
|
-
if (!
|
|
103
|
+
this.go(Url.copy(anchorEl), {}, { src: anchorEl, srcType: 'link', });
|
|
104
|
+
if (!this.isHashAction(anchorEl)) {
|
|
106
105
|
e.preventDefault();
|
|
107
106
|
}
|
|
108
107
|
}
|
|
@@ -143,7 +142,7 @@ export default class Runtime {
|
|
|
143
142
|
method: submitParams.method,
|
|
144
143
|
body: formData,
|
|
145
144
|
}, { ...submitParams, src: form, srcType: 'form', });
|
|
146
|
-
if (!
|
|
145
|
+
if (!this.isHashAction(actionEl)) {
|
|
147
146
|
e.preventDefault();
|
|
148
147
|
}
|
|
149
148
|
}
|
|
@@ -188,8 +187,14 @@ export default class Runtime {
|
|
|
188
187
|
return window.history;
|
|
189
188
|
}
|
|
190
189
|
|
|
191
|
-
// Check is-
|
|
192
|
-
|
|
190
|
+
// Check is-hash-action
|
|
191
|
+
isHashAction(urlObj) {
|
|
192
|
+
const isHashNav = _before(window.document.location.href, '#') === _before(urlObj.href, '#') && urlObj.href.includes('#');
|
|
193
|
+
return isHashNav && urlObj.hash.length > 1 && document.querySelector(urlObj.hash);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check is-spa-route
|
|
197
|
+
isSpaRoute(url, e = undefined) {
|
|
193
198
|
url = typeof url === 'string' ? new whatwag.URL(url) : url;
|
|
194
199
|
if (url.origin && url.origin !== this.location.origin) return false;
|
|
195
200
|
if (e && (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)) return false;
|
|
@@ -278,13 +283,14 @@ export default class Runtime {
|
|
|
278
283
|
// ------------
|
|
279
284
|
if (response.redirected) {
|
|
280
285
|
Observer.set(this.location, { href: response.url }, { detail: { redirected: true }, });
|
|
286
|
+
Observer.set(this.network, 'requesting', null);
|
|
281
287
|
} else if (![302, 301].includes(finalResponse.status)) {
|
|
282
|
-
Observer.set(this.location, url);
|
|
288
|
+
Observer.set(this.location, Url.copy(url)/* copy() is important */);
|
|
289
|
+
Observer.set(this.network, 'requesting', null);
|
|
283
290
|
}
|
|
284
291
|
// ------------
|
|
285
292
|
// States
|
|
286
293
|
// ------------
|
|
287
|
-
Observer.set(this.network, 'requesting', null);
|
|
288
294
|
if (['link', 'form'].includes(detail.srcType)) {
|
|
289
295
|
detail.src.state && (detail.src.state.active = false);
|
|
290
296
|
detail.submitter && detail.submitter.state && (detail.submitter.state.active = false);
|
|
@@ -330,10 +336,20 @@ export default class Runtime {
|
|
|
330
336
|
if (!(response instanceof Response)) { response = new Response(response); }
|
|
331
337
|
if (!response.redirected) {
|
|
332
338
|
let location = response.headers.get('Location');
|
|
333
|
-
if (location
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
339
|
+
if (location) {
|
|
340
|
+
let xActualRedirectCode = parseInt(response.headers.get('X-Redirect-Code'));
|
|
341
|
+
if (xActualRedirectCode && response.status === this._xRedirectCode) {
|
|
342
|
+
response.attrs.status = xActualRedirectCode;
|
|
343
|
+
Observer.set(this.network, 'redirecting', location);
|
|
344
|
+
window.location = location;
|
|
345
|
+
} else if ([302,301].includes(response.status)) {
|
|
346
|
+
if (!this.isSpaRoute(location)) {
|
|
347
|
+
Observer.set(this.network, 'redirecting', location);
|
|
348
|
+
window.location = location;
|
|
349
|
+
} else {
|
|
350
|
+
this.go(location, {}, { srcType: 'rdr' });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
337
353
|
}
|
|
338
354
|
}
|
|
339
355
|
return response;
|
package/test/index.test.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
|
|
1
2
|
/**
|
|
2
3
|
* @imports
|
|
3
4
|
*/
|
|
4
5
|
import { config, runtime, Context } from '../src/index.js';
|
|
5
6
|
|
|
6
7
|
let client = {
|
|
7
|
-
handle: function(httpEvent) {
|
|
8
|
+
handle: function( httpEvent ) {
|
|
8
9
|
return new httpEvent.Response({ abcd: '1234' }, {
|
|
9
10
|
status: 302,
|
|
10
11
|
headers: {
|
|
@@ -18,8 +19,8 @@ let client = {
|
|
|
18
19
|
},
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
const cx = Context.create({ config: config, });
|
|
22
|
-
const clientCallback = (_cx, hostName, defaultClientCallback) => client;
|
|
23
|
-
const app = await runtime.server.start.call(cx, clientCallback);
|
|
22
|
+
const cx = Context.create( { config: config, } );
|
|
23
|
+
const clientCallback = ( _cx, hostName, defaultClientCallback ) => client;
|
|
24
|
+
const app = await runtime.server.start.call( cx, clientCallback );
|
|
24
25
|
|
|
25
|
-
const response = await app.go('http://localhost/', { headers: { range: 'bytes=0-5, 6' } } );
|
|
26
|
+
const response = await app.go( 'http://localhost/', { headers: { range: 'bytes=0-5, 6' } } );
|