diff --git a/.changeset/rich-apes-beg.md b/.changeset/rich-apes-beg.md
new file mode 100644
index 0000000..98ad44e
--- /dev/null
+++ b/.changeset/rich-apes-beg.md
@@ -0,0 +1,5 @@
+---
+'@hono/vite-dev-server': minor
+---
+
+feat: add CSP support to vite's `injectClientScript` option
diff --git a/packages/dev-server/e2e-bun/e2e.test.ts b/packages/dev-server/e2e-bun/e2e.test.ts
index 9cc1c1a..6e13574 100644
--- a/packages/dev-server/e2e-bun/e2e.test.ts
+++ b/packages/dev-server/e2e-bun/e2e.test.ts
@@ -17,6 +17,22 @@ test('Should contain an injected script tag', async ({ page }) => {
const lastScriptTag = await page.$('script:last-of-type')
expect(lastScriptTag).not.toBeNull()
+ const nonce = await lastScriptTag?.getAttribute('nonce')
+ expect(nonce).toBeNull()
+
+ const content = await lastScriptTag?.textContent()
+ expect(content).toBe('import("/@vite/client")')
+})
+
+test('Should contain an injected script tag with a nonce', async ({ page }) => {
+ await page.goto('/with-nonce')
+
+ const lastScriptTag = await page.$('script:last-of-type')
+ expect(lastScriptTag).not.toBeNull()
+
+ const nonce = await lastScriptTag?.getAttribute('nonce')
+ expect(nonce).not.toBeNull()
+
const content = await lastScriptTag?.textContent()
expect(content).toBe('import("/@vite/client")')
})
diff --git a/packages/dev-server/e2e-bun/mock/app.ts b/packages/dev-server/e2e-bun/mock/app.ts
index 4c0c421..f36d83b 100644
--- a/packages/dev-server/e2e-bun/mock/app.ts
+++ b/packages/dev-server/e2e-bun/mock/app.ts
@@ -8,6 +8,11 @@ app.get('/', (c) => {
return c.html('
Hello Vite!
')
})
+app.get('/with-nonce', (c) => {
+ c.header('content-security-policy', 'script-src-elem \'self\' \'nonce-ZMuLoN/taD7JZTUXfl5yvQ==\';')
+ return c.html('Hello Vite!
')
+})
+
app.get('/file.ts', (c) => {
return c.text('console.log("exclude me!")')
})
diff --git a/packages/dev-server/e2e/e2e.test.ts b/packages/dev-server/e2e/e2e.test.ts
index 2ead110..af66363 100644
--- a/packages/dev-server/e2e/e2e.test.ts
+++ b/packages/dev-server/e2e/e2e.test.ts
@@ -17,6 +17,22 @@ test('Should contain an injected script tag', async ({ page }) => {
const lastScriptTag = await page.$('script:last-of-type')
expect(lastScriptTag).not.toBeNull()
+ const nonce = await lastScriptTag?.getAttribute('nonce')
+ expect(nonce).toBeNull()
+
+ const content = await lastScriptTag?.textContent()
+ expect(content).toBe('import("/@vite/client")')
+})
+
+test('Should contain an injected script tag with a nonce', async ({ page }) => {
+ await page.goto('/with-nonce')
+
+ const lastScriptTag = await page.$('script:last-of-type')
+ expect(lastScriptTag).not.toBeNull()
+
+ const nonce = await lastScriptTag?.getAttribute('nonce')
+ expect(nonce).not.toBeNull()
+
const content = await lastScriptTag?.textContent()
expect(content).toBe('import("/@vite/client")')
})
diff --git a/packages/dev-server/e2e/mock/worker.ts b/packages/dev-server/e2e/mock/worker.ts
index 56044cc..ac859b4 100644
--- a/packages/dev-server/e2e/mock/worker.ts
+++ b/packages/dev-server/e2e/mock/worker.ts
@@ -12,6 +12,11 @@ app.get('/', (c) => {
return c.html('Hello Vite!
')
})
+app.get('/with-nonce', (c) => {
+ c.header('content-security-policy', 'script-src-elem \'self\' \'nonce-ZMuLoN/taD7JZTUXfl5yvQ==\';')
+ return c.html('Hello Vite!
')
+})
+
app.get('/name', (c) => c.html(`My name is ${c.env.NAME}
`))
app.get('/wait-until', (c) => {
diff --git a/packages/dev-server/src/dev-server.ts b/packages/dev-server/src/dev-server.ts
index d90ec26..73935dd 100644
--- a/packages/dev-server/src/dev-server.ts
+++ b/packages/dev-server/src/dev-server.ts
@@ -164,7 +164,10 @@ export function devServer(options?: DevServerOptions): VitePlugin {
options?.injectClientScript !== false &&
response.headers.get('content-type')?.match(/^text\/html/)
) {
- const script = ''
+ const nonce = response.headers
+ .get('content-security-policy')
+ ?.match(/'nonce-([^']+)'/)?.[1]
+ const script = ``
return injectStringToResponse(response, script)
}
return response