-
Notifications
You must be signed in to change notification settings - Fork 16
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
Tailwind Regex #10
Comments
@gino A prefix for css classes is required otherwise the plugin won't work. The prefix must be unique in the all your source code since the plugin replace a string in the code using regex. |
Hey, I am going to use this for my project again and now will use the |
Does anyone watching the repo have the knowledge? Anyway, I'll close the issue in a few weeks since it doesn't the issue of the plugin. |
@sndyuk I am experimenting with a couple of different regex patterns but I was wondering, is it possible to have longer generated classnames instead of just having Edit: for example, provide some sort of a pattern like |
@gino How about adding the option module.exports = {
...
plugins: [
new MangleCssClassPlugin({
...,
// original: original class name
// opts: options of the plugin
// context: own context of the class generator(initial value is just an empty object)
classGenerator: (original, opts, context) => {
// return custom generated class name.
// Or return undefined if you want to leave it to the original behavior.
}
}),
],
}; |
@sndyuk That’s a really nice approach! Could you also perhaps provide an example how to achieve random strings. But so far this looks very promising! 🤩 |
@gino I added the new option at version 4.0.12. Please check the example bellow in the test case. It replaces class names starts with mangle-css-class-webpack-plugin/spec/BasicSpec.js Lines 208 to 220 in 54fd701
|
@sndyuk Looks really good! I am currently trying it in a Tailwind project and also using the same scenario as you, with an auto generated number, but without the classGenerator: (original, opts, context) => {
if (!context.id) {
context.id = 1;
}
const className = `${context.id}`;
context.id++;
return className;
}, It does output all the numbers (log) and all the classes in my HTML are having those auto-generated numbers, but all the classes have 0 styles.. even though they all should have a background color. So I am not sure if there is a bug or if I am doing something wrong. I was just trying to get the auto-generated numbers working with all classes. Even though I would love to have just random strings instead of numbers, like how So yeah, looks very good though! But I might be implementing it the wrong way.. |
I think class name starts with number is invalid.
You can use a random 7 chars. For example https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript |
Please tell me how to use I use tailwind with jit and I need a similar expression |
try a more elegant solution https://medium.com/@my_own_grave/recting-css-generated-by-vue-loader-by-using-classnames-shorten-trick-aa1d25d77473 all the magic lies in the |
@mr-httdd I am not quite sure if I am using a great regex either and I am not quite sure how to use the approach from that Medium article since I am not using Vue.. I am using Next.js, not sure if I can also use that method in my environment? |
@gino the development environment does not matter here, everything will be done for you by the haven't tested, but in your case it will be something like this:
|
@mr-httdd Ohh I see! I will test this soon, thank you for your example though. Please let me know if you found a good regex pattern that I can use for Tailwind. I still don't really like that I have to use a prefix in order to make this all work but it's understandable. |
@gino It turned out to make the right interaction with the tailwind + jit (by the way, note that the tailwind has recently merged the jit repository). import incstr from 'incstr';
const classNames = {};
const generateClassName = incstr.idGenerator({
alphabet: 'abcdefghijklmnopqrstuvwxyz'
});
new MangleCssClassPlugin({
classNameRegExp: '(([a-z-:]*)[\\\\\\\\]*:)*tw-[a-z_-]([\\[\\]\\%a-z0-9-]*([\\\\\\\\]*(\\.|\\[|\\]))*)*',
classGenerator: (original, opts, context) => {
if (classNames[original]) {
return classNames[original];
}
let nextId;
do {
// Class name cannot start with a number.
nextId = generateClassName();
} while (/^[0-9_-]/.test(nextId));
return classNames[original] = nextId;
},
}) unfortunately, without the prefix, this is not quite the right approach in this package, it goes through the whole file, and this may give the wrong result. @sndyuk ran into strange behavior in the |
I was having similar issue with Next.js as well. It doesn't work without a prefix. It throws error. Log shows it replaced javascript object as well. I was using the following config: module.exports = {
webpack: (config) => {
const MangleCssClassPlugin = require("mangle-css-class-webpack-plugin");
config.plugins.push(
new MangleCssClassPlugin({
classNameRegExp:
"(([a-z-:]*)[\\\\\\\\]*:)*[a-z_-]([\\[\\]\\%a-z0-9-]*([\\\\\\\\]*(\\.|\\[|\\]))*)*",
ignorePrefixRegExp:
"((hover|focus|active|disabled|visited|first|last|odd|even|group-hover|focus-within|xs|sm|md||lg|xl)(\\\\\\\\\\\\\\\\|\\\\)?:)*",
log: true,
})
);
return config;
},
}; |
@mr-httdd
I couldn't figured out how it happens since that case should re-use the generated class name here: |
Please refer the note here: https://github.com/sndyuk/mangle-css-class-webpack-plugin#usage
|
@sndyuk according to the logic of the code, everything should be correct, there are suspicions only in a synchronous loop, but in my case, when passing a function to the there is a thought that |
@gino If you want deterministic output (the input value always generates the same output "randomness"), then you want to use a hash function. Don't use A simple hash function that is easy to implement and less likely to cause collision issues is FNV1a-32: const fnvOffset = 2166136261
function fnv1a32(str) {
// hash value to operate on
let h = fnvOffset
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i)
// JS number type is inaccurate at calculating 'h *= fnvPrime',
// Uses bit-shifts to accurately multiply the prime: '16777619'
h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)
}
// Cast to 32-bit uint
return h >>> 0
}
// Converts string input to a 32-bit base36 string (0-9, a-z), highest radix `Number.toString()` supports
// Prevent invalid CSS class names in selectors starting with a digit by prefixing with `_`
const getShortKey = (input) => {
fnv1a32(input)
.toString(36)
.replace(/^[0-9]/, `_$&`)
} Just call @sndyuk Even without the hash function part, looking at the current default generator code, I think it would simplify the logic a fair bit? (but not be optimal for minification results): // defaultClassGenerator no longer needed
// ...
if (!newClassName) {
newClassName = this.newClassSize.toString(36).replace(/^[0-9]/, `_$&`);
} Your current implementation does use I am curious why EDIT: It seems the const subs = `§£¥¢þ°Æ汬`
// ...
// If the string starts with a digit, parse it and use that as an index to a string or array for a substitute replacement:
this.newClassSize.toString(36).replace(/^[0-9]/, x => subs[parseInt(x, 10)])
@gino Is there a specific reason you wanted that btw vs shorter names? They're likely doing the same as what I've shown above. I was needing to generate classnames for a PR to use that ideally avoids conflicting against user or third-party classnames, but were still short. The code didn't have context of classes outside of it's own scope and they needed to generate the same value for both server-side and client-side. For those that might have trouble making sense of the code above, it will turn any string length input into a 32-bit number (about 4 billion values), and convert that into base36 via native methods that are much more efficient than the native base64 functions (differs between nodeJS and client/browser JS). This will return a string output anywhere from 1-8 characters long: If speed isn't a concern, and you'd rather slightly better compression then here's my base64 encoding variant of Click for details// Base64 encoding browser + node.js - Expensive to compute this way
// SSR and Client base64 encode methods, expects Uint8Array input
// Used for generating short class names from 32-bit values (hash)
const nodeBtoa = b => Buffer.from(b, `binary`).toString(`base64`)
const clientBtoa = b => btoa(String.fromCharCode(...b))
const base64encode = typeof btoa !== `undefined` ? clientBtoa : nodeBtoa
// NOTE: This method might provide a shorter string, but performance is not great,
// due to 32-bit values needing additional conversion/allocations.
function numToBase64(h) {
// 32-bit number split into 4 separate bytes for encoding into base64. 32-bits is always <=6 base64 characters long.
const b = [h >> 24, h >> 16, h >> 8, h]
// Remove leading empty bytes, otherwise they're also encoded. `Uint8Array` type required for node and clamps the final 'h' byte.
const bytes = Uint8Array.from( b.slice(b.findIndex(x => x > 0)) )
// Replace '+' and '/' values (from base64 charset) which aren't ideal for CSS identifiers (eg class names in selectors).
// `æ` should be a safe value, it's `0xE6` which is within a single byte but over `0xA0` which meets the spec: https://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
// '=' chars (base64 padding) are stripped off.
// '-' & digits are not allowed as the first char of CSS identifiers, prefix with '_'.
return (
base64encode(bytes)
.replace(/[+/]/g, x => (x === `+` ? `æ` : `-`))
.replace(/=/g, '')
.replace(/^-?\d|--/, `_$&`)
)
} The final regex there is a bit more complicated to meet the CSS identifiers spec:
So it's covering the extra cases for That pretty much leaves you with Latin-1 Supplement, any prior to You should be able to avoid the need for a prefix with numbers and const subs = `§£¥¢þ°Æ汬`
base64encode(bytes)
.replace(/^[0-9]/, x => subs[parseInt(x, 10)])
.replace(/[+/]/g, x => (x === `+` ? `¶` : `Ø`))
.replace(/=/g, '') I don't think the extra logic for base64 is worth it.
If you weren't using a hash function and just incrementing, base36 covers 60 million for 5 characters, or up to 1296 values with only 2 characters (ignoring +1 char from prefix for about 30% outputs that begin with digits). ~~If you have more than the ~1300 values to account for, base64 at 2 characters will let you get to 4096 without increasing to 3 characters, otherwise no real benefit.~~ EDIT: It seems I misunderstood base64 a little. Each character (uses 1 byte aka 8 bits) represents a total of 6-bits of input; despite that output characters represent a minimum of 8 bits of input and thus 2 characters minimum. When the 2nd byte is reached (number >255), 16 bits are encoded increasing to 3 characters until that new range is exceeded (65535 + 1) expanding to 4 characters covering 16 to <24-bits. If the number uses the final 8-bits, it goes from 4 characters directly to 6 😞 For the purpose described here it would always be 2-3 characters long for most users (when ignoring any prefix). Likewise for the hash function, the transition from 3 bytes to 4 bytes (32-bit) of input increases characters in the output string from 4 to 6. For base64 method, 4 characters long or less is only 0.4% of the number of values in 32 bits, while the base36 method at 5 characters long or less covers a mere 1.4%. Thus expect mostly 6 characters and with base36 50% of possible values will be 7. If emoji comes to mind to anyone, don't do that. It's not as good as it might seem, and despite the visual length reduction, emoji often use 3-4 bytes or more (some are over 20 bytes for a single glyph) so it tends to be much larger under the hood file size wise. |
hi @yuriti i am using your code, it work so well. Thank you :)
the hover still work but just because the class was created be wrong i think |
@IRediTOTO use new regex
|
Out of curiosity, has anyone of you guys managed to combine this webpack plugin with Nuxt.js project (running in SSR mode)? I can't get it to work properly on both ends (SSR rendered + after hydration). Either of these was malformed etc. If there is someone who managed to make it work, I'd be grateful for any advice :) |
@yuriti do you new regex for Tw 3.0 ? They have so many new class. I think old regex can't handle all of them |
This regex stop working if you apply .css with custom merged classes, like:
|
Hey @sndyuk! To begin with, thank you very much for this package! I use Vue.js + tailwind + webpack Webpack config:
And I have a problem with Animations When I use DEV - everything is perfect:
But when I use PROD - I have a problem. The styles do not change the name to keyframes :(
But in logs i see this:
Can you help me with this? I also have a suggestion - to make a whitelist for classes that will not be renamed. |
What about with How do I include these classes in regex as well? UPDATE:
|
I've experienced the same issue while working with tailwindcss and next.js 13 and here is my workaround, you just need to make your /** @type {import('next').NextConfig} */
const nextConfig = {
swcMinify: true,
reactStrictMode: true,
webpack: (config, { dev }) => {
if (!dev) {
const MangleCssClassPlugin = require("mangle-css-class-webpack-plugin");
config.plugins.push(
new MangleCssClassPlugin({
classNameRegExp:
"((hover|focus|active|disabled|visited|first|last|odd|even|group-hover|focus-within|xs|sm|md|lg|xl)[\\\\]*:)*(|-)tw-[a-zA-Z0-9_-]*([\\\\]*/[0-9]*)?",
ignorePrefixRegExp:
"((hover|focus|active|disabled|visited|first|last|odd|even|group-hover|focus-within|xs|sm|md||lg|xl)[\\\\]*:)*",
})
);
}
return config;
},
};
module.exports = nextConfig; If you want to have the mangle on the development mode as well you can remove the |
Mighty RegEx heroes, please help detect CSS attribute selectors. With the rules from this thread I get:
Is it possible to have:
|
@sndyuk Thank you 🙏 Indeed I was using outdated regex. |
SOLUTIONadd classGenerator to webpack, it's because double generation in nextjs #46 (comment) {
classGenerator: original => btoa(original).replace(/=/g, ''),
} INTROcan someone help? weird output ERRORExpectnext.config.js webpack: (config, { dev }) => {
const MangleCssClassPlugin = require('mangle-css-class-webpack-plugin')
if (!dev) {
config.plugins.push(
new MangleCssClassPlugin({
classNameRegExp:
"((hover|focus|active|disabled|visited|first|last|odd|even|group-hover|focus-within|xs|sm|md|lg|xl)[\\\\]*:)*(|-)tw-[a-zA-Z0-9_-]*([\\\\]*/[0-9]*)?",
ignorePrefixRegExp:
"((hover|focus|active|disabled|visited|first|last|odd|even|group-hover|focus-within|xs|sm|md||lg|xl)[\\\\]*:)*",
})
);
}
return config
} |
Hello,
What regex should I use if I am using TailwindCSS?
I saw an issue with a Tailwind regex but this used the
tw-
prefix. I tried to remove this from the regex and it stopped working unfortunately. Since I am not using that prefix. I am just using basic Tailwind utilities without any configuration so far.Thanks.
The text was updated successfully, but these errors were encountered: