Skip to content

Commit 916660a

Browse files
frostbournesbdcbartlett
authored andcommitted
[ENG-126] Migrate to Vite (RooCodeInc#1876)
* Initial webview vite migration * Make vite work * Fix test running * Enable HMR, disable vite chunking * Silence type checking errors * Vite doesn't use browserslist * get rid of breaking css flag * add doc to getHMRHtmlContent * Make it work * Changeset * Add IS_DEV to env definitions * Update tasks to include HMR * Update CSP image rules * prettier * reintroduce IS_DEV in env * add new deps to pkg lock --------- Co-authored-by: Dennis Bartlett <[email protected]>
1 parent 4506eb3 commit 916660a

31 files changed

+5283
-19494
lines changed

.changeset/lazy-insects-report.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Migrate webview from CRA to Vite

.github/dependabot.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ updates:
2828
patterns:
2929
- "*"
3030
ignore:
31-
# Ignore CRA and related packages that often have false positives
32-
- dependency-name: "react-scripts"
3331
- dependency-name: "@testing-library/*"
34-
- dependency-name: "web-vitals"
3532
- dependency-name: "*"
3633
update-types:
3734
- "version-update:semver-major"

.vscode/tasks.json

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"tasks": [
66
{
77
"label": "watch",
8-
"dependsOn": ["npm: build:webview", "npm: watch:tsc", "npm: watch:esbuild"],
8+
"dependsOn": ["npm: build:webview", "npm: dev:webview", "npm: watch:tsc", "npm: watch:esbuild"],
99
"presentation": {
1010
"reveal": "never"
1111
},
@@ -23,7 +23,42 @@
2323
"label": "npm: build:webview",
2424
"presentation": {
2525
"group": "watch",
26-
"reveal": "never"
26+
"reveal": "never",
27+
"close": true
28+
},
29+
"options": {
30+
"env": {
31+
"IS_DEV": "true"
32+
}
33+
}
34+
},
35+
{
36+
"type": "npm",
37+
"script": "dev:webview",
38+
"group": "build",
39+
"problemMatcher": [
40+
{
41+
"pattern": [
42+
{
43+
"regexp": ".",
44+
"file": 1,
45+
"location": 2,
46+
"message": 3
47+
}
48+
],
49+
"background": {
50+
"activeOnStart": true,
51+
"beginsPattern": ".",
52+
"endsPattern": "."
53+
}
54+
}
55+
],
56+
"isBackground": true,
57+
"label": "npm: dev:webview",
58+
"presentation": {
59+
"group": "watch",
60+
"reveal": "never",
61+
"close": true
2762
},
2863
"options": {
2964
"env": {
@@ -40,7 +75,8 @@
4075
"label": "npm: watch:esbuild",
4176
"presentation": {
4277
"group": "watch",
43-
"reveal": "never"
78+
"reveal": "never",
79+
"close": true
4480
}
4581
},
4682
{
@@ -52,7 +88,8 @@
5288
"label": "npm: watch:tsc",
5389
"presentation": {
5490
"group": "watch",
55-
"reveal": "never"
91+
"reveal": "never",
92+
"close": true
5693
}
5794
},
5895
{
@@ -70,6 +107,19 @@
70107
"label": "tasks: watch-tests",
71108
"dependsOn": ["npm: watch", "npm: watch-tests"],
72109
"problemMatcher": []
110+
},
111+
{
112+
"label": "stop",
113+
"command": "echo ${input:terminate}",
114+
"type": "shell"
115+
}
116+
],
117+
"inputs": [
118+
{
119+
"id": "terminate",
120+
"type": "command",
121+
"command": "workbench.action.tasks.terminate",
122+
"args": "terminateAll"
73123
}
74124
]
75125
}

.vscodeignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ demo.gif
2323
# Ignore all webview-ui files except the build directory (https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/frameworks/hello-world-react-cra/.vscodeignore)
2424
webview-ui/src/**
2525
webview-ui/public/**
26-
webview-ui/scripts/**
2726
webview-ui/index.html
2827
webview-ui/README.md
2928
webview-ui/package.json

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@
206206
"format:fix": "prettier . --write",
207207
"test": "vscode-test",
208208
"install:all": "npm install && cd webview-ui && npm install",
209-
"start:webview": "cd webview-ui && npm run start",
209+
"dev:webview": "cd webview-ui && npm run dev",
210210
"build:webview": "cd webview-ui && npm run build",
211211
"test:webview": "cd webview-ui && npm run test",
212212
"publish:marketplace": "vsce publish && ovsx publish",

src/core/webview/ClineProvider.ts

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
178178
return findLast(Array.from(this.activeInstances), (instance) => instance.view?.visible === true)
179179
}
180180

181-
resolveWebviewView(
182-
webviewView: vscode.WebviewView | vscode.WebviewPanel,
183-
//context: vscode.WebviewViewResolveContext<unknown>, used to recreate a deallocated webview, but we don't need this since we use retainContextWhenHidden
184-
//token: vscode.CancellationToken
185-
): void | Thenable<void> {
181+
async resolveWebviewView(webviewView: vscode.WebviewView | vscode.WebviewPanel) {
186182
this.outputChannel.appendLine("Resolving webview view")
187183
this.view = webviewView
188184

@@ -191,7 +187,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
191187
enableScripts: true,
192188
localResourceRoots: [this.context.extensionUri],
193189
}
194-
webviewView.webview.html = this.getHtmlContent(webviewView.webview)
190+
191+
webviewView.webview.html =
192+
this.context.extensionMode === vscode.ExtensionMode.Development
193+
? await this.getHMRHtmlContent(webviewView.webview)
194+
: this.getHtmlContent(webviewView.webview)
195195

196196
// Sets up an event listener to listen for messages passed from the webview view context
197197
// and executes code based on the message that is received
@@ -342,9 +342,9 @@ export class ClineProvider implements vscode.WebviewViewProvider {
342342
// then convert it to a uri we can use in the webview.
343343

344344
// The CSS file from the React build output
345-
const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "static", "css", "main.css"])
345+
const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.css"])
346346
// The JS file from the React build output
347-
const scriptUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "static", "js", "main.js"])
347+
const scriptUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.js"])
348348

349349
// The codicon font from the React build output
350350
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-codicons-sample/src/extension.ts
@@ -389,18 +389,92 @@ export class ClineProvider implements vscode.WebviewViewProvider {
389389
<meta name="theme-color" content="#000000">
390390
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; font-src ${webview.cspSource}; style-src ${webview.cspSource} 'unsafe-inline'; img-src ${webview.cspSource} https: data:; script-src 'nonce-${nonce}';">
391391
<link rel="stylesheet" type="text/css" href="${stylesUri}">
392-
<link href="${codiconsUri}" rel="stylesheet" />
392+
<link href="${codiconsUri}" rel="stylesheet" />
393393
<title>Cline</title>
394394
</head>
395395
<body>
396396
<noscript>You need to enable JavaScript to run this app.</noscript>
397397
<div id="root"></div>
398-
<script nonce="${nonce}" src="${scriptUri}"></script>
398+
<script type="module" nonce="${nonce}" src="${scriptUri}"></script>
399399
</body>
400400
</html>
401401
`
402402
}
403403

404+
/**
405+
* Connects to the local Vite dev server to allow HMR, with fallback to the bundled assets
406+
*
407+
* @param webview A reference to the extension webview
408+
* @returns A template string literal containing the HTML that should be
409+
* rendered within the webview panel
410+
*/
411+
private async getHMRHtmlContent(webview: vscode.Webview): Promise<string> {
412+
const localPort = 25463
413+
const localServerUrl = `localhost:${localPort}`
414+
415+
// Check if local dev server is running.
416+
try {
417+
await axios.get(`http://${localServerUrl}`)
418+
} catch (error) {
419+
vscode.window.showErrorMessage(
420+
"Cline: Local webview dev server is not running, HMR will not work. Please run 'npm run dev:webview' before launching the extension to enable HMR. Using bundled assets.",
421+
)
422+
423+
return this.getHtmlContent(webview)
424+
}
425+
426+
const nonce = getNonce()
427+
const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.css"])
428+
const codiconsUri = getUri(webview, this.context.extensionUri, [
429+
"node_modules",
430+
"@vscode",
431+
"codicons",
432+
"dist",
433+
"codicon.css",
434+
])
435+
436+
const scriptEntrypoint = "src/main.tsx"
437+
const scriptUri = `http://${localServerUrl}/${scriptEntrypoint}`
438+
439+
const reactRefresh = /*html*/ `
440+
<script nonce="${nonce}" type="module">
441+
import RefreshRuntime from "http://${localServerUrl}/@react-refresh"
442+
RefreshRuntime.injectIntoGlobalHook(window)
443+
window.$RefreshReg$ = () => {}
444+
window.$RefreshSig$ = () => (type) => type
445+
window.__vite_plugin_react_preamble_installed__ = true
446+
</script>
447+
`
448+
449+
const csp = [
450+
"default-src 'none'",
451+
`font-src ${webview.cspSource}`,
452+
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
453+
`img-src ${webview.cspSource} https: data:`,
454+
`script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
455+
`connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
456+
]
457+
458+
return /*html*/ `
459+
<!DOCTYPE html>
460+
<html lang="en">
461+
<head>
462+
<meta charset="utf-8">
463+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
464+
<meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}">
465+
<link rel="stylesheet" type="text/css" href="${stylesUri}">
466+
<link href="${codiconsUri}" rel="stylesheet" />
467+
<title>Roo Code</title>
468+
</head>
469+
<body>
470+
<div id="root"></div>
471+
${reactRefresh}
472+
<script type="module" src="${scriptUri}"></script>
473+
</body>
474+
</html>
475+
`
476+
}
477+
404478
/**
405479
* Sets up an event listener to listen for messages passed from the webview context and
406480
* executes code based on the message that is received.

webview-ui/.gitignore

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1-
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2-
3-
# dependencies
4-
/node_modules
5-
/.pnp
6-
.pnp.js
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
79

8-
# testing
9-
/coverage
10+
node_modules
11+
dist
12+
dist-ssr
13+
build
14+
*.local
15+
coverage
1016

11-
# production
12-
/build
17+
# Environment
18+
.env
19+
.env.*
20+
!.env.example
21+
!.env.test
1322

14-
# misc
23+
# Editor directories and files
24+
.vscode/*
25+
!.vscode/extensions.json
26+
.idea
1527
.DS_Store
16-
.env.local
17-
.env.development.local
18-
.env.test.local
19-
.env.production.local
20-
21-
npm-debug.log*
22-
yarn-debug.log*
23-
yarn-error.log*
28+
*.suo
29+
*.ntvs*
30+
*.njsproj
31+
*.sln
32+
*.sw?

webview-ui/eslint.config.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import js from "@eslint/js"
2+
import globals from "globals"
3+
import reactHooks from "eslint-plugin-react-hooks"
4+
import reactRefresh from "eslint-plugin-react-refresh"
5+
import tseslint from "typescript-eslint"
6+
7+
export default tseslint.config(
8+
{ ignores: ["build"] },
9+
{
10+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
11+
files: ["**/*.{ts,tsx}"],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
globals: globals.browser,
15+
},
16+
plugins: {
17+
"react-hooks": reactHooks,
18+
"react-refresh": reactRefresh,
19+
},
20+
rules: {
21+
...reactHooks.configs.recommended.rules,
22+
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
23+
"@typescript-eslint/no-unused-vars": "off",
24+
},
25+
},
26+
)

webview-ui/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Cline Webview</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>

webview-ui/matchMedia.js

Lines changed: 0 additions & 16 deletions
This file was deleted.

0 commit comments

Comments
 (0)