refactor(iframe): [PKG-IFRAME-04] preserve types across module consumers

This commit is contained in:
Harvey Zhao committed 2026-09-13 01:43:09 +08:00
1 parent 1342de94ef
commit 047e2df945
24 files changed
+1496 -69

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
+30 -7
View File
@@ -110,18 +110,41 @@ history reload is BFCache: actual persisted restoration, full-player integration
devices and externally interrupted navigation remain PKG-IFRAME-05/release gates.
IFRAME-LIFE-01 and IFRAME-TRUST-01 remain open for those integration/review scopes.
Public declarations remain in `types/artplayer-tool-iframe.d.ts`; source typing
does not yet establish historical declaration/entry compatibility. PKG-IFRAME-04
must reconcile the old npm plugin name, namespace/default and extra helper
protocol separately. PKG-IFRAME-06 owns final distribution. No version bump or
publication is implied by this source migration.
Public declarations are maintained in `types/artplayer-tool-iframe.d.ts`.
The `.d.cts` bridge describes the actual CommonJS constructor and its named types;
`.d.mts` routes native ESM to the same class. Keep the root `types` field and
legacy `typesVersions` entry for TS 4.3 consumers. The runtime has no self-default
property. Ordinary default imports use interop in CommonJS consumers; modern
TypeScript can use direct `import Iframe = require(...)` without interop.
The default class intentionally retains required Message.data, void static
onMessage, readonly instance fields, non-null callback and legacy commit inference.
RuntimeConstructor/RuntimeInstance expose optional outgoing data, async static
receiver, nullable callback and application-supplied response types. ResolverInstance
offers an explicit result parameter for the old serialized resolve(...) protocol.
These are erased type views, not new runtime methods or payload validators. The
source class satisfies RuntimeConstructor; its nullable callback intentionally
does not satisfy the old non-null class declaration, and tests pin that difference.
Old npm plugin-iframe used Function callbacks and a CommonJS default namespace;
those differ from the frozen workspace before this refactor. Installed tests
preserve that evidence and the separate helper's static destroy protocol. They
do not claim the renamed tool supplies the old package name or helper entrypoints.
PKG-IFRAME-06 still owns those distribution decisions and compatibility facades.
No version bump or publication is implied by declaration validation.
## Verification
- `yarn test:iframe`: frozen historical contract/defect assertions and candidate
lifecycle assertions. Historical failures are not candidate acceptance.
- `yarn typecheck`: strict source and existing consumers; full package declaration
consumers are still a separate PKG-IFRAME-04 gate.
- `yarn typecheck`: strict source and existing consumers, including old extraction
and new typed views in TS 5.9 and TS 4.3.
- `yarn test:iframe-types-package`: install the real old npm archive, frozen
workspace pack and candidate outside the workspace with offline/frozen Yarn.
Check declaration resolution, exact bytes, exports and positive/negative uses.
- `yarn build:ts artplayer-tool-iframe`: regenerate only this package's standalone
editor declaration (plus the shared core declaration). The generator uses the
actual uppercase class global and exports named types without external imports.
- `yarn build artplayer-tool-iframe`: normal main/legacy/ESM production output and
generated docs copies. Never hand-edit those files.
- `yarn test:browser test/browser/iframe.spec.js`: real same/cross-origin windows
+29
View File
@@ -6,6 +6,35 @@ Iframe tool for ArtPlayer
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-tool-iframe/index.js&example=iframe)
## TypeScript
Default imports keep the historical class signatures, including `resove`,
required message `data`, and the original `commit` result inference. Named types
include `Option`, `Message`, `ProtocolMessage` and optional runtime views:
```ts
import type { ResolverInstance, RuntimeConstructor } from 'artplayer-tool-iframe'
import Iframe from 'artplayer-tool-iframe'
const Runtime = Iframe as RuntimeConstructor
const tool = new Runtime({ iframe: document.querySelector('iframe')!, url: '/iframe.html' })
const resolver = tool as ResolverInstance
const value = await resolver.commit<number>((resolve) => {
resolve(42)
})
```
`commit` serializes the function body. Keep its braces, use the literal `resolve`
name for asynchronous replies, and do not depend on closures or an async function
body. Handle rejection when the child document changes or the tool is destroyed.
The runtime views are types only; they do not validate messages or sandbox code.
The browser global is `ArtplayerToolIframe`. Modern TypeScript CommonJS consumers
can use `import Iframe = require('artplayer-tool-iframe')`; older Node resolution
uses the default import with `esModuleInterop`. The class has no `.default`
property. The historical `artplayer-plugin-iframe` package and its separate helper
have different export/protocol details; they are not aliases for this tool.
## Maintenance
See [ARCHITECTURE.md](ARCHITECTURE.md) for the TypeScript module map, request
+15 -5
View File
@@ -19,12 +19,17 @@
],
"exports": {
".": {
"types": "./types/artplayer-tool-iframe.d.ts",
"import": "./dist/artplayer-tool-iframe.mjs",
"require": "./dist/artplayer-tool-iframe.js"
"import": {
"types": "./types/artplayer-tool-iframe.d.mts",
"default": "./dist/artplayer-tool-iframe.mjs"
},
"require": {
"types": "./types/artplayer-tool-iframe.d.cts",
"default": "./dist/artplayer-tool-iframe.js"
}
},
"./legacy": {
"types": "./types/artplayer-tool-iframe.d.ts",
"types": "./types/artplayer-tool-iframe.d.cts",
"import": "./dist/artplayer-tool-iframe.legacy.js",
"require": "./dist/artplayer-tool-iframe.legacy.js"
}
@@ -33,5 +38,10 @@
"module": "./dist/artplayer-tool-iframe.mjs",
"types": "./types/artplayer-tool-iframe.d.ts",
"legacy": "./dist/artplayer-tool-iframe.legacy.js",
"browserslist": "last 1 Chrome version"
"browserslist": "last 1 Chrome version",
"typesVersions": {
"*": {
"legacy": ["types/artplayer-tool-iframe.d.ts"]
}
}
}
@@ -0,0 +1,22 @@
/* eslint-disable ts/no-redeclare -- Constructor, instance and named-type namespace intentionally merge. */
import type * as Definition from './artplayer-tool-iframe.js'
declare const ArtplayerToolIframe: typeof Definition.default
type ArtplayerToolIframe = Definition.default
declare namespace ArtplayerToolIframe {
type Option = Definition.Option
type Message<T = any> = Definition.Message<T>
type Callbacks = Definition.Callbacks
type Notification<T = unknown> = Definition.Notification<T>
type MessageCallback = Definition.MessageCallback
type OutboundMessage<T = unknown> = Definition.OutboundMessage<T>
type ProtocolMessage<T = unknown> = Definition.ProtocolMessage<T>
type Resolve<T> = Definition.Resolve<T>
type ResolverCallback<T> = Definition.ResolverCallback<T>
type RuntimeInstance = Definition.RuntimeInstance
type ResolverInstance = Definition.ResolverInstance
type RuntimeConstructor = Definition.RuntimeConstructor
}
export = ArtplayerToolIframe
@@ -0,0 +1,2 @@
export { default } from './artplayer-tool-iframe.cjs'
export type { Callbacks, Message, MessageCallback, Notification, Option, OutboundMessage, ProtocolMessage, Resolve, ResolverCallback, ResolverInstance, RuntimeConstructor, RuntimeInstance } from './artplayer-tool-iframe.js'
@@ -1,18 +1,76 @@
interface Message {
export interface Option {
iframe: HTMLIFrameElement
url: string
}
/** Historical open envelope. data remains required in the default class API. */
export interface Message<T = any> {
type: string
data: any
data: T
id?: number
}
export interface Callbacks {
resove: (...args: any[]) => any
reject: (...args: any[]) => any
}
/** Public notifications omit the request id and all private session metadata. */
export interface Notification<T = unknown> {
type: string
data: T
}
export type MessageCallback = (this: ArtplayerToolIframe, message: Notification) => void
/** Actual outgoing calls allow an omitted data field. Custom message types remain valid. */
export interface OutboundMessage<T = unknown> {
type: string
data?: T
id?: number
}
/** Known built-in envelopes; use Message/OutboundMessage for application protocols. */
export type ProtocolMessage<T = unknown>
= | { type: 'inject', data?: undefined, id?: number }
| { type: 'commit', data: string, id: number }
| { type: 'response', data: T, id: number }
| { type: 'error', data: unknown, id: number }
export type Resolve<T> = (value: T | PromiseLike<T>) => void
export type ResolverCallback<T> = (resolve: Resolve<T>) => void
/** Opt-in view. Response T is supplied by the application's protocol, not validated at runtime. */
export interface RuntimeInstance extends Omit<ArtplayerToolIframe, 'messageCallback' | 'postMessage' | 'message'> {
messageCallback: MessageCallback | null
postMessage: <T = unknown>(message: OutboundMessage) => Promise<T>
message: (callback: MessageCallback) => void
}
/** For the existing serialized resolve(...) protocol; callback bodies must use that exact name. */
export interface ResolverInstance extends Omit<RuntimeInstance, 'commit'> {
commit: <T>(callback: ResolverCallback<T>) => Promise<T>
}
/** Opt-in static async/optional-data view. There is no runtime self-default property. */
export interface RuntimeConstructor {
new (option: Option): RuntimeInstance
readonly prototype: RuntimeInstance
readonly iframe: boolean
postMessage: (message: OutboundMessage) => void
onMessage: (event: MessageEvent<OutboundMessage>) => Promise<void>
inject: () => void
}
declare class ArtplayerToolIframe {
constructor(option: { iframe: HTMLIFrameElement, url: string })
constructor(option: Option)
static iframe: boolean
static postMessage(message: Message): void
static onMessage(event: MessageEvent & { data: Message }): void
static inject(): void
readonly promises: Record<number, { resove: (...args: any[]) => any, reject: (...args: any[]) => any }>
readonly promises: Record<number, Callbacks>
readonly injected: boolean
readonly destroyed: boolean
readonly $iframe: HTMLIFrameElement