From f64acdfd15f08631dc3a8bb3da709e438dd67d69 Mon Sep 17 00:00:00 2001 From: Rahim Date: Fri, 19 Jun 2026 19:37:09 -0700 Subject: [PATCH] refactor(compiler)!: move component generation to core --- apps/sandbox/app/styles.css | 5 - packages/compiler/package.json | 15 +- packages/compiler/src/cli.ts | 19 +- packages/compiler/src/config.ts | 25 -- packages/compiler/src/index.ts | 17 +- packages/compiler/src/load-config.ts | 15 +- packages/compiler/src/react/index.ts | 6 +- packages/compiler/src/tailwind/decompose.ts | 2 +- .../compiler/src/tailwind/design-system.ts | 2 +- packages/compiler/src/tailwind/emit.ts | 22 +- packages/compiler/src/tailwind/plugin.ts | 4 +- .../src/tailwind/tests/decompose.test.ts | 2 +- .../compiler/src/tailwind/tests/emit.test.ts | 6 +- packages/compiler/src/tests/compile.test.ts | 26 +- .../src/tests/fixtures/video-skin.tsx | 6 +- .../compiler/src/tests/integration.test.ts | 14 +- packages/compiler/src/transforms/jsx.ts | 75 ++++++ packages/compiler/tsconfig.preset.json | 7 - packages/compiler/tsdown.config.ts | 2 - packages/core/compiler.config.js | 8 - packages/core/components.config.js | 5 + packages/core/package.json | 15 +- .../scripts/generate-components.ts} | 146 ++++++++--- .../tests/generate-components.test.ts} | 71 ++++-- .../airplay-button-component.ts | 2 +- .../ui/alert-dialog/alert-dialog-component.ts | 2 +- .../buffering-indicator-component.ts | 2 +- .../captions-button-component.ts | 2 +- .../captions-radio-group-component.ts | 2 +- .../ui/cast-button/cast-button-component.ts | 2 +- .../core/src/core/ui/components.generated.ts | 4 +- .../core/ui/controls/controls-component.ts | 2 +- .../ui/error-dialog/error-dialog-component.ts | 2 +- .../fullscreen-button-component.ts | 2 +- .../ui/live-button/live-button-component.ts | 2 +- .../src/core/ui/manifest.ts} | 31 +-- .../core/src/core/ui/menu/menu-component.ts | 2 +- .../ui/mute-button/mute-button-component.ts | 2 +- .../ui/pip-button/pip-button-component.ts | 2 +- .../ui/play-button/play-button-component.ts | 2 +- .../playback-rate-button-component.ts | 2 +- .../playback-rate-radio-group-component.ts | 2 +- .../src/core/ui/popover/popover-component.ts | 2 +- .../src/core/ui/poster/poster-component.ts | 2 +- .../quality-radio-group-component.ts | 2 +- .../ui/seek-button/seek-button-component.ts | 2 +- .../seek-indicator-component.ts | 2 +- .../src/core/ui/slider/slider-component.ts | 2 +- .../status-announcer-component.ts | 2 +- .../status-indicator-component.ts | 2 +- .../src/core/ui/tests/jsx-runtime.test-d.tsx} | 39 ++- .../src/core/ui}/tests/tsconfig.json | 4 +- .../core/ui/thumbnail/thumbnail-component.ts | 2 +- .../ui/time-slider/time-slider-component.ts | 2 +- .../core/src/core/ui/time/time-component.ts | 2 +- .../src/core/ui/tooltip/tooltip-component.ts | 2 +- .../volume-indicator-component.ts | 2 +- .../volume-slider/volume-slider-component.ts | 2 +- .../{compiler => core}/src/jsx-dev-runtime.ts | 0 .../{compiler => core}/src/jsx-runtime.ts | 19 +- packages/core/tsconfig.json | 2 +- packages/core/tsdown.config.ts | 11 +- packages/core/vitest.config.ts | 20 ++ .../html/src/define/audio/minimal-skin.css | 1 - .../src/define/audio/minimal-skin.tailwind.ts | 23 +- .../html/src/define/audio/minimal-skin.ts | 4 +- packages/html/src/define/audio/skin.css | 1 - .../html/src/define/audio/skin.tailwind.ts | 23 +- packages/html/src/define/audio/skin.ts | 4 +- .../src/define/live-audio/minimal-skin.css | 1 - .../live-audio/minimal-skin.tailwind.ts | 23 +- .../src/define/live-audio/minimal-skin.ts | 4 +- packages/html/src/define/live-audio/skin.css | 1 - .../src/define/live-audio/skin.tailwind.ts | 23 +- packages/html/src/define/live-audio/skin.ts | 4 +- .../src/define/live-video/minimal-skin.css | 1 - .../live-video/minimal-skin.tailwind.ts | 60 ++--- .../src/define/live-video/minimal-skin.ts | 4 +- packages/html/src/define/live-video/skin.css | 1 - .../src/define/live-video/skin.tailwind.ts | 60 ++--- packages/html/src/define/live-video/skin.ts | 4 +- packages/html/src/define/skin-element.ts | 3 +- .../html/src/define/video/minimal-skin.css | 1 - .../src/define/video/minimal-skin.tailwind.ts | 53 ++-- .../html/src/define/video/minimal-skin.ts | 4 +- packages/html/src/define/video/skin.css | 1 - .../html/src/define/video/skin.tailwind.ts | 53 ++-- packages/html/src/define/video/skin.ts | 4 +- ...ompiler.config.js => components.config.js} | 20 +- packages/icons/package.json | 2 +- packages/icons/scripts/build.ts | 19 +- packages/icons/src/__generated__/default.ts | 4 +- packages/icons/src/__generated__/minimal.ts | 4 +- .../react/src/presets/audio/minimal-skin.css | 1 - .../presets/audio/minimal-skin.tailwind.tsx | 23 +- packages/react/src/presets/audio/skin.css | 1 - .../react/src/presets/audio/skin.tailwind.tsx | 23 +- .../react/src/presets/background/skin.css | 1 - .../src/presets/live-audio/minimal-skin.css | 1 - .../live-audio/minimal-skin.tailwind.tsx | 23 +- .../react/src/presets/live-audio/skin.css | 1 - .../src/presets/live-audio/skin.tailwind.tsx | 23 +- .../src/presets/live-video/minimal-skin.css | 1 - .../live-video/minimal-skin.tailwind.tsx | 66 ++--- .../react/src/presets/live-video/skin.css | 1 - .../src/presets/live-video/skin.tailwind.tsx | 66 ++--- .../react/src/presets/video/minimal-skin.css | 1 - .../presets/video/minimal-skin.tailwind.tsx | 53 ++-- packages/react/src/presets/video/skin.css | 1 - .../react/src/presets/video/skin.tailwind.tsx | 53 ++-- .../src/default/tailwind/audio.tailwind.ts | 121 ++++++++++ .../src/default/tailwind/components/badge.ts | 2 + .../default/tailwind/components/buffering.ts | 4 + .../tailwind/components/button-group.ts | 3 + .../src/default/tailwind/components/button.ts | 36 +++ .../default/tailwind/components/container.ts | 20 ++ .../default/tailwind/components/controls.ts | 12 + .../src/default/tailwind/components/error.ts | 20 ++ .../src/default/tailwind/components/icon.ts | 11 + .../tailwind/components/input-feedback.ts | 151 ++++++++++++ .../src/default/tailwind/components/menu.ts | 82 +++++++ .../default/tailwind/components/overlay.ts | 23 ++ .../tailwind/components/playback-rate.ts | 3 + .../src/default/tailwind/components/popup.ts | 41 ++++ .../src/default/tailwind/components/poster.ts | 9 + .../src/default/tailwind/components/reset.ts | 9 + .../src/default/tailwind/components/seek.ts | 5 + .../src/default/tailwind/components/slider.ts | 86 +++++++ .../default/tailwind/components/surface.ts | 8 + .../default/tailwind/components/thumbnail.ts | 18 ++ .../src/default/tailwind/components/time.ts | 5 + .../src/default/tailwind/video.tailwind.ts | 227 ++++++++++++++++++ .../src/minimal/tailwind/audio.tailwind.ts | 123 ++++++++++ .../src/minimal/tailwind/components/badge.ts | 2 + .../minimal/tailwind/components/buffering.ts | 2 + .../tailwind/components/button-group.ts | 3 + .../src/minimal/tailwind/components/button.ts | 38 +++ .../minimal/tailwind/components/container.ts | 20 ++ .../minimal/tailwind/components/controls.ts | 14 ++ .../src/minimal/tailwind/components/error.ts | 21 ++ .../src/minimal/tailwind/components/icon.ts | 11 + .../tailwind/components/input-feedback.ts | 151 ++++++++++++ .../src/minimal/tailwind/components/menu.ts | 80 ++++++ .../minimal/tailwind/components/overlay.ts | 23 ++ .../tailwind/components/playback-rate.ts | 3 + .../src/minimal/tailwind/components/popup.ts | 43 ++++ .../src/minimal/tailwind/components/poster.ts | 9 + .../src/minimal/tailwind/components/reset.ts | 9 + .../src/minimal/tailwind/components/seek.ts | 5 + .../src/minimal/tailwind/components/slider.ts | 79 ++++++ .../minimal/tailwind/components/thumbnail.ts | 13 + .../src/minimal/tailwind/components/time.ts | 9 + .../src/minimal/tailwind/video.tailwind.ts | 218 +++++++++++++++++ packages/skins/src/shared/tailwind.css | 56 +++++ .../skins/src/shared/tailwind/icon-state.ts | 65 +++++ packages/skins/tsdown.config.ts | 25 +- pnpm-lock.yaml | 186 +++++++++----- site/scripts/build-ejected-skins.ts | 3 +- 158 files changed, 2849 insertions(+), 712 deletions(-) create mode 100644 packages/compiler/src/transforms/jsx.ts delete mode 100644 packages/compiler/tsconfig.preset.json delete mode 100644 packages/core/compiler.config.js create mode 100644 packages/core/components.config.js rename packages/{compiler/src/generate.ts => core/scripts/generate-components.ts} (54%) rename packages/{compiler/src/tests/generate.test.ts => core/scripts/tests/generate-components.test.ts} (69%) rename packages/{compiler/src/define-component.ts => core/src/core/ui/manifest.ts} (62%) rename packages/{compiler/src/tests/jsx-types.test-d.tsx => core/src/core/ui/tests/jsx-runtime.test-d.tsx} (58%) rename packages/{compiler/src => core/src/core/ui}/tests/tsconfig.json (73%) rename packages/{compiler => core}/src/jsx-dev-runtime.ts (100%) rename packages/{compiler => core}/src/jsx-runtime.ts (86%) rename packages/icons/{compiler.config.js => components.config.js} (58%) create mode 100644 packages/skins/src/default/tailwind/audio.tailwind.ts create mode 100644 packages/skins/src/default/tailwind/components/badge.ts create mode 100644 packages/skins/src/default/tailwind/components/buffering.ts create mode 100644 packages/skins/src/default/tailwind/components/button-group.ts create mode 100644 packages/skins/src/default/tailwind/components/button.ts create mode 100644 packages/skins/src/default/tailwind/components/container.ts create mode 100644 packages/skins/src/default/tailwind/components/controls.ts create mode 100644 packages/skins/src/default/tailwind/components/error.ts create mode 100644 packages/skins/src/default/tailwind/components/icon.ts create mode 100644 packages/skins/src/default/tailwind/components/input-feedback.ts create mode 100644 packages/skins/src/default/tailwind/components/menu.ts create mode 100644 packages/skins/src/default/tailwind/components/overlay.ts create mode 100644 packages/skins/src/default/tailwind/components/playback-rate.ts create mode 100644 packages/skins/src/default/tailwind/components/popup.ts create mode 100644 packages/skins/src/default/tailwind/components/poster.ts create mode 100644 packages/skins/src/default/tailwind/components/reset.ts create mode 100644 packages/skins/src/default/tailwind/components/seek.ts create mode 100644 packages/skins/src/default/tailwind/components/slider.ts create mode 100644 packages/skins/src/default/tailwind/components/surface.ts create mode 100644 packages/skins/src/default/tailwind/components/thumbnail.ts create mode 100644 packages/skins/src/default/tailwind/components/time.ts create mode 100644 packages/skins/src/default/tailwind/video.tailwind.ts create mode 100644 packages/skins/src/minimal/tailwind/audio.tailwind.ts create mode 100644 packages/skins/src/minimal/tailwind/components/badge.ts create mode 100644 packages/skins/src/minimal/tailwind/components/buffering.ts create mode 100644 packages/skins/src/minimal/tailwind/components/button-group.ts create mode 100644 packages/skins/src/minimal/tailwind/components/button.ts create mode 100644 packages/skins/src/minimal/tailwind/components/container.ts create mode 100644 packages/skins/src/minimal/tailwind/components/controls.ts create mode 100644 packages/skins/src/minimal/tailwind/components/error.ts create mode 100644 packages/skins/src/minimal/tailwind/components/icon.ts create mode 100644 packages/skins/src/minimal/tailwind/components/input-feedback.ts create mode 100644 packages/skins/src/minimal/tailwind/components/menu.ts create mode 100644 packages/skins/src/minimal/tailwind/components/overlay.ts create mode 100644 packages/skins/src/minimal/tailwind/components/playback-rate.ts create mode 100644 packages/skins/src/minimal/tailwind/components/popup.ts create mode 100644 packages/skins/src/minimal/tailwind/components/poster.ts create mode 100644 packages/skins/src/minimal/tailwind/components/reset.ts create mode 100644 packages/skins/src/minimal/tailwind/components/seek.ts create mode 100644 packages/skins/src/minimal/tailwind/components/slider.ts create mode 100644 packages/skins/src/minimal/tailwind/components/thumbnail.ts create mode 100644 packages/skins/src/minimal/tailwind/components/time.ts create mode 100644 packages/skins/src/minimal/tailwind/video.tailwind.ts create mode 100644 packages/skins/src/shared/tailwind.css create mode 100644 packages/skins/src/shared/tailwind/icon-state.ts diff --git a/apps/sandbox/app/styles.css b/apps/sandbox/app/styles.css index 33d1ce13..7dd52170 100644 --- a/apps/sandbox/app/styles.css +++ b/apps/sandbox/app/styles.css @@ -1,9 +1,4 @@ @import "tailwindcss"; - -/* NOTE: This is using a private export. You should import from either: -- "@videojs/html/tailwind.css" for HTML skins -- "@videojs/react/tailwind.css" for React skins -*/ @import "@videojs/skins/shared/tailwind.css"; @source "../app"; diff --git a/packages/compiler/package.json b/packages/compiler/package.json index cc78ba7c..471e5281 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -15,14 +15,6 @@ "types": "./dist/index.d.ts", "default": "./dist/index.js" }, - "./jsx-runtime": { - "types": "./dist/jsx-runtime.d.ts", - "default": "./dist/jsx-runtime.js" - }, - "./jsx-dev-runtime": { - "types": "./dist/jsx-dev-runtime.d.ts", - "default": "./dist/jsx-dev-runtime.js" - }, "./vite": { "types": "./dist/plugins/vite.d.ts", "default": "./dist/plugins/vite.js" @@ -46,15 +38,13 @@ "./tailwind": { "types": "./dist/tailwind/index.d.ts", "default": "./dist/tailwind/index.js" - }, - "./tsconfig.preset.json": "./tsconfig.preset.json" + } }, "bin": { "vjs": "./dist/cli.js" }, "files": [ - "dist", - "tsconfig.preset.json" + "dist" ], "scripts": { "build": "tsdown", @@ -80,7 +70,6 @@ "typescript": "^6.0.2" }, "devDependencies": { - "@videojs/core": "workspace:*", "tsdown": "^0.21.4", "vite": "^8.0.0", "vitest": "^4.1.0" diff --git a/packages/compiler/src/cli.ts b/packages/compiler/src/cli.ts index 226af621..83318e63 100644 --- a/packages/compiler/src/cli.ts +++ b/packages/compiler/src/cli.ts @@ -10,8 +10,7 @@ import { formatCompilerDiagnosticJsonLine, formatDiagnosticSummaryJsonLine, } from './diagnostics'; -import { generate } from './generate'; -import { CONFIG_FILENAMES, loadConfig } from './load-config'; +import { loadConfig } from './load-config'; interface ParsedArgs { command: string | undefined; @@ -54,7 +53,6 @@ function printHelp(): void { 'Usage: vjs [options]', '', 'Commands:', - ' generate Generate components from the configured manifests', ' compile Compile a JSX file', '', 'Options:', @@ -67,18 +65,6 @@ function printHelp(): void { ); } -async function runGenerate(configOverride: string | undefined): Promise { - const cwd = process.cwd(); - const loaded = await loadConfig(cwd, configOverride); - if (!loaded) { - throw new Error( - `No compiler config found in ${cwd}. Expected one of: ${CONFIG_FILENAMES.join(', ')}, or pass --config .` - ); - } - const result = await generate(loaded.config); - process.stdout.write(`Wrote ${result.outputPath}\n`); -} - async function runCompile( positional: readonly string[], configOverride: string | undefined, @@ -128,9 +114,6 @@ async function main(): Promise { } switch (command) { - case 'generate': - await runGenerate(configOverride); - return; case 'compile': await runCompile(positional, configOverride, outFile, diagnosticsFormat); return; diff --git a/packages/compiler/src/config.ts b/packages/compiler/src/config.ts index c06ca5be..a5de1d34 100644 --- a/packages/compiler/src/config.ts +++ b/packages/compiler/src/config.ts @@ -41,30 +41,6 @@ export interface StylePipeline { setup(context: CompilerContext): CompilerPipelineStep | Promise; } -/** - * Bulk-defined component entry. Globs `files`, derives each component's name - * from the filename (extension stripped) via `name(stem)`, and inline-emits - * `createComponent({ name })` calls. Components defined this way are - * BaseProps-only — to type Props, parts, or partProps, use a manifest file. - */ -export interface BulkComponentEntry { - files: string; - name: (filename: string) => string; -} - -export type ComponentEntry = string | BulkComponentEntry; - -export interface GenerateConfig { - /** - * Component sources. Each entry is either: - * - a glob string matching `*-component.ts` manifest files, or - * - a `{ files, name }` object that bulk-defines components from arbitrary files. - */ - components: readonly ComponentEntry[]; - /** Path the generator writes the components file to. */ - output: string; -} - /** * Per-target compile configuration. Currently only `react` is shipped, but * the shape is extensible for `html`/etc. @@ -84,7 +60,6 @@ export interface CompilerTarget { export interface CompilerConfig { files?: readonly string[] | undefined; - generate?: GenerateConfig; target?: CompilerTarget | undefined; styles?: StylePipeline | undefined; } diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index 1cff2a5b..f5ff93e0 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -12,13 +12,6 @@ export { react, type StylePipeline, } from './config'; -export { - type ComponentManifest, - defineComponent, - type InferPartProps, - type InferParts, - type InferProps, -} from './define-component'; export { compilerDiagnosticToJsonEvent, type DiagnosticFormat, @@ -38,6 +31,14 @@ export { mapLogLevelToString, shouldUseColor, } from './diagnostics'; -export { type GenerateResult, generate } from './generate'; export { type TailwindMode, type TailwindOptions, tailwind } from './tailwind'; export type { ImportRef, ImportRule } from './transforms/imports'; +export { + accessPath, + type JsxChildReplacement, + jsxExpression, + propertyAccess, + type ReplaceJsxChildOptions, + readStringAttribute, + replaceJsxChild, +} from './transforms/jsx'; diff --git a/packages/compiler/src/load-config.ts b/packages/compiler/src/load-config.ts index 4660c5ae..f9f0c4f9 100644 --- a/packages/compiler/src/load-config.ts +++ b/packages/compiler/src/load-config.ts @@ -43,23 +43,10 @@ export async function loadConfigFile(configPath: string): Promise { const configPath = findConfig(cwd, override); return configPath ? loadConfigFile(configPath) : null; } - -function resolveConfigPaths(config: CompilerConfig, configPath: string): CompilerConfig { - if (!config.generate) return config; - const base = dirname(configPath); - const { output, components } = config.generate; - return { - ...config, - generate: { - components, - output: isAbsolute(output) ? output : resolve(base, output), - }, - }; -} diff --git a/packages/compiler/src/react/index.ts b/packages/compiler/src/react/index.ts index 4a8b8b04..bda5a249 100644 --- a/packages/compiler/src/react/index.ts +++ b/packages/compiler/src/react/index.ts @@ -1,8 +1,8 @@ /** * React-target plugins for `@videojs/compiler`. Houses the framework-pattern - * helpers that lower constrained-JSX skin idioms into React's render-prop - * slot composition idiom. Re-exports `replace` and `wrap` for convenience so - * a config can import everything from one subpath. + * helpers that lower constrained JSX into React-friendly component shapes. + * Re-exports `replace` and `wrap` for convenience so a config can import + * everything from one subpath. */ export { type ReplaceOptions, replace } from '../transforms/replace'; diff --git a/packages/compiler/src/tailwind/decompose.ts b/packages/compiler/src/tailwind/decompose.ts index 9450a185..40817c33 100644 --- a/packages/compiler/src/tailwind/decompose.ts +++ b/packages/compiler/src/tailwind/decompose.ts @@ -46,7 +46,7 @@ export interface UtilityCss { variants: readonly Variant[]; /** * `@property` registrations Tailwind emitted for this utility. These supply - * the typed defaults for `--tw-*` slots referenced (but not set) by the + * the typed defaults for `--tw-*` variables referenced (but not set) by the * declarations — see `emitCss`'s `properties` option. */ properties?: readonly PropertyRule[]; diff --git a/packages/compiler/src/tailwind/design-system.ts b/packages/compiler/src/tailwind/design-system.ts index 6756732b..644d68b1 100644 --- a/packages/compiler/src/tailwind/design-system.ts +++ b/packages/compiler/src/tailwind/design-system.ts @@ -23,7 +23,7 @@ export interface DesignSystem { * Resolve a `@theme` variable (e.g. `--spacing`, `--color-white`) to its * value, or `undefined` if the theme doesn't define it. Used to emit a * self-contained theme block for the variables compiled rules reference. - * Returns `undefined` for `@property`-registered slots like `--tw-*`. + * Returns `undefined` for `@property`-registered variables like `--tw-*`. */ resolveThemeVar(name: string): string | undefined; } diff --git a/packages/compiler/src/tailwind/emit.ts b/packages/compiler/src/tailwind/emit.ts index 139134c7..7f22a34f 100644 --- a/packages/compiler/src/tailwind/emit.ts +++ b/packages/compiler/src/tailwind/emit.ts @@ -62,7 +62,7 @@ export interface EmitCssOptions { /** * Inline matching CSS custom properties into the values that reference * them, then drop the declarations themselves. Useful for stripping - * Tailwind's internal `--tw-*` slots from the final output. + * Tailwind's internal `--tw-*` registered variables from the final output. * * - `true` — inline `--tw-*` (regex `/^--tw-/`). * - `RegExp` — inline any `--name` whose name (excluding the leading @@ -81,7 +81,7 @@ export interface EmitCssOptions { * output references but doesn't itself declare, so the CSS resolves without a * separate Tailwind theme/preflight on the page. Typically * `design.resolveThemeVar`. Returns `undefined` to leave a variable alone - * (e.g. `@property`-registered `--tw-*` slots). + * (e.g. `@property`-registered `--tw-*` variables). */ resolveThemeVar?: (name: string) => string | undefined; /** @@ -90,7 +90,7 @@ export interface EmitCssOptions { */ themeSelector?: string; /** - * How to handle Tailwind's `@property`-registered slots (e.g. `--tw-content`, + * How to handle Tailwind's `@property`-registered variables (e.g. `--tw-content`, * `--tw-shadow`) that the compiled rules reference but never set locally. * Without this they resolve to nothing and break — e.g. `content: * var(--tw-content)` suppresses the `::after`/`::before` box. @@ -112,12 +112,12 @@ export interface PropertyDef { export interface RegisteredPropertiesOptions { /** - * - `'emit'` — emit `@property` rules for referenced slots, preserving + * - `'emit'` — emit `@property` rules for referenced variables, preserving * Tailwind's typed defaults (relies on browser `@property` support). - * - `'inline'` — substitute each slot's `initial-value` into the values that + * - `'inline'` — substitute each variable's `initial-value` into the values that * reference it (and drop any `--tw-*` setter declarations), so the output * is fully self-contained. This is a superset of `inlineVars` for the - * matched slots. + * matched variables. */ mode: 'emit' | 'inline'; /** Which property names to handle. Defaults to `inlineVars`'s matcher, else `/^--tw-/`. */ @@ -126,7 +126,7 @@ export interface RegisteredPropertiesOptions { * Override or supply a property's definition. Receives the name and the * definition captured from Tailwind's output (if any); return a new * definition (merged in), or `undefined` to keep the captured one. Lets you - * fix an initial-value or register a slot Tailwind didn't. + * fix an initial-value or register a variable Tailwind didn't. */ resolve?: (name: string, captured: PropertyDef | undefined) => PropertyDef | undefined; } @@ -148,7 +148,7 @@ export async function emitCss(opts: EmitCssOptions): Promise { const hoist = opts.hoist === false ? undefined : opts.hoist; const inlineVars = normalizeInlineMatcher(opts.inlineVars); - // Registered `@property` (--tw-*) handling. In 'inline' mode the slots' + // Registered `@property` (--tw-*) handling. In 'inline' mode the variables' // initial-values seed the inline pass as fallbacks (and the matcher widens to // cover them, even when `inlineVars` was off). In 'emit' mode they're left in // place to be emitted as `@property` rules. @@ -264,7 +264,7 @@ function collectDefinedVars(css: string): Set { } /* ───────────────────────────────────────────────────────────────────────── - * Registered `@property` slots + * Registered `@property` variables * ───────────────────────────────────────────────────────────────────────── */ /** Aggregate the `@property` defs captured across every rule (first wins). */ @@ -296,7 +296,7 @@ function buildFallbackSetters( } /** - * Build `@property` rules for every matching slot the CSS references but + * Build `@property` rules for every matching variable the CSS references but * doesn't itself declare. Descriptors default to `syntax: "*"` / `inherits: * false` when a resolved def omits them. */ @@ -591,7 +591,7 @@ function applyInline( // Effective setters, narrowest last: registered `@property` initial-values // (lowest), then hoist root, then rule-local. Initial-values resolve - // references to slots no rule ever sets (e.g. `content: var(--tw-content)`). + // references to variables no rule ever sets (e.g. `content: var(--tw-content)`). const setters = new Map(fallbackSetters); for (const [name, value] of rootSetters) setters.set(name, value); for (const [name, value] of localSetters) setters.set(name, value); diff --git a/packages/compiler/src/tailwind/plugin.ts b/packages/compiler/src/tailwind/plugin.ts index aae4902d..83b0440f 100644 --- a/packages/compiler/src/tailwind/plugin.ts +++ b/packages/compiler/src/tailwind/plugin.ts @@ -78,7 +78,7 @@ export interface TailwindOptions { * Inline matching CSS custom properties into their consumers, dropping * the matching declarations. Same shape as `EmitCssOptions['inlineVars']`: * - * - `true` — inline `--tw-*` (Tailwind's internal slots). + * - `true` — inline `--tw-*` (Tailwind's internal registered variables). * - `RegExp` — inline any `--name` matching. * - omitted — no inlining. * @@ -86,7 +86,7 @@ export interface TailwindOptions { */ inlineVars?: true | RegExp; /** - * Handle Tailwind's `@property`-registered slots (`--tw-content`, etc.) that + * Handle Tailwind's `@property`-registered variables (`--tw-content`, etc.) that * are referenced but never set, so the output isn't broken (e.g. * `content: var(--tw-content)`). See `RegisteredPropertiesOptions` — choose * `'emit'` (ship `@property` rules) or `'inline'` (bake initial-values in), diff --git a/packages/compiler/src/tailwind/tests/decompose.test.ts b/packages/compiler/src/tailwind/tests/decompose.test.ts index ebac3328..55662dd6 100644 --- a/packages/compiler/src/tailwind/tests/decompose.test.ts +++ b/packages/compiler/src/tailwind/tests/decompose.test.ts @@ -94,7 +94,7 @@ describe('decompose — variants', () => { }); describe('decompose — @property registrations', () => { - it('captures the @property rule Tailwind appends for a slot', () => { + it('captures the @property rule Tailwind appends for a registered variable', () => { const r = decompose('before:content-["x"]', design); expect(r).not.toBeNull(); const content = r!.properties?.find((p) => p.name === '--tw-content'); diff --git a/packages/compiler/src/tailwind/tests/emit.test.ts b/packages/compiler/src/tailwind/tests/emit.test.ts index 6f76b6be..6b6fdf65 100644 --- a/packages/compiler/src/tailwind/tests/emit.test.ts +++ b/packages/compiler/src/tailwind/tests/emit.test.ts @@ -533,7 +533,7 @@ describe('emitCss — theme variables', () => { }); }); -describe('emitCss — registered @property slots', () => { +describe('emitCss — registered @property variables', () => { // The `after:absolute` pattern: an `::after` rule that references // `--tw-content` but never sets it, relying on Tailwind's @property default. const contentRule = (): CompiledRule => ({ @@ -591,7 +591,7 @@ describe('emitCss — registered @property slots', () => { ); }); - it('honours the match filter (leaves non-matching slots alone)', async () => { + it('honours the match filter (leaves non-matching variables alone)', async () => { const out = await emitCss({ rules: [contentRule()], properties: { mode: 'inline', match: /^--brand-/ }, @@ -600,7 +600,7 @@ describe('emitCss — registered @property slots', () => { expect(out.css).toMatch(/var\(--tw-content\)/); }); - it('leaves slots untouched when no properties option is given (back-compat)', async () => { + it('leaves variables untouched when no properties option is given (back-compat)', async () => { const out = await emitCss({ rules: [contentRule()] }); if (out.kind !== 'merged') throw new Error('expected merged'); expect(out.css).toMatch(/var\(--tw-content\)/); diff --git a/packages/compiler/src/tests/compile.test.ts b/packages/compiler/src/tests/compile.test.ts index 3a0d364e..04339c26 100644 --- a/packages/compiler/src/tests/compile.test.ts +++ b/packages/compiler/src/tests/compile.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { compile, type ReactTargetOptions, react } from '..'; +import { accessPath, compile, jsxExpression, type ReactTargetOptions, react, replaceJsxChild } from '..'; import { parse } from '../ast'; import { anyTag, byTag, hasChild } from '../matchers'; import { addProp, childAsProp, replace, wrap } from '../react'; @@ -53,14 +53,14 @@ describe('compile (transformImports — bare-string rule)', () => { describe('compile (transformImports — function rule)', () => { it('rewrites per-identifier source and bucket-merges by resolved target', async () => { - const source = `import { PlayButton, MuteButton } from '@videojs/core/components';\nconst _ = [PlayButton, MuteButton];`; + const source = `import { Alpha, Beta } from '@fixture/components';\nconst _ = [Alpha, Beta];`; const { code } = await compileReact(source, { imports: { - '@videojs/core/components': (name) => ({ source: `./ui/${name.toLowerCase()}`, name }), + '@fixture/components': (name) => ({ source: `./ui/${name.toLowerCase()}`, name }), }, }); - expect(code).toContain(`import { PlayButton } from "./ui/playbutton"`); - expect(code).toContain(`import { MuteButton } from "./ui/mutebutton"`); + expect(code).toContain(`import { Alpha } from "./ui/alpha"`); + expect(code).toContain(`import { Beta } from "./ui/beta"`); }); it('renames identifiers when the rule returns a different `name`', async () => { @@ -139,6 +139,22 @@ describe('childAsProp', () => { }); }); +describe('replaceJsxChild', () => { + it('replaces matched JSX children with expression helpers', async () => { + const source = `function App({ values }){ return ; }`; + const { code } = await compileReact(source, { + transforms: [ + replaceJsxChild({ + match: byTag('Token'), + replace: (_node, factory) => jsxExpression(factory, accessPath(factory, 'values', 'poster-image')), + }), + ], + }); + + expect(collapse(code)).toContain(collapse(`{values["poster-image"]}`)); + }); +}); + describe('addProp', () => { it('emits a JSX value by default and adds the import', async () => { const source = `function App(){ return ; }`; diff --git a/packages/compiler/src/tests/fixtures/video-skin.tsx b/packages/compiler/src/tests/fixtures/video-skin.tsx index 894751c6..51da6a34 100644 --- a/packages/compiler/src/tests/fixtures/video-skin.tsx +++ b/packages/compiler/src/tests/fixtures/video-skin.tsx @@ -20,7 +20,7 @@ import { TimeSlider, Tooltip, VolumeSlider, -} from '@videojs/core/components'; +} from '@fixture/components'; import { CaptionsOffIcon, CaptionsOnIcon, @@ -38,7 +38,7 @@ import { VolumeHighIcon, VolumeLowIcon, VolumeOffIcon, -} from '@videojs/icons/components'; +} from '@fixture/icons/components'; import { cn } from '@videojs/utils/style'; import { video as styles } from '../tailwind'; @@ -52,7 +52,7 @@ export interface VideoSkinProps { export function VideoSkin({ className }: VideoSkinProps) { return ( - + diff --git a/packages/compiler/src/tests/integration.test.ts b/packages/compiler/src/tests/integration.test.ts index 714d58f6..c8cef653 100644 --- a/packages/compiler/src/tests/integration.test.ts +++ b/packages/compiler/src/tests/integration.test.ts @@ -12,7 +12,7 @@ const skinSource = resolve(__dirname, 'fixtures/video-skin.tsx'); /** * End-to-end smoke test: feed a representative constrained-JSX video skin * (vendored under `fixtures/`) through `compile()` with the same shape - * `@videojs/react`'s build hook uses, and sanity-check the output's structural + * a React package build hook uses, and sanity-check the output's structural * shape. Snapshot-style assertions intentionally use `.toContain` over a full * snapshot to keep the test resilient to incidental whitespace differences * from the TS printer. @@ -22,11 +22,11 @@ describe('integration: default/video skin → React', () => { let code = ''; const imports: Record = { - '@videojs/core/components': (name) => ({ + '@fixture/components': (name) => ({ source: `./src/ui/${name.replace(/^[A-Z]/, (m) => m.toLowerCase()).replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}`, name, }), - '@videojs/icons/components': '@videojs/icons/react', + '@fixture/icons/components': '@fixture/icons/react', '../tailwind': '@videojs/skins/default/tailwind', }; @@ -51,7 +51,7 @@ describe('integration: default/video skin → React', () => { code = result.code; }); - it('rewrites @videojs/core/components imports to per-identifier UI sources', () => { + it('rewrites component imports to per-identifier UI sources', () => { expect(code).toMatch(/import \{ PlayButton \} from "\.\/src\/ui\/play-button"/); // MuteButton lives under the volume Popover.Root subtree, which is replaced // wholesale by VolumePopover — its import is correctly dropped by the @@ -59,9 +59,9 @@ describe('integration: default/video skin → React', () => { expect(code).not.toMatch(/import \{ MuteButton \}/); }); - it('rewrites @videojs/icons/components to @videojs/icons/react', () => { - expect(code).toContain('@videojs/icons/react'); - expect(code).not.toContain('@videojs/icons/components'); + it('rewrites icon component imports', () => { + expect(code).toContain('@fixture/icons/react'); + expect(code).not.toContain('@fixture/icons/components'); }); it('substitutes the volume Popover.Root with VolumePopover', () => { diff --git a/packages/compiler/src/transforms/jsx.ts b/packages/compiler/src/transforms/jsx.ts new file mode 100644 index 00000000..4dd5dfb9 --- /dev/null +++ b/packages/compiler/src/transforms/jsx.ts @@ -0,0 +1,75 @@ +import { isString } from '@videojs/utils/predicate'; +import ts from 'typescript'; +import type { JsxElementLike, Matcher } from '../matchers'; + +const IDENTIFIER_NAME_RE = /^[$A-Z_a-z][$\w]*$/; + +export type JsxChildReplacement = ts.JsxChild | readonly ts.JsxChild[]; + +export interface ReplaceJsxChildOptions { + match: Matcher; + replace: (node: JsxElementLike, factory: ts.NodeFactory) => JsxChildReplacement | undefined; +} + +export function replaceJsxChild(options: ReplaceJsxChildOptions): ts.TransformerFactory { + return (context) => { + const visit: ts.Visitor = (node) => { + if (isJsxElementLike(node) && options.match(node)) { + const replacement = options.replace(node, context.factory); + return replacement ?? node; + } + + return ts.visitEachChild(node, visit, context); + }; + + return (sourceFile) => ts.visitEachChild(sourceFile, visit, context); + }; +} + +export function jsxExpression(factory: ts.NodeFactory, expression: ts.Expression): ts.JsxExpression { + return factory.createJsxExpression(undefined, expression); +} + +export function accessPath( + factory: ts.NodeFactory, + root: string | ts.Expression, + ...path: readonly string[] +): ts.Expression { + let expression = isString(root) ? factory.createIdentifier(root) : root; + + for (const property of path) { + expression = propertyAccess(factory, expression, property); + } + + return expression; +} + +export function propertyAccess(factory: ts.NodeFactory, expression: ts.Expression, property: string): ts.Expression { + if (IDENTIFIER_NAME_RE.test(property)) { + return factory.createPropertyAccessExpression(expression, property); + } + + return factory.createElementAccessExpression(expression, factory.createStringLiteral(property)); +} + +export function readStringAttribute(attributes: ts.JsxAttributes, name: string): string | null | undefined { + const attr = attributes.properties.find( + (property) => ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && property.name.text === name + ); + + if (!attr || !ts.isJsxAttribute(attr)) return undefined; + + const init = attr.initializer; + if (!init) return ''; + if (ts.isStringLiteral(init)) return init.text; + if (ts.isJsxExpression(init) && init.expression) { + if (ts.isStringLiteral(init.expression) || ts.isNoSubstitutionTemplateLiteral(init.expression)) { + return init.expression.text; + } + } + return null; +} + +function isJsxElementLike(node: ts.Node): node is JsxElementLike { + return ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node); +} diff --git a/packages/compiler/tsconfig.preset.json b/packages/compiler/tsconfig.preset.json deleted file mode 100644 index 4c2ec35f..00000000 --- a/packages/compiler/tsconfig.preset.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "compilerOptions": { - "jsx": "react-jsx", - "jsxImportSource": "@videojs/compiler" - } -} diff --git a/packages/compiler/tsdown.config.ts b/packages/compiler/tsdown.config.ts index 8ce7de94..d1765729 100644 --- a/packages/compiler/tsdown.config.ts +++ b/packages/compiler/tsdown.config.ts @@ -4,8 +4,6 @@ export default defineConfig({ entry: { index: './src/index.ts', cli: './src/cli.ts', - 'jsx-runtime': './src/jsx-runtime.ts', - 'jsx-dev-runtime': './src/jsx-dev-runtime.ts', 'plugins/vite': './src/plugins/vite.ts', 'ast/index': './src/ast/index.ts', 'matchers/index': './src/matchers/index.ts', diff --git a/packages/core/compiler.config.js b/packages/core/compiler.config.js deleted file mode 100644 index 30edb9a2..00000000 --- a/packages/core/compiler.config.js +++ /dev/null @@ -1,8 +0,0 @@ -import { defineConfig } from '@videojs/compiler'; - -export default defineConfig({ - generate: { - components: ['./src/core/ui/*/*-component.ts'], - output: './src/core/ui/components.generated.ts', - }, -}); diff --git a/packages/core/components.config.js b/packages/core/components.config.js new file mode 100644 index 00000000..6c840dce --- /dev/null +++ b/packages/core/components.config.js @@ -0,0 +1,5 @@ +export default { + components: ['./src/core/ui/*/*-component.ts'], + output: './src/core/ui/components.generated.ts', + runtimeImport: '../../jsx-runtime', +}; diff --git a/packages/core/package.json b/packages/core/package.json index 2a9ef8f4..f84053a8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -32,6 +32,16 @@ "development": "./dist/dev/components.js", "default": "./dist/default/components.js" }, + "./jsx-runtime": { + "types": "./dist/dev/jsx-runtime.d.ts", + "development": "./dist/dev/jsx-runtime.js", + "default": "./dist/default/jsx-runtime.js" + }, + "./jsx-dev-runtime": { + "types": "./dist/dev/jsx-dev-runtime.d.ts", + "development": "./dist/dev/jsx-dev-runtime.js", + "default": "./dist/default/jsx-dev-runtime.js" + }, "./dom/media/*": { "types": "./dist/dev/dom/media/*/index.d.ts", "development": "./dist/dev/dom/media/*/index.js", @@ -48,7 +58,7 @@ "build": "tsdown", "build:watch": "tsdown --watch ./src --no-clean", "dev": "pnpm run build:watch", - "generate": "vjs generate", + "generate": "node --import tsx scripts/generate-components.ts --config components.config.js", "test": "vitest run", "test:watch": "vitest", "clean": "rimraf --glob dist types '*.tsbuildinfo'" @@ -65,9 +75,10 @@ "devDependencies": { "@types/chrome": "^0.1.40", "@types/chromecast-caf-sender": "^1.0.11", - "@videojs/compiler": "workspace:*", "jsdom": "^26.1.0", "tsdown": "^0.21.9", + "tsx": "^4.21.0", + "typescript": "^6.0.2", "vitest": "^4.1.0" }, "publishConfig": { diff --git a/packages/compiler/src/generate.ts b/packages/core/scripts/generate-components.ts similarity index 54% rename from packages/compiler/src/generate.ts rename to packages/core/scripts/generate-components.ts index 7bec225b..76e1564c 100644 --- a/packages/compiler/src/generate.ts +++ b/packages/core/scripts/generate-components.ts @@ -1,8 +1,30 @@ -import { existsSync, globSync, readFileSync, writeFileSync } from 'node:fs'; +import { existsSync, globSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'; import { basename, dirname, extname, isAbsolute, relative, resolve } from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + import ts from 'typescript'; -import type { BulkComponentEntry, CompilerConfig } from './config'; +export interface BulkComponentEntry { + files: string; + name: (filename: string) => string; +} + +export type ComponentEntry = string | BulkComponentEntry; + +export interface ComponentsConfig { + components: readonly ComponentEntry[]; + output: string; + runtimeImport?: string | undefined; +} + +export interface GenerateComponentsOptions { + cwd?: string | undefined; +} + +export interface GenerateComponentsResult { + outputPath: string; + source: string; +} interface ManifestComponent { kind: 'manifest'; @@ -18,11 +40,17 @@ interface InlineComponent { type ResolvedComponent = ManifestComponent | InlineComponent; +export function defineComponentsConfig( + config: Config +): Config { + return config; +} + function isDefineComponentCall(node: ts.Node): node is ts.CallExpression { if (!ts.isCallExpression(node)) return false; const callee = node.expression; if (ts.isIdentifier(callee) && callee.text === 'defineComponent') return true; - // Curried form: defineComponent

()(...) — outer call's expression is the inner call. + // Curried form: defineComponent()({ ... }). return ( ts.isCallExpression(callee) && ts.isIdentifier(callee.expression) && callee.expression.text === 'defineComponent' ); @@ -43,10 +71,12 @@ function parseComponentName(manifestPath: string): string { if (!call) { throw new Error(`No \`export default defineComponent(...)\` found in ${manifestPath}`); } + const arg = call.arguments[0]; if (!arg || !ts.isObjectLiteralExpression(arg)) { throw new Error(`defineComponent() in ${manifestPath} must take an object literal`); } + for (const prop of arg.properties) { if ( ts.isPropertyAssignment(prop) && @@ -57,13 +87,14 @@ function parseComponentName(manifestPath: string): string { return prop.initializer.text; } } + throw new Error(`defineComponent() in ${manifestPath} is missing a literal \`name:\` field`); } function manifestPathToImport(manifestPath: string, outputFile: string): string { let rel = relative(dirname(outputFile), manifestPath); if (!rel.startsWith('.')) rel = `./${rel}`; - return rel.replace(/\.ts$/, ''); + return rel.replace(/\.[cm]?tsx?$/, ''); } function fileStem(filePath: string): string { @@ -73,7 +104,7 @@ function fileStem(filePath: string): string { } function resolveManifestEntry(pattern: string, cwd: string, outputAbsolute: string): ManifestComponent[] { - const matches = globSync(pattern, { cwd }).map((p) => (isAbsolute(p) ? p : resolve(cwd, p))); + const matches = globSync(pattern, { cwd }).map((path) => (isAbsolute(path) ? path : resolve(cwd, path))); return matches.map((manifestPath) => ({ kind: 'manifest', name: parseComponentName(manifestPath), @@ -89,21 +120,6 @@ function resolveBulkEntry(entry: BulkComponentEntry, cwd: string): InlineCompone })); } -function emitHeader(entries: readonly ResolvedComponent[]): string { - const manifestLines = entries - .filter((e): e is ManifestComponent => e.kind === 'manifest') - .sort((a, b) => compareImportSpecifiers(a.manifestFrom, b.manifestFrom)) - .map((e) => `import ${e.name}Def from '${e.manifestFrom}';`) - .join('\n'); - const head = `// AUTO-GENERATED by \`@videojs/compiler\`. DO NOT EDIT. -import { createComponent } from '@videojs/compiler/jsx-runtime';`; - return manifestLines ? `${head}\n\n${manifestLines}` : head; -} - -function manifestRef(entry: ResolvedComponent): string { - return entry.kind === 'manifest' ? `${entry.name}Def` : `{ name: '${entry.name}' }`; -} - function compareImportSpecifiers(a: string, b: string): number { const aKey = a.replaceAll('/', ' '); const bKey = b.replaceAll('/', ' '); @@ -112,32 +128,41 @@ function compareImportSpecifiers(a: string, b: string): number { return 0; } +function emitHeader(entries: readonly ResolvedComponent[], runtimeImport: string): string { + const manifestLines = entries + .filter((entry): entry is ManifestComponent => entry.kind === 'manifest') + .sort((a, b) => compareImportSpecifiers(a.manifestFrom, b.manifestFrom)) + .map((entry) => `import ${entry.name}Def from '${entry.manifestFrom}';`) + .join('\n'); + const head = `// AUTO-GENERATED by \`@videojs/core/scripts/generate-components\`. DO NOT EDIT. +import { createComponent } from '${runtimeImport}';`; + return manifestLines ? `${head}\n\n${manifestLines}` : head; +} + +function manifestRef(entry: ResolvedComponent): string { + return entry.kind === 'manifest' ? `${entry.name}Def` : `{ name: '${entry.name}' }`; +} + function emitComponents(entries: readonly ResolvedComponent[]): string { - return entries.map((e) => `export const ${e.name} = createComponent(${manifestRef(e)});`).join('\n'); + return entries.map((entry) => `export const ${entry.name} = createComponent(${manifestRef(entry)});`).join('\n'); } function emitMetadata(entries: readonly ResolvedComponent[]): string { - const lines = entries.map((e) => ` ${e.name}: ${manifestRef(e)},`); + const lines = entries.map((entry) => ` ${entry.name}: ${manifestRef(entry)},`); return `export const COMPONENTS = {\n${lines.join('\n')}\n} as const; export type Components = typeof COMPONENTS;`; } -export interface GenerateResult { - outputPath: string; - source: string; -} - -export async function generate(config: CompilerConfig): Promise { - if (!config.generate) { - throw new Error('@videojs/compiler: generate() requires a `generate` field in the compiler config'); - } - - const { components, output } = config.generate; - const cwd = process.cwd(); +export async function generateComponents( + config: ComponentsConfig, + options: GenerateComponentsOptions = {} +): Promise { + const { components, output, runtimeImport = '@videojs/core/jsx-runtime' } = config; + const cwd = options.cwd ?? process.cwd(); const outputAbsolute = isAbsolute(output) ? output : resolve(cwd, output); - const resolved: ResolvedComponent[] = components.flatMap((entry) => + const resolved = components.flatMap((entry) => typeof entry === 'string' ? resolveManifestEntry(entry, cwd, outputAbsolute) : resolveBulkEntry(entry, cwd) ); const entries = resolved.sort((a, b) => a.name.localeCompare(b.name)); @@ -146,14 +171,59 @@ export async function generate(config: CompilerConfig): Promise throw new Error(`No component sources matched: ${JSON.stringify(components)}`); } - const source = `${[emitHeader(entries), emitComponents(entries), emitMetadata(entries)].join('\n\n')}\n`; - // Skip the write when contents are unchanged so watch-mode rebuilds don't - // re-trigger themselves. + const source = `${[emitHeader(entries, runtimeImport), emitComponents(entries), emitMetadata(entries)].join('\n\n')}\n`; const existing = existsSync(outputAbsolute) ? readFileSync(outputAbsolute, 'utf8') : null; if (existing !== source) { + mkdirSync(dirname(outputAbsolute), { recursive: true }); writeFileSync(outputAbsolute, source, 'utf8'); } return { outputPath: outputAbsolute, source }; } + +interface ConfigModule { + default?: ComponentsConfig | readonly ComponentsConfig[]; + config?: ComponentsConfig | readonly ComponentsConfig[]; +} + +function parseConfigArg(argv: readonly string[]): string { + for (let i = 0; i < argv.length; i++) { + const arg = argv[i]!; + if (arg === '--config' || arg === '-c') { + const value = argv[i + 1]; + if (!value) throw new Error('Missing value for --config'); + return value; + } + if (arg.startsWith('--config=')) return arg.slice('--config='.length); + } + + return 'components.config.js'; +} + +async function loadConfig(configPath: string): Promise<{ configs: readonly ComponentsConfig[]; cwd: string }> { + const absolute = isAbsolute(configPath) ? configPath : resolve(process.cwd(), configPath); + const mod = (await import(pathToFileURL(absolute).href)) as ConfigModule; + const config = mod.default ?? mod.config; + if (!config) { + throw new Error(`Config file ${absolute} must export a default component generator config.`); + } + return { configs: Array.isArray(config) ? config : [config], cwd: dirname(absolute) }; +} + +async function runCli(): Promise { + const configPath = parseConfigArg(process.argv.slice(2)); + const { configs, cwd } = await loadConfig(configPath); + + for (const config of configs) { + const result = await generateComponents(config, { cwd }); + process.stdout.write(`Wrote ${result.outputPath}\n`); + } +} + +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + runCli().catch((error: unknown) => { + process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`); + process.exitCode = 1; + }); +} diff --git a/packages/compiler/src/tests/generate.test.ts b/packages/core/scripts/tests/generate-components.test.ts similarity index 69% rename from packages/compiler/src/tests/generate.test.ts rename to packages/core/scripts/tests/generate-components.test.ts index 8587175c..e4659bf2 100644 --- a/packages/compiler/src/tests/generate.test.ts +++ b/packages/core/scripts/tests/generate-components.test.ts @@ -3,12 +3,12 @@ import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { describe, expect, it } from 'vitest'; -import { generate } from '../generate'; +import { generateComponents } from '../generate-components'; const STUB = 'const defineComponent: any = () => (m: any) => m;'; function setup(): { dir: string; output: string; pattern: string } { - const dir = mkdtempSync(join(tmpdir(), 'videojs-compiler-')); + const dir = mkdtempSync(join(tmpdir(), 'videojs-components-')); mkdirSync(join(dir, 'play-button')); mkdirSync(join(dir, 'slider')); mkdirSync(join(dir, 'hotkey')); @@ -51,26 +51,34 @@ function setup(): { dir: string; output: string; pattern: string } { } function setupBulk(): { dir: string; output: string } { - const dir = mkdtempSync(join(tmpdir(), 'videojs-compiler-bulk-')); + const dir = mkdtempSync(join(tmpdir(), 'videojs-components-bulk-')); mkdirSync(join(dir, 'assets')); writeFileSync(join(dir, 'assets', 'play.svg'), ''); writeFileSync(join(dir, 'assets', 'pause.svg'), ''); return { dir, output: join(dir, 'out.ts') }; } -describe('generate (manifest entries)', () => { +describe('generateComponents (manifest entries)', () => { it('imports each manifest as `Def` default-import', async () => { - const { output, pattern } = setup(); - await generate({ generate: { components: [pattern], output } }); + const { dir, output, pattern } = setup(); + await generateComponents({ components: [pattern], output }, { cwd: dir }); const source = readFileSync(output, 'utf8'); + expect(source).toContain("import { createComponent } from '@videojs/core/jsx-runtime';"); expect(source).toContain("import PlayButtonDef from './play-button/play-button-component';"); expect(source).toContain("import SliderDef from './slider/slider-component';"); expect(source).toContain("import HotkeyDef from './hotkey/hotkey-component';"); }); + it('uses an explicit runtime import when configured', async () => { + const { dir, output, pattern } = setup(); + await generateComponents({ components: [pattern], output, runtimeImport: '../../jsx-runtime' }, { cwd: dir }); + const source = readFileSync(output, 'utf8'); + expect(source).toContain("import { createComponent } from '../../jsx-runtime';"); + }); + it('emits createComponent(Def) for each component', async () => { - const { output, pattern } = setup(); - await generate({ generate: { components: [pattern], output } }); + const { dir, output, pattern } = setup(); + await generateComponents({ components: [pattern], output }, { cwd: dir }); const source = readFileSync(output, 'utf8'); expect(source).toContain('export const PlayButton = createComponent(PlayButtonDef);'); expect(source).toContain('export const Slider = createComponent(SliderDef);'); @@ -78,8 +86,8 @@ describe('generate (manifest entries)', () => { }); it('emits COMPONENTS referencing each definition', async () => { - const { output, pattern } = setup(); - await generate({ generate: { components: [pattern], output } }); + const { dir, output, pattern } = setup(); + await generateComponents({ components: [pattern], output }, { cwd: dir }); const source = readFileSync(output, 'utf8'); expect(source).toContain('export const COMPONENTS = {'); expect(source).toContain('export type Components = typeof COMPONENTS;'); @@ -89,15 +97,21 @@ describe('generate (manifest entries)', () => { }); }); -describe('generate (bulk entries)', () => { +describe('generateComponents (bulk entries)', () => { it('inlines createComponent({ name }) for each matched file', async () => { const { dir, output } = setupBulk(); - await generate({ - generate: { - components: [{ files: join(dir, 'assets/*.svg'), name: (f) => `${f[0]!.toUpperCase()}${f.slice(1)}Icon` }], + await generateComponents( + { + components: [ + { + files: join(dir, 'assets/*.svg'), + name: (filename) => `${filename[0]!.toUpperCase()}${filename.slice(1)}Icon`, + }, + ], output, }, - }); + { cwd: dir } + ); const source = readFileSync(output, 'utf8'); expect(source).toContain("export const PauseIcon = createComponent({ name: 'PauseIcon' });"); expect(source).toContain("export const PlayIcon = createComponent({ name: 'PlayIcon' });"); @@ -105,12 +119,18 @@ describe('generate (bulk entries)', () => { it('emits COMPONENTS with inline manifests for bulk entries', async () => { const { dir, output } = setupBulk(); - await generate({ - generate: { - components: [{ files: join(dir, 'assets/*.svg'), name: (f) => `${f[0]!.toUpperCase()}${f.slice(1)}Icon` }], + await generateComponents( + { + components: [ + { + files: join(dir, 'assets/*.svg'), + name: (filename) => `${filename[0]!.toUpperCase()}${filename.slice(1)}Icon`, + }, + ], output, }, - }); + { cwd: dir } + ); const source = readFileSync(output, 'utf8'); expect(source).toContain("PlayIcon: { name: 'PlayIcon' },"); expect(source).toContain("PauseIcon: { name: 'PauseIcon' },"); @@ -119,20 +139,21 @@ describe('generate (bulk entries)', () => { it('strips the file extension before passing to name()', async () => { const { dir, output } = setupBulk(); let received: string | null = null; - await generate({ - generate: { + await generateComponents( + { components: [ { files: join(dir, 'assets/*.svg'), - name: (f) => { - if (received === null) received = f; - return `${f}Icon`; + name: (filename) => { + if (received === null) received = filename; + return `${filename}Icon`; }, }, ], output, }, - }); + { cwd: dir } + ); expect(received).not.toContain('.svg'); }); }); diff --git a/packages/core/src/core/ui/airplay-button/airplay-button-component.ts b/packages/core/src/core/ui/airplay-button/airplay-button-component.ts index 3b3e7e4b..293b4fd8 100644 --- a/packages/core/src/core/ui/airplay-button/airplay-button-component.ts +++ b/packages/core/src/core/ui/airplay-button/airplay-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/alert-dialog/alert-dialog-component.ts b/packages/core/src/core/ui/alert-dialog/alert-dialog-component.ts index fa047b71..110760eb 100644 --- a/packages/core/src/core/ui/alert-dialog/alert-dialog-component.ts +++ b/packages/core/src/core/ui/alert-dialog/alert-dialog-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/buffering-indicator/buffering-indicator-component.ts b/packages/core/src/core/ui/buffering-indicator/buffering-indicator-component.ts index 00d97abf..de0eea4d 100644 --- a/packages/core/src/core/ui/buffering-indicator/buffering-indicator-component.ts +++ b/packages/core/src/core/ui/buffering-indicator/buffering-indicator-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/captions-button/captions-button-component.ts b/packages/core/src/core/ui/captions-button/captions-button-component.ts index c10db60b..da408b0f 100644 --- a/packages/core/src/core/ui/captions-button/captions-button-component.ts +++ b/packages/core/src/core/ui/captions-button/captions-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/captions-radio-group/captions-radio-group-component.ts b/packages/core/src/core/ui/captions-radio-group/captions-radio-group-component.ts index 4e173ecf..721cf003 100644 --- a/packages/core/src/core/ui/captions-radio-group/captions-radio-group-component.ts +++ b/packages/core/src/core/ui/captions-radio-group/captions-radio-group-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/cast-button/cast-button-component.ts b/packages/core/src/core/ui/cast-button/cast-button-component.ts index a031c2d9..45f55eba 100644 --- a/packages/core/src/core/ui/cast-button/cast-button-component.ts +++ b/packages/core/src/core/ui/cast-button/cast-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/components.generated.ts b/packages/core/src/core/ui/components.generated.ts index 0b86df7d..d58b2e2f 100644 --- a/packages/core/src/core/ui/components.generated.ts +++ b/packages/core/src/core/ui/components.generated.ts @@ -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'; diff --git a/packages/core/src/core/ui/controls/controls-component.ts b/packages/core/src/core/ui/controls/controls-component.ts index 00b2d37f..3b2bc67c 100644 --- a/packages/core/src/core/ui/controls/controls-component.ts +++ b/packages/core/src/core/ui/controls/controls-component.ts @@ -1,4 +1,4 @@ -import { defineComponent } from '@videojs/compiler'; +import { defineComponent } from '../manifest'; import { ControlsDataAttrs } from './controls-data-attrs'; export default defineComponent()({ diff --git a/packages/core/src/core/ui/error-dialog/error-dialog-component.ts b/packages/core/src/core/ui/error-dialog/error-dialog-component.ts index 3ed46a17..6ae60cc1 100644 --- a/packages/core/src/core/ui/error-dialog/error-dialog-component.ts +++ b/packages/core/src/core/ui/error-dialog/error-dialog-component.ts @@ -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()({ diff --git a/packages/core/src/core/ui/fullscreen-button/fullscreen-button-component.ts b/packages/core/src/core/ui/fullscreen-button/fullscreen-button-component.ts index 7ffecf57..3b44221c 100644 --- a/packages/core/src/core/ui/fullscreen-button/fullscreen-button-component.ts +++ b/packages/core/src/core/ui/fullscreen-button/fullscreen-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/live-button/live-button-component.ts b/packages/core/src/core/ui/live-button/live-button-component.ts index efda8044..d97d7de2 100644 --- a/packages/core/src/core/ui/live-button/live-button-component.ts +++ b/packages/core/src/core/ui/live-button/live-button-component.ts @@ -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'; diff --git a/packages/compiler/src/define-component.ts b/packages/core/src/core/ui/manifest.ts similarity index 62% rename from packages/compiler/src/define-component.ts rename to packages/core/src/core/ui/manifest.ts index d8162b64..f4b2d228 100644 --- a/packages/compiler/src/define-component.ts +++ b/packages/core/src/core/ui/manifest.ts @@ -1,7 +1,12 @@ 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 = Record, + Props extends object = EmptyProps, Parts extends readonly string[] = readonly string[], PartProps extends Partial> = Partial>, > { @@ -13,7 +18,7 @@ export interface ComponentManifest< } export type InferProps = - T extends ComponentManifest>> ? P : never; + T extends ComponentManifest>> ? Props : never; export type InferParts = T extends ComponentManifest>> @@ -29,26 +34,8 @@ export type InferPartProps = : never : never; -/** - * Define a component manifest. - * - * Curried so the `Props` generic can be supplied without disabling inference - * of `Parts` and `PartProps` from the manifest body: - * - * @example - * const Slider = defineComponent()({ - * name: 'Slider', - * parts: SliderParts, - * dataAttrs: SliderDataAttrs, - * }); - * - * const Controls = defineComponent()({ - * name: 'Controls', - * parts: ControlsParts, - * dataAttrs: ControlsDataAttrs, - * }); - */ -export function defineComponent>() { +/** Define a component manifest. */ +export function defineComponent() { return < const Parts extends readonly string[] = readonly string[], const PartProps extends Partial> = Partial>, diff --git a/packages/core/src/core/ui/menu/menu-component.ts b/packages/core/src/core/ui/menu/menu-component.ts index f6371aae..32f58ca2 100644 --- a/packages/core/src/core/ui/menu/menu-component.ts +++ b/packages/core/src/core/ui/menu/menu-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/mute-button/mute-button-component.ts b/packages/core/src/core/ui/mute-button/mute-button-component.ts index 09c6fa1a..9de8a3f8 100644 --- a/packages/core/src/core/ui/mute-button/mute-button-component.ts +++ b/packages/core/src/core/ui/mute-button/mute-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/pip-button/pip-button-component.ts b/packages/core/src/core/ui/pip-button/pip-button-component.ts index 13142bac..25d47d4a 100644 --- a/packages/core/src/core/ui/pip-button/pip-button-component.ts +++ b/packages/core/src/core/ui/pip-button/pip-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/play-button/play-button-component.ts b/packages/core/src/core/ui/play-button/play-button-component.ts index 67eb575c..704cb6a4 100644 --- a/packages/core/src/core/ui/play-button/play-button-component.ts +++ b/packages/core/src/core/ui/play-button/play-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/playback-rate-button/playback-rate-button-component.ts b/packages/core/src/core/ui/playback-rate-button/playback-rate-button-component.ts index 6dc459cc..07106465 100644 --- a/packages/core/src/core/ui/playback-rate-button/playback-rate-button-component.ts +++ b/packages/core/src/core/ui/playback-rate-button/playback-rate-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-component.ts b/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-component.ts index 917152ad..b3325068 100644 --- a/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-component.ts +++ b/packages/core/src/core/ui/playback-rate-radio-group/playback-rate-radio-group-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/popover/popover-component.ts b/packages/core/src/core/ui/popover/popover-component.ts index a76d6cfe..47c9f1d0 100644 --- a/packages/core/src/core/ui/popover/popover-component.ts +++ b/packages/core/src/core/ui/popover/popover-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/poster/poster-component.ts b/packages/core/src/core/ui/poster/poster-component.ts index fd220061..7d478f93 100644 --- a/packages/core/src/core/ui/poster/poster-component.ts +++ b/packages/core/src/core/ui/poster/poster-component.ts @@ -1,4 +1,4 @@ -import { defineComponent } from '@videojs/compiler'; +import { defineComponent } from '../manifest'; import { PosterDataAttrs } from './poster-data-attrs'; export default defineComponent()({ diff --git a/packages/core/src/core/ui/quality-radio-group/quality-radio-group-component.ts b/packages/core/src/core/ui/quality-radio-group/quality-radio-group-component.ts index d12a5ce2..9b4b0220 100644 --- a/packages/core/src/core/ui/quality-radio-group/quality-radio-group-component.ts +++ b/packages/core/src/core/ui/quality-radio-group/quality-radio-group-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/seek-button/seek-button-component.ts b/packages/core/src/core/ui/seek-button/seek-button-component.ts index 3f5ef437..5e1cb6be 100644 --- a/packages/core/src/core/ui/seek-button/seek-button-component.ts +++ b/packages/core/src/core/ui/seek-button/seek-button-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/seek-indicator/seek-indicator-component.ts b/packages/core/src/core/ui/seek-indicator/seek-indicator-component.ts index 3cec4d17..ee3898f5 100644 --- a/packages/core/src/core/ui/seek-indicator/seek-indicator-component.ts +++ b/packages/core/src/core/ui/seek-indicator/seek-indicator-component.ts @@ -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()({ name: 'SeekIndicator', diff --git a/packages/core/src/core/ui/slider/slider-component.ts b/packages/core/src/core/ui/slider/slider-component.ts index 8f5e8338..6cb3898a 100644 --- a/packages/core/src/core/ui/slider/slider-component.ts +++ b/packages/core/src/core/ui/slider/slider-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/status-announcer/status-announcer-component.ts b/packages/core/src/core/ui/status-announcer/status-announcer-component.ts index b6bdf34c..272b0500 100644 --- a/packages/core/src/core/ui/status-announcer/status-announcer-component.ts +++ b/packages/core/src/core/ui/status-announcer/status-announcer-component.ts @@ -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()({ name: 'StatusAnnouncer', diff --git a/packages/core/src/core/ui/status-indicator/status-indicator-component.ts b/packages/core/src/core/ui/status-indicator/status-indicator-component.ts index ce3e5bc2..430c013e 100644 --- a/packages/core/src/core/ui/status-indicator/status-indicator-component.ts +++ b/packages/core/src/core/ui/status-indicator/status-indicator-component.ts @@ -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()({ name: 'StatusIndicator', diff --git a/packages/compiler/src/tests/jsx-types.test-d.tsx b/packages/core/src/core/ui/tests/jsx-runtime.test-d.tsx similarity index 58% rename from packages/compiler/src/tests/jsx-types.test-d.tsx rename to packages/core/src/core/ui/tests/jsx-runtime.test-d.tsx index 783fd2bf..e74c5fc3 100644 --- a/packages/compiler/src/tests/jsx-types.test-d.tsx +++ b/packages/core/src/core/ui/tests/jsx-runtime.test-d.tsx @@ -1,7 +1,31 @@ -/** @jsxImportSource @videojs/compiler */ +/** @jsxImportSource ../../.. */ -import { PlayButton, Slider, Time } from '@videojs/core/components'; 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', () => { @@ -44,4 +68,15 @@ describe('constrained JSX', () => { // @ts-expect-error - arbitrary HTML attributes (id) are not allowed on layout intrinsics void (

); }); + + it('accepts slot primitives', () => { + void (); + void ( + + + + ); + // @ts-expect-error - slot name must be a string + void (); + }); }); diff --git a/packages/compiler/src/tests/tsconfig.json b/packages/core/src/core/ui/tests/tsconfig.json similarity index 73% rename from packages/compiler/src/tests/tsconfig.json rename to packages/core/src/core/ui/tests/tsconfig.json index 7f93e72f..dd76538a 100644 --- a/packages/compiler/src/tests/tsconfig.json +++ b/packages/core/src/core/ui/tests/tsconfig.json @@ -1,8 +1,8 @@ { - "extends": "../../../../tsconfig.base.json", + "extends": "../../../../../../tsconfig.base.json", "compilerOptions": { "jsx": "react-jsx", - "jsxImportSource": "@videojs/compiler", + "jsxImportSource": "../../..", "noEmit": true, "isolatedDeclarations": false, "composite": false, diff --git a/packages/core/src/core/ui/thumbnail/thumbnail-component.ts b/packages/core/src/core/ui/thumbnail/thumbnail-component.ts index b73cf0ff..7183cc5e 100644 --- a/packages/core/src/core/ui/thumbnail/thumbnail-component.ts +++ b/packages/core/src/core/ui/thumbnail/thumbnail-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/time-slider/time-slider-component.ts b/packages/core/src/core/ui/time-slider/time-slider-component.ts index ecd2a011..5d61b419 100644 --- a/packages/core/src/core/ui/time-slider/time-slider-component.ts +++ b/packages/core/src/core/ui/time-slider/time-slider-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/time/time-component.ts b/packages/core/src/core/ui/time/time-component.ts index 8f1d91e3..27b3b2c6 100644 --- a/packages/core/src/core/ui/time/time-component.ts +++ b/packages/core/src/core/ui/time/time-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/tooltip/tooltip-component.ts b/packages/core/src/core/ui/tooltip/tooltip-component.ts index ae489a70..c1a954dc 100644 --- a/packages/core/src/core/ui/tooltip/tooltip-component.ts +++ b/packages/core/src/core/ui/tooltip/tooltip-component.ts @@ -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'; diff --git a/packages/core/src/core/ui/volume-indicator/volume-indicator-component.ts b/packages/core/src/core/ui/volume-indicator/volume-indicator-component.ts index 521cabf2..20dc446e 100644 --- a/packages/core/src/core/ui/volume-indicator/volume-indicator-component.ts +++ b/packages/core/src/core/ui/volume-indicator/volume-indicator-component.ts @@ -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()({ name: 'VolumeIndicator', diff --git a/packages/core/src/core/ui/volume-slider/volume-slider-component.ts b/packages/core/src/core/ui/volume-slider/volume-slider-component.ts index 34e98ddf..cd27eb5e 100644 --- a/packages/core/src/core/ui/volume-slider/volume-slider-component.ts +++ b/packages/core/src/core/ui/volume-slider/volume-slider-component.ts @@ -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'; diff --git a/packages/compiler/src/jsx-dev-runtime.ts b/packages/core/src/jsx-dev-runtime.ts similarity index 100% rename from packages/compiler/src/jsx-dev-runtime.ts rename to packages/core/src/jsx-dev-runtime.ts diff --git a/packages/compiler/src/jsx-runtime.ts b/packages/core/src/jsx-runtime.ts similarity index 86% rename from packages/compiler/src/jsx-runtime.ts rename to packages/core/src/jsx-runtime.ts index 09e695aa..c168a7ad 100644 --- a/packages/compiler/src/jsx-runtime.ts +++ b/packages/core/src/jsx-runtime.ts @@ -1,7 +1,13 @@ -import type { ComponentManifest, InferPartProps, InferParts, InferProps } from './define-component'; +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 | typeof Fragment; export interface ComponentNode { @@ -16,6 +22,11 @@ export interface BaseProps { children?: unknown; } +export interface SlotProps { + name?: string | undefined; + children?: unknown; +} + export interface Component { (props: BaseProps & Props): ComponentNode; readonly $$component: { name: string; part: string | null }; @@ -24,7 +35,7 @@ export interface Component { type PartComponentProps = K extends 'Root' ? InferProps : [NonNullable>] extends [never] - ? Record + ? EmptyProps : NonNullable>; type CompoundComponent = { @@ -37,7 +48,7 @@ export type CreateComponentResult = [InferParts] extends [never] function makePart(name: string, part: string | null): Component { const fn = (_props: BaseProps & Props): ComponentNode => { - throw new Error(`@videojs/compiler: <${name}${part ? `.${part}` : ''}> can only be evaluated by the compiler.`); + throw new Error(`@videojs/core: <${name}${part ? `.${part}` : ''}> can only be evaluated by the compiler.`); }; Object.assign(fn, { $$component: { name, part } }); @@ -45,6 +56,8 @@ function makePart(name: string, part: string | null): Comp return fn as Component; } +export const Slot = makePart('Slot', null); + export function createComponent< M extends ComponentManifest>>, >(manifest: M): CreateComponentResult { diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 549ac62a..8dfe4235 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -6,5 +6,5 @@ "declarationDir": "types" }, "references": [{ "path": "../utils" }, { "path": "../store" }], - "include": ["src/core/**/*.ts"] + "include": ["src/core/**/*.ts", "src/jsx-runtime.ts", "src/jsx-dev-runtime.ts"] } diff --git a/packages/core/tsdown.config.ts b/packages/core/tsdown.config.ts index 4acf567e..f37a4163 100644 --- a/packages/core/tsdown.config.ts +++ b/packages/core/tsdown.config.ts @@ -1,9 +1,9 @@ -import { generate } from '@videojs/compiler'; import type { UserConfig } from 'tsdown'; import { defineConfig } from 'tsdown'; import { type PackageBuildMode, packageBuildConfig, packageBuildModes } from '../../build/tsdown.ts'; -import compilerConfig from './compiler.config.js'; +import componentsConfig from './components.config.js'; import packageJson from './package.json' with { type: 'json' }; +import { generateComponents } from './scripts/generate-components.ts'; const createConfig = (mode: PackageBuildMode): UserConfig => ({ ...packageBuildConfig(mode, 'neutral'), @@ -11,6 +11,8 @@ const createConfig = (mode: PackageBuildMode): UserConfig => ({ index: './src/core/index.ts', dom: './src/dom/index.ts', components: './src/core/ui/components.generated.ts', + 'jsx-runtime': './src/jsx-runtime.ts', + 'jsx-dev-runtime': './src/jsx-dev-runtime.ts', 'dom/media/media-host/index': './src/dom/media/media-host.ts', 'dom/media/custom-media-element/index': './src/dom/media/custom-media-element/index.ts', 'dom/media/media-played-ranges/index': './src/dom/media/media-played-ranges/index.ts', @@ -25,16 +27,13 @@ const createConfig = (mode: PackageBuildMode): UserConfig => ({ 'dom/media/mux/index': './src/dom/media/mux/index.ts', 'dom/media/google-cast/index': './src/dom/media/google-cast/index.ts', }, - deps: { - neverBundle: [/^@videojs\/compiler/], - }, define: { __DEV__: mode === 'dev' ? 'true' : 'false', __PLAYER_VERSION__: JSON.stringify(packageJson.version), }, hooks: { 'build:prepare': async () => { - await generate(compilerConfig); + await generateComponents(componentsConfig); }, }, }); diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index b87d2035..39091659 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -22,6 +22,26 @@ export default defineConfig({ setupFiles: ['src/dom/tests/setup.ts'], }, }, + { + extends: true, + test: { + name: 'scripts', + include: ['scripts/**/*.test.ts'], + }, + }, + { + extends: true, + test: { + name: 'types', + include: [], + typecheck: { + enabled: true, + checker: 'tsgo', + include: ['src/core/ui/**/*.test-d.tsx'], + tsconfig: 'src/core/ui/tests/tsconfig.json', + }, + }, + }, ], }, }); diff --git a/packages/html/src/define/audio/minimal-skin.css b/packages/html/src/define/audio/minimal-skin.css index 21810537..cbc3a5fb 100644 --- a/packages/html/src/define/audio/minimal-skin.css +++ b/packages/html/src/define/audio/minimal-skin.css @@ -1,3 +1,2 @@ @import "../global.css"; @import "../shared.css"; -@import "@videojs/skins/minimal/css/audio.css"; diff --git a/packages/html/src/define/audio/minimal-skin.tailwind.ts b/packages/html/src/define/audio/minimal-skin.tailwind.ts index 82ef8020..b40522bd 100644 --- a/packages/html/src/define/audio/minimal-skin.tailwind.ts +++ b/packages/html/src/define/audio/minimal-skin.tailwind.ts @@ -2,16 +2,17 @@ import { renderIcon } from '@videojs/icons/render/minimal'; import { button, buttonGroup, + container, controls, error, icon, iconContainer, iconFlipped, - iconState, menu, + muteIcon, playbackRate, + playIcon, popup, - root, seek, slider, time, @@ -28,7 +29,7 @@ const SEEK_TIME = 10; function getTemplateHTML() { return /*html*/ ` - + @@ -48,10 +49,10 @@ function getTemplateHTML() {
- - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + ${renderIcon('restart', { class: cn(icon, playIcon.restart) })} + ${renderIcon('play', { class: cn(icon, playIcon.play) })} + ${renderIcon('pause', { class: cn(icon, playIcon.pause) })} @@ -116,10 +117,10 @@ function getTemplateHTML() { - - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + ${renderIcon('volume-off', { class: cn(icon, muteIcon.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, muteIcon.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, muteIcon.volumeHigh) })} diff --git a/packages/html/src/define/audio/minimal-skin.ts b/packages/html/src/define/audio/minimal-skin.ts index 3899f5ad..989c0b74 100644 --- a/packages/html/src/define/audio/minimal-skin.ts +++ b/packages/html/src/define/audio/minimal-skin.ts @@ -1,5 +1,5 @@ import { renderIcon } from '@videojs/icons/render/minimal'; -import { createShadowStyle, createTemplate } from '@videojs/utils/dom'; +import { createTemplate } from '@videojs/utils/dom'; import { safeDefine } from '../safe-define'; import { SkinElement } from '../skin-element'; import styles from './minimal-skin.css?inline'; @@ -121,7 +121,7 @@ function getTemplateHTML() { export class MinimalAudioSkinElement extends SkinElement { static readonly tagName = 'audio-minimal-skin'; - static styles = createShadowStyle(styles); + static styles = styles; static template = createTemplate(getTemplateHTML()); } diff --git a/packages/html/src/define/audio/skin.css b/packages/html/src/define/audio/skin.css index e5bfa381..cbc3a5fb 100644 --- a/packages/html/src/define/audio/skin.css +++ b/packages/html/src/define/audio/skin.css @@ -1,3 +1,2 @@ @import "../global.css"; @import "../shared.css"; -@import "@videojs/skins/default/css/audio.css"; diff --git a/packages/html/src/define/audio/skin.tailwind.ts b/packages/html/src/define/audio/skin.tailwind.ts index 7e4d5850..941e2f27 100644 --- a/packages/html/src/define/audio/skin.tailwind.ts +++ b/packages/html/src/define/audio/skin.tailwind.ts @@ -2,16 +2,17 @@ import { renderIcon } from '@videojs/icons/render'; import { button, buttonGroup, + container, controls, error, icon, iconContainer, iconFlipped, - iconState, menu, + muteIcon, playbackRate, + playIcon, popup, - root, seek, slider, time, @@ -28,7 +29,7 @@ const SEEK_TIME = 10; function getTemplateHTML() { return /*html*/ ` - + @@ -48,10 +49,10 @@ function getTemplateHTML() {
- - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + ${renderIcon('restart', { class: cn(icon, playIcon.restart) })} + ${renderIcon('play', { class: cn(icon, playIcon.play) })} + ${renderIcon('pause', { class: cn(icon, playIcon.pause) })} @@ -111,10 +112,10 @@ function getTemplateHTML() { - - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + ${renderIcon('volume-off', { class: cn(icon, muteIcon.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, muteIcon.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, muteIcon.volumeHigh) })} diff --git a/packages/html/src/define/audio/skin.ts b/packages/html/src/define/audio/skin.ts index ebc0718c..b68aa663 100644 --- a/packages/html/src/define/audio/skin.ts +++ b/packages/html/src/define/audio/skin.ts @@ -1,5 +1,5 @@ import { renderIcon } from '@videojs/icons/render'; -import { createShadowStyle, createTemplate } from '@videojs/utils/dom'; +import { createTemplate } from '@videojs/utils/dom'; import { safeDefine } from '../safe-define'; import { SkinElement } from '../skin-element'; import styles from './skin.css?inline'; @@ -133,7 +133,7 @@ function getTemplateHTML() { export class AudioSkinElement extends SkinElement { static readonly tagName = 'audio-skin'; - static styles = createShadowStyle(styles); + static styles = styles; static template = createTemplate(getTemplateHTML()); } diff --git a/packages/html/src/define/live-audio/minimal-skin.css b/packages/html/src/define/live-audio/minimal-skin.css index 21810537..cbc3a5fb 100644 --- a/packages/html/src/define/live-audio/minimal-skin.css +++ b/packages/html/src/define/live-audio/minimal-skin.css @@ -1,3 +1,2 @@ @import "../global.css"; @import "../shared.css"; -@import "@videojs/skins/minimal/css/audio.css"; diff --git a/packages/html/src/define/live-audio/minimal-skin.tailwind.ts b/packages/html/src/define/live-audio/minimal-skin.tailwind.ts index c82093d3..7be80559 100644 --- a/packages/html/src/define/live-audio/minimal-skin.tailwind.ts +++ b/packages/html/src/define/live-audio/minimal-skin.tailwind.ts @@ -2,12 +2,13 @@ import { renderIcon } from '@videojs/icons/render/minimal'; import { button, buttonGroup, + container, controls, error, icon, - iconState, + muteIcon, + playIcon, popup, - root, slider, } from '@videojs/skins/minimal/tailwind/audio.tailwind'; import { createTemplate } from '@videojs/utils/dom'; @@ -20,7 +21,7 @@ import './minimal-ui'; function getTemplateHTML() { return /*html*/ ` - + @@ -40,10 +41,10 @@ function getTemplateHTML() {
- - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + ${renderIcon('restart', { class: cn(icon, playIcon.restart) })} + ${renderIcon('play', { class: cn(icon, playIcon.play) })} + ${renderIcon('pause', { class: cn(icon, playIcon.pause) })} @@ -56,10 +57,10 @@ function getTemplateHTML() {
- - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + ${renderIcon('volume-off', { class: cn(icon, muteIcon.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, muteIcon.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, muteIcon.volumeHigh) })} diff --git a/packages/html/src/define/live-audio/minimal-skin.ts b/packages/html/src/define/live-audio/minimal-skin.ts index d7c0ccf6..d3341c15 100644 --- a/packages/html/src/define/live-audio/minimal-skin.ts +++ b/packages/html/src/define/live-audio/minimal-skin.ts @@ -1,5 +1,5 @@ import { renderIcon } from '@videojs/icons/render/minimal'; -import { createShadowStyle, createTemplate } from '@videojs/utils/dom'; +import { createTemplate } from '@videojs/utils/dom'; import { safeDefine } from '../safe-define'; import { SkinElement } from '../skin-element'; import styles from './minimal-skin.css?inline'; @@ -68,7 +68,7 @@ function getTemplateHTML() { export class MinimalLiveAudioSkinElement extends SkinElement { static readonly tagName = 'live-audio-minimal-skin'; - static styles = createShadowStyle(styles); + static styles = styles; static template = createTemplate(getTemplateHTML()); } diff --git a/packages/html/src/define/live-audio/skin.css b/packages/html/src/define/live-audio/skin.css index e5bfa381..cbc3a5fb 100644 --- a/packages/html/src/define/live-audio/skin.css +++ b/packages/html/src/define/live-audio/skin.css @@ -1,3 +1,2 @@ @import "../global.css"; @import "../shared.css"; -@import "@videojs/skins/default/css/audio.css"; diff --git a/packages/html/src/define/live-audio/skin.tailwind.ts b/packages/html/src/define/live-audio/skin.tailwind.ts index 495c4468..82295c62 100644 --- a/packages/html/src/define/live-audio/skin.tailwind.ts +++ b/packages/html/src/define/live-audio/skin.tailwind.ts @@ -2,12 +2,13 @@ import { renderIcon } from '@videojs/icons/render'; import { button, buttonGroup, + container, controls, error, icon, - iconState, + muteIcon, + playIcon, popup, - root, slider, } from '@videojs/skins/default/tailwind/audio.tailwind'; import { createTemplate } from '@videojs/utils/dom'; @@ -20,7 +21,7 @@ import './ui'; function getTemplateHTML() { return /*html*/ ` - + @@ -40,10 +41,10 @@ function getTemplateHTML() {
- - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + ${renderIcon('restart', { class: cn(icon, playIcon.restart) })} + ${renderIcon('play', { class: cn(icon, playIcon.play) })} + ${renderIcon('pause', { class: cn(icon, playIcon.pause) })} @@ -56,10 +57,10 @@ function getTemplateHTML() {
- - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + ${renderIcon('volume-off', { class: cn(icon, muteIcon.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, muteIcon.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, muteIcon.volumeHigh) })} diff --git a/packages/html/src/define/live-audio/skin.ts b/packages/html/src/define/live-audio/skin.ts index 54206b55..e8d68d71 100644 --- a/packages/html/src/define/live-audio/skin.ts +++ b/packages/html/src/define/live-audio/skin.ts @@ -1,5 +1,5 @@ import { renderIcon } from '@videojs/icons/render'; -import { createShadowStyle, createTemplate } from '@videojs/utils/dom'; +import { createTemplate } from '@videojs/utils/dom'; import { safeDefine } from '../safe-define'; import { SkinElement } from '../skin-element'; import styles from './skin.css?inline'; @@ -75,7 +75,7 @@ function getTemplateHTML() { export class LiveAudioSkinElement extends SkinElement { static readonly tagName = 'live-audio-skin'; - static styles = createShadowStyle(styles); + static styles = styles; static template = createTemplate(getTemplateHTML()); } diff --git a/packages/html/src/define/live-video/minimal-skin.css b/packages/html/src/define/live-video/minimal-skin.css index 400e59a1..cbc3a5fb 100644 --- a/packages/html/src/define/live-video/minimal-skin.css +++ b/packages/html/src/define/live-video/minimal-skin.css @@ -1,3 +1,2 @@ @import "../global.css"; @import "../shared.css"; -@import "@videojs/skins/minimal/css/video.css"; diff --git a/packages/html/src/define/live-video/minimal-skin.tailwind.ts b/packages/html/src/define/live-video/minimal-skin.tailwind.ts index 611833f4..0f440803 100644 --- a/packages/html/src/define/live-video/minimal-skin.tailwind.ts +++ b/packages/html/src/define/live-video/minimal-skin.tailwind.ts @@ -1,19 +1,25 @@ import { renderIcon } from '@videojs/icons/render/minimal'; import { + airplayIcon, bufferingIndicator, button, buttonGroupEnd, buttonGroupStart, + captionsIcon, + castIcon, + container, controls, error, + fullscreenIcon, icon, - iconState, inputFeedback, menu, + muteIcon, overlay, + pipIcon, + playIcon, popup, poster, - root, slider, } from '@videojs/skins/minimal/tailwind/video.tailwind'; import { createTemplate } from '@videojs/utils/dom'; @@ -26,12 +32,12 @@ import './minimal-ui'; function getTemplateHTML() { return /*html*/ ` - + - + @@ -54,10 +60,10 @@ function getTemplateHTML() {
- - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + ${renderIcon('restart', { class: cn(icon, playIcon.restart) })} + ${renderIcon('play', { class: cn(icon, playIcon.play) })} + ${renderIcon('pause', { class: cn(icon, playIcon.pause) })} @@ -70,10 +76,10 @@ function getTemplateHTML() {
- - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + ${renderIcon('volume-off', { class: cn(icon, muteIcon.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, muteIcon.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, muteIcon.volumeHigh) })} @@ -84,9 +90,9 @@ function getTemplateHTML() { - - ${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })} - ${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })} + + ${renderIcon('captions-off', { class: cn(icon, captionsIcon.off) })} + ${renderIcon('captions-on', { class: cn(icon, captionsIcon.on) })} @@ -104,33 +110,33 @@ function getTemplateHTML() { - - ${renderIcon('cast-enter', { class: cn(icon, iconState.cast.enter) })} - ${renderIcon('cast-exit', { class: cn(icon, iconState.cast.exit) })} + + ${renderIcon('cast-enter', { class: cn(icon, castIcon.enter) })} + ${renderIcon('cast-exit', { class: cn(icon, castIcon.exit) })} - - ${renderIcon('airplay-enter', { class: cn(icon, iconState.airplay.enter) })} - ${renderIcon('airplay-exit', { class: cn(icon, iconState.airplay.exit) })} + + ${renderIcon('airplay-enter', { class: cn(icon, airplayIcon.enter) })} + ${renderIcon('airplay-exit', { class: cn(icon, airplayIcon.exit) })} - - ${renderIcon('pip-enter', { class: cn(icon, iconState.pip.off) })} - ${renderIcon('pip-exit', { class: cn(icon, iconState.pip.on) })} + + ${renderIcon('pip-enter', { class: cn(icon, pipIcon.off) })} + ${renderIcon('pip-exit', { class: cn(icon, pipIcon.on) })} - - ${renderIcon('fullscreen-enter', { class: cn(icon, iconState.fullscreen.enter) })} - ${renderIcon('fullscreen-exit', { class: cn(icon, iconState.fullscreen.exit) })} + + ${renderIcon('fullscreen-enter', { class: cn(icon, fullscreenIcon.enter) })} + ${renderIcon('fullscreen-exit', { class: cn(icon, fullscreenIcon.exit) })} diff --git a/packages/html/src/define/live-video/minimal-skin.ts b/packages/html/src/define/live-video/minimal-skin.ts index c073e2a3..332e8cfc 100644 --- a/packages/html/src/define/live-video/minimal-skin.ts +++ b/packages/html/src/define/live-video/minimal-skin.ts @@ -1,5 +1,5 @@ import { renderIcon } from '@videojs/icons/render/minimal'; -import { createShadowStyle, createTemplate } from '@videojs/utils/dom'; +import { createTemplate } from '@videojs/utils/dom'; import { safeDefine } from '../safe-define'; import { SkinElement } from '../skin-element'; import styles from './minimal-skin.css?inline'; @@ -183,7 +183,7 @@ function getTemplateHTML() { export class MinimalLiveVideoSkinElement extends SkinElement { static readonly tagName = 'live-video-minimal-skin'; - static styles = createShadowStyle(styles); + static styles = styles; static template = createTemplate(getTemplateHTML()); } diff --git a/packages/html/src/define/live-video/skin.css b/packages/html/src/define/live-video/skin.css index 368cb41f..cbc3a5fb 100644 --- a/packages/html/src/define/live-video/skin.css +++ b/packages/html/src/define/live-video/skin.css @@ -1,3 +1,2 @@ @import "../global.css"; @import "../shared.css"; -@import "@videojs/skins/default/css/video.css"; diff --git a/packages/html/src/define/live-video/skin.tailwind.ts b/packages/html/src/define/live-video/skin.tailwind.ts index 3f036d14..2d0a2fab 100644 --- a/packages/html/src/define/live-video/skin.tailwind.ts +++ b/packages/html/src/define/live-video/skin.tailwind.ts @@ -1,19 +1,25 @@ import { renderIcon } from '@videojs/icons/render'; import { + airplayIcon, bufferingIndicator, button, buttonGroupEnd, buttonGroupStart, + captionsIcon, + castIcon, + container, controls, error, + fullscreenIcon, icon, - iconState, inputFeedback, menu, + muteIcon, overlay, + pipIcon, + playIcon, popup, poster, - root, slider, } from '@videojs/skins/default/tailwind/video.tailwind'; import { createTemplate } from '@videojs/utils/dom'; @@ -26,12 +32,12 @@ import './ui'; function getTemplateHTML() { return /*html*/ ` - + - + @@ -56,10 +62,10 @@ function getTemplateHTML() {
- - ${renderIcon('restart', { class: cn(icon, iconState.play.restart) })} - ${renderIcon('play', { class: cn(icon, iconState.play.play) })} - ${renderIcon('pause', { class: cn(icon, iconState.play.pause) })} + + ${renderIcon('restart', { class: cn(icon, playIcon.restart) })} + ${renderIcon('play', { class: cn(icon, playIcon.play) })} + ${renderIcon('pause', { class: cn(icon, playIcon.pause) })} @@ -72,10 +78,10 @@ function getTemplateHTML() {
- - ${renderIcon('volume-off', { class: cn(icon, iconState.mute.volumeOff) })} - ${renderIcon('volume-low', { class: cn(icon, iconState.mute.volumeLow) })} - ${renderIcon('volume-high', { class: cn(icon, iconState.mute.volumeHigh) })} + + ${renderIcon('volume-off', { class: cn(icon, muteIcon.volumeOff) })} + ${renderIcon('volume-low', { class: cn(icon, muteIcon.volumeLow) })} + ${renderIcon('volume-high', { class: cn(icon, muteIcon.volumeHigh) })} @@ -86,9 +92,9 @@ function getTemplateHTML() { - - ${renderIcon('captions-off', { class: cn(icon, iconState.captions.off) })} - ${renderIcon('captions-on', { class: cn(icon, iconState.captions.on) })} + + ${renderIcon('captions-off', { class: cn(icon, captionsIcon.off) })} + ${renderIcon('captions-on', { class: cn(icon, captionsIcon.on) })} @@ -106,33 +112,33 @@ function getTemplateHTML() { - - ${renderIcon('cast-enter', { class: cn(icon, iconState.cast.enter) })} - ${renderIcon('cast-exit', { class: cn(icon, iconState.cast.exit) })} + + ${renderIcon('cast-enter', { class: cn(icon, castIcon.enter) })} + ${renderIcon('cast-exit', { class: cn(icon, castIcon.exit) })} - - ${renderIcon('airplay-enter', { class: cn(icon, iconState.airplay.enter) })} - ${renderIcon('airplay-exit', { class: cn(icon, iconState.airplay.exit) })} + + ${renderIcon('airplay-enter', { class: cn(icon, airplayIcon.enter) })} + ${renderIcon('airplay-exit', { class: cn(icon, airplayIcon.exit) })} - - ${renderIcon('pip-enter', { class: cn(icon, iconState.pip.off) })} - ${renderIcon('pip-exit', { class: cn(icon, iconState.pip.on) })} + + ${renderIcon('pip-enter', { class: cn(icon, pipIcon.off) })} + ${renderIcon('pip-exit', { class: cn(icon, pipIcon.on) })} - - ${renderIcon('fullscreen-enter', { class: cn(icon, iconState.fullscreen.enter) })} - ${renderIcon('fullscreen-exit', { class: cn(icon, iconState.fullscreen.exit) })} + + ${renderIcon('fullscreen-enter', { class: cn(icon, fullscreenIcon.enter) })} + ${renderIcon('fullscreen-exit', { class: cn(icon, fullscreenIcon.exit) })} diff --git a/packages/html/src/define/live-video/skin.ts b/packages/html/src/define/live-video/skin.ts index 8991ee78..81a3a745 100644 --- a/packages/html/src/define/live-video/skin.ts +++ b/packages/html/src/define/live-video/skin.ts @@ -1,5 +1,5 @@ import { renderIcon } from '@videojs/icons/render'; -import { createShadowStyle, createTemplate } from '@videojs/utils/dom'; +import { createTemplate } from '@videojs/utils/dom'; import { safeDefine } from '../safe-define'; import { SkinElement } from '../skin-element'; import styles from './skin.css?inline'; @@ -184,7 +184,7 @@ function getTemplateHTML() { export class LiveVideoSkinElement extends SkinElement { static readonly tagName = 'live-video-skin'; - static styles = createShadowStyle(styles); + static styles = styles; static template = createTemplate(getTemplateHTML()); } diff --git a/packages/html/src/define/skin-element.ts b/packages/html/src/define/skin-element.ts index 517f3125..664ae872 100644 --- a/packages/html/src/define/skin-element.ts +++ b/packages/html/src/define/skin-element.ts @@ -14,8 +14,7 @@ const sharedSheet = createShadowStyle(sharedStyles); /** * Base element for skin definitions. Attaches a shadow root, clones - * `static template` into it, and applies shared + per-skin styles - * via `adoptedStyleSheets` (or `