Skip to content

Commit c1e8594

Browse files
authored
Merge pull request #552 from coderoad/replace-jsdom
replace jsdom
2 parents d74bcb1 + 2678f5a commit c1e8594

File tree

5 files changed

+75
-195
lines changed

5 files changed

+75
-195
lines changed

.vscodeignore

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ vsc-extension-quickstart.md
99
## Modules
1010
node_modules/**
1111
!node_modules/fsevents/**
12-
!node_modules/jsdom/**
1312

1413
## TypeScript
1514
**/tsconfig.json

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
"package": "./scripts/package.sh",
3232
"storybook": "yarn --cwd web-app storybook",
3333
"test": "jest",
34-
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=build/extension.js --external:vscode --external:fsevents --external:jsdom --format=cjs --platform=node",
34+
"esbuild-base": "esbuild ./src/extension.ts --bundle --outfile=build/extension.js --external:vscode --external:fsevents --format=cjs --platform=node",
3535
"esbuild": "npm run esbuild-base -- --sourcemap",
3636
"esbuild-watch": "npm run esbuild-base -- --sourcemap --watch",
3737
"test-compile": "tsc -watch -p ./"
@@ -42,7 +42,6 @@
4242
"eslint": "7.32.0",
4343
"git-url-parse": "11.6.0",
4444
"jest": "27.3.1",
45-
"jsdom": "18.1.1",
4645
"node-fetch": "2.6.6",
4746
"semver": "7.3.5",
4847
"ts-jest": "27.0.7",

src/services/webview/render.ts

+69-45
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { JSDOM } from 'jsdom'
21
import * as path from 'path'
32
import * as vscode from 'vscode'
43
import { asyncReadFile } from '../node'
@@ -14,39 +13,64 @@ const getNonce = (): string => {
1413
return text
1514
}
1615

