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
10 changes: 10 additions & 0 deletions .changeset/26131-console-chunk-membership-attribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@object-ui/console': patch
---

fix(console): the build's chunk-membership report attributes only objectui's own packages

Clause-②: no
Metadata-protocol: none

The Console build writes `dist/chunk-membership.json`, which the eager-closure gate reads to check that each budgeted chunk holds the packages it declares. Inside the objectstack monorepo the framework's own packages resolve to a second `packages/` directory, and four of their names are objectui package names too (`cli`, `core`, `sdui-parser`, `types`). The report matched any `packages/<name>/` segment, so `@objectstack/core`'s bundle file, which belongs in `vendor-objectstack`, was counted as a module of objectui's `packages/core`, and the gate reported `framework` as having lost one module to a vendor chunk. The report now counts only modules under objectui's own `packages/` directory. No chunk's contents change.
19 changes: 15 additions & 4 deletions ui/apps/console/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import { viteCryptoStub } from '../../scripts/vite-crypto-stub.ts';
import { viteMaplibreWorker } from '../../scripts/vite-maplibre-worker.ts';
import { assertSingleZodInstance } from '../../scripts/vite-single-zod.ts';
import { viteIneffectiveDynamicImports } from '../../scripts/vite-ineffective-dynamic-imports.ts';
import { frameworkPackagesDir, workspaceChunkTests } from '../../scripts/vite-objectstack-workspace.ts';
import {
frameworkPackagesDir,
objectuiPackagesDir,
workspaceChunkTests,
workspaceModuleMatcher,
} from '../../scripts/vite-objectstack-workspace.ts';
import { viteDeclaredLazyViews } from '../../scripts/vite-declared-lazy-views.ts';
import { viteTypesZodLazy } from '../../scripts/vite-types-zod-lazy.ts';
import { compression } from 'vite-plugin-compression2';
Expand Down Expand Up @@ -400,8 +405,9 @@ function emitEagerClosureReport(reportFileName = 'eager-closure.json'): Plugin {
*
* ## What it counts, said in words
*
* The population is every module in every emitted chunk whose id contains a
* `packages/<name>/` segment — workspace source, not `node_modules`, and not
* The population is every module in every emitted chunk whose id lies under
* objectui's own `packages/<name>/` — workspace source, not `node_modules`, not
* the objectstack framework's `packages/` beside it in the monorepo, and not
* limited to the eager closure: a budgeted package's module hiding in a LAZY
* chunk is exactly as much a membership breach as one hiding in an eager
* neighbour, and the eager-closure walk cannot see it.
Expand Down Expand Up @@ -429,10 +435,15 @@ function emitChunkMembershipReport(reportFileName = 'chunk-membership.json'): Pl
// The module ids rolldown records are realpaths, so a workspace module reads
// as `<repo>/packages/<name>/src/...` no matter which symlink resolved it —
// the same assumption the `advancedChunks` group tests above are written on.
// Anchored on objectui's OWN `packages/` directory: inside the objectstack
// monorepo the framework's packages resolve to a second `packages/`
// directory whose names overlap objectui's (`core`, `types`, ...), and an
// unanchored segment attributed `@objectstack/core` to objectui's
// `packages/core`. `workspaceModuleMatcher` says why in full.
// The second capture is the rest of the id — the package-relative path the
// `directories` table is keyed by (its directory, `/`-separated, any query
// suffix dropped: `?inline` and friends are the same file).
const WORKSPACE_MODULE = /[\\/]packages[\\/]([^\\/]+)[\\/](.*)$/;
const WORKSPACE_MODULE = workspaceModuleMatcher(objectuiPackagesDir(import.meta.dirname));
const directoryOf = (rest: string) => {
const file = rest.split('?')[0].replace(/\\/g, '/');
const slash = file.lastIndexOf('/');
Expand Down
38 changes: 38 additions & 0 deletions ui/scripts/__tests__/vite-objectstack-workspace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ import {
candidateFrameworkPackagesDir,
escapeRegExp,
frameworkPackagesDir,
objectuiPackagesDir,
workspaceChunkTests,
workspaceModuleMatcher,
} from '../vite-objectstack-workspace.ts';
import { isNestedWorkspace } from '../workspace-root.mjs';

Expand Down Expand Up @@ -171,3 +173,39 @@ describe('workspaceChunkTests (objectstack ruling D1)', () => {
expect(config).toContain('const LINT = WORKSPACE_CHUNK_TESTS.lint;');
});
});

