@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.
@@ -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 { _meta } from '../../util.js';
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 Event(lastNavigationEvent);
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 = _meta(this).get('parentNode');
43
- const clients = webfloTenant && _meta(webfloTenant).get('parentNode');
44
+ const webfloTenant = _portPlusMeta(this).get('parentPort');
45
+ const webfloTenancy = webfloTenant && _portPlusMeta(webfloTenant).get('parentPort');
44
46
 
45
- if (!clients) {
46
- throw new Error('Instance seem not connected to the messaging system.');
47
+ if (!webfloTenancy) {
48
+ throw new Error('Instance is not connected to the multi-tenant system.');
47
49
  }
48
50
 
49
- const channel = clients.getChannel(channelID, true);
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 { RelayPort, StarPort } from '@webqit/port-plus';
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
- if (autoCreate && !this.findPort((tenant) => tenant.tenantID === tenantID)) {
10
- const tenant = new WebfloTenant001(tenantID, { handshake: 1, postAwaitsOpen: true, autoClose: false });
11
- const cleanup = this.addPort(tenant);
12
- tenant.readyStateChange('close').then(cleanup);
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
- return this.findPort((tenant) => tenant.tenantID === tenantID);
19
+
20
+ return tenant;
15
21
  }
16
22
 
17
23
  getChannel(channelName, autoCreate = false) {
18
- if (!this.#channels.has(channelName) && autoCreate) {
19
- const channel = new RelayPort(channelName, { handshake: 1, postAwaitsOpen: true, autoClose: true });
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 this.#channels.get(channelName);
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.close(true);
24
- }, 15000/*15sec*/);
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
- return await this.#extendLifecycle(promise);
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
- /* DO NOT AWAIT */this.waitUntil(new Promise(() => { }));
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.once
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
- thisTick.event.internalLiveResponse.addEventListener('replace', () => {
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 { spawn } from 'child_process';
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({ server: true });
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({ client = false, worker = false, server = false, ...options } = {}) {
123
- const routeDirs = [...new Set([this.config.LAYOUT.CLIENT_DIR, this.config.LAYOUT.WORKER_DIR, this.config.LAYOUT.SERVER_DIR])];
124
- const entryPoints = await $glob(routeDirs.map((d) => `${d}/**/handler{${client ? ',.client' : ''}${worker ? ',.worker' : ''}${server ? ',.server' : ''}}.js`), { absolute: true })
125
- .then((files) => files.map((file) => file.replace(/\\/g, '/')));
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
- return await EsBuild.build(bundlingConfig);
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
- // This is purely a route handler source request from HMR
574
- scopeObj.filename = Path.join(RUNTIME_LAYOUT.PUBLIC_DIR, filename);
575
- } else {
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 = {}) => Fs.createReadStream(scopeObj.filename, { ...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, spawn } from 'child_process';
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 if (event.realm === 'server') {
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
- let buildResult;
176
- try {
177
- if (this.#jsMeta.prevBuildResult) {
178
- if (fullBuild) await this.#jsMeta.prevBuildResult.rebuild.dispose();
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(buildResult.metafile.inputs)) {
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(buildResult.metafile.inputs).filter((f) => this.#handlerMatch.test(f));
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 || []) {