mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-13 07:46:17 -08:00
refactor(tooling): [MOD-PLUGIN-01] generate typed plugins with safe output handling
This commit is contained in:
1 parent
07d5bfef2e
commit
28c7abb2bb
32 files changed
+958
-161
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,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)
|
||||
}
|
||||
})
|
||||
@@ -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}}
|
||||
Reference in new issue
Block a user