@uniflowed/vite 0.0.0-alpha.41 → 0.0.0-alpha.43

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 CHANGED
@@ -42,6 +42,7 @@
42
42
  import { randomUUID } from "node:crypto";
43
43
  import { createServer as createHttpServer } from "node:http";
44
44
  import { builtinModules, register } from "node:module";
45
+ import { esmExternalRequirePlugin } from "rolldown/plugins";
45
46
  import { installFlowHooks } from "@uniflowed/host/internal/sync-hooks.js";
46
47
  import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
47
48
  import path from "node:path";
@@ -1579,7 +1580,12 @@ async function deploy() {
1579
1580
  plugins: [
1580
1581
  ...inline.plugins,
1581
1582
  nativeAddonGuard(),
1582
- ...(shape.workerBuiltins === true ? [workerBuiltinGuard()] : []),
1583
+ ...(shape.workerBuiltins === true
1584
+ ? [
1585
+ workerBuiltinGuard(),
1586
+ esmExternalRequirePlugin({ external: [...builtinModules, /^node:/] }),
1587
+ ]
1588
+ : []),
1583
1589
  ],
1584
1590
  // Fixed, because this bundle inlines every dependency and so both of each
1585
1591
  // React package's builds, and a runtime lookup of `NODE_ENV` in a worker
@@ -1610,6 +1616,10 @@ async function deploy() {
1610
1616
  emptyOutDir: false,
1611
1617
  rollupOptions: {
1612
1618
  input,
1619
+ // Workers provide the selected built-ins, but cannot use Node's
1620
+ // createRequire(import.meta.url) runtime helper. External requires
1621
+ // above become ESM imports; the remaining helpers are platform neutral.
1622
+ ...(shape.workerBuiltins === true ? { platform: "neutral" } : {}),
1613
1623
  output: {
1614
1624
  entryFileNames: "[name].js",
1615
1625
  // Route modules are lazy `import()`s, so the server bundle splits
package/index.js CHANGED
@@ -44,6 +44,8 @@
44
44
  // `uniflowed(options)` returns the array; a project that wants to add a plugin
45
45
  // declares it in `uf.config.js` and the driver appends it after these.
46
46
 
47
+ import { instrumentationFile } from "./internal/instrumentation.js";
48
+
47
49
  import path from "node:path";
48
50
  import { nativeWebPlugin } from "./internal/native-web.js";
49
51
 
@@ -579,6 +581,7 @@ function flowPlugin({
579
581
  // looking at. See `clientModuleSource`.
580
582
  if (id === resolved(VIRTUAL.client) && flightState != null) {
581
583
  return flightClientSource(entryPath, {
584
+ instrumentation: instrumentationFile(appRoot, true),
582
585
  strictMode: strictMode && !isProduction,
583
586
  navigation,
584
587
  staleTime,
@@ -587,6 +590,7 @@ function flowPlugin({
587
590
  }
588
591
  if (id === resolved(VIRTUAL.client)) {
589
592
  return clientModuleSource(entryPath, {
593
+ instrumentation: instrumentationFile(appRoot, true),
590
594
  strictMode: strictMode && !isProduction,
591
595
  navigation,
592
596
  staleTime,
@@ -596,8 +600,14 @@ function flowPlugin({
596
600
  }
597
601
  if (id === resolved(VIRTUAL.server)) {
598
602
  return flightState == null
599
- ? serverModuleSource(entryPath, routing)
600
- : flightServerSource(entryPath, VIRTUAL.routes, VIRTUAL.actions, routing);
603
+ ? serverModuleSource(entryPath, routing, instrumentationFile(appRoot))
604
+ : flightServerSource(
605
+ entryPath,
606
+ VIRTUAL.routes,
607
+ VIRTUAL.actions,
608
+ routing,
609
+ instrumentationFile(appRoot),
610
+ );
601
611
  }
602
612
  if (flightState != null) {
603
613
  if (id === resolved(FLIGHT_VIRTUAL.entry)) return rscEntrySource(VIRTUAL.routes, routing);
@@ -864,8 +874,10 @@ function flowPlugin({
864
874
  const reserved = new RegExp(`/(${stems})(\\.[a-z]+)?\\.(js|jsx|mdx)$`);
865
875
  const onRouteFile = (file) => {
866
876
  if (!reserved.test(file) || !file.startsWith(appRoot)) return;
867
- const routes = devServer.moduleGraph.getModuleById(resolved(VIRTUAL.routes));
868
- if (routes) devServer.moduleGraph.invalidateModule(routes);
877
+ for (const id of [VIRTUAL.routes, VIRTUAL.server, VIRTUAL.client]) {
878
+ const module = devServer.moduleGraph.getModuleById(resolved(id));
879
+ if (module) devServer.moduleGraph.invalidateModule(module);
880
+ }
869
881
  devServer.ws.send({ type: "full-reload", path: "*" });
870
882
  };
871
883
  devServer.watcher.on("add", onRouteFile);
@@ -49,6 +49,8 @@
49
49
  // In development the rsc environment's module graph is read instead, once the
50
50
  // route's modules have been imported.
51
51
 
52
+ import { clientInstrumentationSource } from "./instrumentation.js";
53
+
52
54
  import path from "node:path";
53
55
  import { relayDependencies } from "./relay.js";
54
56
 
@@ -666,7 +668,7 @@ export function flightClientSource(appEntry, options = {}) {
666
668
  options.staleTime > 0 ? `, staleTime: ${JSON.stringify(options.staleTime)}` : "";
667
669
  return `import { hydrateFlight } from "@uniflowed/router/rsc/client";
668
670
  import App from ${JSON.stringify(appEntry)};
669
- hydrateFlight({ App${strictMode}${navigation}${staleTime}${routing} });
671
+ ${clientInstrumentationSource(options.instrumentation)}hydrateFlight({ App${strictMode}${navigation}${staleTime}${routing} });
670
672
  `;
671
673
  }
672
674
 
@@ -687,9 +689,13 @@ export function flightServerSource(
687
689
  routesId,
688
690
  actionsId,
689
691
  routing = { redirects: [], rewrites: [], headers: [], basePath: "", trailingSlash: "ignore" },
692
+ instrumentation = null,
690
693
  ) {
691
694
  return `import {
692
695
  createActionDispatcher,
696
+ createInstrumentation,
697
+ instrumentRender,
698
+ traceRequestPhase,
693
699
  createDispatcher,
694
700
  createMiddlewareRunner,
695
701
  installRouting,
@@ -703,15 +709,22 @@ import App from ${JSON.stringify(appEntry)};
703
709
  export const routing = ${JSON.stringify(routing)};
704
710
  installRouting(routing);
705
711
  export { routes, handlers, middleware, notFound, errors };
706
- export { beginRequest } from "@uniflowed/router/server";
712
+ ${instrumentation == null ? "" : `import * as hooks from ${JSON.stringify(instrumentation)};`}
713
+ export const { beginRequest } = createInstrumentation(${instrumentation == null ? "" : "hooks"});
707
714
  const renderer = createDocumentRenderer({ App, renderFlight, loadClientModule });
708
- export const render = renderer.render;
715
+ export const render = (url, assets, options = {}) => instrumentRender(
716
+ (onError) => renderer.render(url, assets, { ...options, onError }), options.onError,
717
+ );
709
718
  export const prerender = renderer.prerender;
710
- export const flight = renderer.flight;
719
+ export const flight = (url, options = {}) => instrumentRender(
720
+ (onError) => renderer.flight(url, { ...options, onError }), options.onError,
721
+ );
711
722
  export { shellDocument } from "@uniflowed/router/server";
712
- export const dispatch = createDispatcher({ handlers });
723
+ const dispatchRoute = createDispatcher({ handlers });
724
+ export const dispatch = (request) => traceRequestPhase("route", () => dispatchRoute(request));
713
725
  export const callAction = createActionDispatcher({ actions });
714
- export const runMiddleware = createMiddlewareRunner({ middleware });
726
+ const guard = createMiddlewareRunner({ middleware });
727
+ export const runMiddleware = (request) => traceRequestPhase("middleware", () => guard(request));
715
728
  `;
716
729
  }
717
730
 
@@ -0,0 +1,23 @@
1
+ // @noflow
2
+
3
+ import { existsSync } from "node:fs";
4
+ import path from "node:path";
5
+
6
+ export function instrumentationFile(root, client = false) {
7
+ const stem = client ? "$instrumentation.client" : "$instrumentation";
8
+ const files = [".js", ".jsx"]
9
+ .map((extension) => path.join(root, stem + extension))
10
+ .filter(existsSync);
11
+ if (files.length > 1)
12
+ throw new Error(`uf: choose one instrumentation module: ${files.join(", ")}`);
13
+ return files[0] ?? null;
14
+ }
15
+
16
+ export function clientInstrumentationSource(file) {
17
+ if (file == null) return "";
18
+ return `import * as instrumentation from ${JSON.stringify(file)};
19
+ import { installClientInstrumentation } from "@uniflowed/router/instrumentation";
20
+ const disposeInstrumentation = await installClientInstrumentation(instrumentation);
21
+ if (import.meta.hot) import.meta.hot.dispose(disposeInstrumentation);
22
+ `;
23
+ }
@@ -14,6 +14,8 @@
14
14
  // route table imports every page and layout lazily, so a route is a chunk of
15
15
  // its own and the client only downloads what it navigates to.
16
16
 
17
+ import { clientInstrumentationSource } from "./instrumentation.js";
18
+
17
19
  import { readdirSync, statSync } from "node:fs";
18
20
  import path from "node:path";
19
21
 
@@ -28,6 +30,7 @@ export const RESERVED = Object.freeze({
28
30
  error: "$error",
29
31
  loading: "$loading",
30
32
  route: "$route",
33
+ instrumentation: "$instrumentation",
31
34
  });
32
35
 
33
36
  /**
@@ -381,6 +384,15 @@ export function scanRoutes(appRoot, options = {}) {
381
384
  );
382
385
 
383
386
  refuseUnsupportedTemplateFiles(directory, entries);
387
+ for (const entry of entries) {
388
+ if (entry.name.startsWith("$instrumentation") && !/\.test\.jsx?$/.test(entry.name)) {
389
+ if (depth !== 0 || !/^\$instrumentation(?:\.client)?\.jsx?$/.test(entry.name)) {
390
+ throw new Error(
391
+ `uf: ${path.join(directory, entry.name)} must be $instrumentation.js or $instrumentation.client.js at the router root`,
392
+ );
393
+ }
394
+ }
395
+ }
384
396
 
385
397
  // A `$default.js` answers one question — what a slot renders when the
386
398
  // URL says nothing about it — and this walk is everywhere a slot is not,
@@ -1660,7 +1672,7 @@ export function clientModuleSource(appEntry, options = {}) {
1660
1672
  return `import { ${mount} } from "@uniflowed/router/client";
1661
1673
  import { routes, notFound, errors } from ${JSON.stringify(VIRTUAL.routes)};
1662
1674
  import App from ${JSON.stringify(appEntry)};
1663
- ${mount}({ App, routes, notFound, errors${strictMode}${navigation}${staleTime}${routing} });
1675
+ ${clientInstrumentationSource(options.instrumentation)}${mount}({ App, routes, notFound, errors${strictMode}${navigation}${staleTime}${routing} });
1664
1676
  `;
1665
1677
  }
1666
1678
 
@@ -1718,9 +1730,12 @@ ${mount}({ App, routes, notFound, errors${strictMode}${navigation}${staleTime}${
1718
1730
  * shorter proof of the paragraph above: the copy the router dispatches and
1719
1731
  * renders with is by construction the copy the host is handed.
1720
1732
  */
1721
- export function serverModuleSource(appEntry, routing = routingRulesOf({})) {
1733
+ export function serverModuleSource(appEntry, routing = routingRulesOf({}), instrumentation = null) {
1722
1734
  return `import {
1723
1735
  createActionDispatcher,
1736
+ createInstrumentation,
1737
+ instrumentRender,
1738
+ traceRequestPhase,
1724
1739
  createDispatcher,
1725
1740
  createMiddlewareRunner,
1726
1741
  createRenderer,
@@ -1731,14 +1746,19 @@ import { actions } from ${JSON.stringify(VIRTUAL.actions)};
1731
1746
  import App from ${JSON.stringify(appEntry)};
1732
1747
  ${routingExportSource(routing)}installRouting(routing);
1733
1748
  export { routes, handlers, middleware, notFound, errors };
1734
- export { beginRequest } from "@uniflowed/router/server";
1749
+ ${instrumentation == null ? "" : `import * as hooks from ${JSON.stringify(instrumentation)};`}
1750
+ export const { beginRequest } = createInstrumentation(${instrumentation == null ? "" : "hooks"});
1735
1751
  const renderer = createRenderer({ App, routes, notFound, errors });
1736
- export const render = renderer.render;
1752
+ export const render = (url, assets, options = {}) => instrumentRender(
1753
+ (onError) => renderer.render(url, assets, { ...options, onError }), options.onError,
1754
+ );
1737
1755
  export const prerender = renderer.prerender;
1738
1756
  export { shellDocument } from "@uniflowed/router/server";
1739
- export const dispatch = createDispatcher({ handlers });
1757
+ const dispatchRoute = createDispatcher({ handlers });
1758
+ export const dispatch = (request) => traceRequestPhase("route", () => dispatchRoute(request));
1740
1759
  export const callAction = createActionDispatcher({ actions });
1741
- export const runMiddleware = createMiddlewareRunner({ middleware });
1760
+ const guard = createMiddlewareRunner({ middleware });
1761
+ export const runMiddleware = (request) => traceRequestPhase("middleware", () => guard(request));
1742
1762
  `;
1743
1763
  }
1744
1764
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniflowed/vite",
3
- "version": "0.0.0-alpha.41",
3
+ "version": "0.0.0-alpha.43",
4
4
  "description": "Vite, driven by uf.config.js: every Flow module through `uf transform`, MDX, the file-system router and static rendering as Vite plugins.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -35,10 +35,10 @@
35
35
  "@babel/core": "^7.29.7",
36
36
  "@mdx-js/rollup": "^3.1.1",
37
37
  "@shikijs/rehype": "^3.23.0",
38
- "@uniflowed/host": "0.0.0-alpha.41",
39
- "@uniflowed/router": "0.0.0-alpha.41",
40
- "@uniflowed/server": "0.0.0-alpha.41",
41
- "@uniflowed/validator": "0.0.0-alpha.41",
38
+ "@uniflowed/host": "0.0.0-alpha.43",
39
+ "@uniflowed/router": "0.0.0-alpha.43",
40
+ "@uniflowed/server": "0.0.0-alpha.43",
41
+ "@uniflowed/validator": "0.0.0-alpha.43",
42
42
  "babel-plugin-relay": "^21.0.1",
43
43
  "estree-util-value-to-estree": "^3.5.0",
44
44
  "rehype-slug": "^6.0.0",
@@ -47,7 +47,8 @@
47
47
  "shiki": "^3.23.0",
48
48
  "unist-util-mdx-define": "^1.1.2",
49
49
  "vite": "^8.2.2",
50
- "yaml": "^2.9.1"
50
+ "yaml": "^2.9.1",
51
+ "rolldown": "^1.2.7"
51
52
  },
52
53
  "peerDependencies": {
53
54
  "axe-core": ">=4",