@uniflowed/vite 0.0.0-alpha.13 → 0.0.0-alpha.15
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/driver.js +436 -149
- package/index.js +217 -93
- package/internal/assets.js +266 -18
- package/internal/devtools.js +117 -0
- package/internal/diagnostics.js +369 -0
- package/internal/events.js +5 -5
- package/internal/routes.js +62 -8
- package/internal/serve.js +39 -15
- package/package.json +11 -3
package/index.js
CHANGED
|
@@ -14,8 +14,12 @@
|
|
|
14
14
|
// development and the virtual modules that make a directory
|
|
15
15
|
// of pages an application: the route table, the client entry
|
|
16
16
|
// that hydrates it, and the server entry that renders it. In
|
|
17
|
-
// development it also
|
|
18
|
-
//
|
|
17
|
+
// development it also answers every request the way a
|
|
18
|
+
// deployment does — the guard, a server action, a route
|
|
19
|
+
// handler, then the renderer — so `uf dev` serves what
|
|
20
|
+
// `uf start` serves rather than an approximation of it, and it
|
|
21
|
+
// serves the two `/__uf/` paths a browser reports to (see
|
|
22
|
+
// `internal/diagnostics.js`).
|
|
19
23
|
// The client's copy of the route table is not the server's:
|
|
20
24
|
// `internal/rsc.js` reads the RSC analysis and leaves out the
|
|
21
25
|
// page of every route no client boundary reaches, so that
|
|
@@ -56,7 +60,6 @@ import {
|
|
|
56
60
|
refreshRuntimeSource,
|
|
57
61
|
} from "./internal/refresh.js";
|
|
58
62
|
import {
|
|
59
|
-
ACTION_HEADER,
|
|
60
63
|
RSC_MANIFEST_ENV,
|
|
61
64
|
actionReferenceSource,
|
|
62
65
|
actionsModuleSource,
|
|
@@ -75,8 +78,10 @@ import {
|
|
|
75
78
|
serverModuleSource,
|
|
76
79
|
} from "./internal/routes.js";
|
|
77
80
|
import { TransformService, isFlowModule } from "@uniflowed/host/transform";
|
|
81
|
+
import { createChannelMiddleware } from "./internal/diagnostics.js";
|
|
82
|
+
import { devtoolsPreamble } from "./internal/devtools.js";
|
|
78
83
|
import { send, toRequest } from "./internal/http.js";
|
|
79
|
-
import {
|
|
84
|
+
import { beginRequest } from "./internal/serve.js";
|
|
80
85
|
|
|
81
86
|
/** A resolved virtual id: Vite's convention is a leading NUL byte. */
|
|
82
87
|
const resolved = (id) => `\0${id}`;
|
|
@@ -118,19 +123,26 @@ export default function uniflowed(options = {}) {
|
|
|
118
123
|
const appEntry = app.router?.entry ?? ufConfig.build?.entries?.[0] ?? "app.js";
|
|
119
124
|
const markdown = app.builtins?.markdown ?? {};
|
|
120
125
|
const builtins = app.builtins ?? {};
|
|
126
|
+
// On unless the project says otherwise, and read as `!== false` rather than
|
|
127
|
+
// `=== true` because that is what "on by default" means for a field almost
|
|
128
|
+
// no `uf.config.js` will mention. It only ever reaches the *development*
|
|
129
|
+
// client entry; see `flowPlugin`'s `load`. ubugeeei-prod/uf#516.
|
|
130
|
+
const strictMode = app.react?.strictMode !== false;
|
|
121
131
|
|
|
122
132
|
return [
|
|
123
|
-
flowPlugin({ routerRoot, appEntry, command: options.command }),
|
|
133
|
+
flowPlugin({ routerRoot, appEntry, strictMode, command: options.command }),
|
|
124
134
|
mdxPlugin(markdown),
|
|
125
135
|
assetPlugin({
|
|
126
136
|
images: builtins.images ?? {},
|
|
127
137
|
fonts: builtins.fonts ?? {},
|
|
138
|
+
icons: builtins.icons ?? {},
|
|
139
|
+
og: builtins.og ?? {},
|
|
128
140
|
command: options.command,
|
|
129
141
|
}),
|
|
130
142
|
];
|
|
131
143
|
}
|
|
132
144
|
|
|
133
|
-
function flowPlugin({ routerRoot, appEntry, command }) {
|
|
145
|
+
function flowPlugin({ routerRoot, appEntry, strictMode, command }) {
|
|
134
146
|
let root = process.cwd();
|
|
135
147
|
let isProduction = false;
|
|
136
148
|
let base = "/";
|
|
@@ -195,7 +207,15 @@ function flowPlugin({ routerRoot, appEntry, command }) {
|
|
|
195
207
|
if (server == null) {
|
|
196
208
|
emit("rsc-split", { pages: kept.size, routes: table.routes.length });
|
|
197
209
|
}
|
|
198
|
-
|
|
210
|
+
// `relativeTo` only here, and never for the server's copy below. Every
|
|
211
|
+
// `import()` in this table becomes a chunk URL, but `file` is a string and
|
|
212
|
+
// survives the build — so the browser's table was shipping the absolute
|
|
213
|
+
// path of every page on the machine that built the site, to every visitor.
|
|
214
|
+
// The server's table is read where those files are and keeps them.
|
|
215
|
+
return routesModuleSource(table, {
|
|
216
|
+
shipsPage: (route) => kept.has(route),
|
|
217
|
+
relativeTo: root,
|
|
218
|
+
});
|
|
199
219
|
};
|
|
200
220
|
|
|
201
221
|
/**
|
|
@@ -293,7 +313,12 @@ function flowPlugin({ routerRoot, appEntry, command }) {
|
|
|
293
313
|
if (isSsr(this, loadOptions)) return routesModuleSource(table);
|
|
294
314
|
return clientRoutesModule(table);
|
|
295
315
|
}
|
|
296
|
-
|
|
316
|
+
// Strict Mode belongs to the client entry and to development only: a
|
|
317
|
+
// build passes `false`, so the generated module is the one that existed
|
|
318
|
+
// before #516 and a visitor's browser renders once.
|
|
319
|
+
if (id === resolved(VIRTUAL.client)) {
|
|
320
|
+
return clientModuleSource(entryPath, { strictMode: strictMode && !isProduction });
|
|
321
|
+
}
|
|
297
322
|
if (id === resolved(VIRTUAL.server)) return serverModuleSource(entryPath);
|
|
298
323
|
// Only `virtual:uf/server` imports this, so it is only ever asked for in
|
|
299
324
|
// the server environment — but the table it carries is every callable
|
|
@@ -392,9 +417,26 @@ function flowPlugin({ routerRoot, appEntry, command }) {
|
|
|
392
417
|
}
|
|
393
418
|
},
|
|
394
419
|
|
|
420
|
+
// The two scripts a development document loads before its own, and
|
|
421
|
+
// nothing at all in a build — which is the whole of "the hook is out of a
|
|
422
|
+
// production build" (ubugeeei-prod/uf#503) and of "production does not run
|
|
423
|
+
// under Strict Mode" (#516, whose flag is generated into
|
|
424
|
+
// `virtual:uf/client` rather than injected here).
|
|
425
|
+
//
|
|
426
|
+
// DevTools first, and as a *classic* script rather than a module: React
|
|
427
|
+
// registers itself with `__REACT_DEVTOOLS_GLOBAL_HOOK__` while `react-dom`
|
|
428
|
+
// is evaluated and never again, so the hook has to exist before any module
|
|
429
|
+
// runs. A classic inline script runs while the parser is on it; a module
|
|
430
|
+
// waits for the document. `internal/devtools.js` has the rest of the
|
|
431
|
+
// argument, and the three conditions DevTools needs.
|
|
395
432
|
transformIndexHtml() {
|
|
396
433
|
if (isProduction) return [];
|
|
397
434
|
return [
|
|
435
|
+
{
|
|
436
|
+
tag: "script",
|
|
437
|
+
children: devtoolsPreamble(),
|
|
438
|
+
injectTo: "head-prepend",
|
|
439
|
+
},
|
|
398
440
|
{
|
|
399
441
|
tag: "script",
|
|
400
442
|
attrs: { type: "module" },
|
|
@@ -462,107 +504,181 @@ function flowPlugin({ routerRoot, appEntry, command }) {
|
|
|
462
504
|
}
|
|
463
505
|
|
|
464
506
|
// After Vite's own middlewares, so `/@vite/client`, `/@id/...` and
|
|
465
|
-
// static files are served first and only
|
|
466
|
-
//
|
|
507
|
+
// static files are served first and only what Vite declined reaches uf.
|
|
508
|
+
//
|
|
509
|
+
// # One renderer
|
|
510
|
+
//
|
|
511
|
+
// This is the only middleware that renders a document under `uf dev`,
|
|
512
|
+
// and that is worth stating because there were two. `driver.js` added a
|
|
513
|
+
// second one after `createServer` had returned, which put it *later* in
|
|
514
|
+
// the connect stack than this one — so for every request this one
|
|
515
|
+
// claimed, this one decided, and the other was reached only for what
|
|
516
|
+
// this one declined. Route-handler dispatch was in the other. A
|
|
517
|
+
// `GET /feed` from a browser is `Accept: text/html` with no extension,
|
|
518
|
+
// so it looked like a document, so `app/feed/_uf.route.js` was never
|
|
519
|
+
// asked and the reader got the route table's page — or the not-found
|
|
520
|
+
// page — for a path that had a handler. See ubugeeei-prod/uf#349.
|
|
521
|
+
//
|
|
522
|
+
// Two renderers is also how the two came to disagree about the render
|
|
523
|
+
// result's `headers`: this one wrote them and the driver's did not, so a
|
|
524
|
+
// loader calling `redirect()` answered a browser with a `307` carrying
|
|
525
|
+
// no `Location` and a meta-refresh body — a redirect that works when you
|
|
526
|
+
// deploy it and not while you are writing it. See
|
|
527
|
+
// ubugeeei-prod/uf#338.
|
|
528
|
+
//
|
|
529
|
+
// So the driver's middleware is gone and everything it did is here: it
|
|
530
|
+
// claims every request, runs the guard, the action endpoint and the
|
|
531
|
+
// dispatcher for every method, and hands back to Vite's chain what none
|
|
532
|
+
// of them answered.
|
|
533
|
+
//
|
|
534
|
+
// # What is still not `createFetchHandler`
|
|
535
|
+
//
|
|
536
|
+
// One middleware rather than two, and still not the function every
|
|
537
|
+
// deployment runs. It cannot be: `transformIndexHtml` takes a whole
|
|
538
|
+
// document, so the render has to be collected here rather than streamed
|
|
539
|
+
// (ubugeeei-prod/uf#374), and a request nothing claimed has to go back
|
|
540
|
+
// to Vite's chain rather than become a 404 — neither of which a handler
|
|
541
|
+
// that always answers with a `Response` can do.
|
|
542
|
+
//
|
|
543
|
+
// What that still costs, written down so the next reader does not have
|
|
544
|
+
// to find it: `createFetchHandler` renders inside a cache scope even
|
|
545
|
+
// with no cache configured, so a component calling `cacheLife` states a
|
|
546
|
+
// lifetime nobody honours; here there is no scope, so the same component
|
|
547
|
+
// throws under `uf dev` and renders under `uf start`. Closing that means
|
|
548
|
+
// the generated server entry handing the host a scope the way it already
|
|
549
|
+
// hands it `beginRequest` — see `serverModuleSource` in
|
|
550
|
+
// `./internal/routes.js` — and it is the next thing to remove from this
|
|
551
|
+
// list rather than something this middleware can decide on its own.
|
|
467
552
|
return () => {
|
|
553
|
+
// The browser's own reporting channel, mounted above the application
|
|
554
|
+
// so that a report never reaches a project's `_uf.middleware.js` or
|
|
555
|
+
// its route table. See `internal/diagnostics.js`.
|
|
556
|
+
devServer.middlewares.use(
|
|
557
|
+
createChannelMiddleware((diagnostic) => emit("diagnostic", diagnostic)),
|
|
558
|
+
);
|
|
559
|
+
|
|
468
560
|
devServer.middlewares.use(async (request, response, next) => {
|
|
469
|
-
//
|
|
470
|
-
//
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
//
|
|
474
|
-
//
|
|
475
|
-
|
|
476
|
-
|
|
561
|
+
// `request.url` and not `originalUrl`, which is the URL Vite's base
|
|
562
|
+
// middleware has already stripped the base from — and the route
|
|
563
|
+
// table's paths have no base in them either.
|
|
564
|
+
const url = request.url ?? "/";
|
|
565
|
+
// Declared out here so the catch below can still settle: a request
|
|
566
|
+
// that failed is a request that happened, and a middleware that
|
|
567
|
+
// logged its arrival is owed its callback either way.
|
|
568
|
+
let lifecycle = null;
|
|
477
569
|
try {
|
|
478
|
-
const url = request.url ?? "/";
|
|
479
570
|
const entry = await importServerEntry(devServer);
|
|
480
571
|
const asRequest = await toRequest(request, devServer.config);
|
|
481
572
|
|
|
482
|
-
//
|
|
483
|
-
// written
|
|
484
|
-
// `
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
//
|
|
494
|
-
//
|
|
495
|
-
// ubugeeei-prod/uf#260.
|
|
496
|
-
// every method.
|
|
573
|
+
// The request begins here and ends when the response has been
|
|
574
|
+
// written, which is what `after()` promises and what `uf preview`,
|
|
575
|
+
// `uf start` and a compiled binary all do too — a middleware that
|
|
576
|
+
// logs a response's status has to mean the same thing in
|
|
577
|
+
// development as in production. See `internal/serve.js` and
|
|
578
|
+
// ubugeeei-prod/uf#389.
|
|
579
|
+
lifecycle = await beginRequest(entry, asRequest);
|
|
580
|
+
const answered = await lifecycle.run(async () => {
|
|
581
|
+
// Before anything answers: a middleware guards a subtree, so it
|
|
582
|
+
// has to run for a page, for a route handler, and for a path
|
|
583
|
+
// under it that matches neither. Running it inside the
|
|
584
|
+
// dispatcher and again inside the renderer would have left
|
|
585
|
+
// `/dashboard/typo` unguarded and run it twice for a path that
|
|
586
|
+
// is both. See ubugeeei-prod/uf#260.
|
|
497
587
|
const guarded = await entry.runMiddleware(asRequest);
|
|
498
588
|
if (guarded != null) {
|
|
499
589
|
await send(response, guarded);
|
|
500
|
-
return;
|
|
590
|
+
return true;
|
|
501
591
|
}
|
|
502
592
|
|
|
503
593
|
// Then a server action, below the guard and above the handlers.
|
|
504
|
-
// It declines
|
|
505
|
-
//
|
|
506
|
-
//
|
|
507
|
-
//
|
|
508
|
-
//
|
|
509
|
-
//
|
|
594
|
+
// It declines every request that carries no action id, so this
|
|
595
|
+
// costs an ordinary request one `headers.get`; and it answers
|
|
596
|
+
// every request that carries one, refusals included, so an
|
|
597
|
+
// action can never fall through to a route handler that happens
|
|
598
|
+
// to sit at the URL it was posted to. The same two lines are in
|
|
599
|
+
// `@uniflowed/server`'s `fetch.js`, which is what every
|
|
600
|
+
// deployment runs.
|
|
510
601
|
const acted = await entry.callAction(asRequest);
|
|
511
602
|
if (acted != null) {
|
|
512
603
|
await send(response, acted);
|
|
513
|
-
return;
|
|
604
|
+
return true;
|
|
514
605
|
}
|
|
515
606
|
|
|
516
|
-
// Then the route handlers, above the renderer and for
|
|
517
|
-
//
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
// This step is not a duplicate of the dispatcher in `driver.js`,
|
|
523
|
-
// it is the only one that can run: this middleware is mounted by
|
|
524
|
-
// `configureServer`, which Vite calls while it is building the
|
|
525
|
-
// server, and `uf dev` adds its own after `createServer` has
|
|
526
|
-
// returned — so for every request this one claims, it is the one
|
|
527
|
-
// that decides. Without it a route handler under `uf dev` was
|
|
528
|
-
// reachable only by a client that asked for something other than
|
|
529
|
-
// HTML, and answered the 404 page to everyone else.
|
|
607
|
+
// Then the route handlers, above the renderer and for every
|
|
608
|
+
// method: a path that answers a request is not a document,
|
|
609
|
+
// whatever the client said it would accept. `curl /api/thing`
|
|
610
|
+
// and a `<form action>` navigation both send `Accept:
|
|
611
|
+
// text/html`, and both want the handler's answer — which is the
|
|
612
|
+
// whole of ubugeeei-prod/uf#349.
|
|
530
613
|
const handled = await entry.dispatch(asRequest);
|
|
531
614
|
if (handled != null) {
|
|
532
615
|
await send(response, handled);
|
|
533
|
-
return;
|
|
616
|
+
return true;
|
|
534
617
|
}
|
|
535
618
|
|
|
536
|
-
//
|
|
537
|
-
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
// `
|
|
541
|
-
//
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
619
|
+
// Only a navigation reaches the renderer. A page cannot answer a
|
|
620
|
+
// `POST`, and letting one try would turn a missing handler into
|
|
621
|
+
// a rendered page with a 200 rather than a 404.
|
|
622
|
+
//
|
|
623
|
+
// `wantsDocument` is stricter than the production handler, which
|
|
624
|
+
// renders anything a static file did not answer, and the
|
|
625
|
+
// difference is Vite's chain: `/@id/…`, `/node_modules/…` and
|
|
626
|
+
// any path with an extension belong to the module server, and a
|
|
627
|
+
// request one of those declined has to go back to it rather than
|
|
628
|
+
// become a rendered 404 page. What it costs is that
|
|
629
|
+
// `/favicon.svg` on a project that has none is a bare 404 here
|
|
630
|
+
// and the project's own not-found *page* under `uf preview` and
|
|
631
|
+
// `uf start` — a difference in the body of a 404 for a path that
|
|
632
|
+
// is an asset request in the first place.
|
|
633
|
+
if (!wantsDocument(request)) return false;
|
|
547
634
|
|
|
548
635
|
const result = await entry.render(
|
|
549
636
|
url,
|
|
550
637
|
{ scripts: [devUrlFor(VIRTUAL.client)], styles: [], preloads: [] },
|
|
551
|
-
{
|
|
638
|
+
{
|
|
639
|
+
// A boundary that threw after the shell went out.
|
|
640
|
+
// `result.error` cannot carry it — the caller already has the
|
|
641
|
+
// result by then — so the terminal hears about it here or not
|
|
642
|
+
// at all.
|
|
643
|
+
onError: (error) => reportRenderError(devServer, url, error),
|
|
644
|
+
},
|
|
552
645
|
);
|
|
553
646
|
if (result.error != null) reportRenderError(devServer, url, result.error);
|
|
554
|
-
// Collected rather than piped
|
|
555
|
-
//
|
|
647
|
+
// Collected rather than piped: `transformIndexHtml` is a
|
|
648
|
+
// whole-document hook, so there is no first byte to send until it
|
|
649
|
+
// has run. `uf start` and `uf preview` stream — see
|
|
650
|
+
// `internal/serve.js` — and that is a property of the development
|
|
651
|
+
// server rather than of the renderer. ubugeeei-prod/uf#374.
|
|
556
652
|
const html = await devServer.transformIndexHtml(url, await result.text());
|
|
557
|
-
response.statusCode = result.status;
|
|
558
|
-
|
|
653
|
+
response.statusCode = result.status ?? 200;
|
|
654
|
+
// The render's own headers, then the content type over the top:
|
|
655
|
+
// exactly the order `@uniflowed/server`'s `fetch.js` writes them
|
|
656
|
+
// in, so a `Location` from `redirect()` survives here and a
|
|
657
|
+
// render cannot claim to be something other than a document.
|
|
559
658
|
for (const [name, value] of Object.entries(result.headers ?? {})) {
|
|
560
659
|
response.setHeader(name, value);
|
|
561
660
|
}
|
|
661
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
562
662
|
response.end(html);
|
|
663
|
+
return true;
|
|
563
664
|
});
|
|
665
|
+
|
|
666
|
+
if (!answered) {
|
|
667
|
+
// The one path where uf is not the one writing the response: a
|
|
668
|
+
// request nothing claimed goes back to Vite's chain. The guard
|
|
669
|
+
// has still run and may have deferred work, so `close` — the
|
|
670
|
+
// socket saying the response is over, however it ended — is the
|
|
671
|
+
// only honest signal left that the bytes are out.
|
|
672
|
+
response.once("close", lifecycle.settle);
|
|
673
|
+
next();
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
await lifecycle.settle();
|
|
564
677
|
} catch (error) {
|
|
565
|
-
|
|
678
|
+
if (lifecycle != null) await lifecycle.settle();
|
|
679
|
+
// Map the stack back onto the Flow source before it reaches the
|
|
680
|
+
// overlay.
|
|
681
|
+
if (error instanceof Error) devServer.ssrFixStacktrace(error);
|
|
566
682
|
next(error);
|
|
567
683
|
}
|
|
568
684
|
});
|
|
@@ -614,20 +730,6 @@ async function importServerEntry(devServer) {
|
|
|
614
730
|
return devServer.ssrLoadModule(VIRTUAL.server);
|
|
615
731
|
}
|
|
616
732
|
|
|
617
|
-
/**
|
|
618
|
-
* Whether this request is a server action call.
|
|
619
|
-
*
|
|
620
|
-
* The header alone, and never the path: an action is posted to the page's own
|
|
621
|
-
* URL, so there is nothing about the URL to recognise. Deliberately *not* the
|
|
622
|
-
* whole set of checks the endpoint makes — the origin, the content type, the
|
|
623
|
-
* body — because those decide whether the call is *allowed*, and a call that
|
|
624
|
-
* is not allowed must be refused by the endpoint rather than handed on to
|
|
625
|
-
* Vite's chain as though nobody had claimed it.
|
|
626
|
-
*/
|
|
627
|
-
function isActionCall(request) {
|
|
628
|
-
return request.method === "POST" && request.headers[ACTION_HEADER] != null;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
733
|
function wantsDocument(request) {
|
|
632
734
|
if (request.method !== "GET" && request.method !== "HEAD") return false;
|
|
633
735
|
const url = request.url ?? "/";
|
|
@@ -647,14 +749,25 @@ const ALL_DIAGNOSTICS = "UF_REACT_COMPILER_DIAGNOSTICS";
|
|
|
647
749
|
* Report what the React Compiler said about one module.
|
|
648
750
|
*
|
|
649
751
|
* Every finding used to be printed as `a function: <message>` — no file, no
|
|
650
|
-
* line, no column, and the fallback string doing all the work because
|
|
651
|
-
*
|
|
752
|
+
* line, no column, and the fallback string doing all the work, because
|
|
753
|
+
* `diagnostic.function` was read out of a field only a *success* event carries
|
|
754
|
+
* and so was null for every finding there has ever been (#371), not because
|
|
755
|
+
* the compiler was reporting on anonymous inner functions. The transform hook
|
|
652
756
|
* knows the module and the compiler gives a position for most findings, so
|
|
653
757
|
* both go into the message: Vite prints a plugin warning's `message` and
|
|
654
758
|
* nothing else, so a location that is not in the string is a location the
|
|
655
759
|
* reader never sees. `id` and `loc` go along for anything reading the log
|
|
656
760
|
* object rather than the line. See ubugeeei-prod/uf#307.
|
|
657
761
|
*
|
|
762
|
+
* `(in Form)` is real now: `uf transform` recovers the name from the tree it
|
|
763
|
+
* compiled, and leaves it off the diagnostic when the function genuinely has
|
|
764
|
+
* no name to give.
|
|
765
|
+
*
|
|
766
|
+
* The position is real too, and is the expression the compiler objected to
|
|
767
|
+
* rather than the function containing it, so the two halves of the line say
|
|
768
|
+
* different things — `packages/hooks/dom.js:195:7: This value cannot be
|
|
769
|
+
* modified (in useLongPress)` locates the write and names the hook to look in.
|
|
770
|
+
*
|
|
658
771
|
* A dependency's findings are held back. A React Compiler bailout inside
|
|
659
772
|
* `@uniflowed/form` is not something the person running the build can fix, and
|
|
660
773
|
* a channel carrying forty of them on every build is a channel people stop
|
|
@@ -677,9 +790,20 @@ function reportDiagnostics(context, { id, root, diagnostics, environment, report
|
|
|
677
790
|
const mine = isProjectModule(root, id) || process.env[ALL_DIAGNOSTICS] === "all";
|
|
678
791
|
for (const diagnostic of diagnostics) {
|
|
679
792
|
// Everything a reader would be shown, so two findings that would print as
|
|
680
|
-
// the same line collapse into one.
|
|
681
|
-
//
|
|
682
|
-
//
|
|
793
|
+
// the same line collapse into one.
|
|
794
|
+
//
|
|
795
|
+
// This used to collapse far more than that, and the note here used to say
|
|
796
|
+
// the compiler reports "Cannot access refs during render" once per pass
|
|
797
|
+
// that noticed it — three times for one `ref`. That was the wrong reading
|
|
798
|
+
// of the evidence. The position in a finding was the position of the
|
|
799
|
+
// *function* it was found in, so every finding in one function shared a
|
|
800
|
+
// line and a column and any two with the same message were, to this
|
|
801
|
+
// signature, the same finding. They were not: over this repository's own
|
|
802
|
+
// packages it collapsed 173 findings into 119 lines, and the five that
|
|
803
|
+
// became one line in `DatePickerInput` are five different reads of a ref
|
|
804
|
+
// on five different lines. `uf transform` now reports where the compiler
|
|
805
|
+
// actually objected, so the signature separates them, and what it still
|
|
806
|
+
// collapses is a genuine repeat of one site.
|
|
683
807
|
const signature = `${diagnostic.kind}\0${diagnostic.line}\0${diagnostic.column}\0${diagnostic.message}`;
|
|
684
808
|
if (ledger.signatures.has(signature)) continue;
|
|
685
809
|
ledger.signatures.add(signature);
|