Skip to content

Commit

Permalink
perf(jsx/dom): improve performance (#3288)
Browse files Browse the repository at this point in the history
Fixes #3264

* fix(test): fix test name

* refactor(jsx/dom): assign type and ref to node object directly. `Object.defineProperties` is too slow

* fix(jsx/hooks): `useReducer` returns the same function object no matter how many times it is called.

* refactor(jsx/dom): remove redundant property "s", "shadow virtual dom children" is not used

* refactor(jsx/dom): use for-loop instead of recursion for findInsertBefore

* perf(jsx/dom): improve performance of `getEventSpec`

* perf(jsx/dom): improve performance of `toAttributeName`

* perf(jsx/dom): for the same event handler, do nothing.

* perf(jsx/dom): reduce `container.nodeName` access

* perf(jsx/dom): remove `skipProps` and compare `key` with `'children'` directly

* perf(jsx/dom): use `for-in` instead of `Object.entries`

* perf(jsx/dom): if `defaultProps` does not exist, do not create a new `props` object.

* perf(jsx/dom): if `callbacks` is empty, do not call `requestAnimationFrame`

* perf(jsx/dom): optimize `build` function

* perf(jsx/dom): skip `push` call if target list is empty.

* perf(jsx/dom): optimize `apply` function

* refactor(jsx/dom): remove unused type import

* refactor(jsx/dom): remove redundant code `applyNode`

* test(jsx/dom): add tests

* refactor(jsx/dom): tweaks `delete node.vR` timing
  • Loading branch information
usualoma authored Aug 19, 2024
1 parent e6459e7 commit 1854e24
Show file tree
Hide file tree
Showing 9 changed files with 272 additions and 156 deletions.
4 changes: 2 additions & 2 deletions src/jsx/dom/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Child } from '../base'
import { DOM_ERROR_HANDLER } from '../constants'
import type { Context } from '../context'
import { globalContexts } from '../context'
import { newJSXNode, setInternalTagFlag } from './utils'
import { setInternalTagFlag } from './utils'

export const createContextProviderFunction =
<T>(values: T[]): Function =>
Expand Down Expand Up @@ -33,7 +33,7 @@ export const createContextProviderFunction =
}),
props: {},
})
const res = newJSXNode({ tag: '', props })
const res = { tag: '', props, type: '' }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
;(res as any)[DOM_ERROR_HANDLER] = (err: unknown) => {
values.pop()
Expand Down
37 changes: 37 additions & 0 deletions src/jsx/dom/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,16 @@ describe('DOM', () => {
})
})

describe('dangerouslySetInnerHTML', () => {
it('string', () => {
const App = () => {
return <div dangerouslySetInnerHTML={{ __html: '<p>Hello</p>' }} />
}
render(<App />, root)
expect(root.innerHTML).toBe('<div><p>Hello</p></div>')
})
})

describe('Event', () => {
it('bubbling phase', async () => {
const clicked: string[] = []
Expand Down Expand Up @@ -884,6 +894,13 @@ describe('DOM', () => {
render(<App />, root)
expect(addEventListenerSpy).not.toHaveBeenCalled()
})

it('invalid event handler value', async () => {
const App = () => {
return <div onClick={1 as unknown as () => void}></div>
}
expect(() => render(<App />, root)).toThrow()
})
})

it('simple Counter', async () => {
Expand Down Expand Up @@ -1874,6 +1891,16 @@ describe('DOM', () => {
await Promise.resolve()
expect(root.innerHTML).toBe('<div><p>1</p></div>')
})

it('title', async () => {
const App = () => {
return <div>{createElement('title', {}, 'Hello')}</div>
}
const app = <App />
render(app, root)
expect(document.head.innerHTML).toBe('<title>Hello</title>')
expect(root.innerHTML).toBe('<div></div>')
})
})

describe('dom-specific createElement', () => {
Expand All @@ -1889,6 +1916,16 @@ describe('DOM', () => {
await Promise.resolve()
expect(root.innerHTML).toBe('<div><p>1</p></div>')
})

it('title', async () => {
const App = () => {
return <div>{createElementForDom('title', {}, 'Hello')}</div>
}
const app = <App />
render(app, root)
expect(document.head.innerHTML).toBe('<title>Hello</title>')
expect(root.innerHTML).toBe('<div></div>')
})
})

describe('cloneElement', () => {
Expand Down
2 changes: 1 addition & 1 deletion src/jsx/dom/intrinsic-element/components.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,7 @@ describe('intrinsic element', () => {
)
})

it('should be ordered by precedence attribute', () => {
it('should ignore precedence attribute', () => {
const App = () => {
return (
<div>
Expand Down
54 changes: 35 additions & 19 deletions src/jsx/dom/intrinsic-element/components.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { Props } from '../../base'
import type { FC, JSXNode, PropsWithChildren, RefObject } from '../../types'
import { newJSXNode } from '../utils'
import { createPortal, getNameSpaceContext } from '../render'
import type { PreserveNodeType } from '../render'
import { useContext } from '../../context'
Expand Down Expand Up @@ -58,10 +57,12 @@ const documentMetadataTag = (
supportBlocking: boolean
) => {
if (props?.itemProp) {
return newJSXNode({
return {
tag,
props,
})
type: tag,
ref: props.ref,
}
}

const head = document.head
Expand Down Expand Up @@ -192,13 +193,15 @@ const documentMetadataTag = (
}
}

const jsxNode = newJSXNode({
const jsxNode = {
tag,
type: tag,
props: {
...restProps,
ref,
},
}) as JSXNode & { e?: HTMLElement; p?: PreserveNodeType }
ref,
} as unknown as JSXNode & { e?: HTMLElement; p?: PreserveNodeType }

jsxNode.p = preserveNodeType // preserve for unmounting
if (element) {
Expand All @@ -215,30 +218,37 @@ export const title: FC<PropsWithChildren> = (props) => {
const nameSpaceContext = getNameSpaceContext()
const ns = nameSpaceContext && useContext(nameSpaceContext)
if (ns?.endsWith('svg')) {
return newJSXNode({
return {
tag: 'title',
props,
})
type: 'title',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ref: (props as any).ref,
} as unknown as JSXNode
}
return documentMetadataTag('title', props, undefined, false, false)
}

export const script: FC<PropsWithChildren<IntrinsicElements['script']>> = (props) => {
if (!props || ['src', 'async'].some((k) => !props[k])) {
return newJSXNode({
return {
tag: 'script',
props,
})
type: 'script',
ref: props.ref,
} as unknown as JSXNode
}
return documentMetadataTag('script', props, 1, false, true)
}

export const style: FC<PropsWithChildren<IntrinsicElements['style']>> = (props) => {
if (!props || !['href', 'precedence'].every((k) => k in props)) {
return newJSXNode({
return {
tag: 'style',
props,
})
type: 'style',
ref: props.ref,
} as unknown as JSXNode
}
props['data-href'] = props.href
delete props.href
Expand All @@ -251,10 +261,12 @@ export const link: FC<PropsWithChildren<IntrinsicElements['link']>> = (props) =>
['onLoad', 'onError'].some((k) => k in props) ||
(props.rel === 'stylesheet' && (!('precedence' in props) || 'disabled' in props))
) {
return newJSXNode({
return {
tag: 'link',
props,
})
type: 'link',
ref: props.ref,
} as unknown as JSXNode
}
return documentMetadataTag('link', props, 1, 'precedence' in props, true)
}
Expand Down Expand Up @@ -309,7 +321,7 @@ export const form: FC<

const [data, isDirty] = state
state[1] = false
return newJSXNode({
return {
tag: FormContext as unknown as Function,
props: {
value: {
Expand All @@ -318,17 +330,19 @@ export const form: FC<
method: data ? 'post' : null,
action: data ? action : null,
},
children: newJSXNode({
children: {
tag: 'form',
props: {
...restProps,
ref,
},
}),
type: 'form',
ref,
},
},
f: isDirty,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any) as any
} as any
}

const formActionableElement = (
Expand Down Expand Up @@ -357,11 +371,13 @@ const formActionableElement = (
})
}

return newJSXNode({
return {
tag,
props,
type: tag,
ref: props.ref,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) as any
} as any
}

export const input: FC<PropsWithChildren<IntrinsicElements['input']>> = (props) =>
Expand Down
14 changes: 8 additions & 6 deletions src/jsx/dom/jsx-dev-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@
*/

import type { JSXNode, Props } from '../base'
import { newJSXNode } from './utils'
import * as intrinsicElementTags from './intrinsic-element/components'

export const jsxDEV = (tag: string | Function, props: Props, key?: string): JSXNode => {
return newJSXNode({
tag:
(typeof tag === 'string' && intrinsicElementTags[tag as keyof typeof intrinsicElementTags]) ||
tag,
if (typeof tag === 'string' && intrinsicElementTags[tag as keyof typeof intrinsicElementTags]) {
tag = intrinsicElementTags[tag as keyof typeof intrinsicElementTags]
}
return {
tag,
type: tag,
props,
key,
})
ref: props.ref,
} as JSXNode
}

export const Fragment = (props: Record<string, unknown>): JSXNode => jsxDEV('', props, undefined)
Loading

0 comments on commit 1854e24

Please sign in to comment.