02-本地开发

Foxglove Extensions 本地开发指南

概述

本文档介绍如何在本地构建和测试自定义Foxglove扩展,然后再发布到生产环境。

安装扩展到本地

基本安装流程

  1. 构建并安装到本地Foxglove扩展文件夹:

    npm run local-install
    
  2. 安装位置:

    • 扩展将被安装到用户主目录下的扩展文件夹
    • 路径示例:~/.foxglove-studio/extensions/unknown.myExtensionName-0.0.0
    • 文件夹包含编译后的扩展代码
  3. 验证安装:

    • 打开最新版本的Foxglove桌面应用
    • 在应用设置中查看已安装扩展列表
    • 应该能看到myExtensionName出现在列表中
  4. 使用扩展:

    • 打开"添加面板"菜单
    • 查找名为"ExamplePanel"的选项
    • 成功加载表示您的第一个Foxglove扩展安装成功!

Web应用安装

对于Web应用,安装本地扩展的方法略有不同:

  1. 打包扩展:

    npm run package
    
  2. 安装方式:

    • 将生成的.foxe文件拖拽到打开的可视化页面
    • 或者在桌面应用中双击.foxe文件
    • 也可以通过拖拽方式在桌面应用中安装

开发工作流程

开发循环

每次修改扩展代码后,都需要执行以下步骤:

  1. 重新构建和安装:

    npm run local-install
    
  2. 重新加载应用:

    • 重新启动Foxglove应用
    • 或者重新加载应用以执行最新版本的扩展代码
  3. 验证编译(可选):

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

下一步

  • 了解如何发布扩展到生产环境
  • 学习创建自定义面板的详细步骤
  • 掌握消息转换器的开发技巧
  • 优化扩展性能和用户体验
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值