From 9bc8b5c2eef8600582270fdaabbf8f0e23ca9865 Mon Sep 17 00:00:00 2001 From: xfy Date: Tue, 14 Jul 2026 15:39:41 +0800 Subject: [PATCH] =?UTF-8?q?refactor(libs):=20=E6=8A=BD=E5=8F=96=20@yggdras?= =?UTF-8?q?il/shared=20=E6=B6=88=E9=99=A4=E8=B7=A8=20IIFE=20=E5=BA=93?= =?UTF-8?q?=E7=9A=84=E7=B1=BB=E5=9E=8B/=E5=B8=B8=E9=87=8F=E9=87=8D?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 libs/shared 内部包,作为跨 IIFE 库的单一真相源: - ThemeName 类型(原 codemirror/themes.ts + xterm/themes.ts 各一份) - THEME_CHANGE_EVENT 常量(原 yggdrasil-core 导出 + codemirror/xterm 各硬编码同名 string literal,靠注释维系一致) - prefersReducedMotion 函数(原 lightbox + yggdrasil-core 各一份) codemirror-editor / xterm-terminal / lightbox / yggdrasil-core 四个库 改为 workspace 依赖 @yggdrasil/shared,Vite 构建时 inline 进各自 IIFE, 不破坏 IIFE 隔离。消除「改一处忘改另一处导致静默失效」的风险。 附:image_reader_limits 已在上一提交共享;本提交是 libs 侧的对称改动。 --- libs/codemirror-editor/package.json | 1 + libs/codemirror-editor/src/index.ts | 5 ++-- libs/codemirror-editor/src/themes.ts | 3 ++- libs/lightbox/package.json | 3 +++ libs/lightbox/src/index.ts | 5 ++-- libs/pnpm-lock.yaml | 20 +++++++++++++-- libs/shared/package.json | 14 +++++++++++ libs/shared/src/index.test.ts | 19 +++++++++++++++ libs/shared/src/index.ts | 27 +++++++++++++++++++++ libs/shared/tsconfig.json | 4 +++ libs/shared/vitest.config.ts | 9 +++++++ libs/xterm-terminal/package.json | 3 ++- libs/xterm-terminal/src/index.ts | 5 ++-- libs/xterm-terminal/src/themes.ts | 4 +-- libs/yggdrasil-core/package.json | 3 +++ libs/yggdrasil-core/src/theme-transition.ts | 14 +++++------ 16 files changed, 116 insertions(+), 23 deletions(-) create mode 100644 libs/shared/package.json create mode 100644 libs/shared/src/index.test.ts create mode 100644 libs/shared/src/index.ts create mode 100644 libs/shared/tsconfig.json create mode 100644 libs/shared/vitest.config.ts diff --git a/libs/codemirror-editor/package.json b/libs/codemirror-editor/package.json index 49520f3..e05607a 100644 --- a/libs/codemirror-editor/package.json +++ b/libs/codemirror-editor/package.json @@ -23,6 +23,7 @@ "@codemirror/state": "^6.7.1", "@codemirror/view": "^6.43.6", "@replit/codemirror-vim": "^6.3.0", + "@yggdrasil/shared": "workspace:*", "codemirror": "^6.0.2" } } diff --git a/libs/codemirror-editor/src/index.ts b/libs/codemirror-editor/src/index.ts index a5da10f..4fd4b7b 100644 --- a/libs/codemirror-editor/src/index.ts +++ b/libs/codemirror-editor/src/index.ts @@ -1,16 +1,15 @@ +import { THEME_CHANGE_EVENT } from '@yggdrasil/shared'; import { CodeMirrorInstance, EditorOptions } from './editor'; import type { ThemeName } from './themes'; /** - * 主题切换事件名——与 yggdrasil-core 的 THEME_CHANGE_EVENT 保持一致。 + * 主题切换事件由 @yggdrasil/shared 统一定义。 * - * 本包是独立 IIFE,不 import yggdrasil-core,故用同名 string literal 订阅。 * yggdrasil-core 在 VT 回调内(NEW 快照捕获前)同步 dispatch 此事件, * 让 CodeMirror 同步 reconfigure 主题——否则圆形展开扫过编辑器区域时 * OLD/NEW 快照同色(背景由 catppuccin Extension 注入,不随 .dark 翻转), * 看不到变化,动画结束后才瞬切。 */ -const THEME_CHANGE_EVENT = 'yggdrasil:theme-change'; /** * 模块入口:暴露对象字面量 { create } 作为默认导出。 diff --git a/libs/codemirror-editor/src/themes.ts b/libs/codemirror-editor/src/themes.ts index be0532d..ba75d8f 100644 --- a/libs/codemirror-editor/src/themes.ts +++ b/libs/codemirror-editor/src/themes.ts @@ -3,8 +3,9 @@ import { catppuccinLatte, catppuccinMocha } from '@catppuccin/codemirror'; import type { Extension } from '@codemirror/state'; import { EditorView } from '@codemirror/view'; +import type { ThemeName } from '@yggdrasil/shared'; -export type ThemeName = 'light' | 'dark'; +export type { ThemeName }; /** * 覆盖 CodeMirror core 内置 base theme 的两处问题: diff --git a/libs/lightbox/package.json b/libs/lightbox/package.json index 9e17d0a..ed0a909 100644 --- a/libs/lightbox/package.json +++ b/libs/lightbox/package.json @@ -9,5 +9,8 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest" + }, + "dependencies": { + "@yggdrasil/shared": "workspace:*" } } diff --git a/libs/lightbox/src/index.ts b/libs/lightbox/src/index.ts index 467dadd..fe5228e 100644 --- a/libs/lightbox/src/index.ts +++ b/libs/lightbox/src/index.ts @@ -1,3 +1,4 @@ +import { prefersReducedMotion } from '@yggdrasil/shared'; import { fitCentered, originalUrl, type Rect, transformFor } from './geometry'; import './style.css'; @@ -33,9 +34,7 @@ declare global { // ============ 工具函数 ============ -function prefersReducedMotion(): boolean { - return !!window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; -} +// prefersReducedMotion 由 @yggdrasil/shared 提供(lightbox.test.ts 会 mock matchMedia)。 // 读取元素当前在视口里的 rect(用于飞行起点/终点)。 // 统一映射成 {x,y,w,h}:getBoundingClientRect 返回的 DOMRect 用 diff --git a/libs/pnpm-lock.yaml b/libs/pnpm-lock.yaml index 7f3c266..4838513 100644 --- a/libs/pnpm-lock.yaml +++ b/libs/pnpm-lock.yaml @@ -62,11 +62,20 @@ importers: '@replit/codemirror-vim': specifier: ^6.3.0 version: 6.3.0(@codemirror/commands@6.10.4)(@codemirror/language@6.12.4)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/view@6.43.6) + '@yggdrasil/shared': + specifier: workspace:* + version: link:../shared codemirror: specifier: ^6.0.2 version: 6.0.2 - lightbox: {} + lightbox: + dependencies: + '@yggdrasil/shared': + specifier: workspace:* + version: link:../shared + + shared: {} tiptap-editor: dependencies: @@ -115,8 +124,15 @@ importers: '@xterm/xterm': specifier: ^6.0.0 version: 6.0.0 + '@yggdrasil/shared': + specifier: workspace:* + version: link:../shared - yggdrasil-core: {} + yggdrasil-core: + dependencies: + '@yggdrasil/shared': + specifier: workspace:* + version: link:../shared packages: diff --git a/libs/shared/package.json b/libs/shared/package.json new file mode 100644 index 0000000..4caa0aa --- /dev/null +++ b/libs/shared/package.json @@ -0,0 +1,14 @@ +{ + "name": "@yggdrasil/shared", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "src/index.ts", + "types": "src/index.ts", + "scripts": { + "build": "tsc --noEmit", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" + } +} diff --git a/libs/shared/src/index.test.ts b/libs/shared/src/index.test.ts new file mode 100644 index 0000000..833d32c --- /dev/null +++ b/libs/shared/src/index.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest'; +import { prefersReducedMotion, THEME_CHANGE_EVENT, type ThemeName } from './index'; + +describe('@yggdrasil/shared', () => { + it('THEME_CHANGE_EVENT 是固定字符串', () => { + expect(THEME_CHANGE_EVENT).toBe('yggdrasil:theme-change'); + }); + + it('prefersReducedMotion 在无 matchMedia 时返回 false', () => { + // happy-dom 提供了 matchMedia,这里只验证函数可调用且返回 boolean + const result = prefersReducedMotion(); + expect(typeof result).toBe('boolean'); + }); + + it('ThemeName 类型仅接受 light/dark(编译期约束)', () => { + const t: ThemeName = 'light'; + expect(['light', 'dark']).toContain(t); + }); +}); diff --git a/libs/shared/src/index.ts b/libs/shared/src/index.ts new file mode 100644 index 0000000..3831d5f --- /dev/null +++ b/libs/shared/src/index.ts @@ -0,0 +1,27 @@ +/** + * 跨 IIFE 库共享的类型、常量与工具函数。 + * + * 这些库各自打包成独立 IIFE(不能 import 彼此),但它们的主题切换逻辑 + * 需要约定相同的事件名与类型定义。本包作为单一真相源,由各库以 workspace + * dependency 引入,Vite 构建时 inline 进各自的 IIFE bundle。 + */ + +/** 主题名称:亮色 / 暗色。 */ +export type ThemeName = 'light' | 'dark'; + +/** + * 主题切换事件名。 + * + * yggdrasil-core 在切换主题时 dispatch 此事件;codemirror-editor / xterm-terminal + * 监听它以热切换编辑器主题。所有库必须用同一个字符串字面量。 + */ +export const THEME_CHANGE_EVENT = 'yggdrasil:theme-change'; + +/** + * 检测用户是否在系统层面启用了「减少动态效果」(prefers-reduced-motion)。 + * + * 用于决定是否跳过 View Transitions / 动画,直接切换。 + */ +export function prefersReducedMotion(): boolean { + return !!window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; +} diff --git a/libs/shared/tsconfig.json b/libs/shared/tsconfig.json new file mode 100644 index 0000000..4bd6962 --- /dev/null +++ b/libs/shared/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../tsconfig.base.json", + "include": ["src"] +} diff --git a/libs/shared/vitest.config.ts b/libs/shared/vitest.config.ts new file mode 100644 index 0000000..e0cccfb --- /dev/null +++ b/libs/shared/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + // prefersReducedMotion 读 window.matchMedia,需要 happy-dom。 + environment: 'happy-dom', + include: ['src/**/*.test.ts'], + }, +}); diff --git a/libs/xterm-terminal/package.json b/libs/xterm-terminal/package.json index 8e53dc5..12b7b92 100644 --- a/libs/xterm-terminal/package.json +++ b/libs/xterm-terminal/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@xterm/addon-fit": "^0.11.0", - "@xterm/xterm": "^6.0.0" + "@xterm/xterm": "^6.0.0", + "@yggdrasil/shared": "workspace:*" } } diff --git a/libs/xterm-terminal/src/index.ts b/libs/xterm-terminal/src/index.ts index fb2c426..a19cf03 100644 --- a/libs/xterm-terminal/src/index.ts +++ b/libs/xterm-terminal/src/index.ts @@ -1,16 +1,15 @@ +import { THEME_CHANGE_EVENT } from '@yggdrasil/shared'; import { TerminalInstance, XtermOptions } from './terminal'; import type { ThemeName } from './themes'; /** - * 主题切换事件名——与 yggdrasil-core 的 THEME_CHANGE_EVENT 保持一致。 + * 主题切换事件由 @yggdrasil/shared 统一定义。 * - * 本包是独立 IIFE,不 import yggdrasil-core,故用同名 string literal 订阅。 * yggdrasil-core 在 VT 回调内(NEW 快照捕获前)同步 dispatch 此事件, * 让 xterm 同步 setTheme——否则圆形展开扫过终端区域时 OLD/NEW 快照同色 * (背景由 .xterm-scrollable-element 的 inline background-color 注入,不随 * .dark 翻转),看不到变化,动画结束后才瞬切。 */ -const THEME_CHANGE_EVENT = 'yggdrasil:theme-change'; /** * 模块入口:暴露对象字面量 { create } 作为默认导出。 diff --git a/libs/xterm-terminal/src/themes.ts b/libs/xterm-terminal/src/themes.ts index a11afcf..7bc27a6 100644 --- a/libs/xterm-terminal/src/themes.ts +++ b/libs/xterm-terminal/src/themes.ts @@ -1,5 +1,5 @@ -// 主题名:与 codemirror-editor / 项目 themes/ 下 Catppuccin Latte/Mocha 对齐。 -export type ThemeName = 'light' | 'dark'; +// 主题名由 @yggdrasil/shared 统一定义,re-export 保持本模块 API 不变。 +export type { ThemeName } from '@yggdrasil/shared'; // Catppuccin Latte(浅色)终端配色,与 highlight.css 浅色主题视觉一致。 // background 用项目 --color-paper-code-block 的色值(Catppuccin Latte Surface0), diff --git a/libs/yggdrasil-core/package.json b/libs/yggdrasil-core/package.json index 789c6fa..c31cfb1 100644 --- a/libs/yggdrasil-core/package.json +++ b/libs/yggdrasil-core/package.json @@ -9,5 +9,8 @@ "typecheck": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest" + }, + "dependencies": { + "@yggdrasil/shared": "workspace:*" } } diff --git a/libs/yggdrasil-core/src/theme-transition.ts b/libs/yggdrasil-core/src/theme-transition.ts index d39938f..811594a 100644 --- a/libs/yggdrasil-core/src/theme-transition.ts +++ b/libs/yggdrasil-core/src/theme-transition.ts @@ -10,8 +10,10 @@ * API 优先级 bug,是目前最稳定的 VT 主题切换方案。 */ +import { prefersReducedMotion, THEME_CHANGE_EVENT } from '@yggdrasil/shared'; + /** - * 主题切换自定义事件名。 + * 主题切换自定义事件。 * * 在 VT 回调内(NEW 快照捕获前)同步 dispatch,通知 CodeMirror / xterm 等 * 命令式换肤的组件同步调 setTheme——它们的背景色不随 .dark class 翻转, @@ -19,14 +21,10 @@ * * 事件 detail: `{ isDark: boolean }`。 * - * 各编辑器包(codemirror-editor / xterm-terminal)是独立 IIFE,不 import 本包, - * 故各自用同名 string literal 订阅;本常量仅用于本包内部 + 测试断言一致性。 + * 事件名与 prefersReducedMotion 由 @yggdrasil/shared 统一定义,各 IIFE 库 + * (codemirror-editor / xterm-terminal / lightbox)共享同一真相源。 */ -export const THEME_CHANGE_EVENT = 'yggdrasil:theme-change'; - -function prefersReducedMotion(): boolean { - return !!window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; -} +export { THEME_CHANGE_EVENT }; function maxCornerDistance(x: number, y: number): number { const w = window.innerWidth;