fix(types): [PKG-FACTORY-01] preserve published factory compatibility

This commit is contained in:
Harvey Zhao committed 2026-09-13 16:53:27 +08:00
1 parent 1117107032
commit c4c7491e16
35 files changed
+670 -229

No files matched your search

@@ -53,22 +53,36 @@ video/canvas with a lexical native RAF observer across published cores 5.1.7/5.4
and candidate core in Chromium/Firefox/WebKit. Proxy/device and complete packed
browser/demo acceptance remain tasks 05-06; see `refactor/ambilight-validation.md`.
Public types merge a callable value with a type-only namespace. An optional first
overload permits omitted/undefined options; the required final overload preserves
the published 1.1.0 `Parameters` extraction. Do not reorder these signatures. The
runtime factory has a self-referencing `default` property to support both historical
CommonJS forms. `.d.mts`/`.d.cts` forward to the same declaration for the actual ESM/
CommonJS entry kinds; `typesVersions` supplies `/legacy` to older resolvers. The
editor global is generated by `yarn build:ts` through the semantic plugin generator.
Root and `/legacy` public types preserve the exact published 1.1.0 required-option
factory and result. Do not add an optional overload or a required `default`
property: preserving `Parameters` alone does not preserve reverse assignment of
ordinary replacement factories. The JavaScript factory still has its historical
self-referencing `default` property and accepts omitted options.
`RuntimeFactory` and `/runtime` describe those optional calls and the self alias.
The subpath maps to the same production files as root, with `runtime.d.mts` for
native ESM and `runtime.d.cts` for CommonJS. `runtime.d.ts` provides Node10's
export-assignment shape through `typesVersions`. Root and `/legacy` use a
types-first mapping to the original default-export declaration, retaining 1.1.0's
NodeNext ESM namespace shape. The editor global is generated from the root
declaration by `yarn build:ts`; it likewise requires an options argument.
This follows the approved `refactor/type-compatibility-policy.md` and ADR-025.
The earlier 1.0.0 export-assignment and required-field conflicts have migration
examples in README. No source, emitted JavaScript, defaults, or old JS entrypoint
changes are part of this type repair.
Run `node --test refactor/scripts/ambilight-types.test.mjs` for public/editor/format
contracts and `yarn test:ambilight-types-package` for offline tarball installations
outside the workspace. The latter verifies two actual published plugin archives and
candidate against the same packed candidate core, five compiler modes plus two
candidate modes with interop disabled, and ten negative uses. Published 1.1.0 fails
NodeNext ESM with its old declarations; candidate must not inherit that failure.
Published 1.0.0 required all option fields; 1.1.0 already made them optional. Current
inference follows 1.1.0 while retaining valid calls from both generations.
candidate modes with interop disabled, and fifteen negative uses at their exact
statement lines. Published 1.1.0 has four NodeNext ESM diagnostics for the frozen
direct-call fixture; candidate preserves those codes and its valid namespace
consumer. Earlier 1.0.0 raw `import = require` root calls are separately checked:
they compile against 1.0.0 and retain the 1.1.0/candidate non-callable diagnostic.
Candidate `/runtime` raw require and native ESM calls compile successfully.
Published 1.0.0 required all option fields; 1.1.0 already made them optional.
`test/ambilight-proxy.test.js` and `test/browser/ambilight-proxy.spec.js` protect
resized output geometry. Browser tests play real video with the actual workspace
+38 -5
View File
@@ -20,11 +20,44 @@ const option: Option = { blur: '50px', opacity: 0.5, frequency: 10, duration: 0.
const plugins = [artplayerPluginAmbilight(option)]
```
Options can be omitted. `require('artplayer-plugin-ambilight')` and the historical
`require('artplayer-plugin-ambilight').default` refer to the same factory. The
`/legacy` entry remains available. The historical `zIndex` input is accepted but
ignored; the grid uses 9. Option fields stay optional as in version 1.1.0; code
extracting their types should account for `undefined`.
Root and `/legacy` types preserve the latest published 1.1.0 contract: the options
argument is required, its fields are optional, and an ordinary replacement factory
does not need a `default` property. Pass `{}` to use defaults with these types.
The historical `zIndex` input is accepted but ignored; the grid uses 9.
For accurate optional invocation and module interop, use `/runtime`:
```ts
import ambilight from 'artplayer-plugin-ambilight/runtime'
const plugins = [ambilight(), ambilight(undefined), ambilight.default({})]
```
It reuses the root implementation. Existing JavaScript calls with omitted options,
`require('artplayer-plugin-ambilight')` and
`require('artplayer-plugin-ambilight').default` still work. Start/stop remain
synchronous and `/legacy` keeps the historical build.
### Earlier TypeScript declarations
Version 1.0.0 used `export =` and required every option field; 1.1.0 changed to a
default export and optional fields. The root follows 1.1.0 rather than combining
incompatible factory signatures. Earlier TypeScript `import = require` callers
can switch only their module path:
```ts
import ambilight = require('artplayer-plugin-ambilight/runtime')
ambilight({ blur: '50px', opacity: 0.5, frequency: 10, zIndex: 9, duration: 0.3 })
```
Code reading an option field as a required value should supply a fallback, for
example `option.blur ?? '50px'`. This optional-field change already existed in
1.1.0. NodeNext ESM sees the historical root declaration as a CommonJS namespace;
that type shape is preserved, including its previous direct-call diagnostics.
Use `/runtime` for an accurate callable default import in native ESM. Node10
TypeScript default imports of `/runtime` require `esModuleInterop`; `import =
require` also works with interop disabled. The runtime does not add any dependency.
## Demo
@@ -19,24 +19,24 @@
],
"exports": {
".": {
"types": "./types/artplayer-plugin-ambilight.d.ts",
"import": "./dist/artplayer-plugin-ambilight.mjs",
"require": "./dist/artplayer-plugin-ambilight.js"
},
"./legacy": {
"types": "./types/artplayer-plugin-ambilight.d.ts",
"import": "./dist/artplayer-plugin-ambilight.legacy.js",
"require": "./dist/artplayer-plugin-ambilight.legacy.js"
},
"./runtime": {
"import": {
"types": "./types/artplayer-plugin-ambilight.d.mts",
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-plugin-ambilight.mjs"
},
"require": {
"types": "./types/artplayer-plugin-ambilight.d.cts",
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-plugin-ambilight.js"
}
},
"./legacy": {
"import": {
"types": "./types/artplayer-plugin-ambilight.d.cts",
"default": "./dist/artplayer-plugin-ambilight.legacy.js"
},
"require": {
"types": "./types/artplayer-plugin-ambilight.d.cts",
"default": "./dist/artplayer-plugin-ambilight.legacy.js"
}
}
},
"main": "./dist/artplayer-plugin-ambilight.js",
@@ -48,6 +48,9 @@
"*": {
"legacy": [
"types/artplayer-plugin-ambilight.d.ts"
],
"runtime": [
"types/runtime.d.ts"
]
}
}
@@ -1,4 +0,0 @@
// eslint-disable-next-line ts/no-require-imports -- Preserve the CommonJS export-assignment namespace.
import artplayerPluginAmbilight = require('./artplayer-plugin-ambilight.js')
export = artplayerPluginAmbilight
@@ -1,2 +0,0 @@
export { default } from './artplayer-plugin-ambilight.js'
export type { Callable, Factory, Option, Result } from './artplayer-plugin-ambilight.js'
@@ -1,40 +1,35 @@
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
import type Artplayer from 'artplayer'
declare namespace artplayerPluginAmbilight {
interface Option {
/** CSS blur radius. @default '50px' */
blur?: string
/** Grid cell opacity. @default 0.5 */
opacity?: number
/** Maximum sampling frequency in frames per second. @default 10 */
frequency?: number
/** Historical input retained for compatibility; runtime uses a fixed z-index of 9. */
zIndex?: number
/** Background color transition duration in seconds. @default 0.3 */
duration?: number
}
interface Result {
name: 'artplayerPluginAmbilight'
/** Start sampling; does nothing after the player is destroyed. */
start: () => void
/** Stop sampling while retaining the last colors. */
stop: () => void
}
interface Callable {
(option?: Option): (art: Artplayer) => Result
/** Keep Parameters extraction identical to the published 1.1.0 signature. */
(option: Option): (art: Artplayer) => Result
}
interface Factory extends Callable {
/** Same function, including historical require(package).default calls. */
readonly default: Factory
}
export interface Option {
/** CSS blur radius. @default '50px' */
blur?: string
/** Grid cell opacity. @default 0.5 */
opacity?: number
/** Maximum sampling frequency in frames per second. @default 10 */
frequency?: number
/** Historical input retained for compatibility; runtime uses a fixed z-index of 9. */
zIndex?: number
/** Background color transition duration in seconds. @default 0.3 */
duration?: number
}
declare const artplayerPluginAmbilight: artplayerPluginAmbilight.Factory
export = artplayerPluginAmbilight
export as namespace artplayerPluginAmbilight
export interface Result {
name: 'artplayerPluginAmbilight'
/** Start sampling; does nothing after the player is destroyed. */
start: () => void
/** Stop sampling while retaining the last colors. */
stop: () => void
}
/** Published 1.1.0 factory shape; the options argument remains required. */
export type Callable = (option: Option) => (art: Artplayer) => Result
export type Factory = Callable
/** Accurate optional invocation and CommonJS self alias, exposed by /runtime. */
export interface RuntimeFactory {
(option?: Option): (art: Artplayer) => Result
readonly default: RuntimeFactory
}
declare const artplayerPluginAmbilight: (option: Option) => (art: Artplayer) => Result
export default artplayerPluginAmbilight
@@ -0,0 +1,10 @@
import type { RuntimeFactory } from './artplayer-plugin-ambilight.js'
declare const artplayerPluginAmbilight: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginAmbilight {
type Option = import('./artplayer-plugin-ambilight.js').Option
type Result = import('./artplayer-plugin-ambilight.js').Result
type RuntimeFactory = import('./artplayer-plugin-ambilight.js').RuntimeFactory
}
export = artplayerPluginAmbilight
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './artplayer-plugin-ambilight.js'
declare const artplayerPluginAmbilight: RuntimeFactory
export default artplayerPluginAmbilight
export type { Option, Result, RuntimeFactory } from './artplayer-plugin-ambilight.js'
+10
View File
@@ -0,0 +1,10 @@
import type { RuntimeFactory } from './artplayer-plugin-ambilight.js'
declare const artplayerPluginAmbilight: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginAmbilight {
type Option = import('./artplayer-plugin-ambilight.js').Option
type Result = import('./artplayer-plugin-ambilight.js').Result
type RuntimeFactory = import('./artplayer-plugin-ambilight.js').RuntimeFactory
}
export = artplayerPluginAmbilight
@@ -3,10 +3,18 @@
Public entry: `src/index.ts`; public declarations remain in
`types/artplayer-proxy-canvas.d.ts`. The factory accepts the optional drawing callback,
then synchronously returns an actual HTMLCanvasElement when registered with ArtPlayer.
The public namespace preserves the optional callback and exact Canvas return extraction.
The root preserves the latest npm 1.1.0 pure factory, optional callback and exact Canvas
return extraction. Do not attach a required default member to this type: that breaks
assigning an ordinary historical replacement function back to typeof factory.
MediaCanvas is an explicit type view, not a narrowed factory result. CommonJS and browser
factory.default are self aliases; .d.mts/.d.cts wrappers distinguish ESM and CommonJS
resolution, and typesVersions keeps the legacy path available to older TypeScript.
factory.default remain runtime self aliases. Accurate RuntimeFactory lives behind the
additive /runtime entry, whose runtime.d.mts/runtime.d.cts bridges distinguish ESM and
CommonJS and whose runtime.d.ts supports old Node10 export= resolution. Both entrypoints
load the same existing dist files. The old root declaration bridges remain packaged,
but root/legacy conditional types deliberately select the original .d.ts module shape.
Root NodeNext ESM therefore keeps the published namespace shape; it is not silently made
callable. Use /runtime for precise ESM default calls. The package README documents the
approved 1.0 export= migration; no JavaScript calls were removed by this type decision.
Historical JavaScript falsy callback arguments remain ignored at runtime; the typed public
callback remains optional and function-valued.
@@ -72,8 +80,13 @@ geometry and is separate from native media pixel acceptance. npm core 5.1.7 has
option and is only a native-player capability control.
`yarn test:canvas-types-package` installs real archived and candidate packages outside the
workspace, verifies installed bytes and checks old/current compiler modes. The public
namespace also generates the local editor declaration through the semantic generator.
workspace, verifies installed bytes and checks old/current compiler modes. It preserves
the actual old NodeNext direct-import errors and 1.0 export= differences, while testing
latest namespace replacements, plain factory bidirectional assignments, runtime self
identity and negative statements at their exact lines. No skipLibCheck or strictness
relaxation is used. The public declarations also generate the local editor declaration
through the semantic generator; the editor root stays plain, with an explicit
artplayerProxyCanvas.RuntimeFactory assertion for code needing the runtime self alias.
Broader installed artifact contents/deep paths and 8082 demos remain 06.
Real Safari/mobile/device scope remains 05; Windows Playwright WebKit is not a device claim.
See `refactor/canvas-validation.md` and the task evidence for actual outcomes and limitations.
+30 -4
View File
@@ -29,14 +29,19 @@ historical `require('artplayer-proxy-canvas').default` refer to the same factory
## TypeScript
The optional callback and exact `HTMLCanvasElement` return type retain published 1.1.0
inference. `Option`, `Result`, `Factory` and `MediaCanvas` are exported types. The last is
an explicit view for code using supported forwarded media members:
The root and `/legacy` declarations preserve the latest published 1.1.0 factory:
an optional callback, an exact `HTMLCanvasElement` result, and no required factory
properties. Ordinary replacement functions remain assignable in both directions.
`Factory` describes that historical shape. The additive `/runtime` entry uses the same
JavaScript implementation and describes its readonly `default` self alias with
`RuntimeFactory`. It also exports an explicit `MediaCanvas` view:
```ts
import type { MediaCanvas, Option } from 'artplayer-proxy-canvas'
import type { MediaCanvas, Option } from 'artplayer-proxy-canvas/runtime'
import canvas from 'artplayer-proxy-canvas/runtime'
const callback: Option = (context, video) => context.drawImage(video, 0, 0)
canvas.default(callback)
const media = art.template.$video as MediaCanvas
await media.play()
```
@@ -48,6 +53,27 @@ Runtime falsy callback arguments retain historical behavior; declarations accept
optional function, matching the published types. Implementation and test guidance is in
[ARCHITECTURE.md](ARCHITECTURE.md).
### Older TypeScript consumers
The 1.0.0 declaration used `export =` and required a callback; 1.1.0 changed to
`export default` and made the callback optional. These conflicting type shapes cannot
both remain the root factory. Following the approved compatibility policy, the root
keeps 1.1.0. A 1.0.0 TypeScript `import canvas = require('artplayer-proxy-canvas')`
consumer that directly calls `canvas(...)` can select the accurate entry instead:
```ts
import canvas = require('artplayer-proxy-canvas/runtime')
canvas((context, video) => context.drawImage(video, 0, 0))
canvas.default()
```
This works with Node10 resolution even without `esModuleInterop`. Default imports from
`/runtime` work with interoperability enabled and with native NodeNext ESM. Under
NodeNext ESM, the root intentionally preserves 1.1.0's historical declaration namespace
shape (`root.default(...)` after a default import); that declaration shape does not
describe the actual ESM factory. Use a default import from `/runtime` for accurate ESM
typing. Existing JavaScript root/legacy calls and distribution paths keep working.
## Demo
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-proxy-canvas/index.js&example=canvas)
+15 -12
View File
@@ -19,24 +19,24 @@
],
"exports": {
".": {
"types": "./types/artplayer-proxy-canvas.d.ts",
"import": "./dist/artplayer-proxy-canvas.mjs",
"require": "./dist/artplayer-proxy-canvas.js"
},
"./legacy": {
"types": "./types/artplayer-proxy-canvas.d.ts",
"import": "./dist/artplayer-proxy-canvas.legacy.js",
"require": "./dist/artplayer-proxy-canvas.legacy.js"
},
"./runtime": {
"import": {
"types": "./types/artplayer-proxy-canvas.d.mts",
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-proxy-canvas.mjs"
},
"require": {
"types": "./types/artplayer-proxy-canvas.d.cts",
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-proxy-canvas.js"
}
},
"./legacy": {
"import": {
"types": "./types/artplayer-proxy-canvas.d.cts",
"default": "./dist/artplayer-proxy-canvas.legacy.js"
},
"require": {
"types": "./types/artplayer-proxy-canvas.d.cts",
"default": "./dist/artplayer-proxy-canvas.legacy.js"
}
}
},
"main": "./dist/artplayer-proxy-canvas.js",
@@ -48,6 +48,9 @@
"*": {
"legacy": [
"types/artplayer-proxy-canvas.d.ts"
],
"runtime": [
"types/runtime.d.ts"
]
}
}
@@ -1,4 +1,2 @@
// eslint-disable-next-line ts/no-require-imports -- Preserve the CommonJS export-assignment namespace.
import artplayerProxyCanvas = require('./artplayer-proxy-canvas.js')
export = artplayerProxyCanvas
export { default } from './artplayer-proxy-canvas.js'
export type { Callable, Factory, MediaCanvas, Option, Result, RuntimeFactory } from './artplayer-proxy-canvas.js'
@@ -1,2 +1,5 @@
export { default } from './artplayer-proxy-canvas.js'
export type { Callable, Factory, MediaCanvas, Option, Result } from './artplayer-proxy-canvas.js'
import type { Factory } from './artplayer-proxy-canvas.js'
declare const artplayerProxyCanvas: Factory
export default artplayerProxyCanvas
export type { Callable, Factory, MediaCanvas, Option, Result, RuntimeFactory } from './artplayer-proxy-canvas.js'
@@ -1,27 +1,22 @@
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
import type Artplayer from 'artplayer'
declare namespace artplayerProxyCanvas {
/** Runs after drawing and bitmap release, before the draw event. */
type Option = (ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => void
/** Runs after drawing and bitmap release, before the draw event. */
export type Option = (ctx: CanvasRenderingContext2D, video: HTMLVideoElement) => void
/** Preserve the exact published return type and its assignability. */
type Result = HTMLCanvasElement
/** Preserve the exact published 1.1.0 return type and its assignability. */
export type Result = HTMLCanvasElement
/** Opt-in view of supported enumerable media members; native Canvas members win. */
type MediaCanvas = HTMLCanvasElement & Pick<HTMLVideoElement, Exclude<keyof HTMLVideoElement, keyof HTMLCanvasElement>>
export type Factory = (option?: Option) => (art: Artplayer) => Result
export type Callable = Factory
interface Callable {
/** Keep Parameters extraction optional, as published in 1.1.0. */
(option?: Option): (art: Artplayer) => Result
}
/** Explicit view of forwarded media members; native Canvas members win. */
export type MediaCanvas = HTMLCanvasElement & Pick<HTMLVideoElement, Exclude<keyof HTMLVideoElement, keyof HTMLCanvasElement>>
interface Factory extends Callable {
/** Same factory, for historical require(package).default calls. */
readonly default: Factory
}
/** Opt-in runtime identity; the historical root factory has no required properties. */
export interface RuntimeFactory extends Factory {
readonly default: RuntimeFactory
}
declare const artplayerProxyCanvas: artplayerProxyCanvas.Factory
export = artplayerProxyCanvas
export as namespace artplayerProxyCanvas
declare const artplayerProxyCanvas: (option?: Option) => (art: Artplayer) => Result
export default artplayerProxyCanvas
@@ -0,0 +1,12 @@
import type { RuntimeFactory } from './artplayer-proxy-canvas.js'
declare const artplayerProxyCanvas: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerProxyCanvas {
type Option = import('./artplayer-proxy-canvas.js').Option
type Result = import('./artplayer-proxy-canvas.js').Result
type Factory = import('./artplayer-proxy-canvas.js').Factory
type MediaCanvas = import('./artplayer-proxy-canvas.js').MediaCanvas
type RuntimeFactory = import('./artplayer-proxy-canvas.js').RuntimeFactory
}
export = artplayerProxyCanvas
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './artplayer-proxy-canvas.js'
declare const artplayerProxyCanvas: RuntimeFactory
export default artplayerProxyCanvas
export type { Factory, MediaCanvas, Option, Result, RuntimeFactory } from './artplayer-proxy-canvas.js'
+12
View File
@@ -0,0 +1,12 @@
import type { RuntimeFactory } from './artplayer-proxy-canvas.js'
declare const artplayerProxyCanvas: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerProxyCanvas {
type Option = import('./artplayer-proxy-canvas.js').Option
type Result = import('./artplayer-proxy-canvas.js').Result
type Factory = import('./artplayer-proxy-canvas.js').Factory
type MediaCanvas = import('./artplayer-proxy-canvas.js').MediaCanvas
type RuntimeFactory = import('./artplayer-proxy-canvas.js').RuntimeFactory
}
export = artplayerProxyCanvas