unjs / unhead

Unhead is the any-framework document head manager built for performance and delightful developer experience.
https://unhead.unjs.io
MIT License
548 stars 36 forks source link

Allow data-* attributes with empty value or without value #348

Open Jak-Ch-ll opened 2 months ago

Jak-Ch-ll commented 2 months ago

Describe the feature

Currently data-* attributes that are set to true or "" are rendered as data-foo="true". This seems to be a deliberate decision made in #107.

The issue

While data-foo and data-foo="" can be used interchangeably, data-foo="true" is not the same in all situations.

Real world example

I stumbled onto that issue while working with the UI5 Web Component Library. Specifically we want to customize the default font loading which requires us to add a style tag in the following form: <style data-ui5-font-face="">. I checked through the libraries code and they are using the following helper to check if the stylesheet exists (shortened for brevity, full code here):

const hasStyle = (name: string, value = ""): boolean => {
    return !!document.querySelector(`head>style[${name}="${value}"]`);
}

This boils down to an attribute selector that looks like [data-foo=""], which selects both data-foo and data-foo="", but not data-foo="true".

Workaround

We are working with Nuxt, so Unhead is configured via the nuxt.config.ts. To keep the workaround close by, I added a script tag that replaces the "true" with "", but this feels kinda dirty, so I'm open to suggestions in that regard.

export default defineNuxtConfig({
  app: {
    head: {
      style: [{ 'data-ui5-font-face': '' }],
      script: [{
        innerHTML: 'document.querySelector("[data-ui5-font-face]").setAttribute("data-ui5-font-face", "");',
      }]
    }
  }
}]

Proposed solution

Minimal

At a minimum there should be an option to get to at least one version without the "true", for example setting the empty string could result in data-foo="".

Extended

Why not give the user full control about the result. This could look something like this:

  1. Boolean { 'data-foo': true } results in data-foo
  2. Empty string { 'data-foo': "" } results in data-foo=""
  3. Boolean as string { 'data-foo': "true" } results in data-foo="true"

I feel like this is what is most congruent with user expectation (including #107) and would allow to deal with any weird behaviour of other libraries. For consistency this could even be extended to all attributes, not just data-*.

Notes

From my brief check through the code, changes should be contained witihin the normaliseProps function, so it shouldn't be too hard. But since this would be a breaking change, I think it's important to first discuss how to proceed.

Additional information

harlan-zw commented 2 months ago

Hey, thanks for the detailed issue. You are right and I will try and sort this out when I have a chance.

PRs are of course welcome I the mean time :)