refactor(jassub): [PKG-JASSUB-04] migrate owned TypeScript and preserve public types

This commit is contained in:
Harvey Zhao committed 2026-09-14 01:41:31 +08:00
1 parent 38e7b37878
commit 5e445b1af6
33 files changed
+3636 -61

No files matched your search

+2 -1
View File
@@ -2,4 +2,5 @@ node_modules
.DS_Store
yarn.lock
package-lock.json
src
src
tsconfig.json
@@ -1,7 +1,7 @@
# JASSUB maintenance
`src/index.js` preserves the lazy factory and delegates registration to
`src/registration.js`. The registration module constructs the actual vendor object
`src/index.ts` preserves the lazy factory and delegates registration to
`src/registration.ts`. The registration module constructs the actual vendor object
with `{ video: art.video, ...option }`, styles only a vendor-created canvas parent,
binds host cleanup and synchronously returns `{ name: 'artplayerPluginJassub', instance }`.
Options remain live until registration; a supplied video wins. This small split keeps
@@ -27,11 +27,27 @@ messages. Keep this third-party file separate from the adapter's TypeScript migr
files. The local demo explicitly hosts its resources under `docs/assets/jassub/` and selects
fonts in `docs/assets/example/jassub.js`. Do not move or rename those URLs as an internal cleanup.
`types/artplayer-plugin-jassub.d.ts` currently requires three resource URLs and describes
resize/setVideo/destroy as Promise-returning. Actual historical methods are synchronous, and
resize is width/height/top/left/force. Keep the existing declarations until the dedicated public
type compatibility work has assessed extraction, callbacks and replacement consumers.
Both option and instance expose extension indexes; they are existing compatibility boundaries.
`types/artplayer-plugin-jassub.d.ts` preserves the actual npm 1.0.0/1.1.0 declarations,
including three required resource URLs, Promise-returning resize/setVideo/destroy and
the historical force-first resize signature. Both option and instance retain their existing
extension indexes. Do not correct this root file by narrowing fields or adding overloads:
complete factory assignment and Parameters/ReturnType are compatibility boundaries.
`types/runtime-api.d.ts` separately describes the actual optional options, synchronous
registration and methods, width/height/top/left/force resize, EventTarget events and
Worker query/mutation data. `types/runtime.d.ts`, `.d.cts` and `.d.mts` expose it through
the optional `/runtime` entry; it resolves to the same main/ESM JavaScript as the root.
There is no second implementation and no new factory.default property. Root/legacy
types remain unchanged; exact typesVersions mappings support old Node module resolution.
See [type migration notes](types/README.md) before changing either surface.
`src/jassub.es.d.ts` is a private bridge for the frozen vendor JS, not a public export.
It adds only the adapter's `_destroyed` read and `_canvasParent.style.zIndex` write to the
accurate instance. The minimal style contract permits the historical numeric 20, which
the native DOM setter converts to a string. The owned TS modules depend on RuntimeOption
and RuntimeResult, while JassubHost needs only video/on/off. The implementation fixture
checks actual Artplayer assignability. No broad any index, allowJs or ts-nocheck hides
owned code; the isolated third-party JS remains an explicit provenance/07 exception.
The wrapper matches upstream jassub 1.8.8 apart from formatting and the ESLint header;
worker JS and default font match that archive byte-for-byte. Local WASM instead matches
@@ -48,6 +64,10 @@ Use the pinned Node/Yarn toolchain:
```sh
yarn test:jassub
yarn test:jassub-types
yarn test:jassub-types-package
yarn typecheck
yarn build:ts artplayer-plugin-jassub
node --test test/jassub-registration.test.js
node refactor/scripts/jassub-provenance.test.mjs --network
yarn build artplayer-plugin-jassub
@@ -11,6 +11,10 @@ jassub plugin for ArtPlayer
See [maintenance notes](ARCHITECTURE.md) for adapter/vendor boundaries, resource paths,
historical declaration differences and the current validation scope.
Existing imports keep their historical types. The optional
[`artplayer-plugin-jassub/runtime` entry](types/README.md) provides accurate options,
synchronous instance methods and subtitle query types using the same implementation.
## License
MIT © Harvey Zhao
+21 -1
View File
@@ -27,11 +27,31 @@
"types": "./types/artplayer-plugin-jassub.d.ts",
"import": "./dist/artplayer-plugin-jassub.legacy.js",
"require": "./dist/artplayer-plugin-jassub.legacy.js"
},
"./runtime": {
"import": {
"types": "./types/runtime.d.mts",
"default": "./dist/artplayer-plugin-jassub.mjs"
},
"require": {
"types": "./types/runtime.d.cts",
"default": "./dist/artplayer-plugin-jassub.js"
}
}
},
"main": "dist/artplayer-plugin-jassub.js",
"module": "./dist/artplayer-plugin-jassub.mjs",
"types": "types/artplayer-plugin-jassub.d.ts",
"legacy": "dist/artplayer-plugin-jassub.legacy.js",
"browserslist": "last 1 Chrome version"
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": [
"types/artplayer-plugin-jassub.d.ts"
],
"runtime": [
"types/runtime.d.ts"
]
}
}
}
@@ -1,5 +0,0 @@
import { registerJassub } from './registration.js'
export default function artplayerPluginJassub(option) {
return art => registerJassub(art, option)
}
@@ -0,0 +1,7 @@
import type { RuntimeOption } from '../types/runtime-api.js'
import type { JassubHost } from './registration.js'
import { registerJassub } from './registration.js'
export default function artplayerPluginJassub(option?: RuntimeOption) {
return (art: JassubHost) => registerJassub(art, option)
}
+12
View File
@@ -0,0 +1,12 @@
import type { RuntimeInstance, RuntimeOption } from '../types/runtime-api.js'
interface VendorInstance extends RuntimeInstance {
_destroyed?: boolean
// Minimal write contract: the DOM setter coerces the historical numeric 20.
_canvasParent?: { style: { zIndex: string | number } }
}
declare const JASSUB: {
new (option: RuntimeOption): VendorInstance
}
export default JASSUB
@@ -1,6 +1,13 @@
import type { RuntimeOption, RuntimeResult } from '../types/runtime-api.js'
import JASSUB from './jassub.es.js'
export function registerJassub(art, option) {
export interface JassubHost {
video: HTMLVideoElement
on: (name: 'destroy', callback: () => void) => unknown
off: (name: 'destroy', callback: () => void) => unknown
}
export function registerJassub(art: JassubHost, option?: RuntimeOption): RuntimeResult {
const instance = new JASSUB({ video: art.video, ...option })
let disposed = false
const dispose = () => {
@@ -0,0 +1,5 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": { "allowJs": false },
"include": ["src/**/*.ts", "../../refactor/fixtures/implementation/jassub.ts"]
}
@@ -0,0 +1,67 @@
# JASSUB type compatibility
The root and `/legacy` declarations retain the complete npm 1.0.0 and 1.1.0 type
shape, including known mistakes. Existing TypeScript consumers do not need to
change imports as part of this migration. Both releases used the same declarations.
The generated online-editor global also preserves this historical surface.
For accurate types, explicitly opt into the additive entry:
```ts
import jassub from 'artplayer-plugin-jassub/runtime'
const register = jassub({
workerUrl: '/assets/jassub-worker.js',
wasmUrl: '/assets/jassub-worker.wasm',
modernWasmUrl: '/assets/jassub-worker-modern.wasm',
subUrl: '/subtitles.ass',
})
// Add register to the existing Artplayer plugins option.
// When called with an Artplayer, it synchronously returns { name, instance }.
```
`/runtime` uses the same JavaScript file as the root entry. It does not change
defaults, wrap instances, download resources differently or introduce a second
implementation. URLs are optional because the vendor has defaults; this does not
mean its default relative URLs will exist on your site. Continue hosting compatible
worker, WASM and font files and supplying the paths your deployment needs.
| Area | Historical root types | Accurate `/runtime` types and existing JavaScript |
| --- | --- | --- |
| Factory options | Required, with three required URLs | Optional options; optional resource URLs |
| Instance resize | force, width, height, top, left | width, height, top, left, force |
| resize/setVideo/destroy | Promise<void> | Synchronous; no-argument calls return void |
| sendMessage | Unchecked extension | Promise<void>, resolves after posting, not after Worker completion |
| Events/styles | Unchecked extension | Typed callbacks and partial mutation objects |
| ASS event Style | Unchecked extension | Numeric libass style index |
| ASS Start/Duration | Unchecked extension | Milliseconds; setCurrentTime uses seconds |
| Unknown fields/methods | Existing any indexes | Named supported fields; unknown members rejected |
Switching to `/runtime` intentionally changes type inference. For example,
`instance.resize(640, 360, 0, 0, true)` describes the actual call order.
`await instance.destroy()` remains legal JavaScript, but `.then()` on that result
never worked at runtime. Resource teardown completion is not represented by a Promise.
The callback error channel is `Error | ErrorEvent | null`; an error may omit the data.
The currently frozen vendor has a request timeout/error bug that throws before user
callbacks run. PKG-JASSUB-07 tracks the fix; typing that channel does not fix or guarantee
callback delivery. Query outputs do not contain the upstream npm declaration's `_index`.
The accurate instance extends EventTarget and preserves its normal listener APIs.
`destroy(error)` returns the original Error, or converts a nonempty string to Error;
an empty string is returned unchanged. Repeated direct destruction and construction/
video clock failures remain separate vendor lifecycle work, documented in ARCHITECTURE.md.
NodeNext ESM retains the root declaration's historical CommonJS namespace behavior.
If existing code uses the root namespace's `.default` to satisfy that declaration,
keep that form or opt into `/runtime`, whose conditional `.d.mts` is directly callable.
CommonJS accurate types use `import jassub = require('artplayer-plugin-jassub/runtime')`.
Do not invent `jassub.default`: the current JavaScript factory has no self alias.
Older TypeScript resolves `/legacy` and `/runtime` through exact typesVersions mappings.
Existing direct declaration paths are retained; no wildcard remaps unrelated paths.
Maintain `runtime-api.d.ts` against the frozen wrapper and worker behavior, not a new
upstream version. Keep CJS/ESM entry aliases synchronized. Run `yarn test:jassub-types`,
`yarn test:jassub-types-package`, `yarn test:jassub`, and `yarn typecheck` after changes.
The package runner installs actual historical archives and the candidate outside the
workspace, checks strict positive/negative consumers, and verifies artifact identity.
This is type/distribution evidence, not browser playback or final release acceptance.
+155
View File
@@ -0,0 +1,155 @@
// Core exposes a default constructor in Node10/ESM and export= in NodeNext CJS.
// Extract the same instance without requiring esModuleInterop in consumers.
type CoreModule = typeof import('artplayer')
type Artplayer = CoreModule extends { default: { prototype: infer Instance } }
? Instance
: CoreModule extends { prototype: infer Instance } ? Instance : never
export type FontSource = string | Uint8Array
/** Options are read when the registrar runs; the host supplies video by default. */
export interface RuntimeOption {
video?: HTMLVideoElement | null
canvas?: HTMLCanvasElement
blendMode?: 'js' | 'wasm'
asyncRender?: boolean
offscreenRender?: boolean
onDemandRender?: boolean
targetFps?: number
timeOffset?: number
debug?: boolean
prescaleFactor?: number
prescaleHeightLimit?: number
maxRenderHeight?: number
dropAllAnimations?: boolean
dropAllBlur?: boolean
workerUrl?: string
wasmUrl?: string
legacyWasmUrl?: string
modernWasmUrl?: string
subUrl?: string
subContent?: string
fonts?: FontSource[]
availableFonts?: Record<string, FontSource>
fallbackFont?: string
useLocalFonts?: boolean
libassMemoryLimit?: number
libassGlyphLimit?: number
}
/** Fields actually serialized by the frozen worker's getEvents response. */
export interface AssEvent {
/** Milliseconds; distinct from setCurrentTime's seconds. */
Start: number
Duration: number
/** Numeric libass style index, not the ASS text format's style name. */
Style: number
Name: string
MarginL: number
MarginR: number
MarginV: number
Effect: string
Text: string
ReadOrder: number
Layer: number
}
/** Fields actually serialized by the frozen worker's getStyles response. */
export interface AssStyle {
Name: string
FontName: string
FontSize: number
PrimaryColour: number
SecondaryColour: number
OutlineColour: number
BackColour: number
Bold: number
Italic: number
Underline: number
StrikeOut: number
ScaleX: number
ScaleY: number
Spacing: number
Angle: number
BorderStyle: number
Outline: number
Shadow: number
Alignment: number
MarginL: number
MarginR: number
MarginV: number
Encoding: number
treat_fontname_as_pattern: number
Blur: number
Justify: number
}
/** Worker mutation handlers assign supplied keys; unspecified fields are not sent. */
export type AssEventInput = Partial<AssEvent>
export type AssStyleInput = Partial<AssStyle>
/** Timeout errors and native Worker error events are separate channels. */
export type WorkerRequestError = Error | ErrorEvent
/**
* Success supplies null plus an array. Error payloads are absent.
* The frozen vendor currently throws before notifying this callback on request
* errors/timeouts; PKG-JASSUB-07 owns that defect. These types do not guarantee
* callback delivery or claim that the error path has been repaired.
*/
export type EventsCallback = (error: WorkerRequestError | null, events?: AssEvent[]) => void
export type StylesCallback = (error: WorkerRequestError | null, styles?: AssStyle[]) => void
export interface RuntimeEventMap {
ready: CustomEvent<null>
error: ErrorEvent
}
/** The actual vendor EventTarget instance, rather than an adapter copy. */
export interface RuntimeInstance extends EventTarget {
timeOffset: number
debug: boolean
prescaleFactor: number
prescaleHeightLimit: number
maxRenderHeight: number
/** Present when the selected rendering path uses demand/busy state. */
busy?: boolean
/** These two fields were explicitly exposed by npm jassub 1.8.8. */
_canvas: HTMLCanvasElement
_ctx: CanvasRenderingContext2D | false | null
resize: (width?: number, height?: number, top?: number, left?: number, force?: boolean) => void
setVideo: (video: HTMLVideoElement) => void
runBenchmark: () => void
setTrackByUrl: (url: string) => void
setTrack: (content: string) => void
freeTrack: () => void
setIsPaused: (isPaused: boolean) => void
setRate: (rate: number) => void
/** Current time is measured in seconds. */
setCurrentTime: (isPaused?: boolean, currentTime?: number, rate?: number) => void
createEvent: (event: AssEventInput) => void
setEvent: (event: AssEventInput, index: number) => void
removeEvent: (index: number) => void
getEvents: (callback: EventsCallback) => void
createStyle: (style: AssStyleInput) => void
setStyle: (style: AssStyleInput, index: number) => void
removeStyle: (index: number) => void
getStyles: (callback: StylesCallback) => void
styleOverride: (style: AssStyleInput) => void
disableStyleOverride: () => void
setDefaultFont: (font: string) => void
addFont: (font: FontSource) => void
/** Resolves after posting the message; it is not a Worker-operation acknowledgement. */
sendMessage: (target: string, data?: Record<string, unknown> | null, transferable?: Transferable[]) => Promise<void>
destroy: (() => void) & ((error: Error) => Error) & ((error: string) => Error | '') & ((error: Error | string | undefined) => Error | '' | undefined)
addEventListener: (<Name extends keyof RuntimeEventMap>(type: Name, listener: (this: RuntimeInstance, event: RuntimeEventMap[Name]) => void, options?: boolean | AddEventListenerOptions) => void) & ((type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | AddEventListenerOptions) => void)
removeEventListener: (<Name extends keyof RuntimeEventMap>(type: Name, listener: (this: RuntimeInstance, event: RuntimeEventMap[Name]) => void, options?: boolean | EventListenerOptions) => void) & ((type: string, listener: EventListenerOrEventListenerObject | null, options?: boolean | EventListenerOptions) => void)
}
export interface RuntimeResult {
name: 'artplayerPluginJassub'
instance: RuntimeInstance
}
export type RuntimeFactory = (option?: RuntimeOption) => (art: Artplayer) => RuntimeResult
@@ -0,0 +1,20 @@
import type { RuntimeFactory } from './runtime-api.js'
declare const artplayerPluginJassub: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginJassub {
type FontSource = import('./runtime-api.js').FontSource
type RuntimeOption = import('./runtime-api.js').RuntimeOption
type AssEvent = import('./runtime-api.js').AssEvent
type AssStyle = import('./runtime-api.js').AssStyle
type AssEventInput = import('./runtime-api.js').AssEventInput
type AssStyleInput = import('./runtime-api.js').AssStyleInput
type WorkerRequestError = import('./runtime-api.js').WorkerRequestError
type EventsCallback = import('./runtime-api.js').EventsCallback
type StylesCallback = import('./runtime-api.js').StylesCallback
type RuntimeEventMap = import('./runtime-api.js').RuntimeEventMap
type RuntimeInstance = import('./runtime-api.js').RuntimeInstance
type RuntimeResult = import('./runtime-api.js').RuntimeResult
type RuntimeFactory = import('./runtime-api.js').RuntimeFactory
}
export = artplayerPluginJassub
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './runtime-api.js'
declare const artplayerPluginJassub: RuntimeFactory
export default artplayerPluginJassub
export type { AssEvent, AssEventInput, AssStyle, AssStyleInput, EventsCallback, FontSource, RuntimeEventMap, RuntimeFactory, RuntimeInstance, RuntimeOption, RuntimeResult, StylesCallback, WorkerRequestError } from './runtime-api.js'
+20
View File
@@ -0,0 +1,20 @@
import type { RuntimeFactory } from './runtime-api.js'
declare const artplayerPluginJassub: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Ambient merge exposes types on the CommonJS factory.
declare namespace artplayerPluginJassub {
type FontSource = import('./runtime-api.js').FontSource
type RuntimeOption = import('./runtime-api.js').RuntimeOption
type AssEvent = import('./runtime-api.js').AssEvent
type AssStyle = import('./runtime-api.js').AssStyle
type AssEventInput = import('./runtime-api.js').AssEventInput
type AssStyleInput = import('./runtime-api.js').AssStyleInput
type WorkerRequestError = import('./runtime-api.js').WorkerRequestError
type EventsCallback = import('./runtime-api.js').EventsCallback
type StylesCallback = import('./runtime-api.js').StylesCallback
type RuntimeEventMap = import('./runtime-api.js').RuntimeEventMap
type RuntimeInstance = import('./runtime-api.js').RuntimeInstance
type RuntimeResult = import('./runtime-api.js').RuntimeResult
type RuntimeFactory = import('./runtime-api.js').RuntimeFactory
}
export = artplayerPluginJassub