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 = `import("/@vite/client")` return injectStringToResponse(response, script) } return response