@rsc-kit/core 0.30.23 → 0.30.24
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/dist/clientImports.d.ts +27 -7
- package/dist/clientImports.js +72 -4
- package/dist/clientImports.js.map +1 -1
- package/dist/vite.js +9 -9
- package/dist/vite.js.map +1 -1
- package/package.json +1 -1
package/dist/clientImports.d.ts
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server files that
|
|
2
|
+
* Server files that reach client-only React on the server.
|
|
3
3
|
*
|
|
4
4
|
* A server component may import a client library - `<SheetClose>` from
|
|
5
5
|
* base-ui rendered inside server output is legal here and in Next.js. What
|
|
6
6
|
* is worth a line is the shape that cost an afternoon: a file with no
|
|
7
|
-
* "use client"
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
7
|
+
* "use client" (a shadcn ui/ file that lost its directive in a port) whose
|
|
8
|
+
* code, or a library's under it, calls a hook in the rsc environment, which
|
|
9
|
+
* fails at render as "(0, import_react_react_server.useState) is not a
|
|
10
|
+
* function" - naming React, not the file.
|
|
11
|
+
*
|
|
12
|
+
* This used to name every server file importing any package that depends on
|
|
13
|
+
* React, which on a real app was 26 files of icons, markdown and base-ui
|
|
14
|
+
* parts that carry their own "use client", and the one that mattered was
|
|
15
|
+
* lost among them. So the code is read instead: from the server file into
|
|
16
|
+
* the packages it imports, stopping at a client reference - a module with
|
|
17
|
+
* the directive, whose code never runs on the server - and a file is named
|
|
18
|
+
* only when what is left mentions a React API the server build does not
|
|
19
|
+
* have. Mentions, not calls: a library can hold a hook in an export the
|
|
20
|
+
* page never renders, so the line names the API for the reader to judge.
|
|
12
21
|
*
|
|
13
22
|
* Read off the rsc environment's module graph after it is built. A file with
|
|
14
23
|
* the directive never appears: in that graph it is already a reference, and
|
|
@@ -17,8 +26,10 @@
|
|
|
17
26
|
export interface ClientLibraryImport {
|
|
18
27
|
/** The server file, relative to the source dir. */
|
|
19
28
|
file: string;
|
|
20
|
-
/** The
|
|
29
|
+
/** The packages whose server-run code reaches a client-only API, by name. Empty when the file itself does. */
|
|
21
30
|
packages: string[];
|
|
31
|
+
/** The client-only React APIs reached, by name. */
|
|
32
|
+
calls: string[];
|
|
22
33
|
/** The first app file that imports it, relative to the source dir - or null for a route file. */
|
|
23
34
|
from: string | null;
|
|
24
35
|
}
|
|
@@ -26,7 +37,16 @@ export interface ModuleGraph {
|
|
|
26
37
|
moduleIds(): Iterable<string>;
|
|
27
38
|
importedIds(id: string): readonly string[];
|
|
28
39
|
importers(id: string): readonly string[];
|
|
40
|
+
/** The module's code as built - for a client reference, the reference. */
|
|
41
|
+
code(id: string): string | null;
|
|
29
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* React's APIs that its server build leaves out, as of React 19: the ones a
|
|
45
|
+
* render needs state or an effect for. A test reads them off React itself.
|
|
46
|
+
* Not Component or startTransition - the one is a word in every library's
|
|
47
|
+
* prose, the other runs in an event handler, never during a render.
|
|
48
|
+
*/
|
|
49
|
+
export declare const CLIENT_ONLY_APIS: readonly ["createContext", "useActionState", "useContext", "useDeferredValue", "useEffect", "useEffectEvent", "useFormState", "useFormStatus", "useImperativeHandle", "useInsertionEffect", "useLayoutEffect", "useOptimistic", "useReducer", "useRef", "useState", "useSyncExternalStore", "useTransition"];
|
|
30
50
|
/** The package a node_modules id belongs to, or null for anything else. */
|
|
31
51
|
export declare function packageOf(id: string): string | null;
|
|
32
52
|
export declare function serverImportsOfClientPackages(graph: ModuleGraph, options: {
|
package/dist/clientImports.js
CHANGED
|
@@ -1,3 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React's APIs that its server build leaves out, as of React 19: the ones a
|
|
3
|
+
* render needs state or an effect for. A test reads them off React itself.
|
|
4
|
+
* Not Component or startTransition - the one is a word in every library's
|
|
5
|
+
* prose, the other runs in an event handler, never during a render.
|
|
6
|
+
*/
|
|
7
|
+
export const CLIENT_ONLY_APIS = [
|
|
8
|
+
"createContext",
|
|
9
|
+
"useActionState",
|
|
10
|
+
"useContext",
|
|
11
|
+
"useDeferredValue",
|
|
12
|
+
"useEffect",
|
|
13
|
+
"useEffectEvent",
|
|
14
|
+
"useFormState",
|
|
15
|
+
"useFormStatus",
|
|
16
|
+
"useImperativeHandle",
|
|
17
|
+
"useInsertionEffect",
|
|
18
|
+
"useLayoutEffect",
|
|
19
|
+
"useOptimistic",
|
|
20
|
+
"useReducer",
|
|
21
|
+
"useRef",
|
|
22
|
+
"useState",
|
|
23
|
+
"useSyncExternalStore",
|
|
24
|
+
"useTransition",
|
|
25
|
+
];
|
|
26
|
+
const CLIENT_ONLY = new RegExp(`\\b(${CLIENT_ONLY_APIS.join("|")})\\b`, "g");
|
|
27
|
+
const COMMENT = /\/\*[\s\S]*?\*\/|(^|[^:])\/\/[^\n]*/g;
|
|
28
|
+
/** plugin-rsc's reference for a "use client" module: its code stays on the client. */
|
|
29
|
+
const isReference = (code) => code.includes("registerClientReference");
|
|
30
|
+
/** The client-only APIs a module's own code mentions, outside its comments. */
|
|
31
|
+
function clientOnlyIn(code) {
|
|
32
|
+
if (!code || isReference(code))
|
|
33
|
+
return [];
|
|
34
|
+
return [...new Set([...code.replace(COMMENT, "$1").matchAll(CLIENT_ONLY)].map((m) => m[1]))];
|
|
35
|
+
}
|
|
1
36
|
const PACKAGE = /\/node_modules\/(@[^/]+\/[^/]+|[^/@][^/]*)/;
|
|
2
37
|
/** The package a node_modules id belongs to, or null for anything else. */
|
|
3
38
|
export function packageOf(id) {
|
|
@@ -13,21 +48,54 @@ export function serverImportsOfClientPackages(graph, options) {
|
|
|
13
48
|
!id.startsWith("\0");
|
|
14
49
|
const relative = (id) => (id.split("?")[0] ?? id).slice(root.length);
|
|
15
50
|
const out = [];
|
|
51
|
+
const inClientPackage = (id) => {
|
|
52
|
+
const name = packageOf(id);
|
|
53
|
+
return name !== null && client.has(name) && !ignore.has(name);
|
|
54
|
+
};
|
|
55
|
+
// What a package module and everything it runs on the server reach,
|
|
56
|
+
// memoised: one barrel is imported by many files. A cycle reads as nothing
|
|
57
|
+
// more on its second visit, which is all a union needs.
|
|
58
|
+
const reached = new Map();
|
|
59
|
+
const reach = (id) => {
|
|
60
|
+
const known = reached.get(id);
|
|
61
|
+
if (known)
|
|
62
|
+
return known;
|
|
63
|
+
const apis = new Set();
|
|
64
|
+
reached.set(id, apis);
|
|
65
|
+
const code = graph.code(id);
|
|
66
|
+
if (code !== null && isReference(code))
|
|
67
|
+
return apis;
|
|
68
|
+
for (const api of clientOnlyIn(code))
|
|
69
|
+
apis.add(api);
|
|
70
|
+
for (const next of graph.importedIds(id)) {
|
|
71
|
+
if (inClientPackage(next))
|
|
72
|
+
for (const api of reach(next))
|
|
73
|
+
apis.add(api);
|
|
74
|
+
}
|
|
75
|
+
return apis;
|
|
76
|
+
};
|
|
16
77
|
for (const id of graph.moduleIds()) {
|
|
17
78
|
if (!isApp(id))
|
|
18
79
|
continue;
|
|
80
|
+
const calls = new Set(clientOnlyIn(graph.code(id)));
|
|
19
81
|
const packages = new Set();
|
|
20
82
|
for (const imported of graph.importedIds(id)) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
83
|
+
if (!inClientPackage(imported))
|
|
84
|
+
continue;
|
|
85
|
+
const apis = reach(imported);
|
|
86
|
+
if (apis.size === 0)
|
|
87
|
+
continue;
|
|
88
|
+
packages.add(packageOf(imported));
|
|
89
|
+
for (const api of apis)
|
|
90
|
+
calls.add(api);
|
|
24
91
|
}
|
|
25
|
-
if (
|
|
92
|
+
if (calls.size === 0)
|
|
26
93
|
continue;
|
|
27
94
|
const from = graph.importers(id).find(isApp);
|
|
28
95
|
out.push({
|
|
29
96
|
file: relative(id),
|
|
30
97
|
packages: [...packages].sort(),
|
|
98
|
+
calls: [...calls].sort(),
|
|
31
99
|
from: from ? relative(from) : null,
|
|
32
100
|
});
|
|
33
101
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clientImports.js","sourceRoot":"","sources":["../src/clientImports.ts"],"names":[],"mappings":"AA+BA,MAAM,OAAO,GAAG,4CAA4C,CAAC;AAE7D,2EAA2E;AAC3E,MAAM,UAAU,SAAS,CAAC,EAAU;IAClC,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAEnD,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,6BAA6B,CAC3C,KAAkB,EAClB,OAIC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC;IACzD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC7C,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAC3B,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QACnB,CAAC,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAC9B,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IACvB,MAAM,QAAQ,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7E,MAAM,GAAG,GAA0B,EAAE,CAAC;IAEtC,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAAE,SAAS;QAEzB,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;QAEnC,KAAK,MAAM,QAAQ,IAAI,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,CAAC;YAC7C,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YAEjC,IAAI,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;gBAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACxE,CAAC;QAED,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,SAAS;QAElC,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE7C,GAAG,CAAC,IAAI,CAAC;YACP,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;YAClB,QAAQ,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC,IAAI,EAAE;YAC9B,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI;SACnC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1D,CAAC","sourcesContent":["/**\n * Server files that import a client library.\n *\n * A server component may import a client library - `<SheetClose>` from\n * base-ui rendered inside server output is legal here and in Next.js. What\n * is worth a line is the shape that cost an afternoon: a file with no\n * \"use client\" that only wraps client components (a shadcn ui/ file that\n * lost its directive in a port) imported from the server, so the library's\n * internals run in the rsc environment. The build cannot tell the two\n * apart; it can say which server files do it, and with which packages, so\n * the person reading can.\n *\n * Read off the rsc environment's module graph after it is built. A file with\n * the directive never appears: in that graph it is already a reference, and\n * a reference imports nothing.\n */\nexport interface ClientLibraryImport {\n /** The server file, relative to the source dir. */\n file: string;\n /** The client packages it imports, by name. */\n packages: string[];\n /** The first app file that imports it, relative to the source dir - or null for a route file. */\n from: string | null;\n}\n\nexport interface ModuleGraph {\n moduleIds(): Iterable<string>;\n importedIds(id: string): readonly string[];\n importers(id: string): readonly string[];\n}\n\nconst PACKAGE = /\\/node_modules\\/(@[^/]+\\/[^/]+|[^/@][^/]*)/;\n\n/** The package a node_modules id belongs to, or null for anything else. */\nexport function packageOf(id: string): string | null {\n const match = PACKAGE.exec(id.split(\"?\")[0] ?? id);\n\n return match ? match[1]! : null;\n}\n\nexport function serverImportsOfClientPackages(\n graph: ModuleGraph,\n options: {\n sourceDir: string;\n clientPackages: Iterable<string>;\n ignore?: Iterable<string>;\n },\n): ClientLibraryImport[] {\n const root = options.sourceDir.replace(/\\/+$/, \"\") + \"/\";\n const client = new Set(options.clientPackages);\n const ignore = new Set(options.ignore ?? []);\n const isApp = (id: string) =>\n id.startsWith(root) &&\n !id.includes(\"/node_modules/\") &&\n !id.startsWith(\"\\0\");\n const relative = (id: string) => (id.split(\"?\")[0] ?? id).slice(root.length);\n const out: ClientLibraryImport[] = [];\n\n for (const id of graph.moduleIds()) {\n if (!isApp(id)) continue;\n\n const packages = new Set<string>();\n\n for (const imported of graph.importedIds(id)) {\n const name = packageOf(imported);\n\n if (name && client.has(name) && !ignore.has(name)) packages.add(name);\n }\n\n if (packages.size === 0) continue;\n\n const from = graph.importers(id).find(isApp);\n\n out.push({\n file: relative(id),\n packages: [...packages].sort(),\n from: from ? relative(from) : null,\n });\n }\n\n return out.sort((a, b) => a.file.localeCompare(b.file));\n}\n"]}
|
|
1
|
+
{"version":3,"file":"clientImports.js","sourceRoot":"","sources":["../src/clientImports.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Server files that reach client-only React on the server.\n *\n * A server component may import a client library - `<SheetClose>` from\n * base-ui rendered inside server output is legal here and in Next.js. What\n * is worth a line is the shape that cost an afternoon: a file with no\n * \"use client\" (a shadcn ui/ file that lost its directive in a port) whose\n * code, or a library's under it, calls a hook in the rsc environment, which\n * fails at render as \"(0, import_react_react_server.useState) is not a\n * function\" - naming React, not the file.\n *\n * This used to name every server file importing any package that depends on\n * React, which on a real app was 26 files of icons, markdown and base-ui\n * parts that carry their own \"use client\", and the one that mattered was\n * lost among them. So the code is read instead: from the server file into\n * the packages it imports, stopping at a client reference - a module with\n * the directive, whose code never runs on the server - and a file is named\n * only when what is left mentions a React API the server build does not\n * have. Mentions, not calls: a library can hold a hook in an export the\n * page never renders, so the line names the API for the reader to judge.\n *\n * Read off the rsc environment's module graph after it is built. A file with\n * the directive never appears: in that graph it is already a reference, and\n * a reference imports nothing.\n */\nexport interface ClientLibraryImport {\n /** The server file, relative to the source dir. */\n file: string;\n /** The packages whose server-run code reaches a client-only API, by name. Empty when the file itself does. */\n packages: string[];\n /** The client-only React APIs reached, by name. */\n calls: string[];\n /** The first app file that imports it, relative to the source dir - or null for a route file. */\n from: string | null;\n}\n\nexport interface ModuleGraph {\n moduleIds(): Iterable<string>;\n importedIds(id: string): readonly string[];\n importers(id: string): readonly string[];\n /** The module's code as built - for a client reference, the reference. */\n code(id: string): string | null;\n}\n\n/**\n * React's APIs that its server build leaves out, as of React 19: the ones a\n * render needs state or an effect for. A test reads them off React itself.\n * Not Component or startTransition - the one is a word in every library's\n * prose, the other runs in an event handler, never during a render.\n */\nexport const CLIENT_ONLY_APIS = [\n \"createContext\",\n \"useActionState\",\n \"useContext\",\n \"useDeferredValue\",\n \"useEffect\",\n \"useEffectEvent\",\n \"useFormState\",\n \"useFormStatus\",\n \"useImperativeHandle\",\n \"useInsertionEffect\",\n \"useLayoutEffect\",\n \"useOptimistic\",\n \"useReducer\",\n \"useRef\",\n \"useState\",\n \"useSyncExternalStore\",\n \"useTransition\",\n] as const;\n\nconst CLIENT_ONLY = new RegExp(`\\\\b(${CLIENT_ONLY_APIS.join(\"|\")})\\\\b`, \"g\");\nconst COMMENT = /\\/\\*[\\s\\S]*?\\*\\/|(^|[^:])\\/\\/[^\\n]*/g;\n\n/** plugin-rsc's reference for a \"use client\" module: its code stays on the client. */\nconst isReference = (code: string) => code.includes(\"registerClientReference\");\n\n/** The client-only APIs a module's own code mentions, outside its comments. */\nfunction clientOnlyIn(code: string | null): string[] {\n if (!code || isReference(code)) return [];\n\n return [...new Set([...code.replace(COMMENT, \"$1\").matchAll(CLIENT_ONLY)].map((m) => m[1]!))];\n}\n\nconst PACKAGE = /\\/node_modules\\/(@[^/]+\\/[^/]+|[^/@][^/]*)/;\n\n/** The package a node_modules id belongs to, or null for anything else. */\nexport function packageOf(id: string): string | null {\n const match = PACKAGE.exec(id.split(\"?\")[0] ?? id);\n\n return match ? match[1]! : null;\n}\n\nexport function serverImportsOfClientPackages(\n graph: ModuleGraph,\n options: {\n sourceDir: string;\n clientPackages: Iterable<string>;\n ignore?: Iterable<string>;\n },\n): ClientLibraryImport[] {\n const root = options.sourceDir.replace(/\\/+$/, \"\") + \"/\";\n const client = new Set(options.clientPackages);\n const ignore = new Set(options.ignore ?? []);\n const isApp = (id: string) =>\n id.startsWith(root) &&\n !id.includes(\"/node_modules/\") &&\n !id.startsWith(\"\\0\");\n const relative = (id: string) => (id.split(\"?\")[0] ?? id).slice(root.length);\n const out: ClientLibraryImport[] = [];\n const inClientPackage = (id: string) => {\n const name = packageOf(id);\n return name !== null && client.has(name) && !ignore.has(name);\n };\n\n // What a package module and everything it runs on the server reach,\n // memoised: one barrel is imported by many files. A cycle reads as nothing\n // more on its second visit, which is all a union needs.\n const reached = new Map<string, Set<string>>();\n const reach = (id: string): Set<string> => {\n const known = reached.get(id);\n if (known) return known;\n\n const apis = new Set<string>();\n reached.set(id, apis);\n\n const code = graph.code(id);\n if (code !== null && isReference(code)) return apis;\n\n for (const api of clientOnlyIn(code)) apis.add(api);\n\n for (const next of graph.importedIds(id)) {\n if (inClientPackage(next)) for (const api of reach(next)) apis.add(api);\n }\n\n return apis;\n };\n\n for (const id of graph.moduleIds()) {\n if (!isApp(id)) continue;\n\n const calls = new Set(clientOnlyIn(graph.code(id)));\n const packages = new Set<string>();\n\n for (const imported of graph.importedIds(id)) {\n if (!inClientPackage(imported)) continue;\n\n const apis = reach(imported);\n if (apis.size === 0) continue;\n\n packages.add(packageOf(imported)!);\n for (const api of apis) calls.add(api);\n }\n\n if (calls.size === 0) continue;\n\n const from = graph.importers(id).find(isApp);\n\n out.push({\n file: relative(id),\n packages: [...packages].sort(),\n calls: [...calls].sort(),\n from: from ? relative(from) : null,\n });\n }\n\n return out.sort((a, b) => a.file.localeCompare(b.file));\n}\n"]}
|
package/dist/vite.js
CHANGED
|
@@ -1907,18 +1907,17 @@ async function prerenderAfterBundles(bundle, staticDir, assetsDir, knownActions
|
|
|
1907
1907
|
console.log(" React's cache() dedupes only inside a component render; in a guard, an action or\n" +
|
|
1908
1908
|
" an api route it calls straight through. Import it from @rsc-kit/core/cache instead.");
|
|
1909
1909
|
}
|
|
1910
|
-
// Server files
|
|
1911
|
-
//
|
|
1912
|
-
//
|
|
1913
|
-
// library's internals run on the server. Said, with the packages and the
|
|
1914
|
-
// importer, so the person reading can tell which it is.
|
|
1910
|
+
// Server files whose code, or a library's under them, reaches a hook on the
|
|
1911
|
+
// server (clientImports.ts). The render that calls one fails naming React,
|
|
1912
|
+
// not the file; this names the file, the package and the hook.
|
|
1915
1913
|
if (clientLibraryImports.length > 0) {
|
|
1916
|
-
console.log(`\n \u2139 ${clientLibraryImports.length} server ${clientLibraryImports.length === 1 ? "file
|
|
1914
|
+
console.log(`\n \u2139 ${clientLibraryImports.length} server ${clientLibraryImports.length === 1 ? "file reaches" : "files reach"} client-only React: ` +
|
|
1917
1915
|
clientLibraryImports
|
|
1918
|
-
.map((c) => `${c.file} (${c.packages.join(", ")}${c.from ? `; imported by ${c.from}` : ""})`)
|
|
1916
|
+
.map((c) => `${c.file} (${c.packages.length > 0 ? `${c.packages.join(", ")}: ` : ""}${c.calls.join(", ")}${c.from ? `; imported by ${c.from}` : ""})`)
|
|
1919
1917
|
.join("; "));
|
|
1920
|
-
console.log('
|
|
1921
|
-
|
|
1918
|
+
console.log(' The server has none of these, so a render that calls one fails with "... is not a function".\n' +
|
|
1919
|
+
' A file that only wraps client components wants "use client" - as shadcn ships it. A library\n' +
|
|
1920
|
+
" export holding a hook the page never renders is fine.");
|
|
1922
1921
|
}
|
|
1923
1922
|
// Written from the rows that were just printed rather than recomputed: the
|
|
1924
1923
|
// report and the terminal must not be able to disagree about what happened.
|
|
@@ -7128,6 +7127,7 @@ function clientImportsAudit() {
|
|
|
7128
7127
|
moduleIds: () => this.getModuleIds(),
|
|
7129
7128
|
importedIds: (id) => this.getModuleInfo(id)?.importedIds ?? [],
|
|
7130
7129
|
importers: (id) => this.getModuleInfo(id)?.importers ?? [],
|
|
7130
|
+
code: (id) => this.getModuleInfo(id)?.code ?? null,
|
|
7131
7131
|
}, {
|
|
7132
7132
|
sourceDir,
|
|
7133
7133
|
clientPackages,
|