feat(vue): [EX-02] verify installed consumers and preserve lifecycle contracts

This commit is contained in:
Harvey Zhao committed 2026-09-14 06:02:13 +08:00
1 parent 4df9689364
commit d62ab13a35
29 files changed
+2326 -82

No files matched your search

+68 -21
View File
@@ -1,29 +1,76 @@
# vue-project
# Vue consumer example
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
Run the example from the repository root with Node from .node-version and Yarn
Classic 1.22.22. Use yarn install --frozen-lockfile and only the root yarn.lock.
```sh
npm install
yarn dev:vue
yarn typecheck:vue
yarn lint:vue
yarn build:vue
yarn test:vue-consumer
```
### Compile and Hot-Reload for Development
The build output is refactor/.cache/vue-example-dist.
Rebuild changed library distributions with yarn build followed by the package name.
```sh
npm run dev
```
## Files and ownership
### Compile and Minify for Production
- src/Player.vue owns the player and container through shallow refs. It constructs
once on mount, emits getInstance, then releases its reference before calling
destroy(false) on owner unmount. Vue owns the outer div. Player state is private:
the component ref exposes the normal Vue surface, not an art property.
- src/player-options.ts holds this example's existing configuration overrides:
French language, French/Indonesian dictionaries and fresh Danmuku/Document PiP
factories per mount. They still override option.lang, i18n and plugins. The
container comes from Vue; other options pass through. The url assertion preserves
the historical Partial input while the core performs runtime validation.
- src/App.vue demonstrates the existing event/prop syntax. Its reactive option
object keeps narrow field inference and is checked against `Partial<Option>`.
- src/main.ts is the client entry referenced by index.html. tsconfig.json replaces
jsconfig.json, preserves the @ alias and enables strict source/template checking.
- vite.config.js uses the standard Vue plugin. The unused optional devtools overlay
was removed from this example's build dependencies; no player UI was changed.
```sh
npm run build
```
## Compatibility and updates
The wrapper retains required option: `Partial<Option>` and the getInstance event.
The array-form event declaration remains intentionally unchanged: historical
listener type extraction is not narrowed. Annotate event handlers with Artplayer
as App.vue does. Missing URLs may typecheck through Partial but still fail the
core's runtime validation.
Changing an option object or its fields does not recreate or update the player.
Use the received instance's methods, such as switchUrl, for live updates. Change
the component key to reconstruct using the latest options. No deep option watcher
or new event/ref contract was introduced. CSS classes and styles still fall through
to the root div; other native attributes continue to work for JS consumers.
Strict template checking distinguishes declared props from arbitrary attributes.
Vue captures listener errors through its error handling. A mounted component
retains its instance after a captured listener error and destroys it on unmount.
KeepAlive deactivation retains the instance; owner unmount destroys it. The example
does not silently pause or destroy players when cached. Use player APIs if your
application needs that behavior.
## Reproducible checks
Root development dependencies pin Vue/compiler-sfc 3.5.28, Vue Vite plugin 6.0.1,
vue-tsc 3.3.11, TypeScript 5.9.3 and Vite 7.3.6. No player runtime dependency changes.
The isolated consumer installs actual packed core, Danmuku and Document PiP files,
frozen-reinstalls them, and verifies every package file. Its Vue compiler and all
resolved declarations also live in that isolated install; no source aliases or
workspace type fallbacks are permitted. A plain-JS SFC separately verifies the
legacy event, attribute and repeated-mount syntax.
Development/production browser runs cover native play/pause/seek and pixels,
instance API source updates, option/key behavior, refs, siblings, repeated mount,
listener errors, KeepAlive and real Worker termination. Video/XML responses use
controlled fixtures. Plugin registration does not certify window PiP interaction,
full Danmuku scheduling, CDN availability or physical macOS/iOS Safari.
yarn test:vue-consumer --before runs the original Player.vue from the recorded Git
baseline against the same candidate packages. Both old and new wrappers must pass;
this is a compatibility control, not an old published npm wrapper test. Evidence
is retained in unique refactor/.cache/vue-consumer-* folders. GitHub runs the test
and uploads these reports; local success does not imply verified remote jobs.
+2
View File
@@ -0,0 +1,2 @@
// Reuse the shared Vue and TypeScript rules.
export { default } from '../../eslint.config.js'
+5 -5
View File
@@ -1,13 +1,13 @@
<!DOCTYPE html>
<!doctype html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>artplayer-vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
-8
View File
@@ -1,8 +0,0 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}
+11 -6
View File
@@ -3,23 +3,28 @@
"type": "module",
"version": "0.0.0",
"private": true,
"packageManager": "yarn@1.22.22",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
"build": "yarn typecheck && vite build",
"preview": "vite preview",
"typecheck": "vue-tsc -p tsconfig.json --noEmit",
"lint": "eslint ."
},
"dependencies": {
"artplayer": "^5.3.0-beta.4",
"artplayer-plugin-danmuku": "^5.2.0",
"artplayer-plugin-document-pip": "^1.0.2",
"vue": "^3.5.21"
"vue": "3.5.28"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"vite": "^7.1.4",
"vite-plugin-vue-devtools": "^8.0.1"
"@vitejs/plugin-vue": "6.0.1",
"eslint": "9.39.2",
"typescript": "5.9.3",
"vite": "7.3.6",
"vue-tsc": "3.3.11"
}
}
+5 -3
View File
@@ -1,12 +1,13 @@
<script setup lang="ts">
import type Artplayer from 'artplayer'
import type { Option } from 'artplayer'
import { reactive } from 'vue'
import Artplayer, { type Option } from 'artplayer'
import Player from './Player.vue'
const option = reactive<Partial<Option>>({
const option = reactive({
url: 'https://artplayer.org/assets/sample/video.mp4',
autoSize: true,
})
} satisfies Partial<Option>)
const style = reactive({
width: '800px',
@@ -15,6 +16,7 @@ const style = reactive({
})
function getInstance(art: Artplayer) {
// eslint-disable-next-line no-console
console.log('Artplayer instance:', art)
}
</script>
+12 -28
View File
@@ -1,44 +1,28 @@
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue'
import type { Option } from 'artplayer'
import Artplayer from 'artplayer'
// Test type
import Artplayer, { type Option } from 'artplayer'
// Test i18n
import fr from 'artplayer/i18n/fr'
import id from 'artplayer/i18n/id'
// Test plugins
import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
import { playerOptions } from './player-options'
const props = defineProps<{ option: Partial<Option> }>()
const emit = defineEmits(['getInstance'])
const art = shallowRef<Artplayer | null>(null)
const $container = ref<HTMLDivElement | null>(null)
const $container = shallowRef<HTMLDivElement | null>(null)
onMounted(() => {
art.value = new Artplayer({
...props.option,
url: props.option.url as string,
container: $container.value as HTMLDivElement,
i18n: { id, fr },
lang: 'fr',
plugins: [
artplayerPluginDocumentPip({
//
}),
artplayerPluginDanmuku({
danmuku: 'https://artplayer.org/assets/sample/danmuku.xml',
}),
],
})
const container = $container.value
if (!container)
return
art.value = new Artplayer(playerOptions(props.option, container))
emit('getInstance', art.value)
})
onBeforeUnmount(() => {
art.value?.destroy(false)
const instance = art.value
art.value = null
instance?.destroy(false)
})
</script>
File renamed without changes.
+23
View File
@@ -0,0 +1,23 @@
import type { Option } from 'artplayer'
import artplayerPluginDanmuku from 'artplayer-plugin-danmuku'
import artplayerPluginDocumentPip from 'artplayer-plugin-document-pip'
import fr from 'artplayer/i18n/fr'
import id from 'artplayer/i18n/id'
// Preserve this example's existing overrides and create plugins per instance.
export function playerOptions(option: Partial<Option>, container: HTMLDivElement): Option {
return {
...option,
container,
// Partial<Option> is the historical wrapper API; Artplayer validates missing URLs.
url: option.url as string,
i18n: { id, fr },
lang: 'fr',
plugins: [
artplayerPluginDocumentPip({}),
artplayerPluginDanmuku({
danmuku: 'https://artplayer.org/assets/sample/danmuku.xml',
}),
],
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"moduleResolution": "Bundler",
"paths": { "@/*": ["./src/*"] },
"types": [],
"allowJs": true,
"checkJs": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"skipLibCheck": false
},
"vueCompilerOptions": { "strictTemplates": true },
"include": ["src"]
}
-2
View File
@@ -2,13 +2,11 @@ import { fileURLToPath, URL } from 'node:url'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {