mirror of
https://github.com/honojs/hono.git
synced 2024-11-21 18:18:57 +01:00
feat(css): add CSP nonce to hono/css related style and script tags
This commit is contained in:
parent
c8f6a866c0
commit
5f7e640929
@ -111,6 +111,29 @@ Deno.test('JSX: css', async () => {
|
||||
)
|
||||
})
|
||||
|
||||
Deno.test('JSX: css with CSP nonce', async () => {
|
||||
const className = css`
|
||||
color: red;
|
||||
`
|
||||
const html = (
|
||||
<html>
|
||||
<head>
|
||||
<Style nonce='1234' />
|
||||
</head>
|
||||
<body>
|
||||
<div class={className}></div>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
const awaitedHtml = await html
|
||||
const htmlEscapedString = 'callbacks' in awaitedHtml ? awaitedHtml : await awaitedHtml.toString()
|
||||
assertEquals(
|
||||
await resolveCallback(htmlEscapedString, HtmlEscapedCallbackPhase.Stringify, false, {}),
|
||||
'<html><head><style nonce="1234" id="hono-css">.css-3142110215{color:red}</style></head><body><div class="css-3142110215"></div></body></html>'
|
||||
)
|
||||
})
|
||||
|
||||
Deno.test('JSX: normalize key', async () => {
|
||||
const className = <div className='foo'></div>
|
||||
const htmlFor = <div htmlFor='foo'></div>
|
||||
|
@ -488,6 +488,21 @@ export const renderTest = (
|
||||
'<style id="hono-css">.css-478287868{padding:0}</style><h1 class="css-478287868">Hello!</h1>'
|
||||
)
|
||||
})
|
||||
|
||||
it('Should render CSS styles with CSP nonce', async () => {
|
||||
const headerClass = css`
|
||||
background-color: blue;
|
||||
`
|
||||
const template = (
|
||||
<>
|
||||
<Style nonce='1234' />
|
||||
<h1 class={headerClass}>Hello!</h1>
|
||||
</>
|
||||
)
|
||||
expect(await toString(template)).toBe(
|
||||
'<style id="hono-css" nonce="1234">.css-2458908649{background-color:blue}</style><h1 class="css-2458908649">Hello!</h1>'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
/** @jsxImportSource ../../jsx */
|
||||
import { Hono } from '../../'
|
||||
import { html } from '../../helper/html'
|
||||
import { isValidElement } from '../../jsx'
|
||||
import type { JSXNode } from '../../jsx'
|
||||
import { isValidElement } from '../../jsx'
|
||||
import { Suspense, renderToReadableStream } from '../../jsx/streaming'
|
||||
import type { HtmlEscapedString } from '../../utils/html'
|
||||
import { HtmlEscapedCallbackPhase, resolveCallback } from '../../utils/html'
|
||||
@ -58,6 +58,18 @@ describe('CSS Helper', () => {
|
||||
<h1 class="css-2458908649">Hello!</h1>`
|
||||
)
|
||||
})
|
||||
|
||||
it('Should render CSS styles with `html` tag function and CSP nonce', async () => {
|
||||
const headerClass = css`
|
||||
background-color: blue;
|
||||
`
|
||||
const template = html`${Style({ nonce: '1234' })}
|
||||
<h1 class="${headerClass}">Hello!</h1>`
|
||||
expect(await toString(template)).toBe(
|
||||
`<style id="hono-css" nonce="1234">.css-2458908649{background-color:blue}</style>
|
||||
<h1 class="css-2458908649">Hello!</h1>`
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('cx()', () => {
|
||||
|
@ -50,7 +50,7 @@ interface ViewTransitionType {
|
||||
}
|
||||
|
||||
interface StyleType {
|
||||
(args?: { children?: Promise<string> }): HtmlEscapedString
|
||||
(args?: { children?: Promise<string>; nonce?: string }): HtmlEscapedString
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,9 +88,12 @@ export const createCssContext = ({ id }: { id: Readonly<string> }): DefaultConte
|
||||
return
|
||||
}
|
||||
|
||||
const appendStyleScript = `<script>document.querySelector('#${id}').textContent+=${JSON.stringify(
|
||||
stylesStr
|
||||
)}</script>`
|
||||
const styleNonce = (context as any)?.style?.nonce
|
||||
|
||||
const appendStyleScript = `<script${
|
||||
styleNonce ? ` nonce="${styleNonce}"` : ''
|
||||
}>document.querySelector('#${id}').textContent+=${JSON.stringify(stylesStr)}</script>`
|
||||
|
||||
if (buffer) {
|
||||
buffer[0] = `${appendStyleScript}${buffer[0]}`
|
||||
return
|
||||
@ -156,10 +159,36 @@ export const createCssContext = ({ id }: { id: Readonly<string> }): DefaultConte
|
||||
return newCssClassNameObject(viewTransitionCommon(strings as any, values))
|
||||
}) as ViewTransitionType
|
||||
|
||||
const Style: StyleType = ({ children } = {}) =>
|
||||
children
|
||||
? raw(`<style id="${id}">${(children as unknown as CssClassName)[STYLE_STRING]}</style>`)
|
||||
: raw(`<style id="${id}"></style>`)
|
||||
const Style: StyleType = ({ children, nonce } = {}) => {
|
||||
const styleTag = children
|
||||
? raw(
|
||||
`<style id="${id}"${nonce ? ` nonce="${nonce}"` : ''}>${
|
||||
(children as unknown as CssClassName)[STYLE_STRING]
|
||||
}</style>`
|
||||
)
|
||||
: raw(`<style id="${id}"${nonce ? ` nonce="${nonce}"` : ''}></style>`)
|
||||
|
||||
;(styleTag as any).nonce = nonce
|
||||
|
||||
const storeNonce: HtmlEscapedCallback = ({ context }) => {
|
||||
if (!nonce) {
|
||||
return
|
||||
}
|
||||
if (!(context as any)?.style) {
|
||||
;(context as any).style = {}
|
||||
}
|
||||
;(context as any).style.nonce = nonce
|
||||
return Promise.resolve(nonce)
|
||||
}
|
||||
|
||||
if (!styleTag.callbacks) {
|
||||
styleTag.callbacks = []
|
||||
}
|
||||
styleTag.callbacks.push(storeNonce)
|
||||
|
||||
return styleTag
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
;(Style as any)[DOM_RENDERER] = StyleRenderToDom
|
||||
|
||||
|
@ -52,6 +52,18 @@ describe('Style and css for jsx/dom', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('<Style nonce="1234" />', async () => {
|
||||
const App = () => {
|
||||
return (
|
||||
<div>
|
||||
<Style nonce='1234' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
render(<App />, root)
|
||||
expect(root.innerHTML).toBe('<div><style id="hono-css" nonce="1234"></style></div>')
|
||||
})
|
||||
|
||||
it('<Style>{css`global`}</Style>', async () => {
|
||||
const App = () => {
|
||||
return (
|
||||
|
@ -120,11 +120,12 @@ export const createCssJsxDomObjects: CreateCssJsxDomObjectsType = ({ id }) => {
|
||||
},
|
||||
}
|
||||
|
||||
const Style: FC<PropsWithChildren<void>> = ({ children }) =>
|
||||
const Style: FC<PropsWithChildren<{ nonce?: string }>> = ({ children, nonce }) =>
|
||||
({
|
||||
tag: 'style',
|
||||
props: {
|
||||
id,
|
||||
nonce,
|
||||
children:
|
||||
children &&
|
||||
(Array.isArray(children) ? children : [children]).map(
|
||||
|
Loading…
Reference in New Issue
Block a user