refactor(libs): 抽取 @yggdrasil/shared 消除跨 IIFE 库的类型/常量重复

新增 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 侧的对称改动。
This commit is contained in:
xfy 2026-07-14 15:39:41 +08:00
parent 6837b4cfa2
commit 9bc8b5c2ee
16 changed files with 116 additions and 23 deletions

View File

@ -23,6 +23,7 @@
"@codemirror/state": "^6.7.1", "@codemirror/state": "^6.7.1",
"@codemirror/view": "^6.43.6", "@codemirror/view": "^6.43.6",
"@replit/codemirror-vim": "^6.3.0", "@replit/codemirror-vim": "^6.3.0",
"@yggdrasil/shared": "workspace:*",
"codemirror": "^6.0.2" "codemirror": "^6.0.2"
} }
} }

View File

@ -1,16 +1,15 @@
import { THEME_CHANGE_EVENT } from '@yggdrasil/shared';
import { CodeMirrorInstance, EditorOptions } from './editor'; import { CodeMirrorInstance, EditorOptions } from './editor';
import type { ThemeName } from './themes'; import type { ThemeName } from './themes';
/** /**
* yggdrasil-core THEME_CHANGE_EVENT * @yggdrasil/shared
* *
* IIFE, import yggdrasil-core, string literal
* yggdrasil-core VT (NEW ) dispatch , * yggdrasil-core VT (NEW ) dispatch ,
* CodeMirror reconfigure * CodeMirror reconfigure
* OLD/NEW ( catppuccin Extension , .dark ), * OLD/NEW ( catppuccin Extension , .dark ),
* , * ,
*/ */
const THEME_CHANGE_EVENT = 'yggdrasil:theme-change';
/** /**
* { create } * { create }

View File

@ -3,8 +3,9 @@
import { catppuccinLatte, catppuccinMocha } from '@catppuccin/codemirror'; import { catppuccinLatte, catppuccinMocha } from '@catppuccin/codemirror';
import type { Extension } from '@codemirror/state'; import type { Extension } from '@codemirror/state';
import { EditorView } from '@codemirror/view'; import { EditorView } from '@codemirror/view';
import type { ThemeName } from '@yggdrasil/shared';
export type ThemeName = 'light' | 'dark'; export type { ThemeName };
/** /**
* CodeMirror core base theme * CodeMirror core base theme

View File

@ -9,5 +9,8 @@
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest" "test:watch": "vitest"
},
"dependencies": {
"@yggdrasil/shared": "workspace:*"
} }
} }

View File

@ -1,3 +1,4 @@
import { prefersReducedMotion } from '@yggdrasil/shared';
import { fitCentered, originalUrl, type Rect, transformFor } from './geometry'; import { fitCentered, originalUrl, type Rect, transformFor } from './geometry';
import './style.css'; import './style.css';
@ -33,9 +34,7 @@ declare global {
// ============ 工具函数 ============ // ============ 工具函数 ============
function prefersReducedMotion(): boolean { // prefersReducedMotion 由 @yggdrasil/shared 提供lightbox.test.ts 会 mock matchMedia
return !!window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
// 读取元素当前在视口里的 rect用于飞行起点/终点)。 // 读取元素当前在视口里的 rect用于飞行起点/终点)。
// 统一映射成 {x,y,w,h}getBoundingClientRect 返回的 DOMRect 用 // 统一映射成 {x,y,w,h}getBoundingClientRect 返回的 DOMRect 用

20
libs/pnpm-lock.yaml generated
View File

@ -62,11 +62,20 @@ importers:
'@replit/codemirror-vim': '@replit/codemirror-vim':
specifier: ^6.3.0 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) 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: codemirror:
specifier: ^6.0.2 specifier: ^6.0.2
version: 6.0.2 version: 6.0.2
lightbox: {} lightbox:
dependencies:
'@yggdrasil/shared':
specifier: workspace:*
version: link:../shared
shared: {}
tiptap-editor: tiptap-editor:
dependencies: dependencies:
@ -115,8 +124,15 @@ importers:
'@xterm/xterm': '@xterm/xterm':
specifier: ^6.0.0 specifier: ^6.0.0
version: 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: packages:

14
libs/shared/package.json Normal file
View File

@ -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"
}
}

View File

@ -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);
});
});

27
libs/shared/src/index.ts Normal file
View File

@ -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;
}

View File

@ -0,0 +1,4 @@
{
"extends": "../tsconfig.base.json",
"include": ["src"]
}

View File

@ -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'],
},
});

View File

@ -12,6 +12,7 @@
}, },
"dependencies": { "dependencies": {
"@xterm/addon-fit": "^0.11.0", "@xterm/addon-fit": "^0.11.0",
"@xterm/xterm": "^6.0.0" "@xterm/xterm": "^6.0.0",
"@yggdrasil/shared": "workspace:*"
} }
} }

View File

@ -1,16 +1,15 @@
import { THEME_CHANGE_EVENT } from '@yggdrasil/shared';
import { TerminalInstance, XtermOptions } from './terminal'; import { TerminalInstance, XtermOptions } from './terminal';
import type { ThemeName } from './themes'; import type { ThemeName } from './themes';
/** /**
* yggdrasil-core THEME_CHANGE_EVENT * @yggdrasil/shared
* *
* IIFE, import yggdrasil-core, string literal
* yggdrasil-core VT (NEW ) dispatch , * yggdrasil-core VT (NEW ) dispatch ,
* xterm setTheme OLD/NEW * xterm setTheme OLD/NEW
* ( .xterm-scrollable-element inline background-color , * ( .xterm-scrollable-element inline background-color ,
* .dark ),, * .dark ),,
*/ */
const THEME_CHANGE_EVENT = 'yggdrasil:theme-change';
/** /**
* { create } * { create }

View File

@ -1,5 +1,5 @@
// 主题名:与 codemirror-editor / 项目 themes/ 下 Catppuccin Latte/Mocha 对齐 // 主题名由 @yggdrasil/shared 统一定义re-export 保持本模块 API 不变
export type ThemeName = 'light' | 'dark'; export type { ThemeName } from '@yggdrasil/shared';
// Catppuccin Latte浅色终端配色与 highlight.css 浅色主题视觉一致。 // Catppuccin Latte浅色终端配色与 highlight.css 浅色主题视觉一致。
// background 用项目 --color-paper-code-block 的色值Catppuccin Latte Surface0 // background 用项目 --color-paper-code-block 的色值Catppuccin Latte Surface0

View File

@ -9,5 +9,8 @@
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"test": "vitest run", "test": "vitest run",
"test:watch": "vitest" "test:watch": "vitest"
},
"dependencies": {
"@yggdrasil/shared": "workspace:*"
} }
} }

View File

@ -10,8 +10,10 @@
* API bug, VT * API bug, VT
*/ */
import { prefersReducedMotion, THEME_CHANGE_EVENT } from '@yggdrasil/shared';
/** /**
* *
* *
* VT (NEW ) dispatch, CodeMirror / xterm * VT (NEW ) dispatch, CodeMirror / xterm
* setTheme .dark class , * setTheme .dark class ,
@ -19,14 +21,10 @@
* *
* detail: `{ isDark: boolean }` * detail: `{ isDark: boolean }`
* *
* (codemirror-editor / xterm-terminal) IIFE, import , * prefersReducedMotion @yggdrasil/shared , IIFE
* string literal ; + * (codemirror-editor / xterm-terminal / lightbox)
*/ */
export const THEME_CHANGE_EVENT = 'yggdrasil:theme-change'; export { THEME_CHANGE_EVENT };
function prefersReducedMotion(): boolean {
return !!window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}
function maxCornerDistance(x: number, y: number): number { function maxCornerDistance(x: number, y: number): number {
const w = window.innerWidth; const w = window.innerWidth;