refactor(compiler)!: move component generation to core

This commit is contained in:
Rahim
2026-06-19 19:37:09 -07:00
parent b2c6b3489f
commit f64acdfd15
158 changed files with 2849 additions and 712 deletions
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { AirPlayButtonProps } from './airplay-button-core';
import { AirPlayButtonDataAttrs } from './airplay-button-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { AlertDialogProps } from './alert-dialog-core';
import { AlertDialogDataAttrs } from './alert-dialog-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { BufferingIndicatorProps } from './buffering-indicator-core';
import { BufferingIndicatorDataAttrs } from './buffering-indicator-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { CaptionsButtonProps } from './captions-button-core';
import { CaptionsButtonDataAttrs } from './captions-button-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { CaptionsRadioGroupProps } from './captions-radio-group-core';
import { CaptionsRadioGroupDataAttrs } from './captions-radio-group-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { CastButtonProps } from './cast-button-core';
import { CastButtonDataAttrs } from './cast-button-data-attrs';
@@ -1,5 +1,5 @@
// AUTO-GENERATED by `@videojs/compiler`. DO NOT EDIT.
import { createComponent } from '@videojs/compiler/jsx-runtime';
// AUTO-GENERATED by `@videojs/core/scripts/generate-components`. DO NOT EDIT.
import { createComponent } from '../../jsx-runtime';
import AirPlayButtonDef from './airplay-button/airplay-button-component';
import AlertDialogDef from './alert-dialog/alert-dialog-component';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import { ControlsDataAttrs } from './controls-data-attrs';
export default defineComponent()({
@@ -1,5 +1,5 @@
import { defineComponent } from '@videojs/compiler';
import type { AlertDialogProps } from '../alert-dialog/alert-dialog-core';
import { defineComponent } from '../manifest';
import { ErrorDialogDataAttrs } from './error-dialog-data-attrs';
export default defineComponent<AlertDialogProps>()({
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { FullscreenButtonProps } from './fullscreen-button-core';
import { FullscreenButtonDataAttrs } from './fullscreen-button-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { LiveButtonProps } from './live-button-core';
import { LiveButtonDataAttrs } from './live-button-data-attrs';
+45
View File
@@ -0,0 +1,45 @@
declare const __PROPS_BRAND__: unique symbol;
declare const __EMPTY_PROPS__: unique symbol;
export type EmptyProps = {
readonly [__EMPTY_PROPS__]?: never;
};
export interface ComponentManifest<
Props extends object = EmptyProps,
Parts extends readonly string[] = readonly string[],
PartProps extends Partial<Record<Parts[number], object>> = Partial<Record<Parts[number], object>>,
> {
name: string;
parts?: Parts;
dataAttrs?: Record<string, string>;
partProps?: PartProps;
readonly [__PROPS_BRAND__]?: Props;
}
export type InferProps<T> =
T extends ComponentManifest<infer Props, readonly string[], Partial<Record<string, object>>> ? Props : never;
export type InferParts<T> =
T extends ComponentManifest<object, infer Parts, Partial<Record<string, object>>>
? readonly string[] extends Parts
? never
: Parts[number]
: never;
export type InferPartProps<T, K extends string> =
T extends ComponentManifest<object, readonly string[], infer PartProps>
? K extends keyof PartProps
? PartProps[K]
: never
: never;
/** Define a component manifest. */
export function defineComponent<Props extends object = EmptyProps>() {
return <
const Parts extends readonly string[] = readonly string[],
const PartProps extends Partial<Record<Parts[number], object>> = Partial<Record<Parts[number], object>>,
>(
manifest: Omit<ComponentManifest<Props, Parts, PartProps>, typeof __PROPS_BRAND__>
): ComponentManifest<Props, Parts, PartProps> => manifest as ComponentManifest<Props, Parts, PartProps>;
}
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { MenuProps } from './menu-core';
import { MenuDataAttrs } from './menu-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { MuteButtonProps } from './mute-button-core';
import { MuteButtonDataAttrs } from './mute-button-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { PiPButtonProps } from './pip-button-core';
import { PiPButtonDataAttrs } from './pip-button-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { PlayButtonProps } from './play-button-core';
import { PlayButtonDataAttrs } from './play-button-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { PlaybackRateButtonProps } from './playback-rate-button-core';
import { PlaybackRateButtonDataAttrs } from './playback-rate-button-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { PlaybackRateRadioGroupProps } from './playback-rate-radio-group-core';
import { PlaybackRateRadioGroupDataAttrs } from './playback-rate-radio-group-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { PopoverProps } from './popover-core';
import { PopoverDataAttrs } from './popover-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import { PosterDataAttrs } from './poster-data-attrs';
export default defineComponent()({
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { QualityRadioGroupProps } from './quality-radio-group-core';
import { QualityRadioGroupDataAttrs } from './quality-radio-group-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { SeekButtonProps } from './seek-button-core';
import { SeekButtonDataAttrs } from './seek-button-data-attrs';
@@ -1,6 +1,6 @@
import { defineComponent } from '@videojs/compiler';
import type { SeekIndicatorProps } from '../input-feedback/seek-indicator-core';
import { SeekIndicatorDataAttrs } from '../input-feedback/seek-indicator-data-attrs';
import { defineComponent } from '../manifest';
export default defineComponent<SeekIndicatorProps>()({
name: 'SeekIndicator',
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { SliderProps } from './slider-core';
import { SliderDataAttrs } from './slider-data-attrs';
@@ -1,5 +1,5 @@
import { defineComponent } from '@videojs/compiler';
import type { StatusAnnouncerProps } from '../input-feedback/status-announcer-core';
import { defineComponent } from '../manifest';
export default defineComponent<StatusAnnouncerProps>()({
name: 'StatusAnnouncer',
@@ -1,6 +1,6 @@
import { defineComponent } from '@videojs/compiler';
import type { StatusIndicatorProps } from '../input-feedback/status-indicator-core';
import { StatusIndicatorDataAttrs } from '../input-feedback/status-indicator-data-attrs';
import { defineComponent } from '../manifest';
export default defineComponent<StatusIndicatorProps>()({
name: 'StatusIndicator',
@@ -0,0 +1,82 @@
/** @jsxImportSource ../../.. */
import { describe, it } from 'vitest';
import { createComponent, Slot } from '../../../jsx-runtime';
import { defineComponent } from '../manifest';
const PlayButton = createComponent(
defineComponent()({
name: 'PlayButton',
})
);
const Slider = createComponent(
defineComponent<{ orientation?: 'horizontal' | 'vertical'; thumbAlignment?: 'center' | 'edge' }>()({
name: 'Slider',
parts: ['Root', 'Track', 'Fill', 'Thumb'] as const,
})
);
const Time = createComponent(
defineComponent()({
name: 'Time',
parts: ['Value'] as const,
partProps: {
Value: {} as { type: 'current' | 'duration' },
},
})
);
describe('constrained JSX', () => {
it('accepts a single component', () => {
void (<PlayButton className="x" />);
});
it('rejects invalid props on a single component', () => {
// @ts-expect-error - className must be a string
void (<PlayButton className={5} />);
});
it('accepts compound parts inside their root', () => {
void (
<Slider.Root orientation="vertical" thumbAlignment="edge">
<Slider.Track>
<Slider.Fill />
</Slider.Track>
<Slider.Thumb />
</Slider.Root>
);
});
it('rejects invalid compound root props', () => {
// @ts-expect-error - `bogus` is not a valid orientation
void (<Slider.Root orientation="bogus" />);
});
it('rejects invalid Time.Value props', () => {
void (<Time.Value type="current" className="t" />);
// @ts-expect-error - `bogus` not in Time type union
void (<Time.Value type="bogus" />);
});
it('accepts div and span as layout intrinsics', () => {
void (
<div className="row">
<span className="label">hello</span>
</div>
);
// @ts-expect-error - arbitrary HTML attributes (id) are not allowed on layout intrinsics
void (<div id="foo" />);
});
it('accepts slot primitives', () => {
void (<Slot />);
void (
<Slot name="poster">
<span className="fallback" />
</Slot>
);
// @ts-expect-error - slot name must be a string
void (<Slot name={5} />);
});
});
@@ -0,0 +1,14 @@
{
"extends": "../../../../../../tsconfig.base.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "../../..",
"noEmit": true,
"isolatedDeclarations": false,
"composite": false,
"incremental": false,
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": ["**/*.test-d.tsx"]
}
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { ThumbnailProps } from './thumbnail-core';
import { ThumbnailDataAttrs } from './thumbnail-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { TimeSliderProps } from './time-slider-core';
import { TimeSliderDataAttrs } from './time-slider-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { TimeProps } from './time-core';
import { TimeDataAttrs } from './time-data-attrs';
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { TooltipProps } from './tooltip-core';
import { TooltipDataAttrs } from './tooltip-data-attrs';
@@ -1,6 +1,6 @@
import { defineComponent } from '@videojs/compiler';
import type { VolumeIndicatorProps } from '../input-feedback/volume-indicator-core';
import { VolumeIndicatorDataAttrs } from '../input-feedback/volume-indicator-data-attrs';
import { defineComponent } from '../manifest';
export default defineComponent<VolumeIndicatorProps>()({
name: 'VolumeIndicator',
@@ -1,4 +1,4 @@
import { defineComponent } from '@videojs/compiler';
import { defineComponent } from '../manifest';
import type { VolumeSliderProps } from './volume-slider-core';
import { VolumeSliderDataAttrs } from './volume-slider-data-attrs';
+2
View File
@@ -0,0 +1,2 @@
export * from './jsx-runtime';
export { jsx as jsxDEV } from './jsx-runtime';
+113
View File
@@ -0,0 +1,113 @@
import type { ComponentManifest, InferPartProps, InferParts, InferProps } from './core/ui/manifest';
export const VIDEOJS_NODE = Symbol.for('@videojs/node');
declare const EMPTY_PROPS_SYMBOL: unique symbol;
type EmptyProps = {
readonly [EMPTY_PROPS_SYMBOL]?: never;
};
export type ComponentType = string | Component<never> | typeof Fragment;
export interface ComponentNode {
readonly [VIDEOJS_NODE]: true;
readonly type: ComponentType;
readonly props: Record<string, unknown>;
readonly key: string | number | null;
}
export interface BaseProps {
className?: string | undefined;
children?: unknown;
}
export interface SlotProps {
name?: string | undefined;
children?: unknown;
}
export interface Component<Props extends object> {
(props: BaseProps & Props): ComponentNode;
readonly $$component: { name: string; part: string | null };
}
type PartComponentProps<M, K extends string> = K extends 'Root'
? InferProps<M>
: [NonNullable<InferPartProps<M, K>>] extends [never]
? EmptyProps
: NonNullable<InferPartProps<M, K>>;
type CompoundComponent<M> = {
[K in InferParts<M> & string]: Component<PartComponentProps<M, K>>;
};
export type CreateComponentResult<M> = [InferParts<M>] extends [never]
? Component<InferProps<M>>
: CompoundComponent<M>;
function makePart<Props extends object>(name: string, part: string | null): Component<Props> {
const fn = (_props: BaseProps & Props): ComponentNode => {
throw new Error(`@videojs/core: <${name}${part ? `.${part}` : ''}> can only be evaluated by the compiler.`);
};
Object.assign(fn, { $$component: { name, part } });
return fn as Component<Props>;
}
export const Slot = makePart<SlotProps>('Slot', null);
export function createComponent<
M extends ComponentManifest<object, readonly string[], Partial<Record<string, object>>>,
>(manifest: M): CreateComponentResult<M> {
const parts = manifest.parts ?? [];
if (parts.length === 0) {
return makePart(manifest.name, null) as CreateComponentResult<M>;
}
const compound: Record<string, Component<never>> = {};
for (const part of parts) {
compound[part] = makePart(manifest.name, part);
}
return compound as CreateComponentResult<M>;
}
function createNode(type: ComponentType, props: Record<string, unknown>, key?: string | number | null): ComponentNode {
return {
[VIDEOJS_NODE]: true,
type,
props,
key: key ?? null,
};
}
export function jsx(type: ComponentType, props: Record<string, unknown>, key?: string | number | null): ComponentNode {
return createNode(type, props, key);
}
export function jsxs(type: ComponentType, props: Record<string, unknown>, key?: string | number | null): ComponentNode {
return createNode(type, props, key);
}
export const Fragment: unique symbol = Symbol.for('@videojs/fragment') as never;
export namespace JSX {
export type Element = ComponentNode;
export interface ElementChildrenAttribute {
children: Record<string, never>;
}
export interface IntrinsicAttributes {
key?: string | number | undefined;
}
export interface IntrinsicElements {
div: BaseProps;
span: BaseProps;
}
}