@webqit/webflo 0.20.4 → 0.20.6
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/package.json +10 -16
- package/src/build-pi/esbuild-plugin-uselive-transform 2.js +42 -0
- package/src/build-pi/esbuild-plugin-uselive-transform.js +42 -0
- package/src/build-pi/index.js +7 -5
- package/src/init-pi/index.js +1 -1
- package/src/init-pi/templates/pwa/.gitignore +6 -0
- package/src/init-pi/templates/pwa/.webqit/webflo/client.json +15 -0
- package/src/init-pi/templates/pwa/.webqit/webflo/layout.json +7 -0
- package/src/init-pi/templates/pwa/public/manifest.json +2 -2
- package/src/init-pi/templates/web/.gitignore +6 -0
- package/src/init-pi/templates/web/.webqit/webflo/client.json +12 -0
- package/src/init-pi/templates/web/.webqit/webflo/layout.json +7 -0
- package/src/runtime-pi/WebfloRuntime.js +23 -14
- package/src/runtime-pi/apis.js +1 -1
- package/src/runtime-pi/webflo-client/DeviceCapabilities.js +1 -1
- package/src/runtime-pi/webflo-client/WebfloClient.js +3 -2
- package/src/runtime-pi/webflo-client/WebfloRootClient1.js +8 -4
- package/src/runtime-pi/webflo-client/WebfloRootClient2.js +1 -1
- package/src/runtime-pi/webflo-client/WebfloSubClient.js +1 -1
- package/src/runtime-pi/webflo-client/bootstrap.js +1 -0
- package/src/runtime-pi/webflo-fetch/LiveResponse.js +34 -27
- package/src/runtime-pi/webflo-fetch/index.js +23 -15
- package/src/runtime-pi/webflo-fetch/util.js +1 -1
- package/src/runtime-pi/webflo-messaging/WQSockPort.js +1 -3
- package/src/runtime-pi/webflo-messaging/wq-message-port.js +2 -1
- package/src/runtime-pi/webflo-routing/HttpEvent.js +8 -6
- package/src/runtime-pi/webflo-routing/WebfloRouter.js +12 -7
- package/src/runtime-pi/webflo-server/WebfloServer.js +56 -22
- package/src/runtime-pi/webflo-server/webflo-devmode.js +11 -7
- package/src/runtime-pi/webflo-url/Url.js +1 -1
- package/src/runtime-pi/webflo-url/xURL.js +1 -1
- package/src/runtime-pi/webflo-worker/bootstrap.js +1 -0
- package/src/build-pi/esbuild-plugin-livejs-transform.js +0 -35
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"vanila-javascript"
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://webqit.io/tooling/webflo",
|
|
15
|
-
"version": "0.20.
|
|
15
|
+
"version": "0.20.6",
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
18
18
|
"type": "git",
|
|
@@ -42,35 +42,29 @@
|
|
|
42
42
|
"webflo-certbot-http-cleanup-hook": "src/services-pi/cert/http-cleanup-hook.js"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@linked-db/linked-ql": "^0.30.13",
|
|
46
45
|
"@octokit/webhooks": "^7.15.1",
|
|
47
46
|
"@webqit/backpack": "^0.1.12",
|
|
48
|
-
"@webqit/oohtml-ssr": "^2.
|
|
49
|
-
"@webqit/
|
|
47
|
+
"@webqit/oohtml-ssr": "^2.2.1",
|
|
48
|
+
"@webqit/use-live": "^0.5.41",
|
|
50
49
|
"@webqit/util": "^0.8.11",
|
|
51
50
|
"dotenv": "^16.4.7",
|
|
52
|
-
"
|
|
53
|
-
"i": "^0.3.7",
|
|
54
|
-
"ioredis": "^5.5.0",
|
|
55
|
-
"jsdom": "^21.1.1",
|
|
56
|
-
"markdown-it-mathjax3": "^4.3.2",
|
|
51
|
+
"esbuild": "^0.14.38",
|
|
57
52
|
"mime-types": "^2.1.33",
|
|
58
|
-
"npm": "^11.4.0",
|
|
59
|
-
"pg": "^8.13.3",
|
|
60
53
|
"simple-git": "^2.20.1",
|
|
61
|
-
"urlpattern-polyfill": "^4.0.3"
|
|
62
|
-
"vitepress-plugin-mermaid": "^2.0.17",
|
|
63
|
-
"web-push": "^3.6.7"
|
|
54
|
+
"urlpattern-polyfill": "^4.0.3"
|
|
64
55
|
},
|
|
65
56
|
"devDependencies": {
|
|
66
57
|
"chai": "^4.3.6",
|
|
67
58
|
"chokidar": "^4.0.3",
|
|
68
59
|
"coveralls": "^3.1.1",
|
|
69
|
-
"esbuild": "^0.14.38",
|
|
70
60
|
"fast-glob": "^3.3.3",
|
|
61
|
+
"jsdom": "^27.0.1",
|
|
62
|
+
"markdown-it-mathjax3": "^4.3.2",
|
|
71
63
|
"mocha": "^10.0.0",
|
|
72
64
|
"mocha-lcov-reporter": "^1.3.0",
|
|
73
|
-
"vitepress": "^1.6.4"
|
|
65
|
+
"vitepress": "^1.6.4",
|
|
66
|
+
"vitepress-plugin-mermaid": "^2.0.17",
|
|
67
|
+
"web-push": "^3.6.7"
|
|
74
68
|
},
|
|
75
69
|
"author": "Oxford Harrison <oxharris.dev@gmail.com>",
|
|
76
70
|
"maintainers": [
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Fs from 'fs/promises';
|
|
2
|
+
import { parse, compile, matchPrologDirective, serialize } from '@webqit/use-live';
|
|
3
|
+
|
|
4
|
+
export function UseLiveTransform() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'uselive-transform',
|
|
7
|
+
setup(build) {
|
|
8
|
+
build.onLoad({ filter: /\.(js|mjs|ts|jsx|tsx)$/ }, async (args) => {
|
|
9
|
+
const code = await Fs.readFile(args.path, 'utf8');
|
|
10
|
+
|
|
11
|
+
// Super dirty detection
|
|
12
|
+
if (matchPrologDirective(code)) {
|
|
13
|
+
// Actual check...
|
|
14
|
+
|
|
15
|
+
let ast;
|
|
16
|
+
try { ast = parse(code, parserParams); } catch (e) { console.error(args.path, '\nUseLive transform error:', e); }
|
|
17
|
+
|
|
18
|
+
if (ast?.isLiveProgram || ast?.hasLiveFunctions) {
|
|
19
|
+
const result = await compile(parserParams.sourceType+'-file', ast, {
|
|
20
|
+
liveMode: ast.isLiveProgram, // Regarding top-level
|
|
21
|
+
fileName: args.path,
|
|
22
|
+
});
|
|
23
|
+
return { contents: serialize(result), loader: 'js' };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { contents: code, loader: 'default' };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const parserParams = {
|
|
34
|
+
ecmaVersion: 'latest',
|
|
35
|
+
sourceType: 'module',
|
|
36
|
+
executionMode: 'RegularProgram', // 'LiveProgram'
|
|
37
|
+
allowReturnOutsideFunction: true,
|
|
38
|
+
allowAwaitOutsideFunction: true,
|
|
39
|
+
allowSuperOutsideMethod: false,
|
|
40
|
+
preserveParens: false,
|
|
41
|
+
locations: true,
|
|
42
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import Fs from 'fs/promises';
|
|
2
|
+
import { parse, compile, matchPrologDirective, serialize } from '@webqit/use-live';
|
|
3
|
+
|
|
4
|
+
export function UseLiveTransform() {
|
|
5
|
+
return {
|
|
6
|
+
name: 'uselive-transform',
|
|
7
|
+
setup(build) {
|
|
8
|
+
build.onLoad({ filter: /\.(js|mjs|ts|jsx|tsx)$/ }, async (args) => {
|
|
9
|
+
const code = await Fs.readFile(args.path, 'utf8');
|
|
10
|
+
|
|
11
|
+
// Super dirty detection
|
|
12
|
+
if (matchPrologDirective(code)) {
|
|
13
|
+
// Actual check...
|
|
14
|
+
|
|
15
|
+
let ast;
|
|
16
|
+
try { ast = parse(code, parserParams); } catch (e) { console.error(args.path, '\nUseLive transform error:', e); }
|
|
17
|
+
|
|
18
|
+
if (ast?.isLiveProgram || ast?.hasLiveFunctions) {
|
|
19
|
+
const result = await compile(parserParams.sourceType+'-file', ast, {
|
|
20
|
+
liveMode: ast.isLiveProgram, // Regarding top-level
|
|
21
|
+
fileName: args.path,
|
|
22
|
+
});
|
|
23
|
+
return { contents: serialize(result), loader: 'js' };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return { contents: code, loader: 'default' };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const parserParams = {
|
|
34
|
+
ecmaVersion: 'latest',
|
|
35
|
+
sourceType: 'module',
|
|
36
|
+
executionMode: 'RegularProgram', // 'LiveProgram'
|
|
37
|
+
allowReturnOutsideFunction: true,
|
|
38
|
+
allowAwaitOutsideFunction: true,
|
|
39
|
+
allowSuperOutsideMethod: false,
|
|
40
|
+
preserveParens: false,
|
|
41
|
+
locations: true,
|
|
42
|
+
};
|
package/src/build-pi/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import { jsFile } from '@webqit/backpack/src/dotfile/index.js';
|
|
|
10
10
|
import { bootstrap as serverBootstrap } from '../runtime-pi/webflo-server/bootstrap.js';
|
|
11
11
|
import { bootstrap as clientBootstrap } from '../runtime-pi/webflo-client/bootstrap.js';
|
|
12
12
|
import { bootstrap as workerBootstrap } from '../runtime-pi/webflo-worker/bootstrap.js';
|
|
13
|
-
import {
|
|
13
|
+
import { UseLiveTransform } from './esbuild-plugin-uselive-transform.js';
|
|
14
14
|
import { CLIContext } from '../CLIContext.js';
|
|
15
15
|
import '../runtime-pi/webflo-url/urlpattern.js';
|
|
16
16
|
|
|
@@ -145,14 +145,14 @@ async function bundleScript({ $context, $source, which, outfile, asModule = true
|
|
|
145
145
|
const bundlingConfig = {
|
|
146
146
|
entryPoints: [moduleFile],
|
|
147
147
|
outfile,
|
|
148
|
-
bundle: which === 'server' ? false : true,
|
|
149
|
-
minify: true,
|
|
150
148
|
format: asModule ? 'esm' : 'iife',
|
|
151
149
|
platform: which === 'server' ? 'node' : 'browser', // optional but good for clarity
|
|
150
|
+
bundle: which === 'server' ? false : true,
|
|
151
|
+
minify: which === 'server' ? false : true,
|
|
152
152
|
treeShaking: true, // Important optimization
|
|
153
153
|
banner: { js: '/** @webqit/webflo */', },
|
|
154
154
|
footer: { js: '', },
|
|
155
|
-
plugins: [
|
|
155
|
+
plugins: [UseLiveTransform()],
|
|
156
156
|
...(restParams.buildParams || {})
|
|
157
157
|
};
|
|
158
158
|
if (!asModule) {
|
|
@@ -290,8 +290,10 @@ async function generateClientScript({ $context, bootstrap, ...restParams }) {
|
|
|
290
290
|
|
|
291
291
|
const configExport = structuredClone({ ENV: bootstrap.config.ENV, CLIENT: bootstrap.config.CLIENT, WORKER: {} });
|
|
292
292
|
if (bootstrap.config.CLIENT.capabilities?.service_worker === true) {
|
|
293
|
+
const outfile_workerBuild = Path.join(FLAGS.outdir || bootstrap.outdir, bootstrap.config.WORKER.filename);
|
|
294
|
+
const outfile_workerBuildPublic = Path.join(publicBaseUrl, Path.relative(bootstrap.config.LAYOUT.PUBLIC_DIR, outfile_workerBuild));
|
|
293
295
|
configExport.WORKER = {
|
|
294
|
-
filename:
|
|
296
|
+
filename: outfile_workerBuildPublic,
|
|
295
297
|
scope: bootstrap.config.WORKER.scope
|
|
296
298
|
};
|
|
297
299
|
}
|
package/src/init-pi/index.js
CHANGED
|
@@ -40,7 +40,7 @@ export async function init(projectName = 'my-webflo-app', projectTitle = '', pro
|
|
|
40
40
|
process.exit(1);
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
LOGGER?.log(LOGGER.style.keyword(`> `) + `Initializing your webflo app
|
|
43
|
+
LOGGER?.log(LOGGER.style.keyword(`> `) + `Initializing your webflo app "${projectName}" using template "${template}"...\n`);
|
|
44
44
|
|
|
45
45
|
// 1. Create project dir
|
|
46
46
|
await Fs2.mkdir(targetDir, { recursive: true });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"filename": "app.js",
|
|
3
|
+
"public_base_url": "/",
|
|
4
|
+
"copy_public_variables": true,
|
|
5
|
+
"spa_routing": true,
|
|
6
|
+
"capabilities": {
|
|
7
|
+
"service_worker": true,
|
|
8
|
+
"webpush": true,
|
|
9
|
+
"custom_install": true,
|
|
10
|
+
"exposed": [
|
|
11
|
+
"display-mode",
|
|
12
|
+
"notifications"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"scope": "/",
|
|
11
11
|
"icons": [
|
|
12
12
|
{
|
|
13
|
-
"src": "/assets
|
|
13
|
+
"src": "/assets/logo.png",
|
|
14
14
|
"sizes": "192x192",
|
|
15
15
|
"type": "image/png"
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
|
-
"src": "/assets
|
|
18
|
+
"src": "/assets/logo.png",
|
|
19
19
|
"sizes": "512x512",
|
|
20
20
|
"type": "image/png",
|
|
21
21
|
"purpose": "maskable"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { WebfloRouter } from './webflo-routing/WebfloRouter.js';
|
|
2
|
-
import {
|
|
2
|
+
import { response as responseShim, headers as headersShim } from './webflo-fetch/index.js';
|
|
3
|
+
import { LiveResponse } from './webflo-fetch/LiveResponse.js';
|
|
3
4
|
import { AppBootstrap } from './AppBootstrap.js';
|
|
4
5
|
import { _wq } from '../util.js';
|
|
5
6
|
|
|
@@ -31,6 +32,14 @@ export class WebfloRuntime {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
async initialize() {
|
|
35
|
+
if (this.bootstrap.init.SETUP) {
|
|
36
|
+
await this.bootstrap.init.SETUP(this);
|
|
37
|
+
}
|
|
38
|
+
await this.initCreateStorage();
|
|
39
|
+
return this.#instanceController;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async initCreateStorage() {
|
|
34
43
|
if (!this.bootstrap.init.createStorage) {
|
|
35
44
|
const inmemSessionRegistry = new Map;
|
|
36
45
|
this.bootstrap.init.createStorage = (namespace) => {
|
|
@@ -55,8 +64,8 @@ export class WebfloRuntime {
|
|
|
55
64
|
return this.#instanceController;
|
|
56
65
|
}
|
|
57
66
|
|
|
58
|
-
|
|
59
|
-
|
|
67
|
+
createStorage(namespace, ttl) {
|
|
68
|
+
return this.bootstrap.init.createStorage(namespace, ttl);
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
createRequest(href, init = {}) {
|
|
@@ -90,7 +99,7 @@ export class WebfloRuntime {
|
|
|
90
99
|
}
|
|
91
100
|
// Dispatch event
|
|
92
101
|
const router = new this.constructor.Router(this, httpEvent.url.pathname);
|
|
93
|
-
await router.route(['SETUP'], httpEvent);
|
|
102
|
+
await router.route(['SETUP'], httpEvent.extend(false));
|
|
94
103
|
// Do proper routing for respone
|
|
95
104
|
const response = await new Promise(async (resolve) => {
|
|
96
105
|
let autoLiveResponse, response;
|
|
@@ -112,10 +121,11 @@ export class WebfloRuntime {
|
|
|
112
121
|
console.error(e);
|
|
113
122
|
response = new Response(null, { status: 500, statusText: e.message });
|
|
114
123
|
}
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
if (!/Response/.test(LiveResponse.test(response))) {
|
|
125
|
+
const isLifecyleComplete = httpEvent.lifeCycleComplete();
|
|
126
|
+
response = LiveResponse.test(response) !== 'Default' || !isLifecyleComplete
|
|
127
|
+
? await LiveResponse.from(response, { done: isLifecyleComplete })
|
|
128
|
+
: responseShim.from.value(response);
|
|
119
129
|
}
|
|
120
130
|
// Any "carry" data?
|
|
121
131
|
//await this.handleCarries(httpEvent, response);
|
|
@@ -126,15 +136,14 @@ export class WebfloRuntime {
|
|
|
126
136
|
resolve(response);
|
|
127
137
|
}
|
|
128
138
|
});
|
|
139
|
+
|
|
129
140
|
// Commit data in the exact order. Reason: in how they depend on each other
|
|
130
141
|
for (const storage of [httpEvent.user, httpEvent.session, httpEvent.cookies]) {
|
|
131
142
|
await storage?.commit?.(response, FLAGS['dev']);
|
|
132
143
|
}
|
|
133
|
-
|
|
144
|
+
// Wait for any whileLive promises to resolve
|
|
145
|
+
if (LiveResponse.test(response) === 'LiveResponse' && response.whileLive()) {
|
|
134
146
|
httpEvent.waitUntil(response.whileLive(true));
|
|
135
|
-
} else {
|
|
136
|
-
httpEvent.waitUntil(Promise.resolve());
|
|
137
|
-
await null; // We need the above resolved before we move on
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
// Send the X-Background-Messaging-Port header
|
|
@@ -173,9 +182,9 @@ export class WebfloRuntime {
|
|
|
173
182
|
});
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
if (!this.isClientSide && response
|
|
185
|
+
if (!this.isClientSide && LiveResponse.test(response) === 'LiveResponse') {
|
|
177
186
|
// Must convert to Response on the server-side before returning
|
|
178
|
-
return response.toResponse({ client: httpEvent.client });
|
|
187
|
+
return await response.toResponse({ client: httpEvent.client });
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
return response;
|
package/src/runtime-pi/apis.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { _before, _toTitle } from '@webqit/util/str/index.js';
|
|
2
2
|
import { _isObject } from '@webqit/util/js/index.js';
|
|
3
|
-
import { Observer } from '@webqit/
|
|
3
|
+
import { Observer } from '@webqit/use-live';
|
|
4
4
|
import { WebfloRuntime } from '../WebfloRuntime.js';
|
|
5
5
|
import { WQMessageChannel } from '../webflo-messaging/WQMessageChannel.js';
|
|
6
|
-
import {
|
|
6
|
+
import { response as responseShim } from '../webflo-fetch/index.js';
|
|
7
|
+
import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
|
|
7
8
|
import { WQStarPort } from '../webflo-messaging/WQStarPort.js';
|
|
8
9
|
import { ClientSideCookies } from './ClientSideCookies.js';
|
|
9
10
|
import { HttpSession } from '../webflo-routing/HttpSession.js';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { Observer } from '@webqit/
|
|
1
|
+
import { Observer } from '@webqit/use-live';
|
|
2
2
|
import { WebfloClient } from './WebfloClient.js';
|
|
3
3
|
import { ClientSideWorkport } from './ClientSideWorkport.js';
|
|
4
4
|
import { DeviceCapabilities } from './DeviceCapabilities.js';
|
|
5
|
-
import {
|
|
5
|
+
import { response as responseShim } from '../webflo-fetch/index.js';
|
|
6
|
+
import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
|
|
6
7
|
import { WebfloHMR } from './webflo-devmode.js';
|
|
7
8
|
|
|
8
9
|
export class WebfloRootClient1 extends WebfloClient {
|
|
@@ -92,8 +93,10 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
92
93
|
cleanups.push(() => this.#capabilities.close());
|
|
93
94
|
if (this.config.CLIENT.capabilities?.service_worker) {
|
|
94
95
|
const { filename, ...restServiceWorkerParams } = this.config.WORKER;
|
|
95
|
-
this
|
|
96
|
-
|
|
96
|
+
this.constructor.Workport.initialize(null, filename, restServiceWorkerParams).then((workport) => {
|
|
97
|
+
this.#workport = workport;
|
|
98
|
+
cleanups.push(() => this.#workport.close());
|
|
99
|
+
});
|
|
97
100
|
}
|
|
98
101
|
return instanceController;
|
|
99
102
|
}
|
|
@@ -113,6 +116,7 @@ export class WebfloRootClient1 extends WebfloClient {
|
|
|
113
116
|
this.background.addPort(backgroundPort);
|
|
114
117
|
}
|
|
115
118
|
if (scopeObj.response.body || backgroundPort) {
|
|
119
|
+
|
|
116
120
|
const httpEvent = this.createHttpEvent({ request: this.createRequest(this.location.href) }, true);
|
|
117
121
|
await this.render(httpEvent, scopeObj.response);
|
|
118
122
|
} else {
|
|
@@ -21,6 +21,7 @@ export async function bootstrap(cx, offset = '') {
|
|
|
21
21
|
};
|
|
22
22
|
if (config.CLIENT.copy_public_variables) {
|
|
23
23
|
const publicEnvPattern = /(?:^|_)PUBLIC(?:_|$)/;
|
|
24
|
+
config.ENV.data = config.ENV.data || {};
|
|
24
25
|
for (const key in process.env) {
|
|
25
26
|
if (publicEnvPattern.test(key)) {
|
|
26
27
|
config.ENV.data[key] = process.env[key];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Observer, LiveMode } from '@webqit/use-live';
|
|
2
2
|
import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
|
|
3
3
|
import { publishMutations, applyMutations } from '../webflo-messaging/wq-message-port.js';
|
|
4
4
|
import { WQBroadcastChannel } from '../webflo-messaging/WQBroadcastChannel.js';
|
|
@@ -9,8 +9,11 @@ import { isTypeStream } from './util.js';
|
|
|
9
9
|
|
|
10
10
|
export class LiveResponse extends EventTarget {
|
|
11
11
|
|
|
12
|
+
[Symbol.toStringTag] = 'LiveResponse';
|
|
13
|
+
|
|
12
14
|
static test(data) {
|
|
13
|
-
if (data instanceof LiveResponse
|
|
15
|
+
if (data instanceof LiveResponse
|
|
16
|
+
|| data?.[Symbol.toStringTag] === 'LiveResponse') {
|
|
14
17
|
return 'LiveResponse';
|
|
15
18
|
}
|
|
16
19
|
if (data instanceof Response) {
|
|
@@ -19,24 +22,25 @@ export class LiveResponse extends EventTarget {
|
|
|
19
22
|
if (isGenerator(data)) {
|
|
20
23
|
return 'Generator';
|
|
21
24
|
}
|
|
22
|
-
if (data instanceof
|
|
23
|
-
|
|
25
|
+
if (data instanceof LiveMode
|
|
26
|
+
|| data?.[Symbol.toStringTag] === 'LiveMode') {
|
|
27
|
+
return 'LiveMode';
|
|
24
28
|
}
|
|
25
29
|
return 'Default';
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
static async from(data, ...args) {
|
|
29
|
-
if (data
|
|
33
|
+
if (this.test(data) === 'LiveResponse') {
|
|
30
34
|
return data.clone(...args);
|
|
31
35
|
}
|
|
32
|
-
if (data
|
|
36
|
+
if (this.test(data) === 'Response') {
|
|
33
37
|
return await this.fromResponse(data, ...args);
|
|
34
38
|
}
|
|
35
|
-
if (
|
|
39
|
+
if (this.test(data) === 'Generator') {
|
|
36
40
|
return await this.fromGenerator(data, ...args);
|
|
37
41
|
}
|
|
38
|
-
if (data
|
|
39
|
-
return this.
|
|
42
|
+
if (this.test(data) === 'LiveMode') {
|
|
43
|
+
return this.fromLiveMode(data, ...args);
|
|
40
44
|
}
|
|
41
45
|
return new this(data, ...args);
|
|
42
46
|
}
|
|
@@ -45,7 +49,7 @@ export class LiveResponse extends EventTarget {
|
|
|
45
49
|
if (!(response instanceof Response)) {
|
|
46
50
|
throw new Error('Argument must be a Response instance.');
|
|
47
51
|
}
|
|
48
|
-
|
|
52
|
+
|
|
49
53
|
const body = await responseShim.prototype.parse.value.call(response);
|
|
50
54
|
|
|
51
55
|
// Instance
|
|
@@ -108,7 +112,7 @@ export class LiveResponse extends EventTarget {
|
|
|
108
112
|
let $$await;
|
|
109
113
|
|
|
110
114
|
const $options = { done: firstFrame.done, ...options };
|
|
111
|
-
if (value
|
|
115
|
+
if (this.test(value) === 'LiveResponse') {
|
|
112
116
|
instance = new this;
|
|
113
117
|
const responseMeta = _wq(value, 'meta');
|
|
114
118
|
_wq(instance).set('meta', responseMeta);
|
|
@@ -133,15 +137,18 @@ export class LiveResponse extends EventTarget {
|
|
|
133
137
|
return instance;
|
|
134
138
|
}
|
|
135
139
|
|
|
136
|
-
static async
|
|
137
|
-
if (!(
|
|
138
|
-
throw new Error('Argument must be a
|
|
140
|
+
static async fromLiveMode(liveMode, options = {}) {
|
|
141
|
+
if (!this.test(liveMode) === 'LiveMode') {
|
|
142
|
+
throw new Error('Argument must be a UseLive LiveMode instance.');
|
|
143
|
+
}
|
|
144
|
+
const instance = new this;
|
|
145
|
+
await instance.replaceWith(liveMode.value, { done: false, ...options });
|
|
146
|
+
if (instance.#generatorType === 'Default') {
|
|
147
|
+
instance.#generator = liveMode;
|
|
148
|
+
instance.#generatorType = 'LiveMode';
|
|
139
149
|
}
|
|
140
|
-
const instance = new this(qState.value, { done: false, ...options });
|
|
141
|
-
instance.#generator = qState;
|
|
142
|
-
instance.#generatorType = 'Quantum';
|
|
143
150
|
Observer.observe(
|
|
144
|
-
|
|
151
|
+
liveMode,
|
|
145
152
|
'value',
|
|
146
153
|
(e) => instance.#replaceWith(e.value),
|
|
147
154
|
{ signal: instance.#abortController.signal }
|
|
@@ -156,8 +163,7 @@ export class LiveResponse extends EventTarget {
|
|
|
156
163
|
}
|
|
157
164
|
|
|
158
165
|
static getBackground(respone) {
|
|
159
|
-
if (
|
|
160
|
-
&& !(respone instanceof LiveResponse)) return;
|
|
166
|
+
if (!/Response/.test(this.test(respone) )) return;
|
|
161
167
|
const responseMeta = _wq(respone, 'meta');
|
|
162
168
|
if (!responseMeta.has('background_port')) {
|
|
163
169
|
const value = respone.headers.get('X-Background-Messaging-Port')?.trim();
|
|
@@ -322,8 +328,9 @@ export class LiveResponse extends EventTarget {
|
|
|
322
328
|
const execReplaceWithResponse = async (response, options) => {
|
|
323
329
|
this.#generator = response;
|
|
324
330
|
this.#generatorType = response instanceof Response ? 'Response' : 'LiveResponse';
|
|
331
|
+
const body = response instanceof Response ? await responseShim.prototype.parse.value.call(response) : response.body;
|
|
325
332
|
execReplaceWith({
|
|
326
|
-
body
|
|
333
|
+
body,
|
|
327
334
|
status: responseShim.prototype.status.get.call(response),
|
|
328
335
|
statusText: response.statusText,
|
|
329
336
|
headers: response.headers,
|
|
@@ -332,7 +339,7 @@ export class LiveResponse extends EventTarget {
|
|
|
332
339
|
redirected: response.redirected,
|
|
333
340
|
url: response.url,
|
|
334
341
|
});
|
|
335
|
-
if (response
|
|
342
|
+
if (this.constructor.test(response) === 'LiveResponse') {
|
|
336
343
|
response.addEventListener('replace', () => execReplaceWith(response), { signal: this.#abortController.signal });
|
|
337
344
|
return await response.whileLive(true);
|
|
338
345
|
}
|
|
@@ -360,7 +367,7 @@ export class LiveResponse extends EventTarget {
|
|
|
360
367
|
};
|
|
361
368
|
|
|
362
369
|
let donePromise;
|
|
363
|
-
if (
|
|
370
|
+
if (/Response/.test(body)) {
|
|
364
371
|
if (frameClosure) {
|
|
365
372
|
throw new Error('frameClosure unsupported for inputs of type response.');
|
|
366
373
|
}
|
|
@@ -439,8 +446,8 @@ export class LiveResponse extends EventTarget {
|
|
|
439
446
|
}));
|
|
440
447
|
}
|
|
441
448
|
|
|
442
|
-
|
|
443
|
-
const state = new
|
|
449
|
+
toLiveMode({ signal: abortSignal } = {}) {
|
|
450
|
+
const state = new LiveModeX;
|
|
444
451
|
const replaceHandler = () => Observer.defineProperty(state, 'value', { value: this.body, enumerable: true, configurable: true });
|
|
445
452
|
this.addEventListener('replace', replaceHandler, { signal: abortSignal });
|
|
446
453
|
replaceHandler();
|
|
@@ -462,7 +469,7 @@ export const isGenerator = (obj) => {
|
|
|
462
469
|
typeof obj?.return === 'function';
|
|
463
470
|
};
|
|
464
471
|
|
|
465
|
-
class
|
|
472
|
+
class LiveModeX extends LiveMode {
|
|
466
473
|
constructor() { }
|
|
467
|
-
|
|
474
|
+
abort() { }
|
|
468
475
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export { LiveResponse } from './LiveResponse.js';
|
|
2
1
|
import { _isObject, _isTypeObject, _isNumeric } from '@webqit/util/js/index.js';
|
|
3
2
|
import { _from as _arrFrom } from '@webqit/util/arr/index.js';
|
|
4
3
|
import { _before, _after } from '@webqit/util/str/index.js';
|
|
5
4
|
import { DeepURLSearchParams } from '../webflo-url/util.js';
|
|
6
5
|
import { dataType } from './util.js';
|
|
7
6
|
import { _wq } from '../../util.js';
|
|
7
|
+
import { Observer } from '@webqit/use-live';
|
|
8
|
+
import { LiveResponse } from './LiveResponse.js';
|
|
8
9
|
|
|
9
10
|
// ----- env & globalize
|
|
10
11
|
|
|
@@ -30,6 +31,8 @@ export function shim(prefix = 'wq') {
|
|
|
30
31
|
patch(api.prototype, prototype);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
globalThis.LiveResponse = LiveResponse;
|
|
35
|
+
globalThis.Observer = Observer;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
// ----- request
|
|
@@ -150,7 +153,14 @@ export const response = {
|
|
|
150
153
|
}
|
|
151
154
|
},
|
|
152
155
|
prototype: {
|
|
153
|
-
status: {
|
|
156
|
+
status: {
|
|
157
|
+
get: function () {
|
|
158
|
+
return _wq(this, 'meta').get('status')
|
|
159
|
+
|| this instanceof Response
|
|
160
|
+
? responseOriginals.prototype.status.get.call(this)
|
|
161
|
+
: this.status;
|
|
162
|
+
}
|
|
163
|
+
},
|
|
154
164
|
carry: { get: function () { return _wq(this, 'meta').get('carry'); } },
|
|
155
165
|
parse: { value: async function () { return await parseHttpMessage(this); } },
|
|
156
166
|
clone: {
|
|
@@ -336,23 +346,23 @@ export function renderHttpMessageInit(httpMessageInit) {
|
|
|
336
346
|
return { ..._headers, [name.toLowerCase()]: httpMessageInit.headers[name] };
|
|
337
347
|
}, {});
|
|
338
348
|
// Process body
|
|
339
|
-
let body = httpMessageInit.body,
|
|
349
|
+
let body = httpMessageInit.body,
|
|
350
|
+
type = dataType(httpMessageInit.body);
|
|
351
|
+
|
|
340
352
|
if (['Blob', 'File'].includes(type)) {
|
|
341
353
|
!headers['content-type'] && (headers['content-type'] = body.type);
|
|
342
354
|
!headers['content-length'] && (headers['content-length'] = body.size);
|
|
343
355
|
} else if (['Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer'].includes(type)) {
|
|
344
356
|
!headers['content-length'] && (headers['content-length'] = body.byteLength);
|
|
345
357
|
} else if (type === 'json' && _isTypeObject(body)/*JSON object*/) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
type = 'FormData';
|
|
355
|
-
}
|
|
358
|
+
const [_body, isJsonfiable] = createFormDataFromJson(body, true/*jsonfy*/, true/*getIsJsonfiable*/);
|
|
359
|
+
if (isJsonfiable) {
|
|
360
|
+
body = JSON.stringify(body, (k, v) => v instanceof Error ? { ...v, message: v.message } : v);
|
|
361
|
+
headers['content-type'] = 'application/json';
|
|
362
|
+
headers['content-length'] = (new Blob([body])).size;
|
|
363
|
+
} else {
|
|
364
|
+
body = _body;
|
|
365
|
+
type = 'FormData';
|
|
356
366
|
}
|
|
357
367
|
} else if (type === 'json'/*JSON string*/ && !headers['content-length']) {
|
|
358
368
|
(headers['content-length'] = (body + '').length);
|
|
@@ -429,7 +439,5 @@ export function renderCookieObjToString(cookieObj) {
|
|
|
429
439
|
|
|
430
440
|
const importUrl = new URL(import.meta.url);
|
|
431
441
|
if (importUrl.searchParams.has('shim')) {
|
|
432
|
-
globalThis.LiveResponse = LiveResponse;
|
|
433
442
|
shim(importUrl.searchParams.get('shim')?.trim());
|
|
434
|
-
console.log('Webflo Fetch APIs shimmed.');
|
|
435
443
|
}
|
|
@@ -22,7 +22,7 @@ export function dataType(value) {
|
|
|
22
22
|
'Uint8Array', 'Uint16Array', 'Uint32Array', 'ArrayBuffer', 'Blob', 'File', 'FormData', 'Stream', 'ReadableStream'
|
|
23
23
|
].reduce((_toStringTag, type) => _toStringTag || (toStringTag === type ? type : null), null);
|
|
24
24
|
if (type) return type;
|
|
25
|
-
if ((_isObject(value)
|
|
25
|
+
if ((_isObject(value)) || (Array.isArray(value) && _isPlainArray(value)) || 'toString' in value) {
|
|
26
26
|
return 'json';
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -6,14 +6,12 @@ import { _wq } from '../../util.js';
|
|
|
6
6
|
|
|
7
7
|
export class WQSockPort extends WQMessagePort {
|
|
8
8
|
|
|
9
|
-
static WebSocket = typeof WebSocket !== 'undefined' ? WebSocket : null;
|
|
10
|
-
|
|
11
9
|
#socket;
|
|
12
10
|
#cleanups = [];
|
|
13
11
|
|
|
14
12
|
constructor(instanceOrConnectionID) {
|
|
15
13
|
super();
|
|
16
|
-
this.#socket = typeof instanceOrConnectionID === 'string' ? new
|
|
14
|
+
this.#socket = typeof instanceOrConnectionID === 'string' ? new WebSocket(`/${instanceOrConnectionID}`) : instanceOrConnectionID;
|
|
17
15
|
const meta = _wq(this, 'meta');
|
|
18
16
|
Object.defineProperty(this, 'wqLifecycle', {
|
|
19
17
|
value: {
|
|
@@ -2,7 +2,7 @@ import { _isObject, _isTypeObject } from '@webqit/util/js/index.js';
|
|
|
2
2
|
import { WQMessagePort, WQMessagePortInstanceTag } from './WQMessagePort.js';
|
|
3
3
|
import { isTypeStream } from '../webflo-fetch/util.js';
|
|
4
4
|
import { WQMessageEvent } from './WQMessageEvent.js';
|
|
5
|
-
import { Observer } from '@webqit/
|
|
5
|
+
import { Observer } from '@webqit/use-live';
|
|
6
6
|
import { _wq } from '../../util.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -294,6 +294,7 @@ export function postRequest(data, callback, options = {}) {
|
|
|
294
294
|
const { signal, once } = eventOptions2;
|
|
295
295
|
const messageChannel = new MessageChannel;
|
|
296
296
|
messageChannel.port1.start();
|
|
297
|
+
toWQPort(messageChannel.port1);
|
|
297
298
|
messageChannel.port1.addEventListener('message', (e) => callback(e), { signal, once });
|
|
298
299
|
return this.postMessage(data, { ...$options, transfer: [messageChannel.port2].concat(transfer) });
|
|
299
300
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _isObject } from '@webqit/util/js/index.js';
|
|
2
2
|
import { _difference } from '@webqit/util/arr/index.js';
|
|
3
|
-
import { LiveResponse } from '../webflo-fetch/
|
|
3
|
+
import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
|
|
4
4
|
import { xURL } from '../webflo-url/xURL.js';
|
|
5
5
|
import { _wq } from '../../util.js';
|
|
6
6
|
|
|
@@ -45,6 +45,8 @@ export class HttpEvent {
|
|
|
45
45
|
get state() { return { ...(this.#init.state || {}) }; }
|
|
46
46
|
|
|
47
47
|
#lifecyclePromises = new Set;
|
|
48
|
+
get lifecyclePromises() { return this.#lifecyclePromises; }
|
|
49
|
+
|
|
48
50
|
#lifeCycleResolve;
|
|
49
51
|
#lifeCycleReject;
|
|
50
52
|
#lifeCycleResolutionPromise = new Promise((resolve, reject) => {
|
|
@@ -80,12 +82,12 @@ export class HttpEvent {
|
|
|
80
82
|
&& !this.#lifecyclePromises.size;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
waitUntil(promise) {
|
|
84
|
-
return this.#extendLifecycle(promise);
|
|
85
|
+
async waitUntil(promise) {
|
|
86
|
+
return await this.#extendLifecycle(promise);
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
waitUntilNavigate() {
|
|
88
|
-
|
|
90
|
+
this.waitUntil(new Promise(() => { }));
|
|
89
91
|
}
|
|
90
92
|
|
|
91
93
|
#internalLiveResponse = new LiveResponse(null, { done: false });
|
|
@@ -100,8 +102,8 @@ export class HttpEvent {
|
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
extend(init = {}) {
|
|
103
|
-
const instance = this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...init });
|
|
104
|
-
this.#extendLifecycle(instance.lifeCycleComplete(true));
|
|
105
|
+
const instance = this.constructor.create(this/*Main difference from clone*/, { ...this.#init, ...(init || {}) });
|
|
106
|
+
if (init !== false) this.#extendLifecycle(instance.lifeCycleComplete(true));
|
|
105
107
|
return instance;
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { State } from '@webqit/quantum-js';
|
|
2
1
|
import { _isFunction, _isArray, _isObject } from '@webqit/util/js/index.js';
|
|
3
2
|
import { _from as _arrFrom } from '@webqit/util/arr/index.js';
|
|
3
|
+
import { LiveResponse } from '../webflo-fetch/LiveResponse.js';
|
|
4
4
|
import { path as Path } from '../webflo-url/util.js';
|
|
5
|
-
import { isGenerator } from '../webflo-fetch/LiveResponse.js';
|
|
6
5
|
|
|
7
6
|
export class WebfloRouter {
|
|
8
7
|
|
|
@@ -170,11 +169,11 @@ export class WebfloRouter {
|
|
|
170
169
|
const returnValue = await handler.call(thisContext, thisTick.event, $next/*next*/, $fetch/*fetch*/);
|
|
171
170
|
|
|
172
171
|
// Handle cleanup on abort
|
|
173
|
-
if (returnValue
|
|
172
|
+
if (LiveResponse.test(returnValue) === 'LiveMode') {
|
|
174
173
|
thisTick.event.signal.addEventListener('abort', () => {
|
|
175
|
-
returnValue.
|
|
174
|
+
returnValue.abort();
|
|
176
175
|
});
|
|
177
|
-
} else if (
|
|
176
|
+
} else if (LiveResponse.test(returnValue) === 'Generator') {
|
|
178
177
|
thisTick.event.signal.addEventListener('abort', () => {
|
|
179
178
|
if (typeof returnValue.return === 'function') {
|
|
180
179
|
returnValue.return();
|
|
@@ -187,13 +186,19 @@ export class WebfloRouter {
|
|
|
187
186
|
resolved = 2;
|
|
188
187
|
resolve(returnValue);
|
|
189
188
|
} else if (typeof returnValue !== 'undefined') {
|
|
190
|
-
|
|
189
|
+
thisTick.event.internalLiveResponse.replaceWith(returnValue, { done: true });
|
|
191
190
|
}
|
|
192
191
|
});
|
|
193
192
|
}
|
|
193
|
+
let returnValue;
|
|
194
194
|
if (_default) {
|
|
195
|
-
|
|
195
|
+
returnValue = await _default.call(thisContext, thisTick.event, remoteFetch);
|
|
196
196
|
}
|
|
197
|
+
try {
|
|
198
|
+
// IMPORTANT: Explicitly terminate the event lifecycle if nothing extends it
|
|
199
|
+
await thisTick.event.waitUntil();
|
|
200
|
+
} catch(e) {}
|
|
201
|
+
return returnValue;
|
|
197
202
|
};
|
|
198
203
|
|
|
199
204
|
return next({
|
|
@@ -23,7 +23,7 @@ import { ServerSideSession } from './ServerSideSession.js';
|
|
|
23
23
|
import { HttpEvent } from '../webflo-routing/HttpEvent.js';
|
|
24
24
|
import { HttpUser } from '../webflo-routing/HttpUser.js';
|
|
25
25
|
import { response as responseShim, headers as headersShim } from '../webflo-fetch/index.js';
|
|
26
|
-
import {
|
|
26
|
+
import { UseLiveTransform } from '../../build-pi/esbuild-plugin-uselive-transform.js';
|
|
27
27
|
import { createWindow } from '@webqit/oohtml-ssr';
|
|
28
28
|
import { _wq } from '../../util.js';
|
|
29
29
|
import '../webflo-fetch/index.js';
|
|
@@ -57,7 +57,6 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
async initialize() {
|
|
60
|
-
const instanceController = await super.initialize();
|
|
61
60
|
const { appMeta: APP_META, flags: FLAGS, logger: LOGGER, } = this.cx;
|
|
62
61
|
|
|
63
62
|
// ----------
|
|
@@ -65,9 +64,17 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
65
64
|
if (FLAGS['dev']) {
|
|
66
65
|
await this.enterDevMode();
|
|
67
66
|
} else {
|
|
68
|
-
await this.buildRoutes();
|
|
67
|
+
await this.buildRoutes({ server: true });
|
|
69
68
|
}
|
|
70
69
|
|
|
70
|
+
// ----------
|
|
71
|
+
// Call default-init
|
|
72
|
+
const instanceController = await super.initialize();
|
|
73
|
+
|
|
74
|
+
// ----------
|
|
75
|
+
// Start serving
|
|
76
|
+
this.control();
|
|
77
|
+
|
|
71
78
|
// ----------
|
|
72
79
|
// Show proxies
|
|
73
80
|
const { PROXY } = this.config;
|
|
@@ -90,10 +97,6 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
// ----------
|
|
94
|
-
// Start serving
|
|
95
|
-
this.control();
|
|
96
|
-
|
|
97
100
|
// ----------
|
|
98
101
|
// Show server details
|
|
99
102
|
if (this.#servers.size) {
|
|
@@ -108,22 +111,22 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
108
111
|
return instanceController;
|
|
109
112
|
}
|
|
110
113
|
|
|
111
|
-
async buildRoutes({ client = false, worker = false, ...options } = {}) {
|
|
114
|
+
async buildRoutes({ client = false, worker = false, server = false, ...options } = {}) {
|
|
112
115
|
const routeDirs = [...new Set([this.config.LAYOUT.CLIENT_DIR, this.config.LAYOUT.WORKER_DIR, this.config.LAYOUT.SERVER_DIR])];
|
|
113
|
-
const entryPoints = await $glob(routeDirs.map((d) => `${d}/**/handler{${client ? ',.client' : ''}${worker ? ',.worker' : ''},.server}.js`), { absolute: true })
|
|
116
|
+
const entryPoints = await $glob(routeDirs.map((d) => `${d}/**/handler{${client ? ',.client' : ''}${worker ? ',.worker' : ''}${server ? ',.server' : ''}}.js`), { absolute: true })
|
|
114
117
|
.then((files) => files.map((file) => file.replace(/\\/g, '/')));
|
|
115
|
-
const
|
|
118
|
+
const initFiles = await $glob(`${process.cwd()}/init.server.js`);
|
|
116
119
|
const bundlingConfig = {
|
|
117
|
-
entryPoints,
|
|
120
|
+
entryPoints: entryPoints.concat(initFiles),
|
|
118
121
|
outdir: this.config.RUNTIME_DIR,
|
|
119
|
-
|
|
120
|
-
bundle: true,
|
|
122
|
+
outbase: process.cwd(),
|
|
121
123
|
format: 'esm',
|
|
122
|
-
|
|
124
|
+
platform: server ? 'node' : 'browser',
|
|
125
|
+
bundle: server ? false : true,
|
|
126
|
+
minify: server ? false : true,
|
|
123
127
|
sourcemap: false,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
plugins: [ LiveJSTransform() ],
|
|
128
|
+
treeShaking: true,
|
|
129
|
+
plugins: [UseLiveTransform()],
|
|
127
130
|
...options,
|
|
128
131
|
};
|
|
129
132
|
return await EsBuild.build(bundlingConfig);
|
|
@@ -141,6 +144,7 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
141
144
|
buildSensitivity: parseInt(FLAGS['build-sensitivity'] || 0),
|
|
142
145
|
});
|
|
143
146
|
await this.#hmr.buildRoutes(true);
|
|
147
|
+
await this.#hmr.bundleAssetsIfPending(true);
|
|
144
148
|
if (FLAGS['open']) {
|
|
145
149
|
for (let [proto, def] of this.#servers) {
|
|
146
150
|
const url = `${proto}://${def.hostnames.find((h) => h !== '*') || 'localhost'}:${def.port}`;
|
|
@@ -149,6 +153,35 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
155
|
|
|
156
|
+
async initCreateStorage() {
|
|
157
|
+
if (this.bootstrap.init.createStorage
|
|
158
|
+
|| !this.bootstrap.init.redis) {
|
|
159
|
+
return super.initCreateStorage();
|
|
160
|
+
}
|
|
161
|
+
const redis = this.bootstrap.init.redis;
|
|
162
|
+
this.bootstrap.init.createStorage = (namespace, ttl = null) => ({
|
|
163
|
+
async has(key) { return await redis.hexists(namespace, key); },
|
|
164
|
+
async get(key) {
|
|
165
|
+
const value = await redis.hget(namespace, key);
|
|
166
|
+
return typeof value === 'undefined' ? value : JSON.parse(value);
|
|
167
|
+
},
|
|
168
|
+
async set(key, value) {
|
|
169
|
+
const returnValue = await redis.hset(namespace, key, JSON.stringify(value));
|
|
170
|
+
if (!this.ttlApplied && ttl) {
|
|
171
|
+
await redis.expire(namespace, ttl);
|
|
172
|
+
this.ttlApplied = true;
|
|
173
|
+
}
|
|
174
|
+
return returnValue;
|
|
175
|
+
},
|
|
176
|
+
async delete(key) { return await redis.hdel(namespace, key); },
|
|
177
|
+
async clear() { return await redis.del(namespace); },
|
|
178
|
+
async keys() { return await redis.hkeys(namespace); },
|
|
179
|
+
async values() { return (await redis.hvals(namespace) || []).map((value) => typeof value === 'undefined' ? value : JSON.parse(value)); },
|
|
180
|
+
async entries() { return Object.entries(await redis.hgetall(namespace) || {}).map(([key, value]) => [key, typeof value === 'undefined' ? value : JSON.parse(value)]); },
|
|
181
|
+
get size() { return redis.hlen(namespace); },
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
152
185
|
control() {
|
|
153
186
|
const { flags: FLAGS } = this.cx;
|
|
154
187
|
const { SERVER, PROXY } = this.config;
|
|
@@ -528,6 +561,10 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
528
561
|
}
|
|
529
562
|
scopeObj.ext = Path.parse(scopeObj.filename).ext;
|
|
530
563
|
const finalizeResponse = (response) => {
|
|
564
|
+
// Qualify Service-Worker responses
|
|
565
|
+
if (httpEvent.request.headers.get('Service-Worker') === 'script') {
|
|
566
|
+
response.headers.set('Service-Worker-Allowed', this.config.WORKER.scope || '/');
|
|
567
|
+
}
|
|
531
568
|
const responseMeta = _wq(response, 'meta');
|
|
532
569
|
responseMeta.set('filename', scopeObj.filename);
|
|
533
570
|
responseMeta.set('static', true);
|
|
@@ -597,10 +634,7 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
597
634
|
scopeObj.response.headers.set('X-Frame-Options', 'SAMEORIGIN');
|
|
598
635
|
// 5. Partial content support
|
|
599
636
|
scopeObj.response.headers.set('Accept-Ranges', 'bytes');
|
|
600
|
-
|
|
601
|
-
if (httpEvent.request.headers.get('Service-Worker') === 'script') {
|
|
602
|
-
scopeObj.response.headers.set('Service-Worker-Allowed', this.config.WORKER.scope || '/');
|
|
603
|
-
}
|
|
637
|
+
|
|
604
638
|
return finalizeResponse(scopeObj.response);
|
|
605
639
|
}
|
|
606
640
|
|
|
@@ -675,7 +709,7 @@ export class WebfloServer extends WebfloRuntime {
|
|
|
675
709
|
const asHTML = requestAccept?.match('text/html');
|
|
676
710
|
const asIs = requestAccept?.match(response.headers.get('Content-Type'));
|
|
677
711
|
const responseMeta = _wq(response, 'meta');
|
|
678
|
-
if (requestAccept && asHTML
|
|
712
|
+
if (requestAccept && asHTML > asIs && !responseMeta.get('static')) {
|
|
679
713
|
response = await this.render(httpEvent, response);
|
|
680
714
|
} else if (requestAccept && response.headers.get('Content-Type') && !asIs) {
|
|
681
715
|
return new Response(response.body, { status: 406, statusText: 'Not Acceptable', headers: response.headers });
|
|
@@ -182,13 +182,17 @@ export class WebfloHMR {
|
|
|
182
182
|
const bundlingConfig = {
|
|
183
183
|
client: true,
|
|
184
184
|
worker: true,
|
|
185
|
+
server: true,
|
|
185
186
|
metafile: true, // This is key
|
|
186
187
|
logLevel: 'silent', // Suppress output
|
|
187
188
|
incremental: true,
|
|
188
189
|
};
|
|
189
190
|
buildResult = await this.#app.buildRoutes(bundlingConfig);
|
|
190
191
|
}
|
|
191
|
-
} catch (e) {
|
|
192
|
+
} catch (e) {
|
|
193
|
+
//console.error(e);
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
192
196
|
|
|
193
197
|
// 1. Forward dependency graph (file -> [imported files])
|
|
194
198
|
const forward = {};
|
|
@@ -228,25 +232,25 @@ export class WebfloHMR {
|
|
|
228
232
|
return true;
|
|
229
233
|
}
|
|
230
234
|
|
|
231
|
-
async bundleAssetsIfPending() {
|
|
235
|
+
async bundleAssetsIfPending(ohForce = false) {
|
|
232
236
|
const entries = {};
|
|
233
237
|
|
|
234
|
-
if (this.#dirtiness.clientRoutesAffected.size || this.#dirtiness.serviceWorkerAffected) {
|
|
238
|
+
if (this.#dirtiness.clientRoutesAffected.size || this.#dirtiness.serviceWorkerAffected || ohForce) {
|
|
235
239
|
entries.js = {};
|
|
236
|
-
entries.js.client = !!this.#dirtiness.clientRoutesAffected.size;
|
|
237
|
-
entries.js.worker = this.#dirtiness.serviceWorkerAffected;
|
|
240
|
+
entries.js.client = !!this.#dirtiness.clientRoutesAffected.size || ohForce;
|
|
241
|
+
entries.js.worker = this.#dirtiness.serviceWorkerAffected || ohForce;
|
|
238
242
|
entries.js.server = false;
|
|
239
243
|
// Clear state
|
|
240
244
|
this.#dirtiness.clientRoutesAffected.clear();
|
|
241
245
|
this.#dirtiness.serviceWorkerAffected = false;
|
|
242
246
|
}
|
|
243
247
|
|
|
244
|
-
if (this.#dirtiness.HTMLAffected) {
|
|
248
|
+
if (this.#dirtiness.HTMLAffected || ohForce) {
|
|
245
249
|
this.#dirtiness.HTMLAffected = false;
|
|
246
250
|
entries.html = {};
|
|
247
251
|
}
|
|
248
252
|
|
|
249
|
-
if (this.#dirtiness.CSSAffected) {
|
|
253
|
+
if (this.#dirtiness.CSSAffected || ohForce) {
|
|
250
254
|
this.#dirtiness.CSSAffected = false;
|
|
251
255
|
entries.css = {};
|
|
252
256
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { _with } from '@webqit/util/obj/index.js';
|
|
2
2
|
import { _isArray, _isObject, _isTypeObject, _isString, _isEmpty } from '@webqit/util/js/index.js';
|
|
3
3
|
import { DeepURLSearchParams } from './util.js';
|
|
4
|
-
import { Observer } from '@webqit/
|
|
4
|
+
import { Observer } from '@webqit/use-live';
|
|
5
5
|
|
|
6
6
|
export class Url {
|
|
7
7
|
|
|
@@ -21,6 +21,7 @@ export async function bootstrap(cx, offset = '') {
|
|
|
21
21
|
};
|
|
22
22
|
if (config.CLIENT.copy_public_variables) {
|
|
23
23
|
const publicEnvPattern = /(?:^|_)PUBLIC(?:_|$)/;
|
|
24
|
+
config.ENV.data = config.ENV.data || {};
|
|
24
25
|
for (const key in process.env) {
|
|
25
26
|
if (publicEnvPattern.test(key)) {
|
|
26
27
|
config.ENV.data[key] = process.env[key];
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import Fs from 'fs/promises';
|
|
2
|
-
//import { transformQuantum } from '@webqit/quantum-js';
|
|
3
|
-
|
|
4
|
-
export function LiveJSTransform() {
|
|
5
|
-
return {
|
|
6
|
-
name: 'livejs-transform',
|
|
7
|
-
setup(build) {
|
|
8
|
-
build.onLoad({ filter: /\.(js|mjs|ts|jsx|tsx)$/ }, async (args) => {
|
|
9
|
-
let code = await Fs.readFile(args.path, 'utf8');
|
|
10
|
-
|
|
11
|
-
//console.log('LiveJS -- transform:', args);
|
|
12
|
-
|
|
13
|
-
// super dirty detection
|
|
14
|
-
if (!/\bquantum\s+function\b/.test(code) &&
|
|
15
|
-
!/\basync\s+quantum\s+function\b/.test(code)) {
|
|
16
|
-
return { contents: code, loader: 'default' };
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
console.log('LiveJS transform:', args.path);
|
|
21
|
-
|
|
22
|
-
return { contents: code, loader: 'default' };
|
|
23
|
-
const result = await transformQuantum(code, {
|
|
24
|
-
filename: args.path,
|
|
25
|
-
sourceMaps: true
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
contents: result.code,
|
|
30
|
-
loader: 'js' // or 'ts' if you want esbuild TS transform after
|
|
31
|
-
};
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
}
|