Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .changeset/5996-eager-closure-missing-chunk.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
---
---

Build tooling only — `apps/console/vite.config.ts`, which is not published
source (`@object-ui/console`'s `files` list carries `dist`, `plugin.*` and
`README.md`), so nothing ships from this change.

`emit-eager-closure-report`'s `writeBundle` hook read every member of the eager
closure off disk with an unguarded `fs.readFileSync`. The closure walk seeds
itself from `chunk.imports`, and rolldown lists a chunk's EXTERNAL imports in
that array beside the file names of real chunks — so a bare specifier vite could
not resolve joins the closure under its own name and is then read as a path. The
result was a bare `ENOENT` from `node:fs`, several frames from the cause, naming
neither the plugin nor the unresolved import.

The read is now guarded by an existence check that `this.error()`s with the
missing name, the chunk that imports it, and the diagnosis. The failure stays
loud — that direction is correct, and the two counter-probes above it exist to
keep it that way; only its message changes.
35 changes: 34 additions & 1 deletion apps/console/vite.config.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -292,7 +292,40 @@ function emitEagerClosureReport(reportFileName = 'eager-closure.json'): Plugin {
}

const files = [...eager].sort().map((fileName) => {
const raw = fs.readFileSync(path.join(outDir, fileName));
const filePath = path.join(outDir, fileName);

// A closure member with no file on disk is not a missing build output —
// it is an UNRESOLVED BARE IMPORT that the walk above swept in. Rolldown
// lists a chunk's EXTERNAL imports in `chunk.imports` beside the file
// names of real chunks, and the walk follows that array without asking
// whether the name is in `chunks`, so a specifier vite could not resolve
// joins `eager` under its own name and is then read as a path. Left
// unguarded that is a bare `ENOENT` from `node:fs`, several frames from
// the cause and naming neither this plugin nor the import (objectui#5996).
if (!fs.existsSync(filePath)) {
const importers = [...chunks.values()]
.filter((chunk) => (chunk.imports ?? []).includes(fileName))
.map((chunk) => chunk.fileName);
this.error(
`[emit-eager-closure-report] eager-closure member \`${fileName}\` has no file in ` +
`\`${outDir}\`, so its bytes cannot be weighed. It is almost certainly an ` +
`UNRESOLVED BARE IMPORT, not a missing build output: rolldown lists a chunk's ` +
`EXTERNAL imports in \`chunk.imports\` beside the file names of real chunks, and ` +
`the walk above follows that array, so a specifier vite could not resolve enters ` +
`the closure under its own name and is then read as a path. The name is the tell — ` +
`a real chunk here is \`assets/<name>-<hash>.js\`, and this one is not a key of the ` +
`output bundle map at all. (imported by: ${importers.join(', ') || 'NONE'}) ` +
`Fix the import rather than skipping the member here: a bare specifier the browser ` +
`cannot load is a broken bundle, not a measurement gap, and dropping it from the ` +
`walk would make this report under-count — the one direction the counter-probes ` +
`above exist to refuse. The known source is an out-of-tree override whose own ` +
`dependencies do not resolve from where it lives: check \`OBJECTSTACK_CLIENT_DIST\` ` +
`and \`OBJECTSTACK_SPEC_DIST\`, and vite's own "could not be resolved" warnings ` +
`earlier in this build.`,
);
}

const raw = fs.readFileSync(filePath);
// Level 6 — zlib's default, and the level `gzip -c` uses in the
// workflow's entry-chunk check, so the two numbers in one PR comment
// are measured the same way.
Expand Down
Loading