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

Remove prefix as a function #5829

Merged
merged 4 commits into from
Oct 19, 2021
Merged

Remove prefix as a function #5829

merged 4 commits into from
Oct 19, 2021

Conversation

RobinMalfait
Copy link
Contributor

@RobinMalfait RobinMalfait commented Oct 19, 2021

While debugging issue #5765 we noticed that there are bigger underlying fundamental problems with the fact that prefix
can be a function a JIT world. Instead we only allow a string prefix, so you either prefix everything or nothing.


Let's dive in a little deeper why this is a problem.

For starters, the prefix function gets a selector, like .text-center, then in the function you can return the prefix
based on this information. In AOT mode (the default mode in a pre-JIT world), we generated all the utilities first, this
allowed us to call this function while generating the utilities.

However in JIT mode, we start from the content files. This means that we don't have anything to run through that prefix
function (yet).

So, what do we do currently with the prefix, because it seems to work without a function? What we do is when we call
addUtilities (think of it as "static"/"hardcoded" utilities) then we already know the selector and the prefix, so we
can properly prefix this identifier and store it in the context. Perfect.

However, when we call matchUtilities (think of it as plugins that read from your config and allow for arbitrary
values), then we only know a "base" part, for example bg. This means that we have a backwards compatibility problem
for people that had something like this (hopefully nobody did this, but I wouldn't be surprised with our user base):

prefix(selector) {
  if (selector.startsWith('.bg-red')) return 'PRIMARY-';
  if (selector.startsWith('.bg-blue')) return 'SECONDARY-';
  return '';
}

What will happen in this case is that we will call prefix() with .bg, because this is what we defined in
matchUtilities and this is the only thing we know upfront in JIT mode land.

This also means that we can't really differentiate between arbitrary values for example PREFIXA-bg-[#0088cc] vs
PREFIXB-bg-[#eee]. We probably don't need to be able to differentiate between those, but if you do want this
behaviour, then we can't really support this.

Until this point, we only registered the plugins with a potential prefix or not. But there is an even more complex part.
The complex part is in resolveMatchedPlugins (./src/lib/generateRules.js). This is the place where we need to find the
correct plugins for the current candidate.

Imagine your candidate looks like this tw-text-center, we then can do a direct lookup because text-center is an
addUtilities plugin, and therefore tw-text-center will exist because we already prefixed it upfront. The annoying
part is if you have something like tw-bg-red-500, that won't exist in the cache, tw-bg will exist because this is a
matchUtilities plugin. At this point, the only way to look this value up is by parsing the candidate piece by piece
like tw-bg-red-500, then tw-bg-red, then tw-bg to find the corresponding plugin. This already works, and this is
what we do currently.

However, we recently wanted to support proper negative values, e.g.: -tw-ml-3 and tw--ml-3 at the same time. The
stored value in the lookup table is tw-ml. But we need to find a way to determine the actual prefix (but we don't know
the selector) so that we can remove it from the selector in order to detect the negative value as well. -tw-ml-3 and
tw--ml-3 both should look for the tw-ml plugin, and know that it is negative. In case of -tw-ml-3, it is
relatively straight forward, it starts with a - so therefore we know that we only have to process tw-ml-3. Now we
can start looking up tw-ml-3, then tw-ml not too bad.

In case of tw--ml-3, it doesn't start with -, so we have to do a bit more parsing. If we use the same algorithm as
above, then we would look for tw--ml-3, then tw--ml, then tw- maybe?

We won't find a match in this case. Remember we don't know what the prefix is because we can only know it if we know the
base class ml-3. We can assume that -- is invalid and that one of those dashes is part of the negative and the
other is part of the prefix.

Now imagine that your prefix was tw--, ...

Alright, so there is a lot of complex computation going on which is probably not worth it at all. The biggest use case
we saw for a prefix function, is so that you can only prefix some classes. For example, to prevent conflicting classes
with existing frameworks you might be using. This can be solved by applying the prefix to every utility, this does solve
the conflicts, but it might be a bit annoying in the beginning to convert existing classes.

Long story short, we are going to remove the prefix as a function. Then, if people have actual issues, then we will
try and solve those issues in a JIT world where we can drastically simplify this.


Closes: #5765

@RobinMalfait RobinMalfait changed the title fix prefix as a function Remove prefix as a function Oct 19, 2021
@RobinMalfait RobinMalfait merged commit 0c2f1a6 into master Oct 19, 2021
@RobinMalfait RobinMalfait deleted the fix-prefix-as-a-function branch October 19, 2021 13:22
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

Successfully merging this pull request may close these issues.

[JIT] prefix as a function breaks some classes
1 participant