Files
twenty/packages/twenty-front-component-renderer/scripts/remote-dom/generators/host-registry.generator.ts
T
Paul RastoinandGitHub 37908114fc [SDK] Extract twenty-front-component-renderer outside of twenty-sdk ( 2.8MB ) (#19021)
Followup https://github.com/twentyhq/twenty/pull/19010

## Dependency diagram

```
┌─────────────────────┐
│     twenty-front    │
│   (React frontend)  │
└─────────┬───────────┘
          │ imports runtime:
          │   FrontComponentRenderer
          │   FrontComponentRendererWithSdkClient
          │   useFrontComponentExecutionContext
          ▼
┌──────────────────────────────────┐         ┌─────────────────────────┐
│ twenty-front-component-renderer  │────────▶│       twenty-sdk        │
│   (remote-dom host + worker)     │         │  (app developer SDK)    │
│                                  │         │                         │
│  imports from twenty-sdk:        │         │  Public API:            │
│   • types only:                  │         │   defineFrontComponent  │
│     FrontComponentExecutionContext│         │   navigate, closeSide…  │
│     NavigateFunction             │         │   useFrontComponent…    │
│     CloseSidePanelFunction       │         │   Command components    │
│     CommandConfirmation…         │         │   conditional avail.    │
│     OpenCommandConfirmation…     │         │                         │
│     EnqueueSnackbarFunction      │         │  Internal only:         │
│     etc.                         │         │   frontComponentHost…   │
│                                  │         │   front-component-build │
│  owns locally:                   │         │   esbuild plugins       │
│   • ALLOWED_HTML_ELEMENTS        │         │                         │
│   • EVENT_TO_REACT               │         └────────────┬────────────┘
│   • HTML_TAG_TO_CUSTOM_ELEMENT…  │                      │
│   • SerializedEventData          │                      │ types
│   • PropertySchema               │                      ▼
│   • frontComponentHostComm…      │         ┌─────────────────────────┐
│     (local ref to globalThis)    │         │     twenty-shared       │
│   • setFrontComponentExecution…  │         │  (common types/utils)   │
│     (local impl, same keys)      │         │   AppPath, SidePanelP…  │
│                                  │         │   EnqueueSnackbarParams │
└──────────────────────────────────┘         │   isDefined, …          │
          │                                  └─────────────────────────┘
          │ also depends on
          ▼
    twenty-shared (types)
    @remote-dom/* (runtime)
    @quilted/threads (runtime)
    react (runtime)
```

**Key points:**

- **`twenty-front`** depends on the renderer, **not** on `twenty-sdk`
directly (for rendering)
- **`twenty-front-component-renderer`** depends on `twenty-sdk` for
**types only** (function signatures, `FrontComponentExecutionContext`).
The runtime bridge (`frontComponentHostCommunicationApi`) is shared via
`globalThis` keys, not module imports
- **`twenty-sdk`** has no dependency on the renderer — clean one-way
dependency
- The renderer owns all remote-dom infrastructure (element schemas,
event mappings, custom element tags) that was previously leaking through
the SDK's public API
- The SDK's `./build` entry point was removed entirely (unused)
2026-03-30 17:06:06 +00:00

85 lines
2.4 KiB
TypeScript

import type { Project, SourceFile } from 'ts-morph';
import { isDefined } from 'twenty-shared/utils';
import { CUSTOM_ELEMENT_NAMES } from './constants';
import { type ComponentSchema } from './schemas';
const getCustomRendererImports = (
components: ComponentSchema[],
): Map<string, string[]> => {
const importsByPath = new Map<string, string[]>();
for (const component of components) {
if (
isDefined(component.customHostRenderer) &&
isDefined(component.customHostRendererPath)
) {
const existing =
importsByPath.get(component.customHostRendererPath) ?? [];
if (!existing.includes(component.customHostRenderer)) {
existing.push(component.customHostRenderer);
}
importsByPath.set(component.customHostRendererPath, existing);
}
}
return importsByPath;
};
const generateRegistryEntries = (components: ComponentSchema[]): string => {
const entries = components
.map((component) => {
if (isDefined(component.customHostRenderer)) {
return ` ['${component.customElementName}', createRemoteComponentRenderer(${component.customHostRenderer})],`;
}
return ` ['${component.customElementName}', createRemoteComponentRenderer(createHtmlHostWrapper('${component.htmlTag}'))],`;
})
.join('\n');
return `type ComponentRegistryValue =
| ReturnType<typeof createRemoteComponentRenderer>
| typeof RemoteFragmentRenderer;
export const componentRegistry: Map<string, ComponentRegistryValue> = new Map([
${entries}
['${CUSTOM_ELEMENT_NAMES.FRAGMENT}', RemoteFragmentRenderer],
]);`;
};
export const generateHostRegistry = (
project: Project,
components: ComponentSchema[],
): SourceFile => {
const sourceFile = project.createSourceFile(
'host-component-registry.ts',
'',
{ overwrite: true },
);
sourceFile.addImportDeclaration({
moduleSpecifier: '@remote-dom/react/host',
namedImports: ['RemoteFragmentRenderer', 'createRemoteComponentRenderer'],
});
sourceFile.addImportDeclaration({
moduleSpecifier: '../utils/createHtmlHostWrapper',
namedImports: ['createHtmlHostWrapper'],
});
const customRendererImports = getCustomRendererImports(components);
for (const [modulePath, namedImports] of customRendererImports) {
sourceFile.addImportDeclaration({
moduleSpecifier: modulePath,
namedImports,
});
}
sourceFile.addStatements(generateRegistryEntries(components));
return sourceFile;
};