From d1713fe4721b537e0bbf428c8433e1c703c5d318 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Tue, 31 Oct 2023 15:49:55 +0100 Subject: [PATCH] Finalize jsx precompile support --- jsx-runtime/src/index.js | 7 ++++- jsx-runtime/test/browser/jsx-runtime.test.js | 30 +++++++++++++++++--- src/index.d.ts | 6 ++++ 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/jsx-runtime/src/index.js b/jsx-runtime/src/index.js index d490c18450..054918f5b9 100644 --- a/jsx-runtime/src/index.js +++ b/jsx-runtime/src/index.js @@ -93,9 +93,14 @@ function jsxTemplate(templates, ...exprs) { * JSX transform * @param {string} name The attribute name * @param {*} value The attribute value - * @returns {string | null} + * @returns {string} */ function jsxAttr(name, value) { + if (options.attr) { + const result = options.attr(name, value); + if (typeof result === 'string') return result; + } + if (name === 'ref' || name === 'key') return ''; if ( diff --git a/jsx-runtime/test/browser/jsx-runtime.test.js b/jsx-runtime/test/browser/jsx-runtime.test.js index e758f37238..7d16370417 100644 --- a/jsx-runtime/test/browser/jsx-runtime.test.js +++ b/jsx-runtime/test/browser/jsx-runtime.test.js @@ -1,4 +1,4 @@ -import { Component, createElement, createRef } from 'preact'; +import { Component, createElement, createRef, options } from 'preact'; import { jsx, jsxs, @@ -110,14 +110,24 @@ describe('encodeEntities', () => { describe('precompiled JSX', () => { describe('jsxAttr', () => { + beforeEach(() => { + options.attr = undefined; + }); + + afterEach(() => { + options.attr = undefined; + }); + it('should render simple values', () => { expect(jsxAttr('foo', 'bar')).to.equal('foo="bar"'); - expect(jsxAttr('foo', "&<'")).to.equal('foo="&<\'"'); + }); - // Boolean attributes + it('should render boolean values', () => { expect(jsxAttr('foo', true)).to.equal('foo'); + expect(jsxAttr('foo', false)).to.equal(''); + }); - // Invalid values + it('should ignore invalid values', () => { expect(jsxAttr('foo', false)).to.equal(''); expect(jsxAttr('foo', null)).to.equal(''); expect(jsxAttr('foo', undefined)).to.equal(''); @@ -126,6 +136,18 @@ describe('precompiled JSX', () => { expect(jsxAttr('key', 'foo')).to.equal(''); expect(jsxAttr('ref', 'foo')).to.equal(''); }); + + it('should escape values', () => { + expect(jsxAttr('foo', "&<'")).to.equal('foo="&<\'"'); + }); + + it('should call options.attr()', () => { + options.attr = (name, value) => { + return `data-${name}="foo${value}"`; + }; + + expect(jsxAttr('foo', 'bar')).to.equal('data-foo="foobar"'); + }); }); describe('jsxTemplate', () => { diff --git a/src/index.d.ts b/src/index.d.ts index e603afdb2b..380721c8e3 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -349,6 +349,12 @@ export interface Options { _addHookName?(name: string | number): void; __suspenseDidResolve?(vnode: VNode, cb: () => void): void; // __canSuspenseResolve?(vnode: VNode, cb: () => void): void; + + /** + * Customize attribute serialization when a precompiled JSX transform + * is used. + */ + attr?(name: string, value: any): string | void; } export const options: Options;