Skip to content

Transform attribute names to proper casing #199

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

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
22 changes: 22 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const UNNAMED = [];

const VOID_ELEMENTS = /^(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/;

const DASHED_ATTRS = /^(acceptC|httpE|(clip|color|fill|font|glyph|marker|stop|stroke|text|vert)[A-Z])/;
const CAMEL_ATTRS = /^(isP|viewB)/;
const COLON_ATTRS = /^(xlink|xml|xmlns)[A-Z]/;

const CAPITAL_REGEXP = /([A-Z])/g;

const UNSAFE_NAME = /[\s\n\\/='"\0<>]/;

const noop = () => {};
Expand Down Expand Up @@ -281,6 +287,8 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
// <textarea value="a&b"> --> <textarea>a&amp;b</textarea>
propChildren = v;
} else if ((v || v === 0 || v === '') && typeof v !== 'function') {
name = getAttributeNameInHtmlCase(name);
Copy link
Member

Choose a reason for hiding this comment

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

Likely faster if this is done on L310:

-				s += ` ${name}="${encodeEntities(v)}"`;
+				s += ` ${getAttributeNameInHtmlCase(name)}="${encodeEntities(v)}"`;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Need the transformed name for the if (v === true || v === '') block


if (v === true || v === '') {
v = name;
// in non-xml mode, allow boolean attributes
Expand All @@ -298,6 +306,7 @@ function _renderToString(vnode, context, opts, inner, isSvgMode, selectValue) {
s += ` selected`;
}
}

s += ` ${name}="${encodeEntities(v)}"`;
}
}
Expand Down Expand Up @@ -426,6 +435,19 @@ function getFallbackComponentName(component) {
}
return name;
}

function getAttributeNameInHtmlCase(name) {
if (CAMEL_ATTRS.test(name)) return name;

if (DASHED_ATTRS.test(name))
return name.replace(CAPITAL_REGEXP, (w) => '-' + w.toLowerCase());

if (COLON_ATTRS.test(name))
return name.replace(CAPITAL_REGEXP, (w) => ':' + w.toLowerCase());

return name.toLowerCase();
}

renderToString.shallowRender = shallowRender;

export default renderToString;
Expand Down
36 changes: 34 additions & 2 deletions test/render.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,38 @@ describe('render', () => {
expect(rendered).to.equal(expected);
});

it('should decamelize attributes', () => {
let rendered = render(<img srcSet="foo.png, foo2.png 2x" />),
expected = `<img srcset="foo.png, foo2.png 2x" />`;

expect(rendered).to.equal(expected);
});

it('should decamelize bool attributes', () => {
let rendered = render(
<link rel="preconnect" href="https://foo.com" crossOrigin />
),
expected = `<link rel="preconnect" href="https://foo.com" crossorigin />`;

expect(rendered).to.equal(expected);
});

it('should dasherize certain attributes', () => {
let rendered = render(<meta httpEquiv="refresh" />),
expected = `<meta http-equiv="refresh" />`;

expect(rendered).to.equal(expected);
});

it('should colonize/dasherize certain attributes & leave certain attributes camelized', () => {
let rendered = render(
<svg xmlSpace="preserve" viewBox="0 0 10 10" fillRule="nonzero" />
),
expected = `<svg xml:space="preserve" viewBox="0 0 10 10" fill-rule="nonzero"></svg>`;

expect(rendered).to.equal(expected);
});

it('should include boolean aria-* attributes', () => {
let rendered = render(<div aria-hidden aria-whatever={false} />),
expected = `<div aria-hidden="true" aria-whatever="false"></div>`;
Expand Down Expand Up @@ -265,7 +297,7 @@ describe('render', () => {

it('should render SVG elements', () => {
let rendered = render(
<svg>
<svg viewBox="0 0 100 100">
<image xlinkHref="#" />
<foreignObject>
<div xlinkHref="#" />
Expand All @@ -277,7 +309,7 @@ describe('render', () => {
);

expect(rendered).to.equal(
`<svg><image xlink:href="#"></image><foreignObject><div xlinkHref="#"></div></foreignObject><g><image xlink:href="#"></image></g></svg>`
`<svg viewBox="0 0 100 100"><image xlink:href="#"></image><foreignObject><div xlink:href="#"></div></foreignObject><g><image xlink:href="#"></image></g></svg>`
);
});
});
Expand Down