6.1 KiB
6.1 KiB
沙拉查词项目规范
架构原则、代码风格、通信协议、安全规范
1. 架构原则
模块职责
| 模块 | 职责 | 禁止行为 |
|---|---|---|
| Background | 网络请求、数据持久化、右键菜单 | 操作DOM |
| Content Script | 读取页面文本、注入UI、用户交互 | 直接跨域请求 |
| Popup | 独立查词、快速设置、历史记录 | 访问页面DOM |
| Options | 配置管理、生词本、词典账号 | 操作页面内容 |
| Shared | 工具函数、常量、基类 | 依赖特定模块 |
通信规则
Content ↔ Background: chrome.runtime.sendMessage
Popup ↔ Background: chrome.runtime.sendMessage
Options → Background: chrome.runtime.sendMessage
2. 代码风格
命名规范
| 类型 | 规范 | 示例 |
|---|---|---|
| 文件 | 小写+连字符 | salad-icon.js |
| 类 | PascalCase | DictionaryBase |
| 函数 | camelCase+动词开头 | getSelectionCoords() |
| 常量 | UPPER_SNAKE_CASE | DEFAULT_TIMEOUT |
| 私有 | 下划线前缀 | _handleMessage() |
| 消息类型 | 大写+点分隔 | DICT.SEARCH |
JavaScript 规范
必须使用:
const/let,禁止var- 箭头函数作为回调
- 模板字符串
- 解构赋值
- 可选链
?.
禁止:
eval(),new Function()innerHTML插入不可信内容- 同步的
chrome.storage调用
异步规范:
// ✅ async/await
async function searchWord(word) {
try {
const result = await dict.search(word);
return result;
} catch (error) {
console.error('Search failed:', error);
throw error;
}
}
CSS 规范
Shadow DOM 隔离:
const shadow = element.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host { display: block; }
.container { background: var(--salad-bg, #fff); }
</style>
<div class="container">...</div>
`;
CSS 变量:
:root {
--salad-primary: #4CAF50;
--salad-bg: #ffffff;
--salad-text: #333333;
}
[data-theme="dark"] {
--salad-bg: #1a1a1a;
--salad-text: #e0e0e0;
}
3. 通信协议
消息格式
// 请求
{
type: 'NAMESPACE.ACTION',
payload: { },
meta: { timestamp, requestId }
}
// 响应
{
type: 'NAMESPACE.ACTION_RESPONSE',
payload: { },
error: null,
meta: { requestId }
}
标准消息类型
DICT.SEARCH / DICT.SEARCH_RESPONSE
CONFIG.GET / CONFIG.SET / CONFIG.CHANGED
WORD.ADD_FAVORITE / WORD.REMOVE_FAVORITE
CLIPBOARD.READ / CLIPBOARD.WRITE
HTTP.GET / HTTP.POST
通信封装
// shared/messaging.js
class MessageClient {
async send(type, payload, timeout = 5000) {
return new Promise((resolve, reject) => {
// 实现:发送消息,监听响应,超时处理
});
}
}
export const messaging = new MessageClient();
4. 词典开发规范
基类定义
export class DictionaryBase {
constructor(config = {}) {
this.name = config.name || 'Unknown';
this.icon = config.icon || '';
this.languages = config.languages || ['en', 'zh'];
}
async search(word) {
throw new Error('search() must be implemented');
}
supports(lang) {
return this.languages.includes(lang);
}
}
返回格式
{
word: 'hello',
phonetic: '/həˈləʊ/',
meanings: [
{ partOfSpeech: 'int.', definitions: ['你好', '喂'] }
],
examples: [
{ sentence: 'Hello, world!', translation: '你好,世界!' }
],
url: 'https://...'
}
实现模板
export class XxxDictionary extends DictionaryBase {
constructor(config = {}) {
super({ name: '词典名', icon: 'icon.png', languages: ['en', 'zh'], ...config });
}
async search(word) {
if (!word?.trim()) throw new Error('Word is empty');
const html = await messaging.send('HTTP.GET', {
url: `https://api.example.com?q=${encodeURIComponent(word)}`
});
return this.parse(html, word);
}
parse(html, word) {
const doc = new DOMParser().parseFromString(html, 'text/html');
return {
word,
phonetic: this.extractPhonetic(doc),
meanings: this.extractMeanings(doc),
examples: this.extractExamples(doc),
url: ''
};
}
}
5. UI 组件规范
组件基类
export class ComponentBase extends HTMLElement {
constructor() {
super();
this.shadow = this.attachShadow({ mode: 'open' });
this._isVisible = false;
}
render() {
throw new Error('render() must be implemented');
}
show(x, y) {
this._isVisible = true;
this.style.display = 'block';
this.style.left = `${x}px`;
this.style.top = `${y}px`;
}
hide() {
this._isVisible = false;
this.style.display = 'none';
}
destroy() {
this.hide();
this.remove();
}
}
6. 存储规范
存储分层
chrome.storage.local // 配置、生词本
chrome.storage.session // 临时数据
IndexedDB // 大数据量
Memory // 运行时缓存
配置结构
const DEFAULT_CONFIG = {
version: '7.20.0',
general: {
enableSelection: true,
enableAnimation: true,
darkMode: false,
language: 'zh_CN'
},
searchMode: {
triggerMode: 'icon', // icon/direct/double/key
iconHoverSearch: false,
shortcutKeys: []
},
dictionaries: [
{ id: 'bing', enabled: true, order: 0 }
]
};
7. 安全规范
XSS 防护
// ✅ textContent
element.textContent = userInput;
// ❌ innerHTML 插入不可信内容
element.innerHTML = userInput;
// ✅ 必须使用时转义
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
CSP
{
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'"
}
}
8. 版本号规范
遵循 SemVer,但 PATCH 与验收点关联:
MAJOR.MINOR.PATCH
│ │ └─ 每个任务完成 +1
│ └─ 模块完成进入下一模块 +1
└─ 正式发布 = 1
详见 README.md 版本规则部分和 VERSION.md