Files
twenty/packages/twenty-front-component-renderer/scripts/remote-dom/generate-remote-dom-elements.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

126 lines
3.6 KiB
TypeScript

import * as prettier from '@prettier/sync';
import * as fs from 'fs';
import * as path from 'path';
import { IndentationText, Project, QuoteKind } from 'ts-morph';
import { fileURLToPath } from 'url';
import { ALLOWED_HTML_ELEMENTS } from '../../src/constants/AllowedHtmlElements';
import { COMMON_HTML_EVENTS } from '../../src/constants/CommonHtmlEvents';
import { HTML_COMMON_PROPERTIES } from '../../src/constants/HtmlCommonProperties';
import {
type ComponentSchema,
generateHostRegistry,
generateRemoteComponents,
generateRemoteElements,
HtmlElementConfigArrayZ,
OUTPUT_FILES,
} from './generators';
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
const PACKAGE_PATH = path.resolve(SCRIPT_DIR, '../..');
const SRC_PATH = path.join(PACKAGE_PATH, 'src');
const HOST_GENERATED_DIR = path.join(SRC_PATH, 'host/generated');
const REMOTE_GENERATED_DIR = path.join(SRC_PATH, 'remote/generated');
const extractHtmlTag = (tag: string): string => tag.slice(5);
const getHtmlElementSchemas = (): ComponentSchema[] => {
const result = HtmlElementConfigArrayZ.safeParse(ALLOWED_HTML_ELEMENTS);
if (!result.success) {
const details = result.error.issues
.map((issue) => ` - ${issue.path.join('.')}: ${issue.message}`)
.join('\n');
throw new Error(`Invalid HTML element configuration:\n${details}`);
}
return result.data.map((element) => ({
name: element.name,
customElementName: element.tag,
properties: {
...HTML_COMMON_PROPERTIES,
...element.properties,
},
events: element.events
? [...COMMON_HTML_EVENTS, ...element.events]
: COMMON_HTML_EVENTS,
htmlTag: extractHtmlTag(element.tag),
}));
};
const getUtilityComponentSchemas = (): ComponentSchema[] => [
{
name: 'RemoteStyle',
customElementName: 'remote-style',
properties: {
cssText: { type: 'string', optional: true },
styleKey: { type: 'string', optional: true },
},
events: [],
customHostRenderer: 'RemoteStyleRenderer',
customHostRendererPath: '../components/RemoteStyleRenderer',
},
];
const writeGeneratedFile = (
dir: string,
filename: string,
content: string,
): void => {
const filePath = path.join(dir, filename);
const formattedContent = prettier.format(content, {
parser: 'typescript',
filepath: filePath,
singleQuote: true,
trailingComma: 'all',
endOfLine: 'lf',
});
fs.writeFileSync(filePath, formattedContent, 'utf-8');
};
const main = (): void => {
const htmlElements = getHtmlElementSchemas();
const utilityComponents = getUtilityComponentSchemas();
const allComponents = [...htmlElements, ...utilityComponents];
fs.mkdirSync(HOST_GENERATED_DIR, { recursive: true });
fs.mkdirSync(REMOTE_GENERATED_DIR, { recursive: true });
const project = new Project({
manipulationSettings: {
indentationText: IndentationText.TwoSpaces,
quoteKind: QuoteKind.Single,
useTrailingCommas: true,
},
});
const hostRegistry = generateHostRegistry(project, allComponents);
writeGeneratedFile(
HOST_GENERATED_DIR,
OUTPUT_FILES.HOST_REGISTRY,
hostRegistry.getFullText(),
);
const remoteElements = generateRemoteElements(
project,
allComponents,
HTML_COMMON_PROPERTIES,
COMMON_HTML_EVENTS,
);
writeGeneratedFile(
REMOTE_GENERATED_DIR,
OUTPUT_FILES.REMOTE_ELEMENTS,
remoteElements.getFullText(),
);
const remoteComponents = generateRemoteComponents(project, allComponents);
writeGeneratedFile(
REMOTE_GENERATED_DIR,
OUTPUT_FILES.REMOTE_COMPONENTS,
remoteComponents.getFullText(),
);
};
main();