docs/archives/119-csp-safe-template-processing/experience.md
Handlebars.compile()调用// 验证CSP限制的简单测试
try {
new Function('return 1')();
console.log('CSP允许动态代码执行');
} catch (e) {
console.log('CSP禁止动态代码执行:', e.message);
}
问题: 单一检测条件容易误判
// ❌ 不够准确的检测
static isExtensionEnvironment(): boolean {
return typeof chrome !== 'undefined';
}
解决: 多层验证确保准确性
// ✅ 准确的检测逻辑
static isExtensionEnvironment(): boolean {
// 1. 环境排除
// 2. API存在性检查
// 3. 功能有效性验证
// 4. 异常处理保护
}
经验: Electron应用可能注入Chrome API,导致误判 解决: 优先检测Electron特征,明确排除
// 多种Electron检测方式
const electronIndicators = [
'window.require',
'window.electronAPI',
'window.electron',
'navigator.userAgent.includes("Electron")'
];
原则: 新功能不能破坏现有功能 实现:
// ✅ 防御性编程
try {
// 环境检测逻辑
} catch (error) {
// 任何错误都返回false,确保其他平台正常工作
return false;
}
经验: 宁可功能受限,也不能影响其他平台的正常运行
// 模拟不同环境的技巧
beforeEach(() => {
// 清理全局状态
delete (global as any).chrome;
delete (global as any).window;
});
// 精确模拟浏览器扩展环境
(global as any).chrome = {
runtime: {
getManifest: vi.fn(() => ({ manifest_version: 3 }))
}
};
/\{\{([^}]+)\}\}/g 足够处理基本需求// ✅ 安全的替换逻辑
result.replace(/\{\{([^}]+)\}\}/g, (match, variableName) => {
const trimmedName = variableName.trim();
const value = context[trimmedName];
// 类型安全 + 默认值处理
return value !== undefined ? String(value) : '';
});
经验: 复用现有接口比创建新接口更好
// ✅ 安全的类型转换
return value !== undefined ? String(value) : '';
// ❌ 可能出问题的方式
return value || ''; // 0, false会被转换为空字符串
问题: 每次模板处理都进行环境检测 优化: 可考虑缓存检测结果(当前未实现)
// 未来优化方向
class CSPSafeTemplateProcessor {
private static _isExtension: boolean | null = null;
static isExtensionEnvironment(): boolean {
if (this._isExtension === null) {
this._isExtension = this.detectEnvironment();
}
return this._isExtension;
}
}
// ❌ 容易误判
if (typeof chrome !== 'undefined') {
// Electron也可能有chrome对象
}
// ❌ 可能导致其他平台崩溃
const manifest = chrome.runtime.getManifest();
return manifest.manifest_version !== undefined;
// ✅ 安全的检测方式
try {
if (isElectronEnvironment()) return false;
if (hasChromeAPI()) {
return validateManifest();
}
return false;
} catch (error) {
return false; // 保护其他平台
}
// ❌ 没有处理空格
const variableName = match[1];
// ✅ 正确处理
const variableName = match[1].trim();
// ❌ 可能返回undefined字符串
return context[variableName];
// ✅ 安全转换
return value !== undefined ? String(value) : '';
// ❌ 测试间相互影响
it('test1', () => {
(global as any).chrome = mockChrome;
// 测试逻辑
});
it('test2', () => {
// chrome对象仍然存在,影响测试结果
});
// ✅ 每个测试独立
beforeEach(() => {
delete (global as any).chrome;
delete (global as any).window;
delete (global as any).navigator;
});
// 环境检测结果缓存
// 正则表达式对象缓存
// 编译结果缓存(如果需要)
// 对于大量模板,可考虑批量处理
static processBatch(templates: Template[], context: TemplateContext) {
const isExtension = this.isExtensionEnvironment();
return templates.map(template =>
isExtension ? this.processCSPSafe(template, context)
: this.processHandlebars(template, context)
);
}
💡 核心经验总结:
核心启发: 经过CSP安全处理的实践,我们意识到"环境特定的兼容性方案"虽然解决了问题,但增加了系统复杂性。最佳实践是选择原生支持目标环境的技术栈。
关键决策: Mustache.js迁移
eval(),原生支持CSP环境经验升华:
实际效果:
对后续项目的指导:
这次从Handlebars到Mustache的迁移,完美诠释了"选择正确的技术比完善错误的技术更重要"这一架构原则。