describe('workspaceModuleMatcher (the chunk-membership attribution)', () => {
const matcher = workspaceModuleMatcher(objectuiPackagesDir(path.join(MONOREPO, 'ui/apps/console')));

it("locates objectui's own packages dir two levels above the console", () => {
expect(objectuiPackagesDir(path.join(MONOREPO, 'ui/apps/console'))).toBe(path.join(MONOREPO, 'ui/packages'));
expect(objectuiPackagesDir(consoleDir)).toBe(path.join(repoRoot, 'packages'));
});

it('attributes an objectui module to its package, with the package-relative rest', () => {
const hit = matcher.exec(`${MONOREPO}/ui/packages/core/src/actions/ActionRunner.ts`);
expect(hit?.[1]).toBe('core');
expect(hit?.[2]).toBe('src/actions/ActionRunner.ts');
expect(matcher.exec(`${MONOREPO}/ui/packages/types/src/zod/index.ts?inline`)?.[1]).toBe('types');
});

it('does not attribute a framework package that shares an objectui package name', () => {
// `@objectstack/core` resolves here in the monorepo; objectui has a `packages/core` too.
for (const name of ['core', 'types', 'cli', 'sdui-parser']) {
expect(matcher.test(`${MONOREPO}/packages/${name}/dist/index.js`)).toBe(false);
}
expect(matcher.test(`/elsewhere${MONOREPO}/ui/packages/core/src/index.ts`)).toBe(false);
expect(matcher.test(`${MONOREPO}/ui/packages-extra/core/src/index.ts`)).toBe(false);
});

it('escapes a directory with regex metacharacters so the prefix is matched literally', () => {
const odd = workspaceModuleMatcher('/w/ui (v1.0)/packages');
expect(odd.exec('/w/ui (v1.0)/packages/core/src/index.ts')?.[1]).toBe('core');
expect(odd.test('/w/uiX(v1A0)/packages/core/src/index.ts')).toBe(false);
});

it('is what the console config attributes membership with', () => {
const config = readFileSync(path.join(consoleDir, 'vite.config.ts'), 'utf8');
expect(config).toContain('const WORKSPACE_MODULE = workspaceModuleMatcher(objectuiPackagesDir(import.meta.dirname));');
});
});
30 changes: 30 additions & 0 deletions ui/scripts/vite-objectstack-workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,33 @@ export function workspaceChunkTests(frameworkPackagesDir: string | null): Worksp
lint: new RegExp('(@objectstack[\\\\/+]lint|^' + prefix + 'lint[\\\\/])'),
};
}

/**
* objectui's own `packages/` directory, seen from a console directory
* (`apps/console` → `../../packages`) — the same in both layouts. Pure.
*/
export function objectuiPackagesDir(consoleDir: string): string {
return path.resolve(consoleDir, '../../packages');
}

/**
* The matcher `emitChunkMembershipReport` in `apps/console/vite.config.ts`
* attributes a module id with: capture 1 is the objectui package name, capture
* 2 the package-relative rest of the id.
*
* It is anchored on objectui's OWN `packages/` directory, and the monorepo is
* why. Inside it the framework's packages are workspace links that Vite
* resolves to `<monorepo root>/packages/<name>/...` (see the top of this file),
* and four of those names are objectui package names too: `cli`, `core`,
* `sdui-parser` and `types`. An unanchored `/packages/<name>/` segment reads
* `@objectstack/core`'s `dist/index.js` — reached through `@objectstack/client`
* and rightly grouped into `vendor-objectstack` — as a module of objectui's
* `packages/core`, and the membership verdict then reports `framework`'s
* declared package as having strayed into a vendor chunk. That verdict was
* about attribution, not about where any objectui module landed. Standalone
* the anchor changes nothing: no other `packages/` directory is in the graph.
*/
export function workspaceModuleMatcher(packagesDir: string): RegExp {
const prefix = escapeRegExp(packagesDir.split(path.sep).join('/'));
return new RegExp('^' + prefix + '[\\\\/]([^\\\\/]+)[\\\\/](.*)$');
}
Loading