mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
a35e43cef3
* feat(skip): add layout safety observation foundations Introduce the per-layout observation primitives that later slices need to decide whether retained static layouts can be reused. This slice adds the machinery only; nothing reads the observations yet, so behavior is unchanged. - app-layout-param-observation: per-layout completeness, param keys, structural scope, finite revalidate, request API, cacheLife, cache tag, cacheable fetch, dynamic fetch, and unstable_cache observation tracker. - app-page-probe: bounded React server subtree probe that walks layout-returned children including memo, forwardRef, and React.lazy wrappers, enforces depth and node limits, and refuses single-use iterable children. - shims: thenable params observer for sync, await, destructure, and enumeration. Cache, fetch, and unified request context record unstable_cache, cacheable fetch, and request-scoped observation state. - app-page-params: structural segment-param scope so empty optional catch-all targets are not treated as param-free downstream. No skip transport behavior. Later slices wire the planner, manifest, and encoder onto these observations. * fix(skip): address review feedback on observation foundations - Add currentFetchSoftTags reset in runWithIsolatedProbeDependencies to prevent soft tag leak from parent scope (matches runWithFetchCache isolation) - Replace unnecessary JSON.stringify in cacheLife check with direct null comparison - Add comment documenting intentional omission of markProbeComplete on probe errors (keeps completeness as unknown, safe fallback to render-and-send) * fix(skip): record cacheable-fetch observation synchronously before first await The patched fetch deferred observation and tag collection past await buildFetchCacheKey(). If a layout probe started a cacheable fetch and returned synchronously without awaiting it, the probe snapshot would see cacheableFetchCount: 0 -- a false proof that could let the skip planner reuse a stale retained layout. Move observation and tag recording before the first await in the cacheable branch. If key generation later fails and the fetch falls back to dynamic, recording both is conservative (false unsafe costs performance, not correctness). Add an integration test that exercises the real patched fetch shim without mocks to verify synchronous observation capture. * fix(test): remove polluting fetch restore and catch background promise Do not restore globalThis.fetch in test cleanup. ensureFetchPatch sets a global _PATCH_KEY guard; restoring fetch without clearing that marker makes later calls skip reinstallation silently. Catch the background fetch promise inside the probe so the patched fetch continuation (cache lookup, network fetch) cannot produce an unhandled rejection after the synchronous assertions complete. * chore: empty commit to trigger CI * chore: empty commit to trigger CI
134 lines
5.7 KiB
TypeScript
134 lines
5.7 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import type { KnipConfig } from "knip";
|
|
|
|
function entriesFromPackageJson(relativePath: string): string[] {
|
|
const pkg = JSON.parse(readFileSync(new URL(relativePath, import.meta.url), "utf8")) as {
|
|
bin?: string | Record<string, string>;
|
|
exports?: Record<string, unknown>;
|
|
};
|
|
const targets = new Set<string>();
|
|
|
|
const visit = (value: unknown) => {
|
|
if (typeof value === "string") targets.add(value);
|
|
else if (value && typeof value === "object") for (const v of Object.values(value)) visit(v);
|
|
};
|
|
|
|
visit(pkg.bin);
|
|
visit(pkg.exports);
|
|
|
|
return [...targets]
|
|
.filter((t) => t.endsWith(".js"))
|
|
.map((t) =>
|
|
t
|
|
.replace(/^\.\//, "")
|
|
.replace(/^dist\//, "src/")
|
|
.replace(/\.js$/, ".{ts,tsx}"),
|
|
);
|
|
}
|
|
|
|
export default {
|
|
workspaces: {
|
|
".": {
|
|
entry: ["scripts/*.{js,ts}", "tests/**/*.test.ts", "tests/helpers.ts"],
|
|
project: ["scripts/**/*.{js,ts}", "tests/**/*.{js,ts}", "!tests/fixtures/**"],
|
|
},
|
|
"packages/vinext": {
|
|
entry: [
|
|
...entriesFromPackageJson("packages/vinext/package.json"),
|
|
// Build-time entries referenced by path constant (Vite reads them
|
|
// from disk via `fs`), so knip wouldn't otherwise trace them.
|
|
"src/server/app-browser-entry.ts",
|
|
"src/server/app-ssr-entry.ts",
|
|
// Runtime helpers imported by generated virtual entries. The imports
|
|
// are emitted as strings, so knip cannot trace them statically.
|
|
"src/server/app-middleware.ts",
|
|
"src/server/app-page-dispatch.ts",
|
|
"src/server/app-page-head.ts",
|
|
"src/server/app-prerender-static-params.ts",
|
|
// Client-side instrumentation bundle: loaded as a side-effect module
|
|
// by the generated hydration entries (import "vinext/instrumentation-client"),
|
|
// so its public surface (clientInstrumentationHooks, getClientInstrumentationHooks)
|
|
// is consumed by the user's app at runtime, not by imports knip can follow.
|
|
"src/client/instrumentation-client.ts",
|
|
"src/client/instrumentation-client-state.ts",
|
|
// Shims for `next/*` internal modules — their exports are consumed by
|
|
// type-only imports in third-party packages' .d.ts files (e.g.
|
|
// @clerk/nextjs, @sentry/nextjs, nextjs-toploader). Those imports
|
|
// originate in node_modules and are invisible to knip.
|
|
"src/shims/internal/api-utils.ts",
|
|
"src/shims/internal/app-router-context.ts",
|
|
"src/shims/internal/utils.ts",
|
|
// Typed WorkUnitStore exports consumed by cache.ts via AsyncLocalStorage
|
|
// generic — knip cannot trace type-only dependencies through ALS.
|
|
"src/shims/internal/work-unit-async-storage.ts",
|
|
// Imported via template string in app-rsc-entry.ts (generated code),
|
|
// so knip cannot trace the import statically.
|
|
"src/server/prerender-work-unit-setup.ts",
|
|
"src/server/app-page-element-builder.ts",
|
|
"src/server/app-hook-warning-suppression.ts",
|
|
"src/server/app-post-middleware-context.ts",
|
|
"src/server/app-request-context.ts",
|
|
"src/server/app-rsc-error-handler.ts",
|
|
"src/server/isr-cache.ts",
|
|
"src/server/rsc-stream-hints.ts",
|
|
// #726-CACHE-01/04 defines the disabled proof boundary before runtime
|
|
// observation recording or cache reuse is wired in later slices.
|
|
"src/server/cache-proof.ts",
|
|
// #726-SKIP layout-safety observation foundation. Consumed by the
|
|
// planner, dispatch wiring, and render in later slices.
|
|
"src/server/app-layout-param-observation.ts",
|
|
// #726-SKIP static layout reuse proof model. Consumed by render in a
|
|
// later slice; standalone planner + helpers here.
|
|
"src/server/skip-cache-proof.ts",
|
|
"src/server/static-layout-client-reuse-proof.ts",
|
|
],
|
|
project: ["src/**/*.{ts,tsx}"],
|
|
},
|
|
},
|
|
ignoreWorkspaces: ["examples/**", "tests/fixtures/**", "benchmarks/**"],
|
|
ignoreDependencies: [
|
|
"@typescript/native-preview",
|
|
|
|
// Declared at root package.json but imported from workspace/example code:
|
|
// @mdx-js/react — no direct imports; retained for MDX runtime resolution.
|
|
// @mdx-js/rollup — imported from examples/app-router-playground/vite.config.ts
|
|
// which doesn't declare it locally and relies on root hoisting.
|
|
"@mdx-js/react",
|
|
"@mdx-js/rollup",
|
|
|
|
// probed via require.resolve
|
|
"next-intl",
|
|
|
|
// Optional peer dep used by Vite's built-in SCSS preprocessor when the
|
|
// user installs it. `tests/scss.test.ts` dynamically imports it to skip
|
|
// the SCSS suite when sass is absent, so we never list it as a hard dep.
|
|
"sass",
|
|
|
|
// vitest reporter used outside CI
|
|
...(process.env.CI ? [] : ["agent"]),
|
|
|
|
// internal module name, not an actual dependency
|
|
"private-next-instrumentation-client",
|
|
|
|
// Cloudflare Workers runtime virtual module — provides `env` for
|
|
// accessing wrangler bindings. Not an npm package. Knip strips the
|
|
// `cloudflare:workers` specifier down to the bare scheme.
|
|
"cloudflare",
|
|
],
|
|
ignoreBinaries: [
|
|
// workspace's own bin, invoked in CI
|
|
"vinext",
|
|
],
|
|
ignoreFiles: [
|
|
"tests/e2e/app-router/nextjs-compat/playwright.nextjs-compat.config.ts",
|
|
// stub module loaded via `path.resolve()` as a Vite alias target
|
|
"packages/vinext/src/client/empty-module.ts",
|
|
],
|
|
// Catalog check is noisy here (deps consumed from the workspace's own
|
|
// sub-packages or `examples/**` which we intentionally ignore).
|
|
// `duplicates` flags intentional compat aliases — see
|
|
// packages/vinext/src/shims/internal/work-unit-async-storage.ts where
|
|
// `requestAsyncStorage` is a legacy alias for `workUnitAsyncStorage`.
|
|
exclude: ["catalog", "duplicates"],
|
|
} satisfies KnipConfig;
|