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

Document the limitations / caveats #1

Closed
danielweck opened this issue Mar 23, 2021 · 3 comments
Closed

Document the limitations / caveats #1

danielweck opened this issue Mar 23, 2021 · 3 comments

Comments

@danielweck
Copy link
Member

Hello :)

@twind/cli is a utility that can greatly help in some cases, however in practice it can also:

  • yield "false positives" (i.e. detect TailwindCSS classes / selectors that are not actually used, due to broad string-based / regular expression search)
  • give false impressions (e.g. class names generated by Twind at runtime via css() apply() and style() cannot possibly be computed via static code analysis, so the CLI-generated stylesheet is likely incomplete in most realistic use-cases).

These known limitations / caveats are absolutely acceptable, but I think the scope of this CLI utility should be documented in the README (i.e. stating goals and non-goals) so that users don't get caught off-guard / waste their time when considering their options.

There are probably several different use-cases for this CLI utility, such as producing useful development-time information, or pre-populating static stylesheets in production builds. On this latter point, if there are indeed benefits in using a pre-rendered stylesheet in combination with the Twind runtime in a live HTML page, then perhaps the README should mention it? (based on my experience, it is my understanding that there is zero advantage in doing this, in fact in my view this would just add unnecessary bytes to the total size of prerendered resources, as the Twind runtime already contains all the non-trivial logic to produce class names and CSS rules just-in-time ... maybe I am not correct, and I am always happy to be proven wrong / to learn :)

Code reference:

const cleanCandidate = (candidate: string): string => {
// 1. remove leading class: (svelte)
return candidate.replace(/^class:/, '')
}
const COMMON_INVALID_CANDIDATES = new Set([
'!DOCTYPE',
'true',
'false',
'null',
'undefined',
'class',
'className',
'currentColor',
])
const removeInvalidCandidate = (candidate: string): boolean => {
return !(
COMMON_INVALID_CANDIDATES.has(candidate) ||
// Remove candiate if it matches the following rules
// - no lower case char
!/[a-z]/.test(candidate) ||
// - containing uppercase letters
// - non number fractions and decimals
// - ending with -, /, @, $, &
// - white space only
/[A-Z]|\D[/.]\D|[-/@$&]$|^\s*$/.test(candidate) ||
// Either of the following two must match
// support @sm:..., >sm:..., <sm:...
/^[@<>][^:]+:/.test(candidate) !=
// - starts with <:#.,;?\d[\]%/$&@_
// - v-*: (vue)
// - aria-*
// - url like
/^-?[<:#.,;?\d[\]%/$&@_]|^v-[^:]+:|^aria-|^https?:\/\/|^mailto:|^tel:/.test(candidate)
)
}
export const extractRulesFromString = (content: string): string[] => {
return (content.match(/[^>"'`\s(){}[\]=][^<>"'`\s(){}=]*[^<>"'`\s(){}=:#.,;?]/g) || [])
.map(cleanCandidate)
.filter(removeInvalidCandidate)
}

Example:

twind.html
=>

<div data-test="font-italic italic">test</div>

<script>
    tw(apply('font-bold'));

    tw(css({
	    ':root body': 'bg-yellow-100',
    }));

    const func = style({
        base: 'underline',
        variants: {
            state: {
                def: 'text-red-500',
                pressed: 'text-black',
            },
            outlined: {
                true: 'ring-4 ring-red-200',
            },
        },
        defaults: {
            state: 'def',
        },
        matches: [
            {
                state: 'rounded',
                outlined: true,
                use: 'border-blue-400',
            },
        ],
    });
    tw(func({ state: 'rounded', outlined: true }));
</script>

twind.config.js
=>

export default {
	hash: false,
	preflight: false,
};

npx @twind/cli ./**/*.html -b -c twind.config.js
=>

* {
  --tw-ring-inset:var(--tw-empty, );
  --tw-ring-offset-width:0px;
  --tw-ring-offset-color:#fff;
  --tw-ring-color:rgba(59,130,246,var(--tw-ring-opacity,0.5));
  --tw-ring-offset-shadow:0 0 transparent;
  --tw-ring-shadow:0 0 transparent;
}
.ring-4 {
  --tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow:
    var(--tw-ring-offset-shadow),
    var(--tw-ring-shadow),
    var(--tw-shadow,0 0 transparent);
}
.text-black {
  --tw-text-opacity:1;
  color: #000;
  color: rgba(0, 0, 0, var(--tw-text-opacity));
}
.text-red-500 {
  --tw-text-opacity:1;
  color: #ef4444;
  color: rgba(239, 68, 68, var(--tw-text-opacity));
}
.bg-yellow-100 {
  --tw-bg-opacity:1;
  background-color: #fef3c7;
  background-color: rgba(254, 243, 199, var(--tw-bg-opacity));
}
.border-blue-400 {
  --tw-border-opacity:1;
  border-color: #60a5fa;
  border-color: rgba(96, 165, 250, var(--tw-border-opacity));
}
.ring-red-200 {
  --tw-ring-opacity:1;
  --tw-ring-color:rgba(254,202,202,var(--tw-ring-opacity));
}
.font-bold {
  font-weight: 700;
}
.font-italic {
  font-style: italic;
}
.italic {
  font-style: italic;
}
.underline {
  -webkit-text-decoration: underline;
  text-decoration: underline;
}
.rounded {
  border-radius: 0.25rem;
}

Issues:

  • rounded is not actually a used class, it is just a "word" found in a string
  • :root body { --tw-bg-opacity:1; background-color: #fef3c7; background-color: rgba(254, 243, 199, var(--tw-bg-opacity)); } is missing from the generated stylesheet, which is completely understandable, as it would normally be generated by the Twind runtime via css(). Instead, the .bg-yellow-100 { } class CSS selector is generated, but this is in fact not used in the content (and therefore wastes bytes).
  • similar remark with css() and apply()

PS: it would be awesome if support for CSS Modules composes was available more broadly, wouldn't it :)
https://github.com/css-modules/css-modules#composition

@sastan
Copy link
Contributor

sastan commented Mar 23, 2021

The goal description is definitely missing. It was an exercise to show a tool similar to tailwindcss could be implemented on top of the existing twind code.

@sastan
Copy link
Contributor

sastan commented Mar 25, 2021

Added the limitations section. Thanks for the write up!

@danielweck
Copy link
Member Author

Fixed via bdf52a5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants