@webqit/webflo 0.20.56 → 0.20.58
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 +5 -4
- package/src/util.js +1 -0
- package/src/webflo-build/index.js +24 -8
- package/src/webflo-config/runtime/Client.js +3 -3
- package/src/webflo-runtime/webflo-client/DeviceCapabilities.js +373 -165
- package/src/webflo-runtime/webflo-client/WebfloClient.js +5 -1
- package/src/webflo-runtime/webflo-client/WebfloRootClientA.js +4 -11
- package/src/webflo-runtime/webflo-client/WebfloRootClientB.js +9 -1
- package/src/webflo-runtime/webflo-client/webflo-elements.js +10 -4
- package/src/webflo-runtime/webflo-messaging/ClientRequestPort001.js +10 -8
- package/src/webflo-runtime/webflo-messaging/WebfloTenancy001.js +17 -9
- package/src/webflo-runtime/webflo-messaging/WebfloTenant001.js +7 -3
- package/src/webflo-runtime/webflo-routing/HttpEvent111.js +44 -3
- package/src/webflo-runtime/webflo-routing/HttpUser111.js +1 -3
- package/src/webflo-runtime/webflo-routing/WebfloRouter111.js +7 -4
- package/src/webflo-runtime/webflo-server/WebfloServer.js +76 -33
- package/src/webflo-runtime/webflo-server/webflo-devmode.js +15 -27
|
@@ -406,7 +406,7 @@ export class ModalElement extends BaseElement {
|
|
|
406
406
|
}
|
|
407
407
|
|
|
408
408
|
static get observedAttributes() {
|
|
409
|
-
return super.observedAttributes.concat(['class']);
|
|
409
|
+
return super.observedAttributes.concat(['class', 'open']);
|
|
410
410
|
}
|
|
411
411
|
|
|
412
412
|
get delegatesFocus() { return false; }
|
|
@@ -559,6 +559,13 @@ export class ModalElement extends BaseElement {
|
|
|
559
559
|
super.attributeChangedCallback?.(name, old, _new);
|
|
560
560
|
|
|
561
561
|
if (name === 'class' && old !== _new) this.#bindDimensionsWorker();
|
|
562
|
+
if (name === 'open' && this.isConnected) {
|
|
563
|
+
if (this.hasAttribute('open') && !this.matches(':popover-open')) {
|
|
564
|
+
this.showPopover();
|
|
565
|
+
} else if (!this.hasAttribute('open') && this.matches(':popover-open')) {
|
|
566
|
+
this.hidePopover();
|
|
567
|
+
}
|
|
568
|
+
}
|
|
562
569
|
}
|
|
563
570
|
|
|
564
571
|
connectedCallback() {
|
|
@@ -567,9 +574,6 @@ export class ModalElement extends BaseElement {
|
|
|
567
574
|
if (!this.popover) {
|
|
568
575
|
this.popover = 'manual';
|
|
569
576
|
}
|
|
570
|
-
if (this.hasAttribute('open')) {
|
|
571
|
-
this.showPopover();
|
|
572
|
-
}
|
|
573
577
|
}
|
|
574
578
|
|
|
575
579
|
disconnectedCallback() {
|
|
@@ -612,6 +616,8 @@ export class ModalElement extends BaseElement {
|
|
|
612
616
|
this.#unbindDimensionsWorker = null;
|
|
613
617
|
this.#unbindMinmaxWorker?.();
|
|
614
618
|
this.#unbindMinmaxWorker = null;
|
|
619
|
+
|
|
620
|
+
this.removeAttribute('open');
|
|
615
621
|
}
|
|
616
622
|
});
|
|
617
623
|
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { ClientPortMixin } from './ClientPortMixin.js';
|
|
2
|
-
import { StarPort } from '@webqit/port-plus';
|
|
3
|
-
import {
|
|
2
|
+
import { StarPort, MessageEventPlus } from '@webqit/port-plus';
|
|
3
|
+
import { _portPlusMeta } from '../../util.js';
|
|
4
4
|
|
|
5
5
|
export class ClientRequestPort001 extends ClientPortMixin(StarPort) {
|
|
6
6
|
|
|
7
7
|
#portID;
|
|
8
8
|
get portID() { return this.#portID; }
|
|
9
9
|
|
|
10
|
+
get broadcast() { return _portPlusMeta(this).get('parentPort'); }
|
|
11
|
+
|
|
10
12
|
#url;
|
|
11
13
|
get url() { return this.#url; }
|
|
12
14
|
|
|
@@ -30,7 +32,7 @@ export class ClientRequestPort001 extends ClientPortMixin(StarPort) {
|
|
|
30
32
|
this.#navigatedAway = true;
|
|
31
33
|
lastNavigationEvent = 'navigateaway';
|
|
32
34
|
}
|
|
33
|
-
const event = new
|
|
35
|
+
const event = new MessageEventPlus(null, { type: lastNavigationEvent, target: this });
|
|
34
36
|
this.dispatchEvent(event);
|
|
35
37
|
if (event.defaultPrevented) {
|
|
36
38
|
e.preventDefault();
|
|
@@ -39,14 +41,14 @@ export class ClientRequestPort001 extends ClientPortMixin(StarPort) {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
enterChannel(channelID, { resolveData = null } = {}) {
|
|
42
|
-
const webfloTenant =
|
|
43
|
-
const
|
|
44
|
+
const webfloTenant = _portPlusMeta(this).get('parentPort');
|
|
45
|
+
const webfloTenancy = webfloTenant && _portPlusMeta(webfloTenant).get('parentPort');
|
|
44
46
|
|
|
45
|
-
if (!
|
|
46
|
-
throw new Error('Instance
|
|
47
|
+
if (!webfloTenancy) {
|
|
48
|
+
throw new Error('Instance is not connected to the multi-tenant system.');
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
const channel =
|
|
51
|
+
const channel = webfloTenancy.getChannel(channelID, true);
|
|
50
52
|
const leave = channel.addPort(webfloTenant, { resolveData });
|
|
51
53
|
|
|
52
54
|
return { channel, leave };
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { StarPort, RelayPort } from '@webqit/port-plus';
|
|
2
2
|
import { WebfloTenant001 } from './WebfloTenant001.js';
|
|
3
3
|
|
|
4
4
|
export class WebfloTenancy001 extends StarPort {
|
|
5
5
|
|
|
6
6
|
#channels = new Map;
|
|
7
7
|
|
|
8
|
+
constructor() {
|
|
9
|
+
super({ handshake: 1, postAwaitsOpen: true, autoClose: false });
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
getTenant(tenantID, autoCreate = false) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
tenant
|
|
13
|
+
let tenant = this.findPort((tenant) => tenant.tenantID === tenantID);
|
|
14
|
+
|
|
15
|
+
if (!tenant && autoCreate) {
|
|
16
|
+
tenant = new WebfloTenant001(tenantID, { handshake: 1, postAwaitsOpen: true, autoClose: true });
|
|
17
|
+
this.addPort(tenant, { enableBubbling: true }); // auto-removed on close - given that handshake is 1, above
|
|
13
18
|
}
|
|
14
|
-
|
|
19
|
+
|
|
20
|
+
return tenant;
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
getChannel(channelName, autoCreate = false) {
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
let channel = this.#channels.get(channelName);
|
|
25
|
+
|
|
26
|
+
if (!channel && autoCreate) {
|
|
27
|
+
channel = new RelayPort(channelName, { handshake: 1, postAwaitsOpen: true, autoClose: true });
|
|
20
28
|
|
|
21
29
|
this.#channels.set(channelName, channel);
|
|
22
30
|
channel.readyStateChange('close').then(() => this.#channels.delete(channelName));
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
return
|
|
33
|
+
return channel;
|
|
26
34
|
}
|
|
27
35
|
}
|
|
@@ -17,11 +17,15 @@ export class WebfloTenant001 extends StarPort {
|
|
|
17
17
|
|
|
18
18
|
createRequestPort(portID, url = null) {
|
|
19
19
|
const requestPort = new ClientRequestPort001(portID, url, { handshake: 1, postAwaitsOpen: true, autoClose: true });
|
|
20
|
-
this.addPort(requestPort);
|
|
20
|
+
this.addPort(requestPort, { enableBubbling: true });
|
|
21
|
+
|
|
21
22
|
setTimeout(() => {
|
|
22
23
|
if (requestPort.length || !this.findPort((port) => port === requestPort)) return;
|
|
23
|
-
requestPort.
|
|
24
|
-
|
|
24
|
+
if (requestPort.autoCloseXPromise) {
|
|
25
|
+
requestPort.autoCloseXPromise.then(() => requestPort.close(true));
|
|
26
|
+
} else requestPort.close(true);
|
|
27
|
+
}, 30000/*30sec*/);
|
|
28
|
+
|
|
25
29
|
return requestPort;
|
|
26
30
|
}
|
|
27
31
|
}
|
|
@@ -127,12 +127,53 @@ export class HttpEvent111 {
|
|
|
127
127
|
|
|
128
128
|
// ------
|
|
129
129
|
|
|
130
|
-
async waitUntil(promise) {
|
|
131
|
-
|
|
130
|
+
async waitUntil(promise, ...args) {
|
|
131
|
+
let gcArray = [];
|
|
132
|
+
const gc = () => {
|
|
133
|
+
gcArray.forEach((unsub) => unsub());
|
|
134
|
+
gcArray.splice(0);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (args.length) {
|
|
138
|
+
promise = Promise.race([promise, new Promise((res, rej) => {
|
|
139
|
+
const timeout = typeof args[0] === 'number' ? args.shift() : null;
|
|
140
|
+
|
|
141
|
+
if (typeof args[0] === 'function') {
|
|
142
|
+
const lifecycleEventsHandler = args.shift();
|
|
143
|
+
const callLifecycleEventsHandler = (e) => lifecycleEventsHandler(e, res, rej);
|
|
144
|
+
|
|
145
|
+
// On abort
|
|
146
|
+
this.#abortController.signal.addEventListener('abort', callLifecycleEventsHandler);
|
|
147
|
+
gcArray.push(() => this.#abortController.signal.removeEventListener('abort', callLifecycleEventsHandler));
|
|
148
|
+
|
|
149
|
+
// On navigate
|
|
150
|
+
this.client.addEventListener('navigate', callLifecycleEventsHandler);
|
|
151
|
+
gcArray.push(() => this.client.removeEventListener('navigate', callLifecycleEventsHandler));
|
|
152
|
+
|
|
153
|
+
// On auth switch
|
|
154
|
+
gcArray.push(this.user.subscribe('id', callLifecycleEventsHandler, { scope: 2/* global */ }));
|
|
155
|
+
|
|
156
|
+
// On timeout
|
|
157
|
+
if (timeout) {
|
|
158
|
+
const id = setTimeout(() => {
|
|
159
|
+
callLifecycleEventsHandler(new CustomEvent('timeout', { detail: { timeout } }));
|
|
160
|
+
}, timeout);
|
|
161
|
+
gcArray.push(() => clearTimeout(id));
|
|
162
|
+
}
|
|
163
|
+
} else if (timeout) {
|
|
164
|
+
const id = setTimeout(() => {
|
|
165
|
+
res(new CustomEvent('timeout', { detail: { timeout } }));
|
|
166
|
+
}, timeout);
|
|
167
|
+
gcArray.push(() => clearTimeout(id));
|
|
168
|
+
}
|
|
169
|
+
})]);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return await this.#extendLifecycle(promise).finally(gc);
|
|
132
173
|
}
|
|
133
174
|
|
|
134
175
|
async waitUntilNavigate() {
|
|
135
|
-
|
|
176
|
+
return this.waitUntil(new Promise(() => { }));
|
|
136
177
|
}
|
|
137
178
|
|
|
138
179
|
async respondWith(data, ...args) {
|
|
@@ -8,9 +8,7 @@ export class HttpUser111 extends HttpKeyvalInterface {
|
|
|
8
8
|
const isSignedIn = await this.get('id');
|
|
9
9
|
if (callback) {
|
|
10
10
|
await callback(isSignedIn);
|
|
11
|
-
return options
|
|
12
|
-
? undefined
|
|
13
|
-
: this.subscribe('id', callback, options);
|
|
11
|
+
return this.subscribe('id', (e) => callback(e.value), options);
|
|
14
12
|
}
|
|
15
13
|
return !!isSignedIn;
|
|
16
14
|
}
|
|
@@ -161,14 +161,16 @@ export class WebfloRouter111 {
|
|
|
161
161
|
let resolved = 0;
|
|
162
162
|
|
|
163
163
|
// Monitor first respondWith()
|
|
164
|
-
|
|
164
|
+
const internalLiveResponseHandler = () => {
|
|
165
165
|
if (!resolved) {
|
|
166
166
|
resolved = 1;
|
|
167
167
|
resolve(thisTick.event.internalLiveResponse);
|
|
168
168
|
} else if (resolved === 2) {
|
|
169
169
|
throw new Error(`Unexpected respondWith() after handler return.`);
|
|
170
170
|
}
|
|
171
|
-
}
|
|
171
|
+
};
|
|
172
|
+
thisTick.event.internalLiveResponse.addEventListener('replace', internalLiveResponseHandler);
|
|
173
|
+
const removeInternalLiveResponseHandler = () => thisTick.event.internalLiveResponse.removeEventListener('replace', internalLiveResponseHandler);
|
|
172
174
|
|
|
173
175
|
// Call the handler
|
|
174
176
|
const returnValue = await handler.call(thisContext, thisTick.event, $next/*next*/, $fetch/*fetch*/);
|
|
@@ -177,19 +179,20 @@ export class WebfloRouter111 {
|
|
|
177
179
|
if (LiveResponse.test(returnValue) === 'LiveProgramHandle') {
|
|
178
180
|
thisTick.event.signal.addEventListener('abort', () => {
|
|
179
181
|
returnValue.abort();
|
|
180
|
-
});
|
|
182
|
+
}, { once: true });
|
|
181
183
|
} else if (LiveResponse.test(returnValue) === 'Generator') {
|
|
182
184
|
thisTick.event.signal.addEventListener('abort', () => {
|
|
183
185
|
if (typeof returnValue.return === 'function') {
|
|
184
186
|
returnValue.return();
|
|
185
187
|
}
|
|
186
|
-
});
|
|
188
|
+
}, { once: true });
|
|
187
189
|
}
|
|
188
190
|
|
|
189
191
|
// Handle return value
|
|
190
192
|
if (!resolved) {
|
|
191
193
|
resolved = 2;
|
|
192
194
|
resolve(returnValue);
|
|
195
|
+
removeInternalLiveResponseHandler(); // Removing this line preserves error handling but introduces a memory leak
|
|
193
196
|
} else if (typeof returnValue !== 'undefined') {
|
|
194
197
|
thisTick.event.internalLiveResponse.replaceWith(returnValue, { done: true });
|
|
195
198
|
}
|
|
@@ -9,7 +9,7 @@ import $glob from 'fast-glob';
|
|
|
9
9
|
import EsBuild from 'esbuild';
|
|
10
10
|
import { Readable } from 'stream';
|
|
11
11
|
import { WebSocketServer } from 'ws';
|
|
12
|
-
import
|
|
12
|
+
import spawn from 'cross-spawn';
|
|
13
13
|
import { createWindow } from '@webqit/oohtml-ssr';
|
|
14
14
|
import { RequestPlus } from '@webqit/fetch-plus';
|
|
15
15
|
import { HttpThread111 } from '../webflo-routing/HttpThread111.js';
|
|
@@ -38,8 +38,10 @@ export class WebfloServer extends AppRuntime {
|
|
|
38
38
|
#keyvals;
|
|
39
39
|
get keyvals() { return this.#keyvals; }
|
|
40
40
|
|
|
41
|
+
#tenancy = new WebfloTenancy001;
|
|
42
|
+
get tenancy() { return this.#tenancy; }
|
|
43
|
+
|
|
41
44
|
#servers = new Map;
|
|
42
|
-
#tenancy = new WebfloTenancy001({ handshake: 1, postAwaitsOpen: true, autoClose: false });
|
|
43
45
|
#hmr;
|
|
44
46
|
|
|
45
47
|
#renderFileCache = new Map;
|
|
@@ -51,6 +53,12 @@ export class WebfloServer extends AppRuntime {
|
|
|
51
53
|
: process.env[key];
|
|
52
54
|
}
|
|
53
55
|
|
|
56
|
+
#buildContexts = {};
|
|
57
|
+
get buildContexts() { return this.#buildContexts; }
|
|
58
|
+
|
|
59
|
+
#buildOutputs = {};
|
|
60
|
+
get buildOutputs() { return this.#buildOutputs; }
|
|
61
|
+
|
|
54
62
|
async initialize() {
|
|
55
63
|
const { appMeta: APP_META, flags: FLAGS, logger: LOGGER, } = this.cx;
|
|
56
64
|
|
|
@@ -59,7 +67,7 @@ export class WebfloServer extends AppRuntime {
|
|
|
59
67
|
if (FLAGS['dev']) {
|
|
60
68
|
await this.enterDevMode();
|
|
61
69
|
} else {
|
|
62
|
-
await this.buildRoutes(
|
|
70
|
+
await this.buildRoutes(['server']);
|
|
63
71
|
}
|
|
64
72
|
|
|
65
73
|
// ----------
|
|
@@ -96,7 +104,6 @@ export class WebfloServer extends AppRuntime {
|
|
|
96
104
|
spawn('npx', ['webflo', 'start', ...flags], {
|
|
97
105
|
cwd: proxy.path, // target directory
|
|
98
106
|
stdio: 'inherit', // inherit stdio so output streams to parent terminal
|
|
99
|
-
shell: true // for Windows compatibility
|
|
100
107
|
});
|
|
101
108
|
}
|
|
102
109
|
|
|
@@ -119,28 +126,61 @@ export class WebfloServer extends AppRuntime {
|
|
|
119
126
|
return instanceController;
|
|
120
127
|
}
|
|
121
128
|
|
|
122
|
-
async buildRoutes(
|
|
123
|
-
const routeDirs =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
const initFiles = await $glob(`${process.cwd()}/init.server.js`);
|
|
128
|
-
|
|
129
|
-
const bundlingConfig = {
|
|
130
|
-
entryPoints: entryPoints.concat(initFiles),
|
|
131
|
-
outdir: this.config.RUNTIME_DIR,
|
|
132
|
-
outbase: process.cwd(),
|
|
133
|
-
format: 'esm',
|
|
134
|
-
platform: server ? 'node' : 'browser',
|
|
135
|
-
bundle: server ? false : true,
|
|
136
|
-
minify: server ? false : true,
|
|
137
|
-
sourcemap: false,
|
|
138
|
-
treeShaking: true,
|
|
139
|
-
plugins: [UseLiveTransform()],
|
|
140
|
-
...options,
|
|
129
|
+
async buildRoutes(realms = ['server'], { revalidate = false } = {}) {
|
|
130
|
+
const routeDirs = {
|
|
131
|
+
server: this.config.LAYOUT.SERVER_DIR,
|
|
132
|
+
client: this.config.LAYOUT.CLIENT_DIR,
|
|
133
|
+
worker: this.config.LAYOUT.WORKER_DIR
|
|
141
134
|
};
|
|
135
|
+
let moduleGraph = {};
|
|
136
|
+
|
|
137
|
+
for (const realm of realms) {
|
|
138
|
+
if (revalidate
|
|
139
|
+
|| !this.#buildContexts[realm]) {
|
|
140
|
+
await this.#buildContexts[realm]?.dispose();
|
|
141
|
+
|
|
142
|
+
const entryPoints = await $glob(`${routeDirs[realm]}/**/handler{,.${realm}}.js`, { absolute: true })
|
|
143
|
+
.then((files) => files.map((f) => f.replace(/\\/g, '/')));
|
|
144
|
+
|
|
145
|
+
const bundlingConfig = {
|
|
146
|
+
entryPoints: entryPoints,
|
|
147
|
+
outbase: process.cwd(),
|
|
148
|
+
format: 'esm',
|
|
149
|
+
bundle: true,
|
|
150
|
+
...(realm === 'server' ? {
|
|
151
|
+
platform: 'node',
|
|
152
|
+
packages: 'external',
|
|
153
|
+
outdir: this.config.RUNTIME_DIR,
|
|
154
|
+
write: true,
|
|
155
|
+
} : {
|
|
156
|
+
platform: 'browser',
|
|
157
|
+
minify: true,
|
|
158
|
+
outdir: '.',
|
|
159
|
+
write: false,
|
|
160
|
+
}),
|
|
161
|
+
sourcemap: 'inline',
|
|
162
|
+
splitting: false,
|
|
163
|
+
treeShaking: true,
|
|
164
|
+
plugins: [UseLiveTransform()],
|
|
165
|
+
metafile: true,
|
|
166
|
+
logLevel: 'silent',
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
this.#buildContexts[realm] = await EsBuild.context(bundlingConfig);
|
|
170
|
+
}
|
|
142
171
|
|
|
143
|
-
|
|
172
|
+
let buildResult;
|
|
173
|
+
try {
|
|
174
|
+
buildResult = await this.#buildContexts[realm].rebuild();
|
|
175
|
+
} catch (e) { continue; }
|
|
176
|
+
|
|
177
|
+
moduleGraph = { ...moduleGraph, ...buildResult.metafile.inputs };
|
|
178
|
+
this.#buildOutputs[realm] = Object.fromEntries(buildResult.outputFiles?.map((f) => {
|
|
179
|
+
return [Path.relative(process.cwd(), f.path), f.text];
|
|
180
|
+
}) ?? []);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return moduleGraph;
|
|
144
184
|
}
|
|
145
185
|
|
|
146
186
|
async enterDevMode() {
|
|
@@ -251,7 +291,7 @@ export class WebfloServer extends AppRuntime {
|
|
|
251
291
|
const secret = this.env('SESSION_KEY');
|
|
252
292
|
|
|
253
293
|
let tenantID = request.headers.get('Cookie', true).find((c) => c.name === '__sessid')?.value;
|
|
254
|
-
|
|
294
|
+
|
|
255
295
|
if (tenantID?.includes('.')) {
|
|
256
296
|
if (secret) {
|
|
257
297
|
const [rand, signature] = tenantID.split('.');
|
|
@@ -426,7 +466,7 @@ export class WebfloServer extends AppRuntime {
|
|
|
426
466
|
wss.handleUpgrade(nodeRequest, socket, head, (ws) => {
|
|
427
467
|
wss.emit('connection', ws, nodeRequest);
|
|
428
468
|
const wsw = new WebSocketPort(ws, { handshake: 1, postAwaitsOpen: true });
|
|
429
|
-
clientRequestPort.addPort(wsw);
|
|
469
|
+
clientRequestPort.addPort(wsw, { enableBubbling: true });
|
|
430
470
|
});
|
|
431
471
|
}
|
|
432
472
|
};
|
|
@@ -568,13 +608,13 @@ export class WebfloServer extends AppRuntime {
|
|
|
568
608
|
|
|
569
609
|
if (FLAGS['dev']) {
|
|
570
610
|
if (httpEvent.url.pathname === '/@hmr') {
|
|
611
|
+
// This is purely a route handler source request from HMR
|
|
571
612
|
const filename = httpEvent.url.searchParams.get('src')?.split('?')[0] || '';
|
|
613
|
+
scopeObj.filename = Path.join(LAYOUT.PUBLIC_DIR, filename);
|
|
572
614
|
if (filename.endsWith('.js')) {
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// This is a static asset (HTML) request from HMR
|
|
577
|
-
scopeObj.filename = Path.join(LAYOUT.PUBLIC_DIR, filename);
|
|
615
|
+
// Attempt reading from build output
|
|
616
|
+
const buildOutputName = filename.replace('../', '');
|
|
617
|
+
scopeObj.fileContents = this.#hmr.buildOutput[buildOutputName];
|
|
578
618
|
}
|
|
579
619
|
} else {
|
|
580
620
|
if (this.#hmr.options.buildSensitivity === 1) {
|
|
@@ -654,7 +694,10 @@ export class WebfloServer extends AppRuntime {
|
|
|
654
694
|
scopeObj.stats.mime = scopeObj.ext && (Mime.lookup(scopeObj.ext) || null)?.replace('application/javascript', 'text/javascript') || 'application/octet-stream';
|
|
655
695
|
|
|
656
696
|
// Range support
|
|
657
|
-
const readStream = (params = {}) =>
|
|
697
|
+
const readStream = (params = {}) => {
|
|
698
|
+
if (scopeObj.fileContents) return Readable.from(scopeObj.fileContents);
|
|
699
|
+
return Fs.createReadStream(scopeObj.filename, { ...params });
|
|
700
|
+
};
|
|
658
701
|
scopeObj.response = this.createStreamingResponse(httpEvent, readStream, scopeObj.stats);
|
|
659
702
|
const statusCode = scopeObj.response.status;
|
|
660
703
|
if (statusCode === 416) return finalizeResponse(scopeObj.response);
|
|
@@ -717,7 +760,7 @@ export class WebfloServer extends AppRuntime {
|
|
|
717
760
|
// Thread
|
|
718
761
|
scopeObj.thread = HttpThread111.create({
|
|
719
762
|
context: {},
|
|
720
|
-
store: this.#keyvals.create({ path: ['thread', scopeObj.tenantID], origins, ttl: 60*60*24*30/* 30 days */ }),
|
|
763
|
+
store: this.#keyvals.create({ path: ['thread', scopeObj.tenantID], origins, ttl: 60 * 60 * 24 * 30/* 30 days */ }),
|
|
721
764
|
threadID: scopeObj.url.searchParams.get('_thread'),
|
|
722
765
|
realm: 3
|
|
723
766
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Path from 'path';
|
|
2
2
|
import chokidar from 'chokidar';
|
|
3
|
-
import { exec
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import spawn from 'cross-spawn';
|
|
4
5
|
import { platform } from 'os';
|
|
5
6
|
|
|
6
7
|
export class WebfloHMR {
|
|
@@ -38,6 +39,9 @@ export class WebfloHMR {
|
|
|
38
39
|
};
|
|
39
40
|
get dirtiness() { return this.#dirtiness; }
|
|
40
41
|
|
|
42
|
+
#buildOutput;
|
|
43
|
+
get buildOutput() { return this.#buildOutput; }
|
|
44
|
+
|
|
41
45
|
#ignoreList = new Set;
|
|
42
46
|
get ignoreList() { return this.#ignoreList; }
|
|
43
47
|
|
|
@@ -109,7 +113,7 @@ export class WebfloHMR {
|
|
|
109
113
|
await this.buildRoutes(this.#jsMeta.mustRevalidate/*fullBuild*/);
|
|
110
114
|
hasJustBeenRebuilt = true;
|
|
111
115
|
}
|
|
112
|
-
const affectedHandlers = this.#jsMeta.dependencyMap[target] || [];
|
|
116
|
+
const affectedHandlers = this.#jsMeta.dependencyMap?.[target] || [];
|
|
113
117
|
for (const affectedHandler of affectedHandlers) {
|
|
114
118
|
const [, dir, affectedRoute = '/', realm] = this.#handlerMatch.exec(affectedHandler) || [];
|
|
115
119
|
for (const r of ['client', 'worker', 'server']) {
|
|
@@ -142,8 +146,9 @@ export class WebfloHMR {
|
|
|
142
146
|
} else if (event.realm === 'server') {
|
|
143
147
|
if (/^unlink/.test(event.actionableEffect)) {
|
|
144
148
|
delete this.#app.routes[event.affectedRoute];
|
|
145
|
-
} else
|
|
149
|
+
} else {
|
|
146
150
|
this.#app.routes[event.affectedRoute] = `${Path.join(this.#app.config.RUNTIME_DIR, event.affectedHandler)}?_webflohmrhash=${Date.now()}`;
|
|
151
|
+
//this.#app.routes[event.affectedRoute] = `data:text/javascript;base64,${Buffer.from(this.#app.buildOutputs.server[event.affectedHandler]).toString('base64')}`;
|
|
147
152
|
}
|
|
148
153
|
} else if (event.fileType === 'css') {
|
|
149
154
|
this.#dirtiness.CSSAffected = true;
|
|
@@ -172,31 +177,14 @@ export class WebfloHMR {
|
|
|
172
177
|
|
|
173
178
|
async buildRoutes(fullBuild = false) {
|
|
174
179
|
// 0. Generate graph
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
else buildResult = await buildResult.rebuild();
|
|
180
|
-
}``
|
|
181
|
-
if (!buildResult) {
|
|
182
|
-
const bundlingConfig = {
|
|
183
|
-
client: true,
|
|
184
|
-
worker: true,
|
|
185
|
-
server: true,
|
|
186
|
-
metafile: true, // This is key
|
|
187
|
-
logLevel: 'silent', // Suppress output
|
|
188
|
-
incremental: true,
|
|
189
|
-
};
|
|
190
|
-
buildResult = await this.#app.buildRoutes(bundlingConfig);
|
|
191
|
-
}
|
|
192
|
-
} catch (e) {
|
|
193
|
-
//console.error(e);
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
180
|
+
const moduleGraph = await this.#app.buildRoutes(
|
|
181
|
+
['server', 'client', 'worker'],
|
|
182
|
+
{ revalidate: fullBuild },
|
|
183
|
+
);
|
|
196
184
|
|
|
197
185
|
// 1. Forward dependency graph (file -> [imported files])
|
|
198
186
|
const forward = {};
|
|
199
|
-
for (const [file, data] of Object.entries(
|
|
187
|
+
for (const [file, data] of Object.entries(moduleGraph)) {
|
|
200
188
|
forward[file] = data.imports?.map((imp) => Path.normalize(imp.path)) || [];
|
|
201
189
|
}
|
|
202
190
|
|
|
@@ -210,7 +198,7 @@ export class WebfloHMR {
|
|
|
210
198
|
}
|
|
211
199
|
|
|
212
200
|
// 3. Trace from leaf file to roots (handler files)
|
|
213
|
-
const handlers = Object.keys(
|
|
201
|
+
const handlers = Object.keys(moduleGraph).filter((f) => this.#handlerMatch.test(f));
|
|
214
202
|
const handlerDepsMap = {};
|
|
215
203
|
|
|
216
204
|
for (const handler of handlers) {
|
|
@@ -226,6 +214,7 @@ export class WebfloHMR {
|
|
|
226
214
|
stack.push(...deps);
|
|
227
215
|
}
|
|
228
216
|
}
|
|
217
|
+
|
|
229
218
|
this.#jsMeta.dependencyMap = handlerDepsMap;
|
|
230
219
|
this.#jsMeta.mustRevalidate = false;
|
|
231
220
|
|
|
@@ -277,7 +266,6 @@ export class WebfloHMR {
|
|
|
277
266
|
return await new Promise((resolve, reject) => {
|
|
278
267
|
const child = spawn(commandArr.shift(), commandArr, {
|
|
279
268
|
stdio: ['pipe', 'pipe', 'inherit', 'ipc'],
|
|
280
|
-
shell: true, // for Windows compatibility
|
|
281
269
|
});
|
|
282
270
|
child.on('message', (msg) => {
|
|
283
271
|
for (const file of msg?.outfiles || []) {
|