From 278ab75b97f02d272eb48bbf7569a11b0b695c4b Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Tue, 6 Oct 2026 19:09:27 +0200 Subject: [PATCH 1/2] perf(site): compile the engine from its sources so the first view loads only what it uses --- packages/engine/AGENTS.md | 2 ++ site/build/package-sources.mts | 9 ++++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/engine/AGENTS.md b/packages/engine/AGENTS.md index 5ec1a1a4ffe..6f8c3fdc46d 100644 --- a/packages/engine/AGENTS.md +++ b/packages/engine/AGENTS.md @@ -23,6 +23,8 @@ Do not import the application, legacy runtime, or object-specific configuration. - Every source file, test, tool, and generated source is limited to 600 physical lines, including blanks/comments. - `pnpm lint:packages` enforces the limit. Split code by responsibility; keep bulk prepared data outside source code. - Maintain README.md and CLAUDE.md as a symlink to this guide. Test behavior and package boundaries. +- The site's client build compiles the main entry from its sources and marks every module free of side effects + ([package-sources.mts](../../site/build/package-sources.mts)), so modules declare and export only. Numeric camera orientation math, heliocentric geometry and solar direction conversions live in `src/navigation/` and `src/solar-system/`, exported by the main entry. They depend on core numerics; no renderer, DOM or resource-loading implementation is imported. Flat solar matrices use the public `FlatMatrix3` name; navigation `Matrix3` remains nested. diff --git a/site/build/package-sources.mts b/site/build/package-sources.mts index f1a024bdb36..725cf242fad 100644 --- a/site/build/package-sources.mts +++ b/site/build/package-sources.mts @@ -3,16 +3,19 @@ import { fileURLToPath } from 'node:url'; import type { Plugin } from 'vite'; import rendererConfig from '../../packages/renderer/tsup.config.ts'; import objectsConfig from '../../packages/objects/tsup.config.ts'; +import engineConfig from '../../packages/engine/tsup.config.ts'; // tsup's shared chunks reach the client bundler as single modules, so one startup import pulled every // renderer module sharing that chunk (the universe, volume and star runtimes) into the first load, and every -// object contract the objects package's one bundle holds into the chunk of its first importer. +// object contract the objects package's one bundle holds into the chunk of its first importer. The engine's one bundle +// did the same: the startup cover's single `smoothstep` put the whole engine (29.6 KB of 50.8 KB) in every page's first load. // The client build compiles the same entries from their TypeScript sources and splits per module. -// Renderer and objects modules only declare and export, so an unused one is dropped rather than kept for its +// Renderer, objects and engine modules only declare and export, so an unused one is dropped rather than kept for its // load-time effects; worker entries keep theirs. Server rendering and Node tools keep the built packages. const packages: readonly { name: string; directory: string; config: unknown }[] = [ { name: '@cssearth/renderer', directory: 'renderer', config: rendererConfig }, - { name: '@cssearth/objects', directory: 'objects', config: objectsConfig }]; + { name: '@cssearth/objects', directory: 'objects', config: objectsConfig }, + { name: '@cssearth/engine', directory: 'engine', config: engineConfig }]; const built = packages.map(({ name, directory, config }) => { const root = fileURLToPath(new URL(`../../packages/${directory}/`, import.meta.url)); return { name, sources: `${root}src/`, entries: new Map(entryPairs(config, root).map(([output, source]) => [`${root}dist/${output}.js`, source])) }; From 3df0452be7f367ea72eb6b8cee0cc612b44653af Mon Sep 17 00:00:00 2001 From: Juan Cruz Fortunatti Date: Tue, 6 Oct 2026 19:09:27 +0200 Subject: [PATCH 2/2] perf(site): import the object directory statically in the scene adapter --- site/scene/object-adapter.mts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/site/scene/object-adapter.mts b/site/scene/object-adapter.mts index bc987bae2bf..522fdff666f 100644 --- a/site/scene/object-adapter.mts +++ b/site/scene/object-adapter.mts @@ -1,6 +1,8 @@ import { importPackagedObjectRuntime } from './scene-imports.mts'; import { parseObjectDescriptor, type ObjectDescriptor } from '@cssearth/objects'; import type { ObjectEntry } from '../directory/objects.mts'; +// The router that holds this adapter imports the directory statically, so a dynamic import here only added a facade chunk. +import { loadObject } from '../directory/object-directory.mts'; export const objectAdapter = Object.freeze({ /** A page's own prepared descriptor mounts without the registry, so the first body loads before it. */ @@ -12,8 +14,7 @@ export const objectAdapter = Object.freeze({ const { loadPackagedObject } = await importPackagedObjectRuntime(); mount = await loadPackagedObject(preparedDescriptor, signal); } else { - const directory = objects ? null : await import('../directory/object-directory.mts'); - const loaded = directory ? await directory.loadObject(objectId) : objects!.find(({ id }) => id === objectId); + const loaded = objects ? objects.find(({ id }) => id === objectId) : await loadObject(objectId); const objectRecord = loaded ?? null; if (!objectRecord) throw new Error(`Unknown cssEarth object: ${objectId ?? "unknown"}.`); mount = await objectRecord.loadScene(signal);