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

+107 -30
View File
@@ -1,32 +1,109 @@
interface Message {
type: string
data: any
id?: number
// Generated from the package public declaration by yarn build:ts. Do not edit.
/* eslint-disable ts/no-redeclare -- Callable and public type namespace intentionally merge. */
declare namespace ArtplayerToolIframeDefinitions {
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: 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
}
export class ArtplayerToolIframe {
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, Callbacks>
readonly injected: boolean
readonly destroyed: boolean
readonly $iframe: HTMLIFrameElement
readonly url: string
readonly messageCallback: (...args: any[]) => any
onMessage(event: MessageEvent & {
data: Message
}): void
postMessage(message: Message): Promise<any>
commit<T extends (...args: any[]) => any>(callback: T): Promise<ReturnType<T>>
message(callback: (...args: any[]) => any): void
destroy(): void
}
}
declare class ArtplayerToolIframe {
constructor(option: { iframe: HTMLIFrameElement, url: string })
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 injected: boolean
readonly destroyed: boolean
readonly $iframe: HTMLIFrameElement
readonly url: string
readonly messageCallback: (...args: any[]) => any
onMessage(event: MessageEvent & { data: Message }): void
postMessage(message: Message): Promise<any>
commit<T extends (...args: any[]) => any>(callback: T): Promise<ReturnType<T>>
message(callback: (...args: any[]) => any): void
destroy(): void
declare const ArtplayerToolIframe: typeof ArtplayerToolIframeDefinitions.ArtplayerToolIframe
type ArtplayerToolIframe = ArtplayerToolIframeDefinitions.ArtplayerToolIframe
declare namespace ArtplayerToolIframe {
export type Option = ArtplayerToolIframeDefinitions.Option
export type Message<T = any> = ArtplayerToolIframeDefinitions.Message<T>
export type Callbacks = ArtplayerToolIframeDefinitions.Callbacks
export type Notification<T = unknown> = ArtplayerToolIframeDefinitions.Notification<T>
export type MessageCallback = ArtplayerToolIframeDefinitions.MessageCallback
export type OutboundMessage<T = unknown> = ArtplayerToolIframeDefinitions.OutboundMessage<T>
export type ProtocolMessage<T = unknown> = ArtplayerToolIframeDefinitions.ProtocolMessage<T>
export type Resolve<T> = ArtplayerToolIframeDefinitions.Resolve<T>
export type ResolverCallback<T> = ArtplayerToolIframeDefinitions.ResolverCallback<T>
export type RuntimeInstance = ArtplayerToolIframeDefinitions.RuntimeInstance
export type ResolverInstance = ArtplayerToolIframeDefinitions.ResolverInstance
export type RuntimeConstructor = ArtplayerToolIframeDefinitions.RuntimeConstructor
}
export default ArtplayerToolIframe
export = artplayerToolIframe
export as namespace artplayerToolIframe;
export = ArtplayerToolIframe
export as namespace ArtplayerToolIframe;
+2 -1
View File
@@ -80,7 +80,8 @@
"test:mediabunny-types-package": "node refactor/scripts/mb-package-types.mjs",
"test:mediabunny-soak": "playwright test --config playwright.soak.config.js",
"generate:media:hls": "node scripts/generate-hls-fixture.mjs",
"test:iframe": "node --test test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe.test.js test/iframe-lifecycle.test.js refactor/scripts/iframe-contract.test.mjs"
"test:iframe": "node --test test/iframe-navigation.test.js test/iframe-boundaries.test.js test/iframe.test.js test/iframe-lifecycle.test.js refactor/scripts/iframe-contract.test.mjs refactor/scripts/iframe-types.test.mjs",
"test:iframe-types-package": "node refactor/scripts/iframe-package-types.mjs"
},
"browserslist": "last 1 Chrome version",
"devDependencies": {
+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
@@ -0,0 +1,723 @@
{
"task": "PKG-IFRAME-04",
"status": "done",
"checkpoint": "verified-public-types-editor-and-installed-consumers",
"beforeCommit": "1342de94ef2e9dc3e06ddac12fcf1fc5229b648d",
"sourceSha256LF": {
"package.json": "1ca2df37409d1634abe58ce40f50bbee162033af4c57a9908697477e564a7909",
"packages/artplayer-tool-iframe/package.json": "91985c93522c98be5d2a6d02dc0a39df5eeededdd94077758511873fe2290cf4",
"packages/artplayer-tool-iframe/.npmignore": "145c5fa0341195e21cf13df3423a057282f8ea374e9a1b3065e2280e1ce1c5c1",
"packages/artplayer-tool-iframe/README.md": "6e0b3afbad28bf0160d744b89d1a4dad997fff77fb23c61d707389f08f49e219",
"packages/artplayer-tool-iframe/ARCHITECTURE.md": "b5754cfecc61728db0742739f488973f6d9330ebd2f2ec9b60505953fc746546",
"packages/artplayer-tool-iframe/types/artplayer-tool-iframe.d.ts": "e1efb4c7f22de24721923f02cf0488728a6ef381505c49187eba0bc2b90b1207",
"packages/artplayer-tool-iframe/types/artplayer-tool-iframe.d.cts": "66850135f45c81d4bd7a0de33b6a2f63a655de9000f2698f43660039727919f3",
"packages/artplayer-tool-iframe/types/artplayer-tool-iframe.d.mts": "cd394fd68e61fab3c4474daab29852a3f87dd0086b007e7937106871272676fb",
"docs/assets/ts/artplayer-tool-iframe.d.ts": "c06c6819146ba64bd6ee498871d9e19ab5a9e88c9cc7bed2c0c4968fa39dfed7",
"scripts/build-ts.js": "ce6e8da55d934c31f2291326b33a577c9b7dd15b84167f3c63142085d3dcb56d",
"scripts/plugin-editor-types.mjs": "c485550ed0f44d38c2c78faced58c2bec08d496f4bd5a66f4c3dd2ed4bc8b57e",
"scripts/typecheck.mjs": "02aa38836fc33b01f1e2de3def1443ed4d35f038a3595e11106a4dfbff12e86d",
"test/types/iframe.ts": "7ecdc1f7cafda5edb3dbe0908c1029ea7cf3308eb909db661f3cfec27f434b90",
"test/types/iframe-commonjs.cts": "16ac9bea7bb5fd1e5b79301d3f4e3d2761357fbe96a05211a941e26853866729",
"test/types/iframe-source.ts": "29d816c19301d16c81521e809245ac33783362a9023d8cd9ac26185fb3e85ad6",
"refactor/fixtures/consumers/iframe-workspace.ts": "c81fc54c919aabab57ead9efcffc9fe3e469be7fb3b57fbcafa5fd2653d83afb",
"refactor/scripts/iframe-types.test.mjs": "69931a4b0779f996c0af46929c92ce1c9ccc437067a42fa1e9fac9d39ab856a0",
"refactor/scripts/iframe-package-types.mjs": "8dd0006f605752ffdbf51c58bf5ee4cca503dc6c3c1793a8b03dc14fc62d27ea"
},
"artifacts": {
"packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.js": "7408fceccb000e1b304abe30ac9bc364da7860c3896ad5aaa6af427804c237e6",
"packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.legacy.js": "4e8bd39cdc37e98bff9cd19b5e0c9453ebe5231264242fc1c573aa96edb5ea8a",
"packages/artplayer-tool-iframe/dist/artplayer-tool-iframe.mjs": "ac4c5efa670c0429fc95bdab7902df60f5b5540f0dad9c7e65c2bd660796dc83"
},
"browserEvidenceReuse": {
"file": "refactor/baselines/iframe-navigation-validation.json",
"reason": "All six production source modules and three runtime build bytes are unchanged; this task changes declarations/editor generation and manifest type routing. Native frame matrices remain 351/351/207 from task 03; installed export routing is separately tested here."
},
"installed": {
"report": "refactor/.cache/iframe-package-types-AHSyi2/report.json",
"sha256": "1dadc173afa37655cd6c148cb34483f93e82fe82ae7897f6f4dfe89b8eb8b18b",
"task": "PKG-IFRAME-04",
"scope": "Actual npm archive, frozen Git workspace pack and candidate Yarn tarball installed outside workspace; byte verification, offline/frozen lock and strict positive/negative consumers. Old npm CJS namespace and distinct helper are preserved observations, not claims the candidate provides old package-name/helper aliases. Native player and distribution migration remain 05/06.",
"packages": [
{
"label": "published",
"name": "artplayer-plugin-iframe",
"archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\releases\\artplayer-plugin-iframe-1.0.0.tgz",
"sha256": "16dc92aa84d93bd99bc0a34815dc49f1609fafc1ff255eb353ea893e70b5f91f",
"files": {
"package/README.md": "52c0a1199b6529d60ce8f4414984f9bd1bc3a8daa5bb6cfec8f0aae0c8338e46",
"package/dist/artplayer-helper-iframe.js": "47730ed608f0637594549b9762c01191686a4a55b3bd467132304a016d227f4a",
"package/dist/artplayer-helper-iframe.legacy.js": "f4c3b11806cad6d984f03a2d9de63deba78cbe538ffe4044a106b0dddc5d4197",
"package/dist/artplayer-plugin-iframe.js": "df469c3e4582b127f35798494d0845f1fc4ca2d19bbaf01b918492e035479745",
"package/dist/artplayer-plugin-iframe.legacy.js": "15299cb5c848cc61614f7573a802443d69b7c6d0a938a1867835c7cbb4884edd",
"package/package.json": "563a6c8407753b92b86319a5e6d9d567525146c32cb010b45b0d88ff481859f6",
"package/src/index.js": "2e0b9efeedef0d6c4f2d2b526db35f634cd02af044e4e28dd1a0b26a5b9a02bf",
"package/types/artplayer-plugin-iframe.d.ts": "d55d99a2488d60892a0ab82882dc96a0f373dab0c3d2a3cee978bc35aa7c2d0c"
}
},
{
"label": "workspace",
"name": "artplayer-tool-iframe",
"archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\iframe-package-types-AHSyi2\\workspace.tgz",
"sha256": "6fc603122610b785aa3ecb43f2692fb92c39e96386ca1bcab5ee23253af92ae6",
"files": {
"package/README.md": "6910a44b767ff80c1d1edc3c90ca7ca77a9b45307fb61d4abd9d52526c7614b3",
"package/dist/artplayer-tool-iframe.js": "2ec257c8055a9448d12af3fecf1fae8ba58c06f47e85072d02c386cd258b0c86",
"package/dist/artplayer-tool-iframe.legacy.js": "c0755d79f316cda71f045da86749648f95faf47c09180d525fb04bb753576d5f",
"package/dist/artplayer-tool-iframe.mjs": "cbcdb0516dd5f17ca2572a6b3e363ea63b0cfd9733f3e47689c02088f23f29b9",
"package/package.json": "b28f5156f02a250539f0acaf302ec87fdd506f63b0e73dd308326ec78c691d03",
"package/types/artplayer-tool-iframe.d.ts": "4f02aa22a6b373393ead533ae3421ad5acf0de7116057f351021c72d3448bd1e"
}
},
{
"label": "candidate",
"name": "artplayer-tool-iframe",
"archive": "D:\\github\\ArtPlayer\\refactor\\.cache\\iframe-package-types-AHSyi2\\candidate.tgz",
"sha256": "25e88268584f67cb690048f8cb53312b6fcfe64d622a70bf954604a402be9471",
"files": {
"package/ARCHITECTURE.md": "b5754cfecc61728db0742739f488973f6d9330ebd2f2ec9b60505953fc746546",
"package/README.md": "ef5af60bcb831667a78cc3421f6e3c7b5d9a50b3d2e571c5cd02d46fc533e9ce",
"package/dist/artplayer-tool-iframe.js": "7408fceccb000e1b304abe30ac9bc364da7860c3896ad5aaa6af427804c237e6",
"package/dist/artplayer-tool-iframe.legacy.js": "4e8bd39cdc37e98bff9cd19b5e0c9453ebe5231264242fc1c573aa96edb5ea8a",
"package/dist/artplayer-tool-iframe.mjs": "ac4c5efa670c0429fc95bdab7902df60f5b5540f0dad9c7e65c2bd660796dc83",
"package/package.json": "0de9c86eeb3f9a2fb46f9297105b5beddb8d6164163ffaa9874cf5cd68ba7a58",
"package/types/artplayer-tool-iframe.d.cts": "66850135f45c81d4bd7a0de33b6a2f63a655de9000f2698f43660039727919f3",
"package/types/artplayer-tool-iframe.d.mts": "cd394fd68e61fab3c4474daab29852a3f87dd0086b007e7937106871272676fb",
"package/types/artplayer-tool-iframe.d.ts": "e760de22559caf7ffff48d7c5713485bf9f0ddbe876a93080295fb6106535bfa"
}
}
],
"matrix": [
{
"package": "published",
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": [],
"negative": []
},
{
"package": "published",
"compiler": "5.9.3",
"mode": "nodenext-cjs",
"diagnostics": [],
"negative": []
},
{
"package": "published",
"compiler": "5.9.3",
"mode": "nodenext-esm",
"diagnostics": [],
"negative": []
},
{
"package": "published",
"compiler": "5.9.3",
"mode": "bundler-esm",
"diagnostics": [],
"negative": []
},
{
"package": "published",
"compiler": "4.3.5",
"mode": "node10-commonjs",
"diagnostics": [],
"negative": []
},
{
"package": "workspace",
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": [],
"negative": []
},
{
"package": "workspace",
"compiler": "5.9.3",
"mode": "nodenext-cjs",
"diagnostics": [],
"negative": []
},
{
"package": "workspace",
"compiler": "5.9.3",
"mode": "nodenext-esm",
"diagnostics": [
{
"code": 2344,
"message": "Type 'false' does not satisfy the constraint 'true'."
},
{
"code": 2344,
"message": "Type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")' does not satisfy the constraint 'abstract new (...args: any) => any'.\n Type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")' provides no match for the signature 'new (...args: any): any'."
},
{
"code": 2344,
"message": "Type 'false' does not satisfy the constraint 'true'."
},
{
"code": 2709,
"message": "Cannot use namespace 'Iframe' as a type."
},
{
"code": 2339,
"message": "Property 'onMessage' does not exist on type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")'."
},
{
"code": 2344,
"message": "Type 'false' does not satisfy the constraint 'true'."
},
{
"code": 2709,
"message": "Cannot use namespace 'Iframe' as a type."
},
{
"code": 2344,
"message": "Type 'false' does not satisfy the constraint 'true'."
},
{
"code": 2709,
"message": "Cannot use namespace 'Iframe' as a type."
},
{
"code": 2709,
"message": "Cannot use namespace 'Iframe' as a type."
},
{
"code": 2351,
"message": "This expression is not constructable.\n Type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")' has no construct signatures."
},
{
"code": 2339,
"message": "Property 'postMessage' does not exist on type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")'."
},
{
"code": 2339,
"message": "Property 'inject' does not exist on type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")'."
},
{
"code": 2339,
"message": "Property 'onMessage' does not exist on type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")'."
},
{
"code": 7006,
"message": "Parameter 'value' implicitly has an 'any' type."
},
{
"code": 7006,
"message": "Parameter 'additional' implicitly has an 'any' type."
},
{
"code": 2339,
"message": "Property 'onMessage' does not exist on type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")'."
},
{
"code": 2339,
"message": "Property 'iframe' does not exist on type 'typeof import(\"<consumer>/node_modules/artplayer-tool-iframe/types/artplayer-tool-iframe\")'."
}
],
"negative": []
},
{
"package": "workspace",
"compiler": "5.9.3",
"mode": "bundler-esm",
"diagnostics": [],
"negative": []
},
{
"package": "workspace",
"compiler": "4.3.5",
"mode": "node10-commonjs",
"diagnostics": [],
"negative": []
},
{
"package": "candidate",
"compiler": "5.9.3",
"mode": "node10-commonjs",
"diagnostics": [],
"negative": [
{
"code": 2554,
"message": "Expected 1 arguments, but got 0."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'HTMLIFrameElement'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2345,
"message": "Argument of type '{ type: string; }' is not assignable to parameter of type 'Message<any>'.\n Property 'data' is missing in type '{ type: string; }' but required in type 'Message<any>'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2540,
"message": "Cannot assign to 'injected' because it is a read-only property."
},
{
"code": 2322,
"message": "Type 'void' is not assignable to type 'Promise<void>'."
},
{
"code": 2339,
"message": "Property 'default' does not exist on type 'typeof ArtplayerToolIframe'."
},
{
"code": 2540,
"message": "Cannot assign to 'iframe' because it is a read-only property."
},
{
"code": 2353,
"message": "Object literal may only specify known properties, and 'id' does not exist in type 'Notification<unknown>'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type '{ type: \"response\"; data: string; id: number; }' is not assignable to parameter of type 'ProtocolMessage<number>'.\n Types of property 'data' are incompatible.\n Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type 'string' is not assignable to parameter of type 'number | PromiseLike<number>'."
},
{
"code": 2551,
"message": "Property 'resolve' does not exist on type 'Callbacks'. Did you mean 'resove'?"
},
{
"code": 18046,
"message": "'message.data' is of type 'unknown'."
}
]
},
{
"package": "candidate",
"compiler": "5.9.3",
"mode": "nodenext-cjs",
"diagnostics": [],
"negative": [
{
"code": 2554,
"message": "Expected 1 arguments, but got 0."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'HTMLIFrameElement'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2345,
"message": "Argument of type '{ type: string; }' is not assignable to parameter of type 'Message<any>'.\n Property 'data' is missing in type '{ type: string; }' but required in type 'Message<any>'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2540,
"message": "Cannot assign to 'injected' because it is a read-only property."
},
{
"code": 2322,
"message": "Type 'void' is not assignable to type 'Promise<void>'."
},
{
"code": 2339,
"message": "Property 'default' does not exist on type 'typeof ArtplayerToolIframe'."
},
{
"code": 2540,
"message": "Cannot assign to 'iframe' because it is a read-only property."
},
{
"code": 2353,
"message": "Object literal may only specify known properties, and 'id' does not exist in type 'Notification<unknown>'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type '{ type: \"response\"; data: string; id: number; }' is not assignable to parameter of type 'ProtocolMessage<number>'.\n Types of property 'data' are incompatible.\n Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type 'string' is not assignable to parameter of type 'number | PromiseLike<number>'."
},
{
"code": 2551,
"message": "Property 'resolve' does not exist on type 'Callbacks'. Did you mean 'resove'?"
},
{
"code": 18046,
"message": "'message.data' is of type 'unknown'."
}
]
},
{
"package": "candidate",
"compiler": "5.9.3",
"mode": "nodenext-esm",
"diagnostics": [],
"negative": [
{
"code": 2554,
"message": "Expected 1 arguments, but got 0."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'HTMLIFrameElement'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2345,
"message": "Argument of type '{ type: string; }' is not assignable to parameter of type 'Message<any>'.\n Property 'data' is missing in type '{ type: string; }' but required in type 'Message<any>'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2540,
"message": "Cannot assign to 'injected' because it is a read-only property."
},
{
"code": 2322,
"message": "Type 'void' is not assignable to type 'Promise<void>'."
},
{
"code": 2339,
"message": "Property 'default' does not exist on type 'typeof ArtplayerToolIframe'."
},
{
"code": 2540,
"message": "Cannot assign to 'iframe' because it is a read-only property."
},
{
"code": 2353,
"message": "Object literal may only specify known properties, and 'id' does not exist in type 'Notification<unknown>'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type '{ type: \"response\"; data: string; id: number; }' is not assignable to parameter of type 'ProtocolMessage<number>'.\n Types of property 'data' are incompatible.\n Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type 'string' is not assignable to parameter of type 'number | PromiseLike<number>'."
},
{
"code": 2551,
"message": "Property 'resolve' does not exist on type 'Callbacks'. Did you mean 'resove'?"
},
{
"code": 18046,
"message": "'message.data' is of type 'unknown'."
}
]
},
{
"package": "candidate",
"compiler": "5.9.3",
"mode": "bundler-esm",
"diagnostics": [],
"negative": [
{
"code": 2554,
"message": "Expected 1 arguments, but got 0."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'HTMLIFrameElement'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2345,
"message": "Argument of type '{ type: string; }' is not assignable to parameter of type 'Message<any>'.\n Property 'data' is missing in type '{ type: string; }' but required in type 'Message<any>'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2540,
"message": "Cannot assign to 'injected' because it is a read-only property."
},
{
"code": 2322,
"message": "Type 'void' is not assignable to type 'Promise<void>'."
},
{
"code": 2339,
"message": "Property 'default' does not exist on type 'typeof ArtplayerToolIframe'."
},
{
"code": 2540,
"message": "Cannot assign to 'iframe' because it is a read-only property."
},
{
"code": 2353,
"message": "Object literal may only specify known properties, and 'id' does not exist in type 'Notification<unknown>'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type '{ type: \"response\"; data: string; id: number; }' is not assignable to parameter of type 'ProtocolMessage<number>'.\n Types of property 'data' are incompatible.\n Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type 'string' is not assignable to parameter of type 'number | PromiseLike<number>'."
},
{
"code": 2551,
"message": "Property 'resolve' does not exist on type 'Callbacks'. Did you mean 'resove'?"
},
{
"code": 18046,
"message": "'message.data' is of type 'unknown'."
}
]
},
{
"package": "candidate",
"compiler": "4.3.5",
"mode": "node10-commonjs",
"diagnostics": [],
"negative": [
{
"code": 2554,
"message": "Expected 1 arguments, but got 0."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'HTMLIFrameElement'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2345,
"message": "Argument of type '{ type: string; }' is not assignable to parameter of type 'Message<any>'.\n Property 'data' is missing in type '{ type: string; }' but required in type 'Message<any>'."
},
{
"code": 2322,
"message": "Type 'number' is not assignable to type 'string'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number | undefined'."
},
{
"code": 2540,
"message": "Cannot assign to 'injected' because it is a read-only property."
},
{
"code": 2322,
"message": "Type 'void' is not assignable to type 'Promise<void>'."
},
{
"code": 2339,
"message": "Property 'default' does not exist on type 'typeof ArtplayerToolIframe'."
},
{
"code": 2540,
"message": "Cannot assign to 'iframe' because it is a read-only property."
},
{
"code": 2322,
"message": "Type '{ type: string; data: number; id: number; }' is not assignable to type 'Notification<unknown>'.\n Object literal may only specify known properties, and 'id' does not exist in type 'Notification<unknown>'."
},
{
"code": 2322,
"message": "Type 'string' is not assignable to type 'number | undefined'."
},
{
"code": 2345,
"message": "Argument of type '{ type: \"response\"; data: string; id: number; }' is not assignable to parameter of type 'ProtocolMessage<number>'.\n Type '{ type: \"response\"; data: string; id: number; }' is not assignable to type '{ type: \"response\"; data: number; id: number; }'.\n Types of property 'data' are incompatible.\n Type 'string' is not assignable to type 'number'."
},
{
"code": 2345,
"message": "Argument of type 'string' is not assignable to parameter of type 'number | PromiseLike<number>'."
},
{
"code": 2551,
"message": "Property 'resolve' does not exist on type 'Callbacks'. Did you mean 'resove'?"
},
{
"code": 2571,
"message": "Object is of type 'unknown'."
}
]
},
{
"package": "candidate",
"compiler": "5.9.3",
"mode": "nodenext-cjs-no-interop",
"diagnostics": [],
"negative": [
{
"code": 2339,
"message": "Property 'default' does not exist on type 'typeof ArtplayerToolIframe'."
}
]
}
],
"runtime": [
{
"package": "published",
"commonjs": "object",
"commonjsDefault": "function",
"esmDefault": "object",
"helperIsDistinct": true,
"ssrImport": true
},
{
"package": "workspace",
"commonjs": "function",
"commonjsDefault": "undefined",
"esmDefault": "function",
"helperIsDistinct": false,
"ssrImport": true
},
{
"package": "candidate",
"commonjs": "function",
"commonjsDefault": "undefined",
"esmDefault": "function",
"helperIsDistinct": false,
"ssrImport": true
}
]
},
"diagnostics": {
"editorBefore": {
"file": "refactor/.cache/iframe04-editor-before.json",
"codes": [
2309,
2686,
2303
],
"meaning": "Frozen default/export= combination and incorrect lowercase global; both compiler versions reproduce the same three codes, in compiler-specific ordering."
},
"workspaceNodeNextEsm": {
"report": "refactor/.cache/iframe-package-types-6qThuH/workspace-5.9.3-nodenext-esm.json",
"count": 18,
"meaning": "Frozen workspace declaration represents a namespace rather than a constructable native ESM default. Candidate paired d.mts/d.cts routes pass the unchanged workspace fixture."
},
"trials": [
"refactor/.cache/iframe04-types-first.log",
"refactor/.cache/iframe04-types-second.log",
"refactor/.cache/iframe04-editor-build.log",
"refactor/.cache/iframe04-lint-fix.log"
],
"meaning": "Initial tests exposed historical diagnostic ordering/context differences and the known nullable-source callback mismatch; exact expectations are explicit. The editor build found method-signature lint errors, repaired in authored types before regeneration.",
"intermediateCi": {
"log": "refactor/.cache/iframe04-ci.log",
"tests": 1542,
"status": "pass-before-error-payload-review",
"scope": "Not final acceptance after ProtocolMessage error payload was corrected to unknown."
},
"nonStringErrors": {
"file": "refactor/.cache/iframe04-error-payload-probe.json",
"sha256": "e5d81d661be5fe555def11f4366443ce463e7b0b53fbd5cca4d11b0ac64a779e",
"observations": 10,
"scope": "Two non-Error throws across four immutable historical artifacts and candidate source produce numeric/undefined data. Permanent baseline regression repeats these checks."
}
},
"preservedDifferences": [
"Default Message.data remains required and static onMessage remains void; RuntimeConstructor exposes the actual optional-data/Promise<void> surface.",
"Default callback remains non-null while actual source allows null. The source satisfies RuntimeConstructor; exact historical mismatch 2419 is asserted separately, not hidden by skipLibCheck.",
"Default commit keeps Promise<ReturnType<T>>, including old nested-Promise inference. ResolverInstance provides an explicit result for the literal resolve(...) body protocol; neither view adds async-function-body or closure support.",
"Published Function callbacks versus callable workspace callbacks are an existing cross-package-name declaration difference, with both compiler diagnostics pinned. Old-name/helper distribution is still IFRAME-06.",
"Historical npm native ESM default is an object despite accepted old declaration usage; the candidate tool native ESM default is a class. No old package/helper alias has been delivered here.",
"No self-default is added. Old-style TypeScript CommonJS default imports require interop; modern direct import= is checked without interop.",
"Known error packets carry unknown data because thrown values need not be Error objects. New typed consumers narrow before treating the payload as text."
],
"checks": [
{
"command": "yarn ci:check",
"result": "pass",
"exitCode": 0,
"tests": 1543,
"unit": 1365,
"engineering": 14,
"baseline": 164,
"repeatedContracts": 44,
"productionTypeScriptFiles": 330,
"log": "refactor/.cache/iframe04-ci-final.log",
"sha256": "7a0b080a0e6f708c0f8bdbd831d8e93b0970b5779f4c7ded42ea96be4fc0da64"
},
{
"command": "yarn test:imports",
"result": "pass",
"tests": 3,
"log": "refactor/.cache/iframe04-imports.log"
},
{
"command": "yarn build:ts artplayer-tool-iframe",
"result": "pass",
"log": "refactor/.cache/iframe04-editor-build-verified.log"
},
{
"command": "yarn build artplayer-tool-iframe",
"result": "pass",
"log": "refactor/.cache/iframe04-build.log",
"scope": "All three runtime outputs remain byte-identical to task 03."
},
{
"command": "yarn test:iframe-types-package",
"result": "pass",
"log": "refactor/.cache/iframe04-package-verified.log",
"compilerCases": 16,
"candidateCases": 6,
"candidateNegativeDiagnostics": 81
}
],
"remaining": [
"PKG-IFRAME-05 full core/demo/BFCache/device/interrupted-navigation integration",
"PKG-IFRAME-06 old package name/helper paths, emitted consumer runtime and final distribution",
"PKG-MB-10 standalone editor treatment of split media declarations surfaced during an initial all-package generation; unrelated generated changes were restored and no completion is claimed"
],
"verifiedAt": "2026-09-12T17:41:36.279Z"
}
@@ -0,0 +1,88 @@
# PKG-IFRAME-04 public types and installed consumers
Started from 1342de94ef2e9dc3e06ddac12fcf1fc5229b648d with a clean worktree.
The previous task completed six strict source modules and native document
ownership. This task owns public declarations and their consumer/editor paths.
## Verified starting points
- Frozen npm plugin-iframe@1.0.0 has export= / global ArtplayerPluginIframe but
actually returns a CommonJS default namespace. Its extra helper is a distinct
protocol and has no dedicated declaration. No tool@1.1.0 npm archive is invented.
- Frozen/current workspace default class has required Message.data, readonly
instance fields, void static onMessage and Promise<ReturnType<T>> commit.
Runtime static onMessage is async, data may be omitted and callback can be null.
Preserve legacy extraction/replacement types; expose precise opt-in views.
- Current main/legacy return the class directly, with no self-default property.
Do not borrow factory.default declarations from other packages.
- The generated editor declaration uses lowercase artplayerToolIframe although
runtime/class is ArtplayerToolIframe, and combines default export with export=.
Reproduce and repair generation, not the generated file by hand.
## Planned acceptance
Named option/message/protocol/callback types, unchanged default class signatures,
precise optional runtime views, correct CJS/ESM declaration routing, strict
current/old compiler consumers and standalone editor generation. Isolated Yarn
pack/install must distinguish the real old npm archive, frozen workspace and
candidate, preserve exact historical errors and verify no workspace type leakage.
Legacy package-name/helper distribution remains independently tracked by 06;
full player/demo/device integration remains 05. Do not claim either passed here.
## Implementation and decisions
The default .d.ts keeps all old field/method types and adds named option, envelope,
notification and callback types. The CJS .d.cts exports the actual class with an
instance alias and type namespace; .d.mts handles native ESM. The legacy route and
typesVersions preserve old compiler resolution. No runtime default property or
JavaScript method is added. Source files and all three runtime artifacts remain
byte-identical to task 03; package/editor declaration routes are tested separately.
RuntimeConstructor/RuntimeInstance opt into async static receipt, optional outgoing
data and a nullable typed callback. Generic response T is an application assertion,
not payload validation. ResolverInstance gives an explicit result for the existing
literal resolve(...) function-body protocol. The default nested-Promise/void commit
inference remains unchanged; no async-function-body or closure execution is added.
Independent review proved error payloads may be numbers or undefined when executed
code throws non-Error values, in four historical main/legacy builds and candidate
source. The new ProtocolMessage error data therefore uses unknown with narrowing;
the first green CI predated this correction and is retained as intermediate only.
The frozen editor yields 2309/2686/2303; semantic class generation removes the
default/export= conflict and uses ArtplayerToolIframe with named-type aliases.
build:ts accepts selected package names to regenerate the current package without
rewriting unrelated packages mid-migration. Its old all-package mode remains;
the initial all-package run exposed split MediaBunny media declaration treatment,
now explicitly assigned to MB-10. No hand-editing of generated declarations.
Isolated Yarn installs cover the real npm archive, a pack made exclusively from
frozen Git inputs, and candidate pack. Every installed file matches its archive;
frozen reinstall preserves the lock and compiler files cannot resolve outside the
consumer except standard libraries. Old workspace NodeNext ESM has 18 exact
diagnostics; candidate accepts the unchanged fixture. Published Function callback
fields and its CJS namespace are existing differences from workspace callable
fields/direct class, not silently rewritten. Old package/helper facade and emitted
consumer compatibility stay under IFRAME-06, with IFRAME-DIST-01 still open.
The default non-null callback also differs from source's nullable field. Tests pin
exact diagnostic 2419 for the old view and prove the source satisfies the opt-in
RuntimeConstructor. This documents a preserved type boundary, not skipLibCheck.
No new dependencies, version changes, push or publish. Final checks and hashes
are in baselines/iframe-types-validation.json. Roll back this task's declarations,
manifest and generator changes together, then regenerate the iframe editor types.
Completion subject: `refactor(iframe): [PKG-IFRAME-04] preserve types across module consumers`.
## Final verification
Full CI passes 1543 (1365 unit + 14 engineering + 164 baseline), 44 repeated
contracts and 330 production TS files. The four new baseline tests include
positive/negative views, exact historical differences, error-payload runtime
evidence and both-compiler standalone editor checks. Installed consumers verify
16 compiler cases across three archives, including six candidate cases with
81 rejected invalid uses; old workspace ESM errors stay explicit negative controls.
Three import/SSR files pass. Source/declaration/editor hashes, final tarball bytes,
all runtime source modules and byte-identical runtime/docs outputs are verified.
Package-name/helper and full integration gates remain open under 05/06. This is
a declaration/consumer completion, not an npm release or final browser/demo review.
@@ -0,0 +1,37 @@
import Iframe from 'artplayer-tool-iframe'
type Equal<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
type Assert<T extends true> = T
interface OldMessage { type: string, data: any, id?: number }
type ExactConstructor = Assert<Equal<ConstructorParameters<typeof Iframe>, [option: { iframe: HTMLIFrameElement, url: string }]>>
type ExactMessage = Assert<Equal<Parameters<Iframe['postMessage']>, [message: OldMessage]>>
type ExactStaticReceiver = Assert<Equal<ReturnType<typeof Iframe.onMessage>, void>>
type ExactCallbacks = Assert<Equal<Iframe['promises'], Record<number, { resove: (...args: any[]) => any, reject: (...args: any[]) => any }>>>
type ExactCallback = Assert<Equal<Iframe['messageCallback'], (...args: any[]) => any>>
declare const frame: HTMLIFrameElement
const tool: Iframe = new Iframe({ iframe: frame, url: '/iframe.html' })
const message: OldMessage = { type: 'custom', data: { enabled: true }, id: 1 }
Iframe.postMessage(message)
Iframe.inject()
const staticResult: void = Iframe.onMessage(new MessageEvent('message', { data: message }))
tool.onMessage(new MessageEvent('message', { data: message }))
const custom: Promise<any> = tool.postMessage(message)
const sync: Promise<number> = tool.commit(() => {
return 1
})
const nested: Promise<Promise<number>> = tool.commit(() => {
return Promise.resolve(1)
})
const resolver: Promise<void> = tool.commit((resolve: (value: number) => void) => {
resolve(1)
})
tool.message((value, additional) => [value, additional])
tool.promises[1] = { resove: (...args: any[]) => args, reject: (...args: any[]) => args }
tool.promises[1].resove(1, 2)
tool.promises[1].reject(new Error('old'))
const replacement: typeof Iframe.onMessage = () => {}
const enabled: boolean = Iframe.iframe
tool.destroy()
void [staticResult, custom, sync, nested, resolver, replacement, enabled]
export type { ExactCallback, ExactCallbacks, ExactConstructor, ExactMessage, ExactStaticReceiver }
+5 -4
View File
@@ -4,7 +4,7 @@
基线:`40fcda6a37d0049d42e49c1e64e70d4fd9ba5f7f`。总任务 222 项,范围 22 个包及工作区/示例。
状态:todo 105 / doing 9 / blocked 0 / done 108 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
状态:todo 104 / doing 9 / blocked 0 / done 109 / deferred 0。风险 L/M/H 表示兼容风险,不表示工期。
前置依赖是启动条件;验收是完成条件。任务可以继续拆分,但不能复用或悄悄删除旧 ID。
@@ -338,7 +338,7 @@
| PKG-MB-07 | artplayer-proxy-mediabunny<br>整理 HLS 配对轨道与 selector | PKG-MB-05, PKG-MB-06, CORE-14 | m3u8 配对、质量/音频选择及拓扑清理 | 实际选择高亮、切到无轨道来源清理、无重复 readiness | H | done |
| PKG-MB-08 | artplayer-proxy-mediabunny<br>完成 TS 与媒体能力声明 | PKG-MB-07, ENG-04, ENG-06 | 8 个自有 JS 模块迁移与 Result/shim 类型 | 解码器/Stream/DOM 类型清楚,旧 Option 和 art.mediabunny 使用保持 | H | done |
| PKG-MB-09 | artplayer-proxy-mediabunny<br>完成新旧核心和真实媒体组合 | PKG-MB-08, CORE-22 | 跨浏览器能力、长播放、DPiP、HLS 音轨/质量报告 | 资源释放、事件顺序、AV sync 与支持范围满足基线 | H | doing |
| PKG-MB-10 | artplayer-proxy-mediabunny<br>完成分发和文档 | PKG-MB-09, ENG-07 | mediabunny.js、README、依赖版本、三产物和许可 | tarball 可消费、无意外依赖升级、旧调用与回退可用 | H | todo |
| PKG-MB-10 | artplayer-proxy-mediabunny<br>完成分发和文档 | PKG-MB-09, ENG-07 | mediabunny.js、README、依赖版本、三产物和许可; 拆分media声明后的独立编辑器生成/辅助文件过滤与语义检查(IFRAME-04全量生成时暴露) | tarball 可消费、无意外依赖升级、旧调用与回退可用 | H | todo |
## 5 包迁移:artplayer-tool-iframe
@@ -347,9 +347,9 @@
| PKG-IFRAME-01 | artplayer-tool-iframe<br>核对包契约与历史用法 | BASE-05 | constructor/commit/message/inject、postMessage 协议与历史公开拼写 | 源码/声明/README/demo/发布包差异已登记;公开形状和版本范围冻结 | H | done |
| PKG-IFRAME-02 | artplayer-tool-iframe<br>建立特有行为与错误测试 | PKG-IFRAME-01, ENG-03, ENG-05 | 跨窗口消息、ID 匹配、请求失败、重复 inject、销毁中请求 | 旧版本行为可重跑,成功/失败/切源/销毁有必要断言 | H | done |
| PKG-IFRAME-03 | artplayer-tool-iframe<br>整理内部职责与资源 | PKG-IFRAME-02, CORE-02, BASE-07 | 请求注册/响应匹配/监听清理分离;origin/source 安全边界独立决策 | 结构变化和缺陷修复分开记录;原 API/事件/资源生命周期通过 | H | done |
| PKG-IFRAME-04 | artplayer-tool-iframe<br>迁移自有源码和公开类型 | PKG-IFRAME-03, ENG-04, ENG-06, CORE-07 | 消息联合类型、回调/Promise 推导、旧公开字段兼容;旧npm export=与实际namespace、额外helper协议分别核验 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | todo |
| PKG-IFRAME-04 | artplayer-tool-iframe<br>迁移自有源码和公开类型 | PKG-IFRAME-03, ENG-04, ENG-06, CORE-07 | 消息联合类型、回调/Promise 推导、旧公开字段兼容;旧npm export=与实际namespace、额外helper协议分别核验 | 严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容 | H | done |
| PKG-IFRAME-05 | artplayer-tool-iframe<br>验证新旧核心和组合 | PKG-IFRAME-04, CORE-22 | 真实同源/跨源 iframe、既有 commit 协议;安全变化有独立结论;新旧核心/demo、实际 BFCache/设备和外部中断导航验收,旧端无文档标记的限制明确 | 最终核心与原支持范围核心分别通过;设备/SDK 缺证据不能标完成 | H | todo |
| PKG-IFRAME-06 | artplayer-tool-iframe<br>验证分发并同步文档 | PKG-IFRAME-05, ENG-07 | iframe.js、示例集成和原 script/class 导出验证;旧包名/额外helper深入口与新工具名的迁移结论 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
| PKG-IFRAME-06 | artplayer-tool-iframe<br>验证分发并同步文档 | PKG-IFRAME-05, ENG-07 | iframe.js、示例集成和原 script/class 导出验证;旧包名/额外helper深入口与新工具名的迁移结论; 保留旧npm Function回调及namespace/helper与工具类的区别,核验实际编译后消费而非仅声明通过 | tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录 | H | todo |
## 5 包迁移:artplayer-tool-thumbnail
@@ -533,4 +533,5 @@
- PKG-IFRAME-01: [记录](changes/2026-09-12-PKG-IFRAME-01-contract.md) [记录](baselines/iframe-release.json) [记录](baselines/iframe-contract.md) [记录](baselines/iframe-contract-validation.json)
- PKG-IFRAME-02: [记录](changes/2026-09-12-PKG-IFRAME-02-behavior.md) [记录](baselines/iframe-behavior-validation.json)
- PKG-IFRAME-03: [记录](changes/2026-09-12-PKG-IFRAME-03-requests-checkpoint.md) [记录](baselines/iframe-requests-checkpoint.json) [记录](changes/2026-09-12-PKG-IFRAME-03-boundaries-checkpoint.md) [记录](baselines/iframe-boundaries-checkpoint.json) [记录](iframe-message-boundary.md) [记录](changes/2026-09-13-PKG-IFRAME-03-navigation.md) [记录](baselines/iframe-navigation-validation.json) [记录](iframe-document-protocol.md)
- PKG-IFRAME-04: [记录](changes/2026-09-13-PKG-IFRAME-04-types.md) [记录](baselines/iframe-types-validation.json)
- PKG-FACTORY-01: [记录](baselines/factory-assignment-gaps.json) [记录](baselines/factory-compatibility-proposals.json) [记录](factory-compatibility-decision.md) [记录](changes/2026-09-12-PKG-FACTORY-01-decision.md)
+14
View File
@@ -1,5 +1,19 @@
# 进度与证据
## PKG-IFRAME-04 完成:公开类型、模块入口和编辑器
保留默认类的required data、void静态接收、readonly、resove及旧commit推导,增加
可选Runtime/Resolver视图、消息联合与命名类型。CJS/ESM声明分别路由,旧TS通过
types/legacy入口;无运行时self-default。编辑器使用实际大写类名并语义生成声明。
旧工作区NodeNext ESM的18条错误已复现并修复;旧编辑器三条错误在两编译器复现。
独立Yarn安装旧npm、冻结工作区和候选共16编译组合,候选6组/81非法使用被拒绝;
历史Function字段、namespace/helper及nullable回调区别明确保留,05/06仍待验收。
复查发现非Error抛出可产生非字符串error载荷,已用四历史产物与源码复现并修正类型。
最终CI1543与44重复契约、3导入/SSR通过,330生产TS。三运行产物及源码字节未变,
沿用03的真实浏览器证据,不冒称新跑浏览器。分包编辑器脚本避免改写迁移中的其他包。
见[变更](changes/2026-09-13-PKG-IFRAME-04-types.md)和[验证](baselines/iframe-types-validation.json)。
222项:109 done、9 doing、104 todo;本项独立本地提交,三个Iframe风险仍open。
## PKG-IFRAME-03 完成:按文档管理请求与六模块拆分
入口、连接、请求、消息边界、父页导航和子页生命周期拆为六个严格TS模块。
+4 -2
View File
@@ -3827,12 +3827,14 @@
],
"status": "open",
"confirmation": "reproduced",
"compatibleResolution": "Keep actual old plugin/helper exports distinct from workspace tool and document package-name/type/export migration; do not fabricate a tool release or use a renamed implementation as an old archive.",
"compatibleResolution": "IFRAME-04 adds paired CJS/ESM declarations and verifies three actual isolated installs, including old namespace/helper and existing Function callback differences. Keep old package-name/helper facade, emitted-consumer runtime and final distribution gates with IFRAME-06; the time-scoped tool registry observation is not a release claim.",
"closureCriteria": "Actual installed entry/declaration tests and documented old-name/helper/new-tool distribution behavior; registry observation remains time scoped.",
"evidence": [
"refactor/baselines/iframe-contract.md",
"refactor/baselines/iframe-release.json",
"refactor/changes/2026-09-12-PKG-IFRAME-01-contract.md"
"refactor/changes/2026-09-12-PKG-IFRAME-01-contract.md",
"refactor/changes/2026-09-13-PKG-IFRAME-04-types.md",
"refactor/baselines/iframe-types-validation.json"
]
}
]
+112
View File
@@ -0,0 +1,112 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkFiles, packedFiles } from '../../scripts/package-check.mjs'
import { consumerDirectory, removeConsumer, run, workspace, writeJson } from '../../scripts/package-consumer.mjs'
import { verifyIframeContract } from './iframe-contract.mjs'
import { hash, readMember } from './releases.mjs'
async function main() {
assert.equal(process.env.npm_config_user_agent?.split(' ')[0], 'yarn/1.22.22', 'Use yarn test:iframe-types-package')
const yarn = process.env.npm_execpath
assert(yarn && fs.existsSync(yarn))
const output = fs.mkdtempSync(path.join(workspace, 'refactor/.cache/iframe-package-types-'))
console.log(`Iframe installed package evidence: ${output}`)
const contract = await verifyIframeContract()
const frozen = path.join(output, 'frozen-workspace')
fs.mkdirSync(frozen)
const prefix = 'packages/artplayer-tool-iframe/'
for (const [file, bytes] of contract.sources) {
if (!file.startsWith(prefix))
continue
const target = path.join(frozen, file.slice(prefix.length))
fs.mkdirSync(path.dirname(target), { recursive: true })
fs.writeFileSync(target, bytes)
}
const packages = [{ label: 'published', name: contract.baseline.release.name, archive: contract.archive, sha256: contract.baseline.release.sha256, files: contract.baseline.release.files }]
for (const [label, folder] of [['workspace', frozen], ['candidate', path.join(workspace, 'packages/artplayer-tool-iframe')]]) {
const archive = path.join(output, `${label}.tgz`)
fs.writeFileSync(path.join(output, `${label}-pack.log`), run([yarn, 'pack', '--filename', archive], folder))
const manifest = JSON.parse(readMember(archive, 'package/package.json'))
const files = packedFiles(archive)
checkFiles(manifest, files)
packages.push({ label, name: manifest.name, archive, sha256: hash(fs.readFileSync(archive)), files: Object.fromEntries(files.map(member => [member, hash(readMember(archive, member))])) })
}
const matrix = []
const runtime = []
for (const pkg of packages) {
const consumer = consumerDirectory()
try {
writeJson(path.join(consumer, 'package.json'), { name: 'iframe-isolated-consumer', private: true, dependencies: { [pkg.name]: `file:${pkg.archive.replaceAll('\\', '/')}` } })
fs.writeFileSync(path.join(output, `${pkg.label}-install.log`), run([yarn, 'install', '--offline', '--ignore-scripts', '--non-interactive'], consumer))
const lock = fs.readFileSync(path.join(consumer, 'yarn.lock'))
fs.writeFileSync(path.join(output, `${pkg.label}-frozen-install.log`), run([yarn, 'install', '--offline', '--frozen-lockfile', '--ignore-scripts', '--non-interactive'], consumer))
assert.deepEqual(fs.readFileSync(path.join(consumer, 'yarn.lock')), lock)
fs.writeFileSync(path.join(output, `${pkg.label}-yarn.lock`), lock)
const installed = path.join(consumer, 'node_modules', pkg.name)
assert.equal(fs.realpathSync(installed), installed, 'Consumer must not use workspace links')
for (const [member, expected] of Object.entries(pkg.files))
assert.equal(hash(fs.readFileSync(path.join(installed, member.slice(8)))), expected, member)
const runtimeFile = path.join(consumer, 'runtime.cjs')
fs.writeFileSync(runtimeFile, `const assert = require('node:assert/strict');
const value = require('${pkg.name}');
assert.equal(typeof value, '${pkg.label === 'published' ? 'object' : 'function'}');
assert.equal(typeof value.default, '${pkg.label === 'published' ? 'function' : 'undefined'}');
${pkg.label === 'published' ? `for (const suffix of ['js', 'legacy.js']) { const helper = require('${pkg.name}/dist/artplayer-helper-iframe.' + suffix); assert.equal(typeof helper.default, 'function'); assert.equal(typeof helper.default.destroy, 'function'); assert.equal(typeof value.default.destroy, 'undefined'); }` : `const legacy = require('${pkg.name}/legacy'); assert.equal(typeof legacy, 'function'); assert.equal(legacy.default, undefined);`}
import('${pkg.name}').then(module => {
assert.equal(typeof module.default, '${pkg.label === 'published' ? 'object' : 'function'}');
${pkg.label === 'published' ? '' : 'assert.deepEqual(Object.keys(module), [\'default\']);'}
console.log(JSON.stringify({ commonjs: typeof value, commonjsDefault: typeof value.default, esmDefault: typeof module.default, helperIsDistinct: ${pkg.label === 'published'}, ssrImport: true }));
}).catch(error => { console.error(error); process.exitCode = 1; });`)
const observation = run([runtimeFile], consumer)
fs.writeFileSync(path.join(output, `${pkg.label}-runtime.log`), observation)
runtime.push({ package: pkg.label, ...JSON.parse(observation.trim()) })
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
if (pkg.label === 'candidate')
modes.push([ts, 'nodenext-cjs-no-interop'])
for (const [compiler, mode] of modes) {
const next = mode.startsWith('nodenext')
const noInterop = mode.endsWith('no-interop')
const filename = path.join(consumer, `consumer.${next ? mode.includes('-cjs') ? 'cts' : 'mts' : 'ts'}`)
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: !noInterop, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'], module: next ? compiler.ModuleKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleKind.ESNext : compiler.ModuleKind.CommonJS, moduleResolution: next ? compiler.ModuleResolutionKind.NodeNext : mode === 'bundler-esm' ? compiler.ModuleResolutionKind.Bundler : compiler.ModuleResolutionKind.NodeJs }
let source = fs.readFileSync(path.join(workspace, pkg.label === 'candidate' ? noInterop ? 'test/types/iframe-commonjs.cts' : 'test/types/iframe.ts' : 'refactor/fixtures/consumers/iframe-workspace.ts'), 'utf8')
const files = [filename]
if (pkg.label === 'published')
source = source.replaceAll('artplayer-tool-iframe', pkg.name).replace('Record<number, { resove: (...args: any[]) => any, reject: (...args: any[]) => any }>', 'Record<number, { resove: Function, reject: Function }>')
if (pkg.label === 'candidate' && !noInterop) {
const oldFile = path.join(consumer, path.basename(filename).replace('consumer', 'historical'))
fs.copyFileSync(path.join(workspace, 'refactor/fixtures/consumers/iframe-workspace.ts'), oldFile)
files.push(oldFile)
}
function compile(code) {
fs.writeFileSync(filename, code)
const program = compiler.createProgram(files, options)
for (const file of program.getSourceFiles()) {
const real = fs.realpathSync(file.fileName)
assert(real.startsWith(fs.realpathSync(consumer) + path.sep) || (program.isSourceFileDefaultLibrary(file) && path.dirname(real) === fs.realpathSync(path.dirname(compiler.sys.getExecutingFilePath()))), `Type escaped installed consumer: ${real}`)
}
return compiler.getPreEmitDiagnostics(program).map(item => ({ code: item.code, message: compiler.flattenDiagnosticMessageText(item.messageText, '\n').replaceAll(consumer.replaceAll('\\', '/'), '<consumer>') }))
}
const diagnostics = compile(source)
const expected = pkg.label === 'workspace' && mode === 'nodenext-esm' ? [2344, 2344, 2344, 2709, 2339, 2344, 2709, 2344, 2709, 2709, 2351, 2339, 2339, 2339, 7006, 7006, 2339, 2339] : []
writeJson(path.join(output, `${pkg.label}-${compiler.version}-${mode}.json`), diagnostics)
assert.deepEqual(diagnostics.map(item => item.code), expected, `${pkg.label} ${compiler.version} ${mode}`)
const negative = pkg.label === 'candidate' ? compile(source.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')) : []
if (pkg.label === 'candidate')
assert.equal(negative.length, noInterop ? 1 : 16)
matrix.push({ package: pkg.label, compiler: compiler.version, mode, diagnostics, negative })
}
}
finally { removeConsumer(consumer) }
}
writeJson(path.join(output, 'report.json'), { task: 'PKG-IFRAME-04', scope: 'Actual npm archive, frozen Git workspace pack and candidate Yarn tarball installed outside workspace; byte verification, offline/frozen lock and strict positive/negative consumers. Old npm CJS namespace and distinct helper are preserved observations, not claims the candidate provides old package-name/helper aliases. Native player and distribution migration remain 05/06.', packages, matrix, runtime })
console.log(`Iframe installed types verified: ${matrix.length} compiler cases; ${output}`)
}
main().catch((error) => {
console.error(error)
process.exitCode = 1
})
+105
View File
@@ -0,0 +1,105 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
// eslint-disable-next-line test/no-import-node-test -- Compiler and historical declaration contract runner.
import test from 'node:test'
import { ESLint } from 'eslint'
import ts from 'typescript'
import compat from 'typescript-compat'
import { checkPluginEditorDeclaration, generatePluginEditorDeclaration } from '../../scripts/plugin-editor-types.mjs'
import { checkConsumer } from '../../scripts/typecheck.mjs'
import { iframeCandidate, iframeEnvironment, iframeHistorical } from '../../test/helpers/iframe.js'
import { verifyIframeContract } from './iframe-contract.mjs'
import { readMember } from './releases.mjs'
const modes = [[ts, 'node10-commonjs'], [ts, 'nodenext-cjs'], [ts, 'nodenext-esm'], [ts, 'bundler-esm'], [compat, 'node10-commonjs']]
const invalid = code => code.replaceAll(/\/\/ @ts-expect-error[^\n]*\n/g, '')
test('Iframe error envelopes preserve non-string thrown messages rather than claiming string-only payloads', async () => {
const implementations = [...(await iframeHistorical()).filter(item => !item.name.includes('helper')), await iframeCandidate()]
for (const implementation of implementations) {
for (const [body, expected] of [['throw { message: 7 }', 7], ['throw 3', undefined]]) {
const env = iframeEnvironment(implementation, true)
await assert.rejects(env.Factory.onMessage({ data: { type: 'commit', data: body, id: 1 }, source: env.box.parent }), () => true)
assert.equal(env.sent.length, 1)
assert.equal(env.sent[0].packet.type, 'error')
assert.equal(env.sent[0].packet.data, expected)
assert.equal(env.sent[0].packet.id, 1)
}
}
})
test('Iframe public types preserve legacy extraction while precise views reject invalid protocol calls', () => {
const source = fs.readFileSync('test/types/iframe.ts', 'utf8')
for (const [compiler, mode] of modes) {
assert.deepEqual(checkConsumer(compiler, mode, source), [], `${compiler.version} ${mode}`)
assert.equal(checkConsumer(compiler, mode, invalid(source)).length, 16)
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync('refactor/fixtures/consumers/iframe-workspace.ts', 'utf8')), [])
}
const commonjs = fs.readFileSync('test/types/iframe-commonjs.cts', 'utf8')
assert.deepEqual(checkConsumer(ts, 'nodenext-cjs', commonjs), [])
assert.equal(checkConsumer(ts, 'nodenext-cjs', invalid(commonjs)).length, 1)
const implementationSource = fs.readFileSync('test/types/iframe-source.ts', 'utf8')
assert.deepEqual(checkConsumer(ts, 'node10-commonjs', implementationSource), [])
const implementation = checkConsumer(ts, 'node10-commonjs', invalid(implementationSource))
assert.deepEqual(implementation.map(item => item.code), [2419], 'Only the preserved non-null legacy callback differs from the actual constructor')
assert(implementation[0].message.includes('messageCallback') && implementation[0].message.includes('null'))
})
test('Iframe frozen workspace keeps exact default signatures and published Function callbacks remain a separate legacy boundary', async () => {
const contract = await verifyIframeContract()
const source = fs.readFileSync('refactor/fixtures/consumers/iframe-workspace.ts', 'utf8')
const filename = path.resolve('test/types/iframe-history.ts')
const declaration = path.resolve('test/types/iframe-history.d.ts')
for (const compiler of [ts, compat]) {
function historical(code, definitions) {
const options = { strict: true, noEmit: true, skipLibCheck: false, types: [], esModuleInterop: true, module: compiler.ModuleKind.CommonJS, moduleResolution: compiler.ModuleResolutionKind.NodeJs, target: compiler.ScriptTarget.ES2020, lib: ['lib.es2020.d.ts', 'lib.dom.d.ts'] }
const host = compiler.createCompilerHost(options)
const original = host.getSourceFile.bind(host)
host.getSourceFile = (file, language, ...rest) => path.resolve(file) === filename
? compiler.createSourceFile(file, code.replaceAll('artplayer-tool-iframe', './iframe-history'), language, true)
: path.resolve(file) === declaration ? compiler.createSourceFile(file, definitions, language, true) : original(file, language, ...rest)
const exists = host.fileExists.bind(host)
host.fileExists = file => path.resolve(file) === declaration || exists(file)
const program = compiler.createProgram([filename, declaration], options, host)
assert(program.getSourceFile(declaration))
return compiler.getPreEmitDiagnostics(program).map(item => item.code)
}
const frozen = contract.sources.get('packages/artplayer-tool-iframe/types/artplayer-tool-iframe.d.ts')
const published = readMember(contract.archive, 'package/types/artplayer-plugin-iframe.d.ts').toString()
assert.deepEqual(historical(source, frozen), [])
assert.deepEqual(historical(source, published), [2344], 'Published Function versus workspace callable field is a pre-refactor difference')
const broad = `import Iframe from 'artplayer-tool-iframe'; declare const tool: Iframe; declare const callback: Function; tool.promises[1] = { resove: callback, reject: callback };`
assert.deepEqual(historical(broad, published), [])
assert.deepEqual(historical(broad, frozen), [2322, 2322])
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', broad).map(item => item.code), [2322, 2322])
assert.deepEqual(historical(`${source}\nIframe.postMessage({ type: 'custom' });`, frozen), [2345])
assert.deepEqual(checkConsumer(compiler, 'node10-commonjs', `${source}\nIframe.postMessage({ type: 'custom' });`).map(item => item.code), [2345])
}
})
test('Iframe standalone editor generation fixes the frozen export errors and preserves class plus named types', async () => {
const contract = await verifyIframeContract()
const historical = contract.sources.get('docs/assets/ts/artplayer-tool-iframe.d.ts')
const source = fs.readFileSync('packages/artplayer-tool-iframe/types/artplayer-tool-iframe.d.ts', 'utf8')
const generated = generatePluginEditorDeclaration(source, 'ArtplayerToolIframe')
const file = 'docs/assets/ts/artplayer-tool-iframe.d.ts'
const [formatted] = await new ESLint({ fix: true, fixTypes: ['layout'] }).lintText(generated, { filePath: file })
assert.equal(formatted.errorCount, 0)
assert.equal(fs.readFileSync(file, 'utf8').replaceAll('\r\n', '\n'), formatted.output || generated)
const consumer = `declare const frame: HTMLIFrameElement;
const option: ArtplayerToolIframe.Option = { iframe: frame, url: '/frame' };
const tool: ArtplayerToolIframe = new ArtplayerToolIframe(option);
const runtime = ArtplayerToolIframe as ArtplayerToolIframe.RuntimeConstructor;
runtime.postMessage({ type: 'custom' });
const promise: Promise<void> = runtime.onMessage(new MessageEvent('message', { data: { type: 'custom' } }));
// @ts-expect-error No lowercase runtime global exists.
new artplayerToolIframe(option);
// @ts-expect-error URL remains text.
new ArtplayerToolIframe({ iframe: frame, url: 1 });`
for (const compiler of [ts, compat]) {
assert.deepEqual(checkPluginEditorDeclaration(historical, '', '', compiler).map(item => item.code).sort(), [2303, 2309, 2686])
assert.deepEqual(checkPluginEditorDeclaration(generated, '', consumer, compiler), [])
assert.equal(checkPluginEditorDeclaration(generated, '', invalid(consumer), compiler).length, 2)
}
})
+7 -4
View File
@@ -3598,7 +3598,7 @@
],
"status": "todo",
"risk": "H",
"deliverable": "mediabunny.js、README、依赖版本、三产物和许可",
"deliverable": "mediabunny.js、README、依赖版本、三产物和许可; 拆分media声明后的独立编辑器生成/辅助文件过滤与语义检查(IFRAME-04全量生成时暴露)",
"acceptance": "tarball 可消费、无意外依赖升级、旧调用与回退可用",
"evidence": []
},
@@ -3684,11 +3684,14 @@
"ENG-06",
"CORE-07"
],
"status": "todo",
"status": "done",
"risk": "H",
"deliverable": "消息联合类型、回调/Promise 推导、旧公开字段兼容;旧npm export=与实际namespace、额外helper协议分别核验",
"acceptance": "严格类型检查、旧消费样例通过;声明路径/导出和同步异步兼容",
"evidence": []
"evidence": [
"changes/2026-09-13-PKG-IFRAME-04-types.md",
"baselines/iframe-types-validation.json"
]
},
{
"id": "PKG-IFRAME-05",
@@ -3720,7 +3723,7 @@
],
"status": "todo",
"risk": "H",
"deliverable": "iframe.js、示例集成和原 script/class 导出验证;旧包名/额外helper深入口与新工具名的迁移结论",
"deliverable": "iframe.js、示例集成和原 script/class 导出验证;旧包名/额外helper深入口与新工具名的迁移结论; 保留旧npm Function回调及namespace/helper与工具类的区别,核验实际编译后消费而非仅声明通过",
"acceptance": "tarball 入口/资源、类型、8082 demo 和 README 一致,有回退记录",
"evidence": []
},
+19 -8
View File
@@ -1,5 +1,7 @@
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { ESLint } from 'eslint'
import { glob } from 'glob'
import compat from 'typescript-compat'
@@ -18,12 +20,14 @@ function parsePluginInfo(pluginPath) {
const file = path.basename(pluginPath)
const baseName = file.replace('.d.ts', '')
const name = baseName
.split('-')
.map((word, index) =>
index === 0 ? word : word[0].toUpperCase() + word.slice(1),
)
.join('')
const name = baseName === 'artplayer-tool-iframe'
? 'ArtplayerToolIframe'
: baseName
.split('-')
.map((word, index) =>
index === 0 ? word : word[0].toUpperCase() + word.slice(1),
)
.join('')
return { name, file }
}
@@ -36,14 +40,19 @@ fs.writeFileSync(artplayerTSoutput, code.trim())
console.log(`✨ Built ${artplayerTSoutput}`);
(async function () {
const pluginsTS = glob.sync('packages/artplayer-*-*/types/*.d.ts')
const available = glob.sync('packages/artplayer-*-*/types/*.d.ts')
const selected = process.argv.slice(2)
const packageOf = file => path.basename(path.dirname(path.dirname(file)))
for (const name of selected)
assert(available.some(file => packageOf(file) === name), `Unknown declaration package: ${name}`)
const pluginsTS = selected.length ? available.filter(file => selected.includes(packageOf(file))) : available
const pluginFiles = []
for (let index = 0; index < pluginsTS.length; index++) {
const type = pluginsTS[index]
const { name, file } = parsePluginInfo(type)
const source = String(fs.readFileSync(type))
const semanticPlugin = ['artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerPluginDocumentPip'].includes(name)
const semanticPlugin = ['artplayerPluginHlsControl', 'artplayerPluginAudioTrack', 'artplayerPluginDashControl', 'artplayerPluginAds', 'artplayerPluginAmbilight', 'artplayerProxyCanvas', 'artplayerPluginDocumentPip', 'ArtplayerToolIframe'].includes(name)
const code = semanticPlugin
? generatePluginEditorDeclaration(source, name)
: `${source.replace(reg, '')}\nexport = ${name};\nexport as namespace ${name};\n`
@@ -71,6 +80,8 @@ console.log(`✨ Built ${artplayerTSoutput}`);
const formatter = await eslint.loadFormatter('stylish')
throw new Error(formatter.format(results))
}
if (selected.length)
return
const commonJsPath = path.join('docs/assets/js/common.js')
const commonJsContent = fs.readFileSync(commonJsPath, 'utf-8')
const newLibUris = allFiles.map(file => `'./assets/ts/${file}'`).join(',\n ')
+6 -3
View File
@@ -16,6 +16,7 @@ export function generatePluginEditorDeclaration(code, name) {
const internal = `${name}Definitions`
let exported = false
let callable = false
let classExport = false
for (const node of source.statements) {
if (ts.isImportDeclaration(node)) {
assert(node.importClause?.isTypeOnly && !node.importClause.namedBindings && node.importClause.name?.text === 'Artplayer' && node.moduleSpecifier.text === 'artplayer', 'Unsupported plugin editor import')
@@ -26,12 +27,13 @@ export function generatePluginEditorDeclaration(code, name) {
exported = true
continue
}
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isFunctionDeclaration(node), 'Unsupported plugin editor declaration')
assert(ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node), 'Unsupported plugin editor declaration')
assert(node.name && node.name.text !== internal, 'Invalid plugin declaration name')
definitions.push(factory.replaceModifiers(node, [factory.createModifier(ts.SyntaxKind.ExportKeyword)]))
if (ts.isFunctionDeclaration(node)) {
if (ts.isFunctionDeclaration(node) || ts.isClassDeclaration(node)) {
assert.equal(node.name.text, name, 'Unexpected plugin callable')
callable = true
classExport = ts.isClassDeclaration(node)
continue
}
if (node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
@@ -43,13 +45,14 @@ export function generatePluginEditorDeclaration(code, name) {
))
}
}
assert(exported && callable, 'Plugin editor requires a default function')
assert(exported && callable, 'Plugin editor requires a default function or class')
const namespace = (identifier, statements) => factory.createModuleDeclaration([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createIdentifier(identifier), factory.createModuleBlock(statements), ts.NodeFlags.Namespace)
const statements = [
namespace(internal, definitions),
factory.createVariableStatement([factory.createModifier(ts.SyntaxKind.DeclareKeyword)], factory.createVariableDeclarationList([
factory.createVariableDeclaration(name, undefined, factory.createTypeQueryNode(factory.createQualifiedName(factory.createIdentifier(internal), factory.createIdentifier(name)))),
], ts.NodeFlags.Const)),
...(classExport ? [factory.createTypeAliasDeclaration(undefined, name, undefined, factory.createTypeReferenceNode(factory.createQualifiedName(factory.createIdentifier(internal), factory.createIdentifier(name))))] : []),
namespace(name, aliases),
factory.createExportAssignment(undefined, true, factory.createIdentifier(name)),
factory.createNamespaceExportDeclaration(factory.createIdentifier(name)),
+4
View File
@@ -80,6 +80,10 @@ export function runTypechecks() {
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Consumer failed: TS ${compiler.version} ${mode} ${fixture}`)
if (mode === 'nodenext-cjs')
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, 'test/types/commonjs.cts'), 'utf8')), [], 'CommonJS export assignment consumer failed')
for (const fixture of ['test/types/iframe.ts', 'refactor/fixtures/consumers/iframe-workspace.ts'])
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, fixture), 'utf8')), [], `Iframe consumer failed: TS ${compiler.version} ${mode}`)
if (mode === 'nodenext-cjs')
assert.deepEqual(checkConsumer(compiler, mode, fs.readFileSync(path.join(root, 'test/types/iframe-commonjs.cts'), 'utf8')), [], 'Iframe CommonJS constructor consumer failed')
console.log(`Consumers passed: TS ${compiler.version} ${mode}`)
}
const runtimeFixtures = ['test/types/runtime-leaf-consumer.ts', 'test/types/runtime-public.ts', 'test/types/runtime-construction.ts']
+16
View File
@@ -0,0 +1,16 @@
/* eslint-disable ts/no-require-imports -- Verify native TypeScript CommonJS constructor imports. */
import Iframe = require('artplayer-tool-iframe')
import Legacy = require('artplayer-tool-iframe/legacy')
declare const frame: HTMLIFrameElement
const option: Iframe.Option = { iframe: frame, url: '/frame' }
const tool: Iframe = new Iframe(option)
const old: Legacy = new Legacy(option)
const message: Iframe.Message<number> = { type: 'custom', data: 2 }
tool.postMessage(message)
old.commit(() => {
return 2
})
// @ts-expect-error Direct CommonJS constructor has no default namespace.
const wrong = Iframe.default
void wrong
+8
View File
@@ -0,0 +1,8 @@
import type { RuntimeConstructor } from 'artplayer-tool-iframe'
import type Public from 'artplayer-tool-iframe'
import Implementation from '../../packages/artplayer-tool-iframe/src/index'
// @ts-expect-error Historical callback is non-null; actual runtime permits null. Keep this legacy mismatch explicit.
const oldView: typeof Public = Implementation
const actualView: RuntimeConstructor = Implementation
void [oldView, actualView]
+77
View File
@@ -0,0 +1,77 @@
import type { Callbacks, Message, MessageCallback, Notification, Option, OutboundMessage, ProtocolMessage, Resolve, ResolverInstance, RuntimeConstructor } from 'artplayer-tool-iframe'
import Iframe from 'artplayer-tool-iframe'
import Legacy from 'artplayer-tool-iframe/legacy'
declare const frame: HTMLIFrameElement
const option: Option = { iframe: frame, url: '/iframe.html' }
const tool = new Iframe(option)
const old = new Legacy(option)
const Runtime = Iframe as RuntimeConstructor
const runtime = Runtime
const precise = new Runtime(option)
runtime.postMessage({ type: 'custom' })
const asyncReceiver: Promise<void> = runtime.onMessage(new MessageEvent('message', { data: { type: 'custom' } }))
const unknownReply: Promise<unknown> = precise.postMessage({ type: 'custom' })
const numeric: Promise<number> = precise.postMessage<number>({ type: 'query', data: 2 })
const callback: MessageCallback = function (message) {
const source: HTMLIFrameElement = this.$iframe
const body: unknown = message.data
void [source, body]
}
precise.message(callback)
precise.messageCallback = null
const resolver = tool as ResolverInstance
const resolved: Promise<number> = resolver.commit<number>((resolve) => {
resolve(3)
})
const resolve: Resolve<number> = value => void value
resolve(Promise.resolve(1))
const notification: Notification<number> = { type: 'custom', data: 3 }
const packet: Message<number> = { type: 'custom', data: 3 }
const outgoing: OutboundMessage = { type: 'arbitrary' }
const callbacks: Callbacks = { resove() {}, reject() {} }
function receive(packet: ProtocolMessage<number>) {
if (packet.type === 'response') {
const value: number = packet.data
return value
}
if (packet.type === 'error' && typeof packet.data === 'string')
return packet.data.toUpperCase()
return packet.id
}
receive({ type: 'inject' })
receive({ type: 'commit', data: 'return 1', id: 1 })
// @ts-expect-error Constructor options are still required.
void new Iframe()
// @ts-expect-error A string selector is not an iframe element.
void new Iframe({ iframe: '#frame', url: '/frame' })
// @ts-expect-error URL is text.
void new Iframe({ iframe: frame, url: 1 })
// @ts-expect-error Default data remains required for old extracted types.
tool.postMessage({ type: 'custom' })
// @ts-expect-error Message types are strings.
precise.postMessage({ type: 1 })
// @ts-expect-error Public request ids remain numeric.
runtime.postMessage({ type: 'custom', id: '1' })
// @ts-expect-error Public flags remain readonly in the default API.
tool.injected = true
// @ts-expect-error The legacy static receiver return is still void.
const notAsync: Promise<void> = Iframe.onMessage(new MessageEvent('message', { data: packet }))
// @ts-expect-error No self-default property exists on the real constructor.
Iframe.default(option)
// @ts-expect-error Runtime iframe is a readonly getter.
runtime.iframe = true
// @ts-expect-error Public notifications do not contain a request id.
const wrongNotification: Notification = { type: 'custom', data: 1, id: 2 }
receive({ type: 'error', data: 1, id: 1 })
// @ts-expect-error Error packets still require a numeric request id.
receive({ type: 'error', data: 1, id: '1' })
// @ts-expect-error Known response data follows its protocol parameter.
receive({ type: 'response', data: '1', id: 1 })
// @ts-expect-error Typed resolver rejects incompatible values.
resolver.commit<number>(resolve => resolve('3'))
// @ts-expect-error Historical misspelling is preserved rather than silently renamed.
callbacks.resolve(1)
// @ts-expect-error Runtime notifications have unknown data until the application narrows it.
precise.message(message => message.data.toFixed())
void [old, asyncReceiver, unknownReply, numeric, resolved, notification, outgoing, notAsync, wrongNotification]