@webqit/webflo 0.10.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1509 -3
- package/bundle.html.json +1665 -0
- package/package.json +3 -3
- package/src/Context.js +8 -4
- package/src/config-pi/deployment/Env.js +2 -2
- package/src/config-pi/deployment/Layout.js +2 -2
- package/src/config-pi/deployment/Origins.js +2 -2
- package/src/config-pi/deployment/Virtualization.js +2 -2
- package/src/config-pi/runtime/Client.js +39 -11
- package/src/config-pi/runtime/Server.js +26 -10
- package/src/config-pi/runtime/client/Worker.js +32 -14
- package/src/config-pi/runtime/server/Headers.js +2 -2
- package/src/config-pi/runtime/server/Redirects.js +2 -2
- package/src/config-pi/static/Manifest.js +2 -2
- package/src/config-pi/static/Ssg.js +2 -2
- package/src/runtime-pi/Router.js +1 -1
- package/src/runtime-pi/client/Runtime.js +116 -62
- package/src/runtime-pi/client/RuntimeClient.js +28 -43
- package/src/runtime-pi/client/Workport.js +163 -0
- package/src/runtime-pi/client/generate.js +285 -77
- package/src/runtime-pi/client/{generate.oohtml.js → oohtml/full.js} +0 -0
- package/src/runtime-pi/client/oohtml/namespacing.js +7 -0
- package/src/runtime-pi/client/oohtml/scripting.js +8 -0
- package/src/runtime-pi/client/oohtml/templating.js +8 -0
- package/src/runtime-pi/client/worker/Worker.js +58 -24
- package/src/runtime-pi/client/worker/Workport.js +80 -0
- package/src/runtime-pi/server/Router.js +2 -2
- package/src/runtime-pi/server/Runtime.js +30 -11
- package/src/runtime-pi/server/RuntimeClient.js +24 -14
- package/src/runtime-pi/util.js +2 -2
- package/src/webflo.js +7 -9
- package/test/site/package.json +9 -0
- package/test/site/public/bundle.html +6 -0
- package/test/site/public/bundle.html.json +4 -0
- package/test/site/public/bundle.js +2 -0
- package/test/site/public/bundle.js.gz +0 -0
- package/test/site/public/bundle.webflo.js +15 -0
- package/test/site/public/bundle.webflo.js.gz +0 -0
- package/test/site/public/index.html +30 -0
- package/test/site/public/index1.html +35 -0
- package/test/site/public/page-2/bundle.html +5 -0
- package/test/site/public/page-2/bundle.html.json +1 -0
- package/test/site/public/page-2/bundle.js +2 -0
- package/test/site/public/page-2/bundle.js.gz +0 -0
- package/test/site/public/page-2/index.html +46 -0
- package/test/site/public/page-2/logo-130x130.png +0 -0
- package/test/site/public/page-2/main.html +3 -0
- package/test/site/public/page-3/logo-130x130.png +0 -0
- package/test/site/public/page-4/subpage/bundle.html +0 -0
- package/test/site/public/page-4/subpage/bundle.html.json +1 -0
- package/test/site/public/page-4/subpage/bundle.js +2 -0
- package/test/site/public/page-4/subpage/bundle.js.gz +0 -0
- package/test/site/public/page-4/subpage/index.html +31 -0
- package/test/site/public/sparoots.json +5 -0
- package/test/site/public/worker.js +3 -0
- package/test/site/public/worker.js.gz +0 -0
- package/test/site/server/index.js +16 -0
- package/src/Cli.js +0 -131
- package/src/Configurator.js +0 -97
- package/src/runtime-pi/client/WorkerComm.js +0 -102
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
export default class Workport {
|
|
3
|
+
|
|
4
|
+
constructor() {
|
|
5
|
+
// --------
|
|
6
|
+
// Post messaging
|
|
7
|
+
// --------
|
|
8
|
+
this.messaging = {
|
|
9
|
+
post: (message, client = this.client) => {
|
|
10
|
+
if (!client) throw new Error(`No client for this operation.`);
|
|
11
|
+
client.postMessage(message);
|
|
12
|
+
return this.post;
|
|
13
|
+
},
|
|
14
|
+
listen: (callback, client = this.client) => {
|
|
15
|
+
if (!client) {
|
|
16
|
+
self.addEventListener('message', evt => {
|
|
17
|
+
this.client = evt.source;
|
|
18
|
+
callback(evt);
|
|
19
|
+
});
|
|
20
|
+
return this.post;
|
|
21
|
+
}
|
|
22
|
+
client.addEventListener('message', callback);
|
|
23
|
+
return this.post;
|
|
24
|
+
},
|
|
25
|
+
request: (message, client = this.client) => {
|
|
26
|
+
if (!client) throw new Error(`No client for this operation.`);
|
|
27
|
+
return new Promise(res => {
|
|
28
|
+
let messageChannel = new MessageChannel();
|
|
29
|
+
client.postMessage(message, [ messageChannel.port2 ]);
|
|
30
|
+
messageChannel.port1.onmessage = e => res(e.data);
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
channel(channelId) {
|
|
34
|
+
if (!this.channels.has(channelId)) { this.channels.set(channelId, new BroadcastChannel(channel)); }
|
|
35
|
+
let channel = this.channels.get(channelId);
|
|
36
|
+
return {
|
|
37
|
+
broadcast: message => channel.postMessage(message),
|
|
38
|
+
listen: callback => channel.addEventListener('message', callback),
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
channels: new Map,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// --------
|
|
45
|
+
// Notifications
|
|
46
|
+
// --------
|
|
47
|
+
this.notifications = {
|
|
48
|
+
fire: (title, params = {}) => {
|
|
49
|
+
return new Promise((res, rej) => {
|
|
50
|
+
if (!(self.Notification && self.Notification.permission === 'granted')) {
|
|
51
|
+
return rej(self.Notification && self.Notification.permission);
|
|
52
|
+
}
|
|
53
|
+
notification.addEventListener('error', rej);
|
|
54
|
+
let notification = new self.Notification(title, params);
|
|
55
|
+
notification.addEventListener('click', res);
|
|
56
|
+
notification.addEventListener('close', res);
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
listen: callback => {
|
|
60
|
+
self.addEventListener('notificationclick', callback);
|
|
61
|
+
return this.notifications;
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// --------
|
|
66
|
+
// Push Notifications
|
|
67
|
+
// --------
|
|
68
|
+
this.push = {
|
|
69
|
+
listen: callback => {
|
|
70
|
+
self.addEventListener('push', callback);
|
|
71
|
+
return this.post;
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
setCurrentClient(client) {
|
|
77
|
+
this.client = client;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
@@ -88,9 +88,9 @@ export default class Router extends _Router {
|
|
|
88
88
|
response = new httpEvent.Response(null, { status: 500, statusText: `Error reading static file: ${filename}` } );
|
|
89
89
|
} else {
|
|
90
90
|
// if the file is found, set Content-type and send data
|
|
91
|
-
|
|
91
|
+
let mime = Mime.lookup(ext);
|
|
92
92
|
response = new httpEvent.Response(data, { headers: {
|
|
93
|
-
contentType:
|
|
93
|
+
contentType: mime === 'application/javascript' ? 'text/javascript' : mime,
|
|
94
94
|
contentLength: Buffer.byteLength(data),
|
|
95
95
|
} });
|
|
96
96
|
if (enc) {
|
|
@@ -245,9 +245,14 @@ export default class Runtime {
|
|
|
245
245
|
Observer.set(this, 'location', {});
|
|
246
246
|
Observer.set(this, 'network', {});
|
|
247
247
|
// ---------------
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
248
|
+
this.ready.then(() => {
|
|
249
|
+
if (!this.cx.logger) return;
|
|
250
|
+
if (this.cx.server.shared) {
|
|
251
|
+
this.cx.logger.info(`> Server running (shared)`);
|
|
252
|
+
} else {
|
|
253
|
+
this.cx.logger.info(`> Server running (${this.cx.app.title || ''})::${this.cx.server.port}`);
|
|
254
|
+
}
|
|
255
|
+
});
|
|
251
256
|
}
|
|
252
257
|
|
|
253
258
|
/**
|
|
@@ -302,19 +307,25 @@ export default class Runtime {
|
|
|
302
307
|
return this.getSession(_context, httpEvent, id, options, callback);
|
|
303
308
|
});
|
|
304
309
|
// Response
|
|
305
|
-
let client = this.clients.get('*');
|
|
310
|
+
let client = this.clients.get('*'), response, finalResponse;
|
|
306
311
|
if (this.cx.server.shared) {
|
|
307
312
|
client = this.clients.get(url.hostname);
|
|
308
313
|
}
|
|
309
|
-
|
|
310
|
-
|
|
314
|
+
try {
|
|
315
|
+
response = await client.handle(httpEvent, ( ...args ) => this.remoteFetch( ...args ));
|
|
316
|
+
finalResponse = await this.handleResponse(_context, httpEvent, response, autoHeaders.filter(header => header.type === 'response'));
|
|
317
|
+
} catch(e) {
|
|
318
|
+
finalResponse = new Response(null, { status: 500, statusText: e.message });
|
|
319
|
+
console.error(e);
|
|
320
|
+
}
|
|
311
321
|
// Logging
|
|
312
322
|
if (this.cx.logger) {
|
|
313
323
|
let log = this.generateLog(httpEvent, finalResponse);
|
|
314
324
|
this.cx.logger.log(log);
|
|
315
325
|
}
|
|
326
|
+
// ------------
|
|
316
327
|
// Return value
|
|
317
|
-
|
|
328
|
+
return finalResponse;
|
|
318
329
|
}
|
|
319
330
|
|
|
320
331
|
// Generates request object
|
|
@@ -373,7 +384,7 @@ export default class Runtime {
|
|
|
373
384
|
}
|
|
374
385
|
|
|
375
386
|
// Handles response object
|
|
376
|
-
async handleResponse(e, response, autoHeaders = []) {
|
|
387
|
+
async handleResponse(cx, e, response, autoHeaders = []) {
|
|
377
388
|
if (!(response instanceof Response)) { response = new Response(response); }
|
|
378
389
|
Observer.set(this.network, 'remote', false);
|
|
379
390
|
Observer.set(this.network, 'error', null);
|
|
@@ -396,14 +407,22 @@ export default class Runtime {
|
|
|
396
407
|
if (response.headers.redirect) {
|
|
397
408
|
let xRedirectPolicy = e.request.headers.get('X-Redirect-Policy');
|
|
398
409
|
let xRedirectCode = e.request.headers.get('X-Redirect-Code') || 300;
|
|
399
|
-
let
|
|
400
|
-
|
|
401
|
-
|
|
410
|
+
let destinationUrl = new whatwag.URL(response.headers.location, e.url.origin);
|
|
411
|
+
let isSameOriginRedirect = destinationUrl.origin === e.url.origin;
|
|
412
|
+
let isSameSpaRedirect, sparootsFile = Path.join(cx.CWD, cx.layout.PUBLIC_DIR, 'sparoots.json');
|
|
413
|
+
if (isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-spa' && Fs.existsSync(sparootsFile)) {
|
|
414
|
+
// Longest-first sorting
|
|
415
|
+
let sparoots = _arrFrom(JSON.parse(Fs.readFileSync(sparootsFile))).sort((a, b) => a.length > b.length ? -1 : 1);
|
|
416
|
+
let matchRoot = path => sparoots.reduce((prev, root) => prev || (`${path}/`.startsWith(`${root}/`) && root), null);
|
|
417
|
+
isSameSpaRedirect = matchRoot(destinationUrl.pathname) === matchRoot(e.url.pathname);
|
|
418
|
+
}
|
|
419
|
+
if (xRedirectPolicy === 'manual' || (!isSameOriginRedirect && xRedirectPolicy === 'manual-when-cross-origin') || (!isSameSpaRedirect && xRedirectPolicy === 'manual-when-cross-spa')) {
|
|
402
420
|
response.headers.json({
|
|
403
421
|
'X-Redirect-Code': response.status,
|
|
404
422
|
'Access-Control-Allow-Origin': '*',
|
|
405
423
|
'Cache-Control': 'no-store',
|
|
406
424
|
});
|
|
425
|
+
response.attrs.status = xRedirectCode;
|
|
407
426
|
}
|
|
408
427
|
return response;
|
|
409
428
|
}
|
|
@@ -45,12 +45,13 @@ export default class RuntimeClient {
|
|
|
45
45
|
// --------
|
|
46
46
|
// Rendering
|
|
47
47
|
// --------
|
|
48
|
+
|
|
48
49
|
if (response.ok && response.bodyAttrs.inputType === 'object' && httpEvent.request.headers.accept.match('text/html')) {
|
|
49
50
|
let rendering = await this.render(httpEvent, router, response);
|
|
50
|
-
if (typeof rendering !== 'string') {
|
|
51
|
-
throw new Error('render() must return a
|
|
51
|
+
if (typeof rendering !== 'string' && !(typeof rendering === 'object' && rendering && typeof rendering.toString === 'function')) {
|
|
52
|
+
throw new Error('render() must return a string response or an object that implements toString()..');
|
|
52
53
|
}
|
|
53
|
-
response = new httpEvent.Response(rendering, {
|
|
54
|
+
response = new httpEvent.Response(rendering.toString(), {
|
|
54
55
|
status: response.status,
|
|
55
56
|
headers: { ...response.headers.json(), contentType: 'text/html' },
|
|
56
57
|
});
|
|
@@ -78,22 +79,31 @@ export default class RuntimeClient {
|
|
|
78
79
|
pathnameSplit.pop();
|
|
79
80
|
}
|
|
80
81
|
const instanceParams = QueryString.stringify({
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
file: renderFile,
|
|
83
|
+
url: httpEvent.url.href,
|
|
84
|
+
root: this.cx.CWD,
|
|
85
|
+
oohtml_level: this.cx.server.oohtml_support,
|
|
84
86
|
});
|
|
85
87
|
const { window } = await import('@webqit/oohtml-ssr/instance.js?' + instanceParams);
|
|
86
88
|
// --------
|
|
87
89
|
// OOHTML would waiting for DOM-ready in order to be initialized
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
if (window.WebQit.DOM) {
|
|
91
|
+
await new Promise(res => window.WebQit.DOM.ready(res));
|
|
92
|
+
}
|
|
93
|
+
if (window.document.templates) {
|
|
94
|
+
await new Promise(res => (window.document.templatesReadyState === 'complete' && res(), window.document.addEventListener('templatesreadystatechange', res)));
|
|
95
|
+
}
|
|
96
|
+
if (window.document.state) {
|
|
97
|
+
if (!window.document.state.env) {
|
|
98
|
+
window.document.setState({
|
|
99
|
+
env: 'server',
|
|
100
|
+
}, { update: true });
|
|
101
|
+
}
|
|
102
|
+
window.document.setState({ data, url: httpEvent.url }, { update: 'merge' });
|
|
103
|
+
}
|
|
104
|
+
if (window.document.templates) {
|
|
105
|
+
window.document.body.setAttribute('template', 'routes/' + httpEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
|
|
94
106
|
}
|
|
95
|
-
window.document.setState({ page: data, url: httpEvent.url }, { update: 'merge' });
|
|
96
|
-
window.document.body.setAttribute('template', 'page/' + httpEvent.url.pathname.split('/').filter(a => a).map(a => a + '+-').join('/'));
|
|
97
107
|
await new Promise(res => setTimeout(res, 10));
|
|
98
108
|
return window;
|
|
99
109
|
});
|
package/src/runtime-pi/util.js
CHANGED
|
@@ -135,9 +135,9 @@ export const path = {
|
|
|
135
135
|
export const urlPattern = (pattern, baseUrl = null) => ({
|
|
136
136
|
pattern: new URLPattern(pattern, baseUrl),
|
|
137
137
|
isPattern() {
|
|
138
|
-
return Object.keys(this.pattern.keys).some(compName => this.pattern.keys[compName].length);
|
|
138
|
+
return Object.keys(this.pattern.keys || {}).some(compName => this.pattern.keys[compName].length);
|
|
139
139
|
},
|
|
140
|
-
test(...args) { this.pattern.test(...args) },
|
|
140
|
+
test(...args) { return this.pattern.test(...args) },
|
|
141
141
|
exec(...args) {
|
|
142
142
|
let components = this.pattern.exec(...args);
|
|
143
143
|
if (!components) return;
|
package/src/webflo.js
CHANGED
|
@@ -5,23 +5,21 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import Url from 'url';
|
|
7
7
|
import Path from 'path';
|
|
8
|
-
import {
|
|
9
|
-
import
|
|
8
|
+
import { jsonFile } from '@webqit/backpack/src/dotfile/index.js';
|
|
9
|
+
import { Logger, Cli } from '@webqit/backpack';
|
|
10
10
|
import * as WebfloPI from './index.js';
|
|
11
|
-
import Context from './Context.js';
|
|
12
|
-
import Cli from './Cli.js';
|
|
13
11
|
|
|
14
12
|
const dirSelf = Path.dirname(Url.fileURLToPath(import.meta.url));
|
|
15
|
-
const webfloJson =
|
|
16
|
-
const appJson =
|
|
13
|
+
const webfloJson = jsonFile.read(Path.join(dirSelf, '../package.json'));
|
|
14
|
+
const appJson = jsonFile.read('./package.json');
|
|
17
15
|
|
|
18
16
|
/**
|
|
19
17
|
* @cx
|
|
20
18
|
*/
|
|
21
|
-
const cx = Context.create({
|
|
22
|
-
|
|
19
|
+
const cx = WebfloPI.Context.create({
|
|
20
|
+
meta: { title: webfloJson.title, version: webfloJson.version },
|
|
23
21
|
app: { title: appJson.title, version: appJson.version },
|
|
24
|
-
logger,
|
|
22
|
+
logger: Logger,
|
|
25
23
|
config: WebfloPI.config,
|
|
26
24
|
middlewares: [ WebfloPI.deployment.origins.webhook, ],
|
|
27
25
|
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Webflo Test",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"generate": "webflo generate::client --recursive --compression --auto-embed",
|
|
6
|
+
"bundle": "cd public && oohtml bundle --recursive --auto-embed=routes",
|
|
7
|
+
"build": "npm run generate && npm run bundle"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/** @webqit/webflo */
|
|
2
|
+
var{start:e}=WebQit.Webflo,r={},o={bundle_filename:"bundle.js",public_base_url:"/",spa_routing:!0,oohtml_support:"full",service_worker_support:!0,worker_scope:"/",worker_filename:"worker.js",routing:{root:"/",subroots:["/page-2","/page-4/subpage"],targets:2}};e.call({layout:r,params:o});
|
|
Binary file
|