refactor(tooling): [MOD-PLUGIN-01] generate typed plugins with safe output handling

This commit is contained in:
Harvey Zhao committed 2026-09-14 10:19:19 +08:00
1 parent 07d5bfef2e
commit 28c7abb2bb
32 files changed
+958 -161

No files matched your search

+56 -1
View File
@@ -4,7 +4,62 @@
## Demo
[https://artplayer.org](https://artplayer.org/?libs=./uncompiled/artplayer-plugin-{{name}}/index.js&example={{name}})
[Local demo](http://localhost:8082/?libs=./uncompiled/artplayer-plugin-{{name}}/index.js&example={{example}})
Run `yarn dev artplayer-plugin-{{name}}` from the repository root first.
The generated example is `docs/assets/example/{{example}}.js`.
## Usage
```js
import {{export}} from 'artplayer-plugin-{{name}}'
const art = new Artplayer({
container: '.artplayer-app',
url: '/assets/sample/video.mp4',
plugins: [{{export}}({})],
})
```
The factory also supports direct CommonJS `require`, its `.default` alias,
the `/legacy` entry, and the browser global `{{export}}`.
This scaffold only registers a name; implement and test the intended behavior.
## Maintenance
| File | Responsibility |
| --- | --- |
| `src/index.ts` | Synchronous plugin factory and registration result |
| `src/stylesheet.ts` | One stylesheet per document on module evaluation; safe to import without a DOM |
| `src/style.less` | Plugin styles; retain the package selector when adding UI |
| `src/assets.d.ts` | Build-time inline stylesheet declaration |
| `types/api.d.ts` | Owned option/result types shared by source and public entrypoints |
| `types/artplayer-plugin-{{name}}.d.ts` | CommonJS and older TypeScript entry |
| `types/artplayer-plugin-{{name}}.d.mts` | Native ESM declaration entry |
| `test/plugin.test.mjs` | Built ESM/CommonJS/legacy synchronous factory and alias contracts |
Replace the empty `Option` contract with explicit fields when implementing behavior.
Keep the return value synchronous unless the plugin explicitly requires an async contract.
Keep source and both declaration entrypoints consistent; test real consumers when changing them.
The stylesheet has document lifetime and is shared by instances. Resources added by the
plugin (listeners, timers, requests, DOM, workers) need per-instance ownership and cleanup
on `art.on('destroy', ...)`; guard late callbacks and source changes. Add behavior-specific
normal, error, concurrency and cleanup tests, plus real browser checks for media/UI work.
Use the repository Node version from `.node-version` and Yarn Classic 1.22.22:
```sh
yarn workspace artplayer-plugin-{{name}} typecheck
yarn build artplayer-plugin-{{name}}
yarn workspace artplayer-plugin-{{name}} test
```
Build before testing; the tests consume the actual distribution files. ESM and modern
UMD target es2020, legacy UMD targets es2015. The generator uses the existing toolchain;
it does not install dependencies, publish, or update the root lockfile. Register this
workspace/demo in the refactor inventories and validation mappings before CI, and update
only the root `yarn.lock`. The declared ArtPlayer peer does not itself prove compatibility:
test the core versions and devices required by the implemented features.
## License
+29 -8
View File
@@ -19,20 +19,41 @@
],
"exports": {
".": {
"types": "./types/artplayer-plugin-{{name}}.d.ts",
"import": "./dist/artplayer-plugin-{{name}}.mjs",
"require": "./dist/artplayer-plugin-{{name}}.js"
"import": {
"types": "./types/artplayer-plugin-{{name}}.d.mts",
"default": "./dist/artplayer-plugin-{{name}}.mjs"
},
"require": {
"types": "./types/artplayer-plugin-{{name}}.d.ts",
"default": "./dist/artplayer-plugin-{{name}}.js"
},
"default": "./dist/artplayer-plugin-{{name}}.js"
},
"./legacy": {
"types": "./types/artplayer-plugin-{{name}}.d.ts",
"import": "./dist/artplayer-plugin-{{name}}.legacy.js",
"require": "./dist/artplayer-plugin-{{name}}.legacy.js"
}
"import": {
"types": "./types/artplayer-plugin-{{name}}.d.mts",
"default": "./dist/artplayer-plugin-{{name}}.legacy.js"
},
"require": {
"types": "./types/artplayer-plugin-{{name}}.d.ts",
"default": "./dist/artplayer-plugin-{{name}}.legacy.js"
},
"default": "./dist/artplayer-plugin-{{name}}.legacy.js"
},
"./dist/*": "./dist/*",
"./package.json": "./package.json"
},
"main": "dist/artplayer-plugin-{{name}}.js",
"module": "./dist/artplayer-plugin-{{name}}.mjs",
"types": "types/artplayer-plugin-{{name}}.d.ts",
"legacy": "dist/artplayer-plugin-{{name}}.legacy.js",
"files": ["README.md", "dist", "types"],
"scripts": {
"typecheck": "node ../../node_modules/typescript/bin/tsc -p tsconfig.json --noEmit",
"test": "node --test test/plugin.test.mjs"
},
"peerDependencies": {
"artplayer": "*"
},
"browserslist": "last 1 Chrome version"
}
@@ -0,0 +1,4 @@
declare module '*.less?inline' {
const css: string
export default css
}
-27
View File
@@ -1,27 +0,0 @@
import style from './style.less?inline';
export default function {{export}}(option) {
return (art) => {
return {
name: '{{export}}',
};
};
}
if (typeof document !== 'undefined') {
const id = 'artplayer-plugin-{{name}}';
let $style = document.getElementById(id)
if (!$style) {
$style = document.createElement('style')
$style.id = id
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
document.head.appendChild($style)
})
}
else {
(document.head || document.documentElement).appendChild($style)
}
}
$style.textContent = style
}
+9
View File
@@ -0,0 +1,9 @@
import type Artplayer from 'artplayer'
import type { Option, Result } from '../types/api.js'
import './stylesheet'
function {{export}}(_option: Option = {}) {
return (_art: Artplayer): Result => ({ name: '{{export}}' })
}
export default Object.assign({{export}}, { default: {{export}} })
+1 -1
View File
@@ -1,3 +1,3 @@
.artplayer-plugin-{{name}} {
//
//
}
@@ -0,0 +1,12 @@
import css from './style.less?inline'
// The stylesheet belongs to the module, not individual player instances.
if (typeof document !== 'undefined') {
const id = 'artplayer-plugin-{{name}}'
if (!document.getElementById(id)) {
const style = document.createElement('style')
style.id = id
style.textContent = css
;(document.head || document.documentElement).appendChild(style)
}
}
@@ -0,0 +1,16 @@
import assert from 'node:assert/strict'
import { createRequire } from 'node:module'
// eslint-disable-next-line test/no-import-node-test -- Verify the generated distribution contracts.
import test from 'node:test'
import plugin from 'artplayer-plugin-{{name}}'
import legacy from 'artplayer-plugin-{{name}}/legacy'
test('classic factory and historical default alias return the synchronous plugin result', () => {
const require = createRequire(import.meta.url)
for (const factory of [plugin, legacy, require('artplayer-plugin-{{name}}'), require('artplayer-plugin-{{name}}/legacy')]) {
assert.equal(factory.default, factory)
const result = factory({})({})
assert.deepEqual(result, { name: '{{export}}' })
assert.equal(result instanceof Promise, false)
}
})
+4
View File
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src", "types"]
}
@@ -0,0 +1,7 @@
import type Artplayer from 'artplayer'
// Replace this empty option type with the documented options your plugin owns.
export type Option = Record<string, never>
export interface Result { name: '{{export}}' }
export type Factory = (option?: Option) => (art: Artplayer) => Result
export type RuntimeFactory = Factory & { default: Factory }
@@ -0,0 +1,5 @@
import type { RuntimeFactory } from './api.js'
declare const {{export}}: RuntimeFactory
export default {{export}}
export type { Factory, Option, Result, RuntimeFactory } from './api.js'
@@ -1,13 +0,0 @@
import type Artplayer from 'artplayer';
interface Option {
//
}
interface Result {
name: '{{export}}';
}
declare const {{export}}: (option: Option) => (art: Artplayer) => Result;
export default {{export}}
@@ -0,0 +1,11 @@
import type { RuntimeFactory } from './api.js'
declare const {{export}}: RuntimeFactory
// eslint-disable-next-line ts/no-redeclare -- Expose types on the CommonJS factory.
declare namespace {{export}} {
type Option = import('./api.js').Option
type Result = import('./api.js').Result
type Factory = import('./api.js').Factory
type RuntimeFactory = import('./api.js').RuntimeFactory
}
export = {{export}}