320 lines
6.1 KiB
Markdown
320 lines
6.1 KiB
Markdown
# 沙拉查词项目规范
|
||
|
||
> 架构原则、代码风格、通信协议、安全规范
|
||
|
||
---
|
||
|
||
## 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` 调用
|
||
|
||
**异步规范**:
|
||
```javascript
|
||
// ✅ 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 隔离**:
|
||
```javascript
|
||
const shadow = element.attachShadow({ mode: 'open' });
|
||
shadow.innerHTML = `
|
||
<style>
|
||
:host { display: block; }
|
||
.container { background: var(--salad-bg, #fff); }
|
||
</style>
|
||
<div class="container">...</div>
|
||
`;
|
||
```
|
||
|
||
**CSS 变量**:
|
||
```css
|
||
:root {
|
||
--salad-primary: #4CAF50;
|
||
--salad-bg: #ffffff;
|
||
--salad-text: #333333;
|
||
}
|
||
[data-theme="dark"] {
|
||
--salad-bg: #1a1a1a;
|
||
--salad-text: #e0e0e0;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 通信协议
|
||
|
||
### 消息格式
|
||
```javascript
|
||
// 请求
|
||
{
|
||
type: 'NAMESPACE.ACTION',
|
||
payload: { },
|
||
meta: { timestamp, requestId }
|
||
}
|
||
|
||
// 响应
|
||
{
|
||
type: 'NAMESPACE.ACTION_RESPONSE',
|
||
payload: { },
|
||
error: null,
|
||
meta: { requestId }
|
||
}
|
||
```
|
||
|
||
### 标准消息类型
|
||
```javascript
|
||
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
|
||
```
|
||
|
||
### 通信封装
|
||
```javascript
|
||
// shared/messaging.js
|
||
class MessageClient {
|
||
async send(type, payload, timeout = 5000) {
|
||
return new Promise((resolve, reject) => {
|
||
// 实现:发送消息,监听响应,超时处理
|
||
});
|
||
}
|
||
}
|
||
export const messaging = new MessageClient();
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 词典开发规范
|
||
|
||
### 基类定义
|
||
```javascript
|
||
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);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 返回格式
|
||
```javascript
|
||
{
|
||
word: 'hello',
|
||
phonetic: '/həˈləʊ/',
|
||
meanings: [
|
||
{ partOfSpeech: 'int.', definitions: ['你好', '喂'] }
|
||
],
|
||
examples: [
|
||
{ sentence: 'Hello, world!', translation: '你好,世界!' }
|
||
],
|
||
url: 'https://...'
|
||
}
|
||
```
|
||
|
||
### 实现模板
|
||
```javascript
|
||
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 组件规范
|
||
|
||
### 组件基类
|
||
```javascript
|
||
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. 存储规范
|
||
|
||
### 存储分层
|
||
```javascript
|
||
chrome.storage.local // 配置、生词本
|
||
chrome.storage.session // 临时数据
|
||
IndexedDB // 大数据量
|
||
Memory // 运行时缓存
|
||
```
|
||
|
||
### 配置结构
|
||
```javascript
|
||
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 防护
|
||
```javascript
|
||
// ✅ textContent
|
||
element.textContent = userInput;
|
||
|
||
// ❌ innerHTML 插入不可信内容
|
||
element.innerHTML = userInput;
|
||
|
||
// ✅ 必须使用时转义
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
```
|
||
|
||
### CSP
|
||
```json
|
||
{
|
||
"content_security_policy": {
|
||
"extension_pages": "script-src 'self'; object-src 'self'"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 版本号规范
|
||
|
||
遵循 SemVer,但 **PATCH 与验收点关联**:
|
||
|
||
```
|
||
MAJOR.MINOR.PATCH
|
||
│ │ └─ 每个任务完成 +1
|
||
│ └─ 模块完成进入下一模块 +1
|
||
└─ 正式发布 = 1
|
||
```
|
||
|
||
详见 [README.md](./README.md) 版本规则部分和 [VERSION.md](./VERSION.md)
|