diff --git a/.nx/version-plans/version-plan-1787025000000.md b/.nx/version-plans/version-plan-1787025000000.md new file mode 100644 index 0000000000..35455e92b3 --- /dev/null +++ b/.nx/version-plans/version-plan-1787025000000.md @@ -0,0 +1,5 @@ +--- +core-bundle: patch +--- + +fix(vue): 解除响应式歌词数据的 Vue Proxy,避免 Core 克隆失败 diff --git a/package.json b/package.json index 4f5f63df30..cb822580cb 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "scripts": { "build:libs": "nx run-many --target=build --projects=tag:library", "ci:build:libs": "pnpm run build:libs", - "test:libs": "nx run-many --target=test --projects=core,ttml,lyric", + "test:libs": "nx run-many --target=test --projects=core,ttml,lyric,vue", "ci:test:libs": "pnpm run test:libs", "format": "biome format . --write", "lint": "biome lint . --fix", diff --git a/packages/playground/vue/src/TestApp.vue b/packages/playground/vue/src/TestApp.vue index fc901828e9..3a6bfe0a55 100644 --- a/packages/playground/vue/src/TestApp.vue +++ b/packages/playground/vue/src/TestApp.vue @@ -45,7 +45,7 @@ import { LyricPlayer, type LyricPlayerRef, } from "@applemusic-like-lyrics/vue"; -import { onMounted, reactive, ref, shallowRef } from "vue"; +import { onMounted, reactive, ref } from "vue"; const audioRef = ref(); const state = reactive({ @@ -54,7 +54,7 @@ const state = reactive({ albumIsVideo: false, currentTime: 0, }); -const lyricLines = shallowRef([]); +const lyricLines = ref([]); const playerRef = ref(); const bgRef = ref(); diff --git a/packages/vue/README-CN.md b/packages/vue/README-CN.md index 72f61fa2eb..d4b494b355 100644 --- a/packages/vue/README-CN.md +++ b/packages/vue/README-CN.md @@ -39,12 +39,18 @@ yarn add @applemusic-like-lyrics/vue # 使用 yarn 一个测试用途的程序可以在 [../playground/vue/src/test.ts](../playground/vue/src/test.ts) 里找到。 ```vue - - - + ``` + +`lyricLines` 可以使用 Vue 的普通 `ref`。Vue 绑定会在将数据传递给 Core +组件前解除响应式代理。 diff --git a/packages/vue/README.md b/packages/vue/README.md index 1f43a2c91f..33429e1d77 100644 --- a/packages/vue/README.md +++ b/packages/vue/README.md @@ -39,12 +39,18 @@ For detailed API documentation, please refer to [AMLL Docs](https://amll.dev/en/ A test program can be found in [../playground/vue/src/test.ts](../playground/vue/src/test.ts). ```vue - - - + ``` + +`lyricLines` can use Vue's regular `ref`. The Vue binding unwraps reactive +proxies before passing the data to the Core component. diff --git a/packages/vue/package.json b/packages/vue/package.json index 4d9bb2a486..d2c4770d1d 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -29,6 +29,7 @@ "build": "run-p typecheck \"build-only {@}\" --", "build:dev": "tsdown", "fmt": "biome format --write ./src", + "test": "vitest test", "dev": "nx run @applemusic-like-lyrics/playground-vue:dev" }, "main": "./dist/amll-vue.cjs", @@ -66,6 +67,7 @@ "tsdown": "catalog:", "typedoc": "^0.28.17", "typedoc-plugin-markdown": "catalog:", - "vue": "^3.5.41" + "vue": "^3.5.41", + "vitest": "catalog:" } } diff --git a/packages/vue/src/LyricPlayer.tsx b/packages/vue/src/LyricPlayer.tsx index ab75663650..af60155d01 100644 --- a/packages/vue/src/LyricPlayer.tsx +++ b/packages/vue/src/LyricPlayer.tsx @@ -24,6 +24,7 @@ import { watch, watchEffect, } from "vue"; +import { unwrapLyricLines } from "./utils/unwrap-lyric-lines"; const lyricPlayerProps = { /** @@ -297,7 +298,7 @@ export const LyricPlayer = defineComponent({ maskMode: props.maskObsceneWordsMode, maskChar: props.maskObsceneWordChar, }); - player.setLyricLines(lyricLines ?? []); + player.setLyricLines(unwrapLyricLines(lyricLines)); if (props.currentTime !== undefined) { player.setCurrentTime(props.currentTime, true); diff --git a/packages/vue/src/utils/unwrap-lyric-lines.ts b/packages/vue/src/utils/unwrap-lyric-lines.ts new file mode 100644 index 0000000000..9a3e66a93d --- /dev/null +++ b/packages/vue/src/utils/unwrap-lyric-lines.ts @@ -0,0 +1,8 @@ +import type { LyricLine } from "@applemusic-like-lyrics/core"; +import { toRaw } from "vue"; + +export function unwrapLyricLines( + lyricLines: LyricLine[] | undefined, +): LyricLine[] { + return toRaw(lyricLines ?? []); +} diff --git a/packages/vue/test/reactive-lyric-lines.test.ts b/packages/vue/test/reactive-lyric-lines.test.ts new file mode 100644 index 0000000000..40c6db0442 --- /dev/null +++ b/packages/vue/test/reactive-lyric-lines.test.ts @@ -0,0 +1,31 @@ +import type { LyricLine } from "@applemusic-like-lyrics/core"; +import { describe, expect, it } from "vitest"; +import { isProxy, ref } from "vue"; +import { unwrapLyricLines } from "../src/utils/unwrap-lyric-lines"; + +function createLine(): LyricLine { + return { + words: [{ word: "test", startTime: 0, endTime: 1000 }], + translatedLyric: "", + romanLyric: "", + startTime: 0, + endTime: 1000, + isBG: false, + isDuet: false, + }; +} + +describe("unwrapLyricLines", () => { + it("unwraps deeply reactive lyric lines before passing them to core", () => { + const lyricLines = ref([createLine()]); + + expect(isProxy(lyricLines.value)).toBe(true); + expect(isProxy(lyricLines.value[0])).toBe(true); + + const unwrapped = unwrapLyricLines(lyricLines.value); + + expect(isProxy(unwrapped)).toBe(false); + expect(isProxy(unwrapped[0])).toBe(false); + expect(() => structuredClone(unwrapped)).not.toThrow(); + }); +}); diff --git a/packages/vue/tsconfig.json b/packages/vue/tsconfig.json index 157df26219..5aca456f99 100644 --- a/packages/vue/tsconfig.json +++ b/packages/vue/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../tsconfig.base.json", - "include": ["src"], + "include": ["src", "test"], "compilerOptions": { // vue 的类型系统非常复杂,基本无法手动定义出来 "isolatedDeclarations": false, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f8b3314d74..b1404582c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -759,6 +759,9 @@ importers: typedoc-plugin-markdown: specifier: 'catalog:' version: 4.12.0(typedoc@0.28.20(typescript@6.0.3)) + vitest: + specifier: 'catalog:' + version: 4.1.10(@types/node@26.2.0)(vite@8.2.1(@types/node@26.2.0)(esbuild@0.28.2)(jiti@2.7.0)(stylus@0.57.0(supports-color@7.2.0))(yaml@2.9.0)) vue: specifier: ^3.5.41 version: 3.5.41(typescript@6.0.3)