Skip to content

Migrate extension webviews to Svelte 5#815

Open
nobody5050 wants to merge 23 commits intowpilibsuite:2027from
nobody5050:feat/convert-to-svelte
Open

Migrate extension webviews to Svelte 5#815
nobody5050 wants to merge 23 commits intowpilibsuite:2027from
nobody5050:feat/convert-to-svelte

Conversation

@nobody5050
Copy link
Copy Markdown

@nobody5050 nobody5050 commented Nov 9, 2025

This PR converts the webviews to a frontend framework, namely Svelte 5. All of the webviews are now built with rollup and share a lot more infrastructure, so i18n and similar should be easier to maintain. They also have the benefit of sharing components so the different wizards are easier to keep in sync.

Closes #55

Status:

  • Dependency View
  • Project Creator
  • Project Importer
  • Help
  • Riolog

- Help
- Project Creator
- Project Importer
- Dependency View
@github-actions github-actions bot added the 2027 label Nov 9, 2025
Comment thread .vscode/launch.json Outdated
@nobody5050 nobody5050 changed the title Convert webviews to Svelte Migrate extension webviews to Svelte 5 Dec 29, 2025
Comment thread vscode-wpilib/package-lock.json Outdated
@Gold856
Copy link
Copy Markdown
Member

Gold856 commented Feb 19, 2026

Per https://code.visualstudio.com/api/extension-guides/webview#passing-messages-from-a-webview-to-an-extension:

For security reasons, you must keep the VS Code API object private and make sure it is never leaked into the global scope.

Copy link
Copy Markdown
Member

@Gold856 Gold856 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may or may not review this more closely later, but just some broad details I'm noticing (and some of the recommendations may be wrong since I completed the Basic Svelte tutorial like 30 minutes ago):

  • Almost all props seem to be optional, but that doesn't actually seem to be the case in practice; they're either defaulted in the child component or initialized with a value in the parent. In fact, several type definitions have their optional declaration erased because of that. How many props can we change to not be optional?
  • Speaking of props, we're passing an insane number of props to several child components. Can some of this be replaced with global state in .svelte.ts files? Global state is kinda icky, but these webviews are small, so globals aren't used in very far away places.
  • Let's try to completely dump Webpack in this PR. I'm not interested in dealing with multiple build systems.
  • Can we make the config files use ES modules?
  • There's a lot of functions without JSDoc. Try to add some API docs to newly added shared functions/components.
  • Can we reduce the main.ts boilerplate by inlining as much as possible?

Comment thread vscode-wpilib/src/webviews/svelte/riolog/time.ts Outdated
{/if}
</div>
</div>
{:else}
Copy link
Copy Markdown
Member

@Gold856 Gold856 Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a seriously impressive amount of indentation (8 indents!) There has to be a way to simplify that...

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The alternative as best I can tell would need to make use of $derived spam and would be truly unreadable. At least this is readable. Open to suggestions for other ways to solve it.

Comment thread vscode-wpilib/rollup.webviews.config.mjs
Comment thread vscode-wpilib/resources/webviews/help.html Outdated
Comment thread vscode-wpilib/tsconfig.json Outdated
Comment thread vscode-wpilib/src/webviews/svelte/projectcreator/ProjectCreator.svelte Outdated
Comment thread vscode-wpilib/src/webviews/svelte/lib/i18n.ts
Comment thread vscode-wpilib/src/webviews/svelte/riolog/RioLog.svelte
Comment thread vscode-wpilib/rollup.config.js Outdated
Comment thread vscode-wpilib/resources/webviews/help.html Outdated
@nobody5050 nobody5050 requested a review from Gold856 April 14, 2026 05:26
Comment on lines +96 to +103
'\u001b[1m\u001b[36m=== WPILib RioLog Started ===\u001b[0m\n' +
'\u001b[32mWaiting for robot connection...\u001b[0m\n' +
'\u001b[33mTIPS:\u001b[0m\n' +
'• \u001b[0mUse \u001b[1mSet\u001b[0m button to change team number\n' +
'• \u001b[0mClick on errors/warnings to expand details\n' +
'• \u001b[0mUse search box to filter messages\n' +
'• \u001b[0mToggle auto-scrolling for viewing older logs\n' +
'• \u001b[0mSave logs to file for later analysis',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a template literal.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove commented out code.

}
});
});
});
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated and I think this is the same as a Promise.any?

const constantIps: string[] = [
'172.22.11.2',
//, '127.0.0.1',
'127.0.0.1',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment it out

Comment on lines +5 to +19
type DistHtmlOptions = {
webview: vscode.Webview;
extensionRoot: vscode.Uri;
distHtmlFileName: string;
extraCss?: vscode.Uri[];
appAttributes?: Record<string, string>;
};

type RewriteHtmlOptions = {
webview: vscode.Webview;
extensionRoot: vscode.Uri;
html: string;
extraCss?: vscode.Uri[];
appAttributes?: Record<string, string>;
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unify these and make the non-common fields a separate parameter at the call site.

Comment on lines +21 to +57
function insertBeforeHeadClose(html: string, insert: string): string {
if (!insert) {
return html;
}
if (html.includes('</head>')) {
return html.replace('</head>', `${insert}\n</head>`);
}
return `${insert}\n${html}`;
}

function patchAppAttributes(html: string, attributes: Record<string, string>): string {
if (!attributes || Object.keys(attributes).length === 0) {
return html;
}

// Prefer the known rollup template shape.
const appDivPattern = /<div\s+id="app"([^>]*)>/i;
const match = html.match(appDivPattern);
if (!match) {
return html;
}

const existingAttrs = match[1] ?? '';
const toInject = Object.entries(attributes)
.map(([key, value]) => ` ${key}="${escapeHtmlAttribute(value)}"`)
.join('');

return html.replace(appDivPattern, `<div id="app"${existingAttrs}${toInject}>`);
}

function escapeHtmlAttribute(value: string): string {
return value
.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The machinery needed for just the vendordep manager to be able to handle separate states feels just a bit too heavy, and I think it might actually be less code to just directly return the HTML for those separate states. It also removes an entire indentation level in the Svelte code (which was bothering me).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rollup files should be ignored.

"prettier": "3.5.3",
"terser-webpack-plugin": "^5.3.14",
"ts-loader": "^9.5.4",
"@rollup/plugin-terser": "^0.4.4",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to the right location.

Comment on lines +111 to +112
entryFileNames: '[name].js',
chunkFileNames: 'chunks/[name]-[hash].js',
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be specified?

} from './types';

const vscode = acquireVsCodeApi();
const t = createTranslator('projectcreator');
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh. The current translation infrastructure is riddled with tech debt. Keeping compat with a future l10n transition would mean all calls look like l10n.t.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants