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

perf: optimize hot paths and reduce overhead for low-end devices #368

Merged
merged 80 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
4e2f278
perf: compare values ​​directly instead of includes
negezor Jul 12, 2024
a8eddd5
perf: compare first character via access index instead of startsWith
negezor Jul 12, 2024
955b33e
perf: use Set + has instead of an array with includes
negezor Jul 12, 2024
d241e9c
perf(shared): implement split once for fixKeyCase and resolveMetaKeyType
negezor Jul 12, 2024
2552521
feat(shared): introduce thenable helper
negezor Jul 13, 2024
5f7418d
perf(shared): use thenable in normalise for reduce async/await functions
negezor Jul 13, 2024
f845e8d
perf(dom): use promise chain instead of async function in debouncedRe…
negezor Jul 13, 2024
0b84990
chore(dom): remove async modifier for debouncedRenderDOMHead
negezor Jul 13, 2024
db93926
perf(shared): remove unnecessary array spread in tagDedupeKey
negezor Jul 13, 2024
933380a
refactor: undefined is allowed for spread object
negezor Jul 13, 2024
159ea79
perf(schema-org): avoid new array allocation in dedupeMerge
negezor Jul 13, 2024
e11e9c2
perf(dom): check empty class or style in foreach instead of allocatio…
negezor Jul 13, 2024
54b9efa
perf(dom): implement split once for style in trackCtx
negezor Jul 13, 2024
d753005
perf(dom): convert tag name to lowercase once in renderDOMHead
negezor Jul 13, 2024
0ee43de
refactor: compare with undefined without typeof in safe places
negezor Jul 13, 2024
598d8e6
test(shared): add benchmark for processTemplateParams
negezor Jul 14, 2024
a8a3f5a
perf(shared): move sub to top module in templateParams
negezor Jul 14, 2024
2846564
perf(shared): avoid unnecessary operations in processTemplateParams
negezor Jul 14, 2024
a683e20
perf(shared): use single replacer in processTemplateParams
negezor Jul 14, 2024
1c03cce
refactor(dom): move condition to else in renderDOMHead
negezor Jul 14, 2024
3b89662
Merge branch 'main' into perf
negezor Jul 14, 2024
f3aad3c
Merge branch 'unjs:main' into perf
negezor Jul 16, 2024
476a410
perf(shared): combine filter in normaliseStyleClassProps
negezor Jul 16, 2024
5158bb1
perf(shared): optimize normaliseEntryTags for handle tag promises
negezor Jul 16, 2024
598719d
refactor: use arrow function instead of regular anonymous
negezor Jul 16, 2024
1761dfb
perf(shared): cache allowed meta properties for tagDedupeKey
negezor Jul 16, 2024
067f9bd
perf(shared): use concurrency chain instead of Promise.all in normali…
negezor Jul 16, 2024
fde9f9c
perf(unhead): allocate once third party dedupe keys in dedupe plugin
negezor Jul 16, 2024
d345bf7
refactor(shared): remove second parameter in tagDedupeKey
negezor Jul 16, 2024
028585b
fix(shared): normalise should handle consistently
negezor Jul 17, 2024
b1caa72
perf(shared): reduce overhead from object.entries and map in normalis…
negezor Jul 17, 2024
fc32e5e
perf(shared): promise should be edge case in normaliseProps
negezor Jul 17, 2024
20ce4ae
chore(shared): remove export from nestedNormaliseProps
negezor Jul 17, 2024
0da77eb
perf(shared): promise should be edge case in normaliseEntryTags
negezor Jul 17, 2024
1088358
fix(shared): switch condition in for i in nestedNormaliseEntryTags
negezor Jul 17, 2024
78e4f0e
chore(shared): rename resolvedTags to tagPromises in nestedNormaliseE…
negezor Jul 17, 2024
afbbf93
perf(shared): reduce overhead by using thenable in normaliseProps
negezor Jul 17, 2024
5aa2c97
perf(shared): promise should be edge case in normaliseTag
negezor Jul 17, 2024
38efeb9
perf(shared): use for of instead of forEach in normaliseTag
negezor Jul 17, 2024
ee3d190
perf(unhead): move common props to top of module in dedupe plugin
negezor Jul 17, 2024
09a9d68
perf(unhead): use for in instead of object values in dedupe plugin
negezor Jul 17, 2024
5478958
perf(unhead): use for of instead of forEach in dedupe plugin
negezor Jul 17, 2024
0ea560f
perf(unhead): use for in instead of object entries in eventHandler pl…
negezor Jul 17, 2024
4a66828
perf(unhead): use slice & substring instead of replace in eventHandler
negezor Jul 17, 2024
3b3481b
perf(unhead): move filter into loop body in sort plugin
negezor Jul 17, 2024
1e64fd8
perf(unhead): use substring instead of replace in sort plugin
negezor Jul 17, 2024
a53e10f
perf(unhead): swap the loops in the sorting plugin
negezor Jul 17, 2024
95a5521
perf(shared): simplify complexity in tagWeight
negezor Jul 17, 2024
f90c77a
perf(unhead): combine sorts in sort plugin
negezor Jul 17, 2024
cb26835
perf(unhead): reduce complexity to search in templateParams plugin
negezor Jul 17, 2024
ce653f8
perf(unhead): move filter into loop body in templateParams plugin
negezor Jul 17, 2024
e6b5d1a
perf(unhead): find and remove once templateParams tag in templatePara…
negezor Jul 17, 2024
f120960
perf(unhead): improve contentAttrs handle in templateParams plugin
negezor Jul 17, 2024
cbae67e
perf(unhead): speed up payload plugin using one loop
negezor Jul 17, 2024
18bf288
perf(unhead): compute props keys after static in dedupe plugin
negezor Jul 17, 2024
b750c32
perf(unhead): use splice instead of delete index + filter in titleTem…
negezor Jul 17, 2024
667e15e
perf(unhead): first check hasProps in dedupe plugin
negezor Jul 17, 2024
e74bbc5
perf(unhead): handle classes & styles without loop in dedupe plugin
negezor Jul 17, 2024
0476089
perf(unhead): use object with null prototype in dedupe plugin
negezor Jul 17, 2024
d1ccd89
perf(unhead): use for of loop instead of map for patch entry
negezor Jul 17, 2024
e29952f
perf(dom): use for in loop for props instead of Object.entries in ren…
negezor Jul 17, 2024
9214fe7
perf(dom): use for in loop for handle _eventHandlers in renderDOMHead
negezor Jul 17, 2024
32851ff
perf(dom): clear side effects in for in loop in renderDOMHead
negezor Jul 17, 2024
4193b98
perf(dom): replace loop with a direct property check for textContent …
negezor Jul 17, 2024
f391c98
perf(dom): remove cast to array HTMLCollection in renderDOMHead
negezor Jul 17, 2024
fd0c261
perf(unhead): speed up hashTag using for in loop & early returns
negezor Jul 17, 2024
ec0f70f
perf(vue): reduce overhead from resolveUnrefHeadInput
negezor Jul 17, 2024
4dbbd59
perf(dom): use set to store taken dedupe keys
negezor Jul 17, 2024
c2aa41b
perf(ssr): use string concatenation in propsToString
negezor Jul 17, 2024
3c99210
perf(ssr): add space before attrs in propsToString
negezor Jul 18, 2024
9e4e271
perf(ssr): use string concatenation in ssrRenderTags
negezor Jul 18, 2024
0967e7a
perf(ssr): use object.assign instead of spread operator
negezor Jul 18, 2024
c476861
perf(shared): use for of instead of array map
negezor Jul 18, 2024
1548f16
perf(shared): check the static string first in fixKeyCase
negezor Jul 18, 2024
168c79a
perf(shared): use for in & for of loops in meta
negezor Jul 18, 2024
bd3038f
perf(schema-org): use for in loop in resolveNodeId
negezor Jul 18, 2024
c6e7c35
perf(schema-org): use for in loop in stripEmptyProperties
negezor Jul 18, 2024
36b1dc9
perf(unhead): delete tag._duped if exists in dedupe plugin
negezor Jul 18, 2024
a6da0f9
perf(unhead): remove loop for check third party dedupe keys in dedupe…
negezor Jul 18, 2024
4f530d8
perf(shared): reduce operations in eventHandler plugin
negezor Jul 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"export:sizes": "pnpm -r --parallel --filter=./packages/** run export:sizes",
"bump": "bumpp package.json packages/*/package.json --commit --push --tag",
"release": "pnpm build && pnpm bump && pnpm -r publish --no-git-checks",
"lint": "eslint . --fix"
"lint": "eslint . --fix",
"benchmark": "vitest bench"
},
"devDependencies": {
"@antfu/eslint-config": "^2.21.2",
Expand Down
2 changes: 1 addition & 1 deletion packages/addons/src/plugins/inferSeoMetaPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export interface InferSeoMetaPluginOptions {
const inputKey = entry.resolvedInput ? 'resolvedInput' : 'input'
const input = entry[inputKey]
const weight = (typeof input.titleTemplate === 'object' ? input.titleTemplate?.tagPriority : false) || entry.tagPriority || 100
if (typeof input.titleTemplate !== 'undefined' && weight <= lastWeight) {
if (input.titleTemplate !== undefined && weight <= lastWeight) {
titleTemplate = input.titleTemplate
lastWeight = weight
}
Expand Down
2 changes: 1 addition & 1 deletion packages/addons/src/unplugin/TreeshakeServerComposables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface TreeshakeServerComposablesOptions extends BaseTransformerTypes
}

export const TreeshakeServerComposables = createUnplugin<TreeshakeServerComposablesOptions, false>((options: TreeshakeServerComposablesOptions = {}) => {
options.enabled = typeof options.enabled !== 'undefined' ? options.enabled : true
options.enabled = options.enabled !== undefined ? options.enabled : true

return {
name: 'unhead:remove-server-composables',
Expand Down
4 changes: 2 additions & 2 deletions packages/addons/src/unplugin/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type { UnpluginOptions }

export default (options: UnpluginOptions = {}): Plugin[] => {
return [
TreeshakeServerComposables.vite({ filter: options.filter, sourcemap: options.sourcemap, ...options.treeshake || {} }),
UseSeoMetaTransform.vite({ filter: options.filter, sourcemap: options.sourcemap, ...options.transformSeoMeta || {} }),
TreeshakeServerComposables.vite({ filter: options.filter, sourcemap: options.sourcemap, ...options.treeshake }),
UseSeoMetaTransform.vite({ filter: options.filter, sourcemap: options.sourcemap, ...options.transformSeoMeta }),
]
}
12 changes: 7 additions & 5 deletions packages/dom/src/debounced.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export interface DebouncedRenderDomHeadOptions extends RenderDomHeadOptions {
/**
* Queue a debounced update of the DOM head.
*/
export async function debouncedRenderDOMHead<T extends Unhead<any>>(head: T, options: DebouncedRenderDomHeadOptions = {}) {
export function debouncedRenderDOMHead<T extends Unhead<any>>(head: T, options: DebouncedRenderDomHeadOptions = {}) {
const fn = options.delayFn || (fn => setTimeout(fn, 10))
return head._domUpdatePromise = head._domUpdatePromise || new Promise<void>(resolve => fn(async () => {
await renderDOMHead(head, options)
delete head._domUpdatePromise
resolve()
return head._domUpdatePromise = head._domUpdatePromise || new Promise<void>(resolve => fn(() => {
return renderDOMHead(head, options)
.then(() => {
delete head._domUpdatePromise
resolve()
})
}))
}
2 changes: 1 addition & 1 deletion packages/dom/src/domPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface DomPluginOptions extends RenderDomHeadOptions {
return {
mode: 'client',
hooks: {
'entries:updated': function (head) {
'entries:updated': (head) => {
// async load the renderDOMHead function
debouncedRenderDOMHead(head, options)
},
Expand Down
96 changes: 68 additions & 28 deletions packages/dom/src/renderDOMHead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function renderDOMHead<T extends Unhead<any>>(head: T, options: Ren
const tags = (await head.resolveTags())
.map(tag => <DomRenderTagContext> {
tag,
id: HasElementTags.includes(tag.tag) ? hashTag(tag) : tag.tag,
id: HasElementTags.has(tag.tag) ? hashTag(tag) : tag.tag,
shouldRender: true,
})
let state = head._dom as DomState
Expand All @@ -40,32 +40,43 @@ export async function renderDOMHead<T extends Unhead<any>>(head: T, options: Ren
state = {
elMap: { htmlAttrs: dom.documentElement, bodyAttrs: dom.body },
} as any as DomState

const takenDedupeKeys = new Set()

for (const key of ['body', 'head']) {
const children = dom[key as 'head' | 'body']?.children
const tags: HeadTag[] = []
for (const c of [...children].filter(c => HasElementTags.includes(c.tagName.toLowerCase()))) {
for (const c of children) {
const tag = c.tagName.toLowerCase() as HeadTag['tag']
if (!HasElementTags.has(tag)) {
continue
}
const t: HeadTag = {
tag: c.tagName.toLowerCase() as HeadTag['tag'],
tag,
props: await normaliseProps(
c.getAttributeNames()
.reduce((props, name) => ({ ...props, [name]: c.getAttribute(name) }), {}),
),
innerHTML: c.innerHTML,
}
// we need to account for the fact that duplicate tags may exist as some are supported, increment the dedupe key
const dedupeKey = tagDedupeKey(t)
let d = dedupeKey
let i = 1
let d = tagDedupeKey(t)
while (d && tags.find(t => t._d === d))
d = `${d}:${i++}`
t._d = d || undefined
while (d && takenDedupeKeys.has(d))
d = `${dedupeKey}:${i++}`
if (d) {
t._d = d
takenDedupeKeys.add(d)
}
tags.push(t)
state.elMap[c.getAttribute('data-hid') || hashTag(t)] = c
}
}
}

// presume all side effects are stale, we mark them as not stale if they're re-introduced
state.pendingSideEffects = { ...state.sideEffects || {} }
state.pendingSideEffects = { ...state.sideEffects }
state.sideEffects = {}

function track(id: string, scope: string, fn: () => void) {
Expand All @@ -78,52 +89,77 @@ export async function renderDOMHead<T extends Unhead<any>>(head: T, options: Ren
const isAttrTag = tag.tag.endsWith('Attrs')
state.elMap[id] = $el
if (!isAttrTag) {
;['textContent', 'innerHTML'].forEach((k) => {
// @ts-expect-error unkeyed
tag[k] && tag[k] !== $el[k] && ($el[k] = tag[k])
})
if (tag.textContent && tag.textContent !== $el.textContent) {
$el.textContent = tag.textContent
}
if (tag.innerHTML && tag.innerHTML !== $el.innerHTML) {
$el.innerHTML = tag.innerHTML
}
track(id, 'el', () => {
// the element may have been removed by a duplicate tag or something out of our control
state.elMap[id]?.remove()
delete state.elMap[id]
})
}
// we need to attach event listeners as they can have side effects such as onload
for (const [k, value] of Object.entries(tag._eventHandlers || {})) {
if ($el.getAttribute(`data-${k}`) !== '') {
// avoid overriding
(tag!.tag === 'bodyAttrs' ? dom!.defaultView! : $el).addEventListener(k.replace('on', ''), value.bind($el))
$el.setAttribute(`data-${k}`, '')
if (tag._eventHandlers) {
// we need to attach event listeners as they can have side effects such as onload
for (const k in tag._eventHandlers) {
if (!Object.prototype.hasOwnProperty.call(tag._eventHandlers, k)) {
continue
}

if ($el.getAttribute(`data-${k}`) !== '') {
// avoid overriding
(tag!.tag === 'bodyAttrs' ? dom!.defaultView! : $el).addEventListener(
// onload -> load
k.substring(2),
tag._eventHandlers[k].bind($el),
)
$el.setAttribute(`data-${k}`, '')
}
}
}
Object.entries(tag.props).forEach(([k, value]) => {
for (const k in tag.props) {
if (!Object.prototype.hasOwnProperty.call(tag.props, k)) {
continue
}
const value = tag.props[k]

const ck = `attr:${k}`
// class attributes have their own side effects to allow for merging
if (k === 'class') {
if (!value) {
continue
}
// if the user is providing an empty string, then it's removing the class
// the side effect clean up should remove it
for (const c of (value || '').split(' ').filter(Boolean)) {
for (const c of value.split(' ')) {
// always clear side effects
isAttrTag && track(id, `${ck}:${c}`, () => $el.classList.remove(c))
!$el.classList.contains(c) && $el.classList.add(c)
}
}
else if (k === 'style') {
if (!value) {
continue
}
// style attributes have their own side effects to allow for merging
for (const c of (value || '').split(';').filter(Boolean)) {
const [k, ...v] = c.split(':').map(s => s.trim())
for (const c of value.split(';')) {
const propIndex = c.indexOf(':')
const k = c.substring(0, propIndex).trim()
const v = c.substring(propIndex + 1).trim()
track(id, `${ck}:${k}`, () => {
($el as any as ElementCSSInlineStyle).style.removeProperty(k)
})
;($el as any as ElementCSSInlineStyle).style.setProperty(k, v.join(':'))
;($el as any as ElementCSSInlineStyle).style.setProperty(k, v)
}
}
else {
// attribute values get set directly
$el.getAttribute(k) !== value && $el.setAttribute(k, (value as string | boolean) === true ? '' : String(value))
isAttrTag && track(id, ck, () => $el.removeAttribute(k))
}
})
}
}

const pending: DomRenderTagContext[] = []
Expand All @@ -144,11 +180,13 @@ export async function renderDOMHead<T extends Unhead<any>>(head: T, options: Ren
continue
}
ctx.$el = ctx.$el || state.elMap[id]
if (ctx.$el)
if (ctx.$el) {
trackCtx(ctx)
else
}
else if (HasElementTags.has(tag.tag)) {
// tag does not exist, we need to render it (if it's an element tag)
HasElementTags.includes(tag.tag) && pending.push(ctx)
pending.push(ctx)
}
}
// 3. render tags which require a dom element to be created or requires scanning DOM to determine duplicate
for (const ctx of pending) {
Expand All @@ -168,7 +206,9 @@ export async function renderDOMHead<T extends Unhead<any>>(head: T, options: Ren
frag.bodyClose && dom.body.appendChild(frag.bodyClose)

// clear all side effects still pending
Object.values(state.pendingSideEffects).forEach(fn => fn())
for (const k in state.pendingSideEffects) {
state.pendingSideEffects[k]()
}
head._dom = state
head.dirty = false
await head.hooks.callHook('dom:rendered', { renders: tags })
Expand Down
17 changes: 12 additions & 5 deletions packages/schema-org/src/core/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,18 @@ export function resolveNodeId<T extends Thing>(node: T, ctx: SchemaOrgGraph, res
alias = type.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()
}
const hashNodeData: Record<string, any> = {}
Object.entries(node).forEach(([key, val]) => {
// remove runtime private fields
if (!key.startsWith('_'))
hashNodeData[key] = val
})
for (const key in node) {
if (key[0] === '_') {
continue
}

if (!Object.prototype.hasOwnProperty.call(node, key)) {
continue
}

hashNodeData[key] = node[key]
}

node['@id'] = prefixId(ctx.meta[prefix], `#/schema/${alias}/${node['@id'] || hashCode(JSON.stringify(hashNodeData))}`)
return node
}
Expand Down
2 changes: 1 addition & 1 deletion packages/schema-org/src/core/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function normaliseNodes(nodes: SchemaOrgNode[]) {
const nodeKey = resolveAsGraphKey(n['@id'] || hash(n)) as Id
const groupedKeys = groupBy(Object.keys(n), (key) => {
const val = n[key]
if (key.startsWith('_'))
if (key[0] === '_')
return 'ignored'
if (Array.isArray(val) || typeof val === 'object')
return 'relations'
Expand Down
6 changes: 3 additions & 3 deletions packages/schema-org/src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ export function SchemaOrgUnheadPlugin(config: MetaInput, meta: () => Partial<Met
return defineHeadPlugin(head => ({
key: 'schema-org',
hooks: {
'entries:resolve': function () {
'entries:resolve': () => {
graph = createSchemaOrgGraph()
},
'tag:normalise': async function ({ tag }) {
'tag:normalise': async ({ tag }) => {
if (tag.key === 'schema-org-graph') {
// this is a bit expensive, load in seperate chunk
const { loadResolver } = await import('./resolver')
Expand Down Expand Up @@ -75,7 +75,7 @@ export function SchemaOrgUnheadPlugin(config: MetaInput, meta: () => Partial<Met
delete tag.props.schemaOrg
}
},
'tags:resolve': async function (ctx) {
'tags:resolve': async (ctx) => {
// find the schema.org node, should be a single instance
for (const tag of ctx.tags) {
if (tag.tag === 'script' && tag.key === 'schema-org-graph') {
Expand Down
22 changes: 12 additions & 10 deletions packages/schema-org/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,17 @@ export function asArray(input: any) {
}

export function dedupeMerge<T extends Thing>(node: T, field: keyof T, value: any) {
const dedupeMerge: any[] = []
const input = asArray(node[field])
dedupeMerge.push(...input)
const data = new Set(dedupeMerge)
const data = new Set(asArray(node[field]))
data.add(value)
// @ts-expect-error untyped key
node[field] = [...data.values()].filter(Boolean)
node[field] = [...data].filter(Boolean)
}

export function prefixId(url: string, id: Id | string) {
// already prefixed
if (hasProtocol(id))
return id as Id
if (!id.startsWith('#'))
if (id[0] !== '#')
id = `#${id}`
return withBase(id, url) as Id
}
Expand Down Expand Up @@ -117,7 +114,7 @@ export function resolveDefaultType(node: Thing, defaultType: Arrayable<string>)

export function resolveWithBase(base: string, urlOrPath: string) {
// can't apply base if there's a protocol
if (!urlOrPath || hasProtocol(urlOrPath) || (!urlOrPath.startsWith('/') && !urlOrPath.startsWith('#')))
if (!urlOrPath || hasProtocol(urlOrPath) || ((urlOrPath[0] !== '/') && (urlOrPath[0] !== '#')))
return urlOrPath
return withBase(urlOrPath, base)
}
Expand All @@ -132,16 +129,21 @@ export function resolveAsGraphKey(key?: Id | string) {
* Removes attributes which have a null or undefined value
*/
export function stripEmptyProperties(obj: any) {
Object.keys(obj).forEach((k) => {
for (const k in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, k)) {
continue
}

if (obj[k] && typeof obj[k] === 'object') {
// avoid walking vue reactivity
if (obj[k].__v_isReadonly || obj[k].__v_isRef)
return
stripEmptyProperties(obj[k])
return
}
if (obj[k] === '' || obj[k] === null || typeof obj[k] === 'undefined')
if (obj[k] === '' || obj[k] === null || obj[k] === undefined)
delete obj[k]
})
}

return obj
}
2 changes: 1 addition & 1 deletion packages/schema-org/src/vue/runtime/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function ignoreKey(s: string) {
if (s.startsWith('aria-') || s.startsWith('data-'))
return false

return ['class', 'style'].includes(s)
return s === 'class' || s === 'style'
}

export function defineSchemaOrgComponent(name: string, defineFn: (input: any) => any): ReturnType<typeof defineComponent> {
Expand Down
Loading