Skip to content

Commit

Permalink
feat(jsx): "className" is now an alias for "class" (#2146)
Browse files Browse the repository at this point in the history
* refactor(jsx/dom): make sure that even if props is undefiend, it does not cause an error.

* feat(jsx): "className" is now an alias for "class"

* chore: denoify
  • Loading branch information
usualoma committed Feb 4, 2024
1 parent a52cd0a commit cb9d6a8
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 4 deletions.
13 changes: 11 additions & 2 deletions deno_dist/jsx/dom/jsx-dev-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import type { Props } from '../index.ts'
import { normalizeIntrinsicElementProps } from '../utils.ts'

export const jsxDEV = (tag: string | Function, props: Props, key: string | undefined) => {
const children = 'children' in props ? props.children : []
delete props['children']
if (typeof tag === 'string') {
normalizeIntrinsicElementProps(props)
}
let children
if (props && 'children' in props) {
children = props.children
delete props['children']
} else {
children = []
}
return {
tag,
props,
Expand Down
2 changes: 2 additions & 0 deletions deno_dist/jsx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { StringBuffer, HtmlEscaped, HtmlEscapedString } from '../utils/html
import type { Context } from './context.ts'
import { globalContexts } from './context.ts'
import type { IntrinsicElements as IntrinsicElementsDefined } from './intrinsic-elements.ts'
import { normalizeIntrinsicElementProps } from './utils.ts'

export { ErrorBoundary } from './components.ts'
export { Suspense } from './streaming.ts'
Expand Down Expand Up @@ -265,6 +266,7 @@ export const jsxFn = (
if (typeof tag === 'function') {
return new JSXFunctionNode(tag, props, children)
} else {
normalizeIntrinsicElementProps(props)
return new JSXNode(tag, props, children)
}
}
Expand Down
6 changes: 6 additions & 0 deletions deno_dist/jsx/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const normalizeIntrinsicElementProps = (props: Record<string, unknown>): void => {
if (props && 'className' in props) {
props['class'] = props['className']
delete props['className']
}
}
23 changes: 23 additions & 0 deletions src/jsx/dom/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { JSDOM } from 'jsdom'
import type { FC } from '..'
// run tests by old style jsx default
// hono/jsx/jsx-runtime and hono/jsx/dom/jsx-runtime are tested in their respective settings
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Expand Down Expand Up @@ -471,6 +472,28 @@ describe('DOM', () => {
)
})

describe('className', () => {
it('should convert to class attribute for intrinsic elements', () => {
const App = <h1 className='h1'>Hello</h1>
render(App, root)
expect(root.innerHTML).toBe('<h1 class="h1">Hello</h1>')
})

it('should convert to class attribute for custom elements', () => {
const App = <custom-element className='h1'>Hello</custom-element>
render(App, root)
expect(root.innerHTML).toBe('<custom-element class="h1">Hello</custom-element>')
})

it('should not convert to class attribute for custom components', () => {
const App: FC<{ className: string }> = ({ className }) => (
<div data-class-name={className}>Hello</div>
)
render(<App className='h1' />, root)
expect(root.innerHTML).toBe('<div data-class-name="h1">Hello</div>')
})
})

it('memo', async () => {
let renderCount = 0
const Counter = ({ count }: { count: number }) => {
Expand Down
13 changes: 11 additions & 2 deletions src/jsx/dom/jsx-dev-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import type { Props } from '..'
import { normalizeIntrinsicElementProps } from '../utils'

export const jsxDEV = (tag: string | Function, props: Props, key: string | undefined) => {
const children = 'children' in props ? props.children : []
delete props['children']
if (typeof tag === 'string') {
normalizeIntrinsicElementProps(props)
}
let children
if (props && 'children' in props) {
children = props.children
delete props['children']
} else {
children = []
}
return {
tag,
props,
Expand Down
20 changes: 20 additions & 0 deletions src/jsx/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,26 @@ describe('render to string', () => {
})
})

describe('className', () => {
it('should convert to class attribute for intrinsic elements', () => {
const template = <h1 className='h1'>Hello</h1>
expect(template.toString()).toBe('<h1 class="h1">Hello</h1>')
})

it('should convert to class attribute for custom elements', () => {
const template = <custom-element className='h1'>Hello</custom-element>
expect(template.toString()).toBe('<custom-element class="h1">Hello</custom-element>')
})

it('should not convert to class attribute for custom components', () => {
const CustomComponent: FC<{ className: string }> = ({ className }) => (
<div data-class-name={className}>Hello</div>
)
const template = <CustomComponent className='h1' />
expect(template.toString()).toBe('<div data-class-name="h1">Hello</div>')
})
})

describe('memo', () => {
it('memoized', () => {
let counter = 0
Expand Down
2 changes: 2 additions & 0 deletions src/jsx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { StringBuffer, HtmlEscaped, HtmlEscapedString } from '../utils/html
import type { Context } from './context'
import { globalContexts } from './context'
import type { IntrinsicElements as IntrinsicElementsDefined } from './intrinsic-elements'
import { normalizeIntrinsicElementProps } from './utils'

export { ErrorBoundary } from './components'
export { Suspense } from './streaming'
Expand Down Expand Up @@ -265,6 +266,7 @@ export const jsxFn = (
if (typeof tag === 'function') {
return new JSXFunctionNode(tag, props, children)
} else {
normalizeIntrinsicElementProps(props)
return new JSXNode(tag, props, children)
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/jsx/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const normalizeIntrinsicElementProps = (props: Record<string, unknown>): void => {
if (props && 'className' in props) {
props['class'] = props['className']
delete props['className']
}
}

0 comments on commit cb9d6a8

Please sign in to comment.