Skip to content
Back to skills

2493 Conversion Guide 5c2c951d

ASecurity

Step-by-step guide for converting existing JS/React/Vue apps into VS Code extensions.

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
developmenttypescriptbashreactvuenextjsexpressapidatabasefrontendbackend

Works with

  • vscode
  • cli
  • api

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 2493-conversion-guide_5c2c951d --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 2493 Conversion Guide 5c2c951d?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 2493 Conversion Guide 5c2c951d
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-2493-conversion-guide-5c2c951d/badge)](https://www.skillsdirectory.com/skills/tools-only-2493-conversion-guide-5c2c951d)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# Web App Conversion Guide

Step-by-step guide for converting existing JS/React/Vue apps into VS Code extensions.

## Table of Contents

- [Assessment](#assessment)
- [Architecture Mapping](#architecture-mapping)
- [API Mapping](#api-mapping)
- [Step-by-Step Conversion](#step-by-step-conversion)
- [Common Patterns](#common-patterns)
- [Limitations](#limitations)

---

## Assessment

Before converting, assess your app:

### Questions to Answer

1. **What does the app do?**
   - Data visualization → Webview panel
   - File editing → Custom editor
   - Project navigation → Tree view
   - Background tasks → Extension commands

2. **What web APIs does it use?**
   - DOM manipulation → Webview (works as-is)
   - localStorage → VS Code state API
   - fetch() → Keep for external APIs, or use VS Code fs for files
   - File System Access API → VS Code workspace.fs

3. **Does it need a server?**
   - Static app → Convert directly
   - API server → Keep external or embed in extension

4. **What framework?**
   - React/Vue/Svelte → Webview with Vite
   - Vanilla JS → Webview (simplest)
   - Next.js/Nuxt → Extract client-side code only

### Conversion Difficulty

| App Type | Difficulty | Notes |
|----------|------------|-------|
| Static React/Vue SPA | Easy | Direct webview conversion |
| Form-based app | Easy | Map inputs to VS Code APIs |
| File viewer/editor | Medium | Use custom editor pattern |
| App with routing | Medium | Map routes to panels/views |
| Real-time/WebSocket | Medium | Keep external connection |
| Full-stack app | Hard | Extract frontend, keep backend external |
| SSR app (Next.js) | Hard | Extract client code, lose SSR |

---

## Architecture Mapping

### Web App → Extension Structure

```
Web App                          VS Code Extension
─────────────────────────────────────────────────────────
index.html          →            Webview HTML (generated)
src/App.tsx         →            src/webview/App.tsx
src/components/     →            src/webview/components/
src/hooks/          →            src/webview/hooks/
src/api/            →            src/extension.ts (or keep external)
public/assets/      →            media/ or dist/webview/
package.json        →            package.json (merged)
```

### State Management

| Web Pattern | VS Code Pattern |
|-------------|-----------------|
| React useState | Same (webview) |
| Redux/Zustand | Same (webview) + sync to extension |
| localStorage | `context.globalState` / `context.workspaceState` |
| IndexedDB | `context.globalStorageUri` (file-based) |
| URL params | Command arguments or `context.workspaceState` |

### Routing

| Web Pattern | VS Code Pattern |
|-------------|-----------------|
| Single route | Single webview panel |
| Multiple routes | Multiple panels or tabs in webview |
| Nested routes | Sidebar (tree) + panel combination |
| Modal routes | VS Code quick pick or webview modal |

---

## API Mapping

### Browser APIs → VS Code APIs

```typescript
// localStorage
// Before:
localStorage.setItem('key', JSON.stringify(data));
const data = JSON.parse(localStorage.getItem('key') || '{}');

// After:
await context.globalState.update('key', data);
const data = context.globalState.get('key', {});
```

```typescript
// alert/confirm/prompt
// Before:
alert('Done!');
const confirmed = confirm('Are you sure?');
const name = prompt('Enter name:');

// After:
vscode.window.showInformationMessage('Done!');
const confirmed = await vscode.window.showWarningMessage('Are you sure?', 'Yes', 'No') === 'Yes';
const name = await vscode.window.showInputBox({ prompt: 'Enter name:' });
```

```typescript
// File reading
// Before:
const response = await fetch('/data/config.json');
const config = await response.json();

// After (workspace file):
const uri = vscode.Uri.joinPath(vscode.workspace.workspaceFolders![0].uri, 'config.json');
const content = await vscode.workspace.fs.readFile(uri);
const config = JSON.parse(new TextDecoder().decode(content));
```

```typescript
// File writing
// Before:
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
// ... download link

// After:
const uri = await vscode.window.showSaveDialog({ filters: { 'Text': ['txt'] } });
if (uri) {
  await vscode.workspace.fs.writeFile(uri, new TextEncoder().encode(content));
}
```

```typescript
// Clipboard
// Before:
await navigator.clipboard.writeText(text);

// After:
await vscode.env.clipboard.writeText(text);
```

```typescript
// Open external URL
// Before:
window.open('https://example.com', '_blank');

// After:
await vscode.env.openExternal(vscode.Uri.parse('https://example.com'));
```

### Keep These As-Is

These work unchanged in webviews:
- `fetch()` for external APIs
- `setTimeout`, `setInterval`
- `console.log` (appears in webview devtools)
- DOM APIs (`document.querySelector`, etc.)
- Canvas API
- Web Workers (with some setup)

---

## Step-by-Step Conversion

### Step 1: Set Up Extension Structure

```bash
# Create extension from template
cp -r assets/webview-react my-converted-app
cd my-converted-app
npm install
```

### Step 2: Copy App Code

```bash
# Copy your React components
cp -r ../my-web-app/src/components src/webview/
cp -r ../my-web-app/src/hooks src/webview/
cp ../my-web-app/src/App.tsx src/webview/
```

### Step 3: Create VS Code Bridge Hook

```typescript
// src/webview/hooks/useVsCodeBridge.ts
import { useCallback, useEffect, useState } from 'react';

declare function acquireVsCodeApi(): {
  postMessage(message: any): void;
  getState(): any;
  setState(state: any): void;
};

const vscode = acquireVsCodeApi();

export function useVsCodeState<T>(key: string, defaultValue: T) {
  const [value, setValue] = useState<T>(() => {
    const state = vscode.getState() || {};
    return state[key] ?? defaultValue;
  });

  const updateValue = useCallback((newValue: T) => {
    setValue(newValue);
    const state = vscode.getState() || {};
    vscode.setState({ ...state, [key]: newValue });
  }, [key]);

  return [value, updateValue] as const;
}

export function useExtensionCommand() {
  return useCallback((command: string, args?: any) => {
    vscode.postMessage({ type: 'command', command, args });
  }, []);
}

export function useExtensionMessage<T>(handler: (data: T) => void) {
  useEffect(() => {
    const listener = (event: MessageEvent) => handler(event.data);
    window.addEventListener('message', listener);
    return () => window.removeEventListener('message', listener);
  }, [handler]);
}
```

### Step 4: Update App to Use Bridge

```typescript
// src/webview/App.tsx
import { useVsCodeState, useExtensionCommand } from './hooks/useVsCodeBridge';

export function App() {
  // Replace localStorage with VS Code state
  const [settings, setSettings] = useVsCodeState('settings', { theme: 'dark' });

  // Replace direct file operations with commands
  const sendCommand = useExtensionCommand();

  const handleSave = () => {
    sendCommand('save', { data: settings });
  };

  const handleOpenFile = () => {
    sendCommand('openFile');
  };

  return (
    // Your existing JSX
  );
}
```

### Step 5: Handle Commands in Extension

```typescript
// src/extension.ts
panel.webview.onDidReceiveMessage(async (message) => {
  switch (message.type) {
    case 'command':
      switch (message.command) {
        case 'save':
          await saveData(message.args.data);
          break;
        case 'openFile':
          const uri = await vscode.window.showOpenDialog({});
          if (uri?.[0]) {
            const content = await vscode.workspace.fs.readFile(uri[0]);
            panel.webview.postMessage({
              type: 'fileContent',
              content: new TextDecoder().decode(content)
            });
          }
          break;
      }
      break;
  }
});
```

### Step 6: Update Imports and Dependencies

```json
// package.json - merge dependencies
{
  "dependencies": {
    // Keep your app's dependencies
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
    // Remove server-side deps (express, etc.)
  },
  "devDependencies": {
    "@types/vscode": "^1.85.0",
    // ... extension dev deps
  }
}
```

### Step 7: Update Build Configuration

Ensure Vite builds your webview code. See [build-config.md](build-config.md).

### Step 8: Test and Iterate

1. Press F5 to launch Extension Development Host
2. Open Command Palette → Your Extension Command
3. Test functionality, fix issues
4. Check browser console (Developer Tools) for webview errors

---

## Common Patterns

### Converting a Form App

```typescript
// Before (web)
function ContactForm() {
  const [name, setName] = useState(localStorage.getItem('name') || '');

  const handleSubmit = async () => {
    await fetch('/api/submit', { method: 'POST', body: JSON.stringify({ name }) });
    alert('Submitted!');
  };
}

// After (extension webview)
function ContactForm() {
  const [name, setName] = useVsCodeState('name', '');
  const sendCommand = useExtensionCommand();

  const handleSubmit = () => {
    sendCommand('submit', { name });
  };
}

// Extension handles the command
panel.webview.onDidReceiveMessage(async (msg) => {
  if (msg.command === 'submit') {
    await fetch('https://api.example.com/submit', {
      method: 'POST',
      body: JSON.stringify(msg.args)
    });
    vscode.window.showInformationMessage('Submitted!');
  }
});
```

### Converting a Dashboard

```typescript
// Before: Multiple pages with router
// After: Tabs within single webview

function Dashboard() {
  const [activeTab, setActiveTab] = useVsCodeState('activeTab', 'overview');

  return (
    <div>
      <nav>
        <button onClick={() => setActiveTab('overview')}>Overview</button>
        <button onClick={() => setActiveTab('analytics')}>Analytics</button>
        <button onClick={() => setActiveTab('settings')}>Settings</button>
      </nav>
      {activeTab === 'overview' && <Overview />}
      {activeTab === 'analytics' && <Analytics />}
      {activeTab === 'settings' && <Settings />}
    </div>
  );
}
```

---

## Limitations

### What Won't Work

1. **Server-Side Rendering** — Webviews are client-only
2. **Server API routes** — Use external API or VS Code commands
3. **Real-time databases (Firebase Realtime, Supabase)** — May work but test carefully
4. **OAuth redirects** — Use VS Code's authentication API instead
5. **Service Workers** — Not supported in webviews
6. **Web Push Notifications** — Use VS Code notifications instead

### Workarounds

| Web Feature | VS Code Alternative |
|-------------|---------------------|
| OAuth login | `vscode.authentication.getSession()` |
| Push notifications | `vscode.window.showInformationMessage()` |
| Background sync | Extension background tasks |
| PWA install | Publish to VS Marketplace |
| SEO | Not applicable |

### Performance Considerations

- Webviews are Chromium instances (memory overhead)
- Consider `retainContextWhenHidden: true` for frequently used panels
- Large datasets: process in extension, send summary to webview
- Heavy computation: use Web Workers in webview or process in extension

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.