16+
/**
17+
* render
18+
* configures the index.html into a webview panel
19+
*
20+
* React + webpack generate a number of files that are injected on build time
21+
* and must be accommodated for the webview using a vscode:// path.
22+
*
23+
* Modified paths include
24+
* - /static/js/x.chunk.js
25+
* - /static/js/main.chunk.js
26+
* - runtime-main.js
27+
* - /static/css/x.chunk.css
28+
* - /static/css/main.chunk.css
29+
*
30+
* This function also:
31+
* - modifies the base href of the index.html to be the root of the workspace
32+
* - manages the CSP policy for all the loaded files
33+
*/
1734
async function render(panel: vscode.WebviewPanel, rootPath: string): Promise<void> {
35+
// generate vscode-resource build path uri
36+
const createUri = (_filePath: string): any => {
37+
const filePath = (_filePath.startsWith('vscode') ? _filePath.substr(16) : _filePath).replace('///', '\\')
38+
39+
// @ts-ignore
40+
return panel.webview.asWebviewUri(vscode.Uri.file(path.join(rootPath, filePath)))
41+
}
42+
1843
try {
1944
// load copied index.html from web app build
20-
const dom = await JSDOM.fromFile(path.join(rootPath, 'index.html'))
21-
const { document } = dom.window
45+
let html = await asyncReadFile(path.join(rootPath, 'index.html'), 'utf8')
2246

23-
// set base href
24-
const base: HTMLBaseElement = document.createElement('base')
25-
base.href = `${vscode.Uri.file(path.join(rootPath, 'build')).with({ scheme: 'vscode-resource' })}`
26-
27-
document.head.appendChild(base)
47+
// set base href at top of <head>
48+
const baseHref = `${vscode.Uri.file(path.join(rootPath, 'build')).with({ scheme: 'vscode-resource' })}`
49+
html = html.replace('<head>', `<head><base href="${baseHref}" />`)
2850

2951
// used for CSP
3052
const nonces: string[] = []
3153
const hashes: string[] = []
3254

33-
// generate vscode-resource build path uri
34-
const createUri = (_filePath: string): any => {
35-
const filePath = (_filePath.startsWith('vscode') ? _filePath.substr(16) : _filePath).replace('///', '\\')
36-
37-
// @ts-ignore
38-
return panel.webview.asWebviewUri(vscode.Uri.file(path.join(rootPath, filePath)))
55+
// fix paths for react static scripts to use vscode-resource paths
56+
var jsBundleChunkRegex = /\/static\/js\/[\d].[^"]*\.js/g
57+
var jsBundleChunk: RegExpExecArray | null = jsBundleChunkRegex.exec(html)
58+
if (jsBundleChunk) {
59+
const nonce: string = getNonce()
60+
nonces.push(nonce)
61+
const src = createUri(jsBundleChunk[0])
62+
// replace script src, add nonce
63+
html = html.replace(jsBundleChunk[0], `${src}" nonce="${nonce}`)
3964
}
4065

41-
// fix paths for scripts
42-
const scripts: HTMLScriptElement[] = Array.from(document.getElementsByTagName('script'))
43-
for (const script of scripts) {
44-
if (script.src) {
45-
const nonce: string = getNonce()
46-
nonces.push(nonce)
47-
script.nonce = nonce
48-
script.src = createUri(script.src)
49-
}
66+
var mainBundleChunkRegex = /\/static\/js\/main.[^"]*\.js/g
67+
var mainBundleChunk: RegExpExecArray | null = mainBundleChunkRegex.exec(html)
68+
if (mainBundleChunk) {
69+
const nonce: string = getNonce()
70+
nonces.push(nonce)
71+
const src = createUri(mainBundleChunk[0])
72+
// replace script src, add nonce
73+
html = html.replace(mainBundleChunk[0], `${src}" nonce="${nonce}`)
5074
}
5175

5276
// support additional CSP exemptions when CodeRoad is embedded
@@ -61,43 +85,45 @@ async function render(panel: vscode.WebviewPanel, rootPath: string): Promise<voi
6185
}
6286
}
6387

64-
// add run-time script from webpack
65-
const runTimeScript = document.createElement('script')
66-
runTimeScript.nonce = getNonce()
67-
nonces.push(runTimeScript.nonce)
68-
6988
// note: file cannot be imported or results in esbuild error. Easier to read it.
7089
let manifest
7190
try {
7291
const manifestPath = path.join(rootPath, 'asset-manifest.json')
73-
console.log(manifestPath)
7492
const manifestFile = await asyncReadFile(manifestPath, 'utf8')
7593
manifest = JSON.parse(manifestFile)
7694
} catch (e) {
7795
throw new Error('Failed to read manifest file')
7896
}
7997

80-
runTimeScript.src = createUri(manifest.files['runtime-main.js'])
81-
document.body.appendChild(runTimeScript)
98+
// add run-time script from webpack at top of <body>
99+
const runtimeNonce = getNonce()
100+
nonces.push(runtimeNonce)
101+
const runtimeSrc = createUri(manifest.files['runtime-main.js'])
102+
html = html.replace('<body>', `<body><script src="${runtimeSrc}" nonce="${runtimeNonce}"></script>`)
103+
104+
var cssBundleChunkRegex = /\/static\/css\/[\d].[^"]*\.css/g
105+
var cssBundleChunk: RegExpExecArray | null = cssBundleChunkRegex.exec(html)
106+
if (cssBundleChunk) {
107+
const href = createUri(cssBundleChunk[0])
108+
// replace script src, add nonce
109+
html = html.replace(cssBundleChunk[0], href)
110+
}
82111

83-
// fix paths for links
84-
const styles: HTMLLinkElement[] = Array.from(document.getElementsByTagName('link'))
85-
for (const style of styles) {
86-
if (style.href) {
87-
style.href = createUri(style.href)
88-
}
112+
var mainCssBundleChunkRegex = /\/static\/css\/main.[^"]*\.css/g
113+
var mainCssBundleChunk: RegExpExecArray | null = mainCssBundleChunkRegex.exec(html)
114+
if (mainCssBundleChunk) {
115+
const href = createUri(mainCssBundleChunk[0])
116+
// replace script src, add nonce
117+
html = html.replace(mainCssBundleChunk[0], href)
89118
}
90119

91120
// set CSP (content security policy) to grant permission to local files
92121
// while blocking unexpected malicious network requests
93-
const cspMeta: HTMLMetaElement = document.createElement('meta')
94-
cspMeta.httpEquiv = 'Content-Security-Policy'
95-
96122
const wrapInQuotes = (str: string) => `'${str}'`
97123
const nonceString = nonces.map((nonce: string) => wrapInQuotes(`nonce-${nonce}`)).join(' ')
98124
const hashString = hashes.map(wrapInQuotes).join(' ')
99125

100-
cspMeta.content =
126+
const cspMetaString =
101127
[
102128
`default-src 'self'`,
103129
`manifest-src ${hashString} 'self'`,
@@ -110,10 +136,8 @@ async function render(panel: vscode.WebviewPanel, rootPath: string): Promise<voi
110136
// @ts-ignore
111137
`style-src ${panel.webview.cspSource} https: 'self' 'unsafe-inline'`,
112138
].join('; ') + ';'
113-
document.head.appendChild(cspMeta)
114-
115-
// stringify dom
116-
const html = dom.serialize()
139+
// add CSP to end of <head>
140+
html = html.replace('</head>', `<meta http-equiv="Content-Security-Policy" content="${cspMetaString}" /></head>`)
117141

118142
// set view
119143
panel.webview.html = html

web-app/public/index.html

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<head>
4+
<!-- INJECT_BASE -->
45
<meta charset="utf-8" />
56
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
67
<meta name="theme-color" content="#000000" />
@@ -47,6 +48,7 @@
4748
}
4849
}
4950
</script>
51+
<!-- INJECT_CSP -->
5052
</head>
5153

5254
<body>
@@ -68,5 +70,6 @@ <h3 id="coderoad-message">
6870
To begin the development, run `npm start` or `yarn start`.
6971
To create a production bundle, use `npm run build` or `yarn build`.
7072
-->
73+
<!-- INJECT_SCRIPT -->
7174
</body>
7275
</html>

0 commit comments

Comments
 (0)