Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating the function is good!

if (props && 'className' in props) {
props['class'] = props['className']
delete props['className']
}
}