Foxglove Extensions 本地开发指南
概述
本文档介绍如何在本地构建和测试自定义Foxglove扩展,然后再发布到生产环境。
安装扩展到本地
基本安装流程
-
构建并安装到本地Foxglove扩展文件夹:
npm run local-install
-
安装位置:
- 扩展将被安装到用户主目录下的扩展文件夹
- 路径示例:
~/.foxglove-studio/extensions/unknown.myExtensionName-0.0.0
- 文件夹包含编译后的扩展代码
-
验证安装:
- 打开最新版本的Foxglove桌面应用
- 在应用设置中查看已安装扩展列表
- 应该能看到
myExtensionName
出现在列表中
-
使用扩展:
- 打开"添加面板"菜单
- 查找名为"ExamplePanel"的选项
- 成功加载表示您的第一个Foxglove扩展安装成功!
Web应用安装
对于Web应用,安装本地扩展的方法略有不同:
-
打包扩展:
npm run package
-
安装方式:
- 将生成的
.foxe
文件拖拽到打开的可视化页面 - 或者在桌面应用中双击
.foxe
文件 - 也可以通过拖拽方式在桌面应用中安装
- 将生成的
开发工作流程
开发循环
每次修改扩展代码后,都需要执行以下步骤:
-
重新构建和安装:
npm run local-install
-
重新加载应用:
- 重新启动Foxglove应用
- 或者重新加载应用以执行最新版本的扩展代码
-
验证编译(可选):
npm run build
- 仅验证代码编译是否成功
- 不会安装到本地扩展文件夹
开发最佳实践
1. 快速迭代开发
# 开发脚本示例
#!/bin/bash
echo "构建扩展..."
npm run build
if [ $? -eq 0 ]; then
echo "安装到本地..."
npm run local-install
echo "请重新启动Foxglove以查看更改"
else
echo "构建失败,请检查错误信息"
fi
2. 调试技巧
控制台调试:
// 在面板代码中添加调试信息
console.log("面板初始化", panelContext);
console.log("接收到消息", messageEvent);
错误处理:
export function activate(extensionContext: ExtensionContext) {
try {
extensionContext.registerPanel({
name: "my-panel",
initPanel: initMyPanel
});
} catch (error) {
console.error("注册面板失败:", error);
}
}
3. 热重载开发
虽然Foxglove扩展不支持热重载,但可以通过以下方式提高开发效率:
# 监听文件变化并自动重新构建
npx nodemon --watch src --ext ts,tsx --exec "npm run local-install"
项目结构最佳实践
推荐目录结构
my-extension/
├── src/
│ ├── panels/
│ │ ├── MyPanel.tsx
│ │ └── AnotherPanel.tsx
│ ├── converters/
│ │ ├── SchemaConverter.ts
│ │ └── TopicConverter.ts
│ ├── utils/
│ │ └── helpers.ts
│ └── index.ts
├── package.json
├── tsconfig.json
├── README.md
└── CHANGELOG.md
配置文件优化
tsconfig.json 开发配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["ES2020", "DOM"],
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"sourceMap": true,
"declaration": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
package.json 脚本优化:
{
"scripts": {
"build": "tsc",
"build:watch": "tsc --watch",
"local-install": "foxglove-extension install",
"package": "foxglove-extension package",
"dev": "npm run build:watch",
"clean": "rm -rf dist",
"lint": "eslint src --ext .ts,.tsx",
"test": "jest"
}
}
调试和故障排除
常见问题
1. 扩展未出现在面板列表中
可能原因:
- 扩展未正确安装
- 注册函数调用错误
- TypeScript编译错误
解决方案:
# 检查构建错误
npm run build
# 重新安装
npm run local-install
# 检查扩展文件夹
ls ~/.foxglove-studio/extensions/
2. 面板加载失败
检查控制台错误:
- 打开Foxglove开发者工具
- 查看控制台错误信息
- 检查网络请求失败
常见错误修复:
// 确保正确导出activate函数
export function activate(extensionContext: ExtensionContext) {
// 扩展逻辑
}
// 确保面板初始化函数正确
function initMyPanel(context: PanelExtensionContext) {
// 面板逻辑
return () => {
// 清理函数
};
}
3. 消息订阅问题
调试消息订阅:
function initMyPanel(context: PanelExtensionContext) {
context.subscribe(["/my/topic"]);
context.onRender = (renderState, done) => {
console.log("当前主题:", renderState.currentFrame);
console.log("消息数量:", renderState.currentFrame?.length || 0);
done();
};
}
性能调试
内存使用监控
// 监控内存使用
function initMyPanel(context: PanelExtensionContext) {
let messageCount = 0;
context.onRender = (renderState, done) => {
messageCount++;
if (messageCount % 100 === 0) {
console.log(`处理了 ${messageCount} 条消息`);
if (performance.memory) {
console.log("内存使用:", {
used: Math.round(performance.memory.usedJSHeapSize / 1024 / 1024) + "MB",
total: Math.round(performance.memory.totalJSHeapSize / 1024 / 1024) + "MB"
});
}
}
done();
};
}
测试策略
单元测试
// __tests__/MyPanel.test.ts
import { render } from '@testing-library/react';
import MyPanel from '../src/panels/MyPanel';
describe('MyPanel', () => {
it('应该正确渲染', () => {
const { getByText } = render(<MyPanel />);
expect(getByText('我的面板')).toBeInTheDocument();
});
});
集成测试
// 测试扩展注册
import { activate } from '../src/index';
describe('扩展激活', () => {
it('应该注册面板', () => {
const mockContext = {
registerPanel: jest.fn()
};
activate(mockContext as any);
expect(mockContext.registerPanel).toHaveBeenCalledWith({
name: 'my-panel',
initPanel: expect.any(Function)
});
});
});
下一步
- 了解如何发布扩展到生产环境
- 学习创建自定义面板的详细步骤
- 掌握消息转换器的开发技巧
- 优化扩展性能和用户体验