Coder Social home page Coder Social logo

dropcss's Introduction

πŸ—‘ DropCSS

An exceptionally fast, thorough and tiny (~10 KB min) unused-CSS cleaner (MIT Licensed)


Introduction

DropCSS takes your HTML and CSS as input and returns only the used CSS as output. Its custom HTML and CSS parsers are highly optimized for the 99% use case and thus avoid the overhead of handling malformed markup or stylesheets, so well-formed input is required. There is minimal handling for complex escaping rules, so there will always exist cases of valid input that cannot be processed by DropCSS; for these infrequent cases, please start a discussion. While the HTML spec allows html, head, body and tbody to be implied/omitted, DropCSS makes no such assumptions; selectors will only be retained for tags that can be parsed from provided markup.

It's also a good idea to run your CSS through a structural optimizer like clean-css, csso, cssnano or crass to re-group selectors, merge redundant rules, etc. It probably makes sense to do this after DropCSS, which can leave redundant blocks, e.g. .foo, .bar { color: red; } .bar { width: 50%; } -> .bar { color: red; } .bar { width: 50%; } if .foo is absent from your markup.

More on this project's backstory & discussions: v0.1.0 alpha: /r/javascript, Hacker News and v1.0.0 release: /r/javascript.



Installation

npm install -D dropcss

Usage & API

const dropcss = require('dropcss');

let html = `
    <html>
        <head></head>
        <body>
            <p>Hello World!</p>
        </body>
    </html>
`;

let css = `
    .card {
      padding: 8px;
    }

    p:hover a:first-child {
      color: red;
    }
`;

const whitelist = /#foo|\.bar/;

let dropped = new Set();

let cleaned = dropcss({
    html,
    css,
    shouldDrop: (sel) => {
        if (whitelist.test(sel))
            return false;
        else {
            dropped.add(sel);
            return true;
        }
    },
});

console.log(cleaned.css);

console.log(dropped);

The shouldDrop hook is called for every CSS selector that could not be matched in the html. Return false to retain the selector or true to drop it.


Features

  • Supported selectors

    Common Attribute Positional Positional (of-type) Other
    * - universal
    <tag> - tag
    # - id
    . - class
    Β  - descendant
    > - child
    + - adjacent sibling
    ~ - general sibling
    [attr]
    [attr=val]
    [attr*=val]
    [attr^=val]
    [attr$=val]
    [attr~=val]
    :first-child
    :last-child
    :only-child
    :nth-child()
    :nth-last-child()
    :first-of-type
    :last-of-type
    :only-of-type
    :nth-of-type()
    :nth-last-of-type()
    :not()
  • Retention of all transient pseudo-class and pseudo-element selectors which cannot be deterministically checked from the parsed HTML.

  • Removal of unused @font-face and @keyframes blocks.

  • Removal of unused CSS variables.

  • Deep resolution of composite CSS variables, e.g:

    :root {
      --font-style: italic;
      --font-weight: bold;
      --line-height: var(--height)em;
      --font-family: 'Open Sans';
      --font: var(--font-style) var(--font-weight) 1em/var(--line-height) var(--font-family);
      --height: 1.6;
    }
    
    @font-face {
      font-family: var(--font-family);
      src: url("/fonts/OpenSans-Regular-webfont.woff2") format("woff2"),
           url("/fonts/OpenSans-Regular-webfont.woff") format("woff");
    }
    
    body {
      font: var(--font);
    }

Performance

Input

test.html

  • 18.8 KB minified
  • 502 dom nodes via document.querySelectorAll("*").length

styles.min.css

  • 27.67 KB combined, optimized and minified via clean-css
  • contents: Bootstrap's reboot.css, an in-house flexbox grid, global layout, navbars, colors & page-specific styles. (the grid accounts for ~85% of this starting weight, lots of media queries & repetition)

Output

lib size w/deps output size reduction time elapsed unused bytes (test.html coverage)
DropCSS 58.4 KB
6 Files, 2 Folders
6.58 KB 76.15% 21 ms 575 / 8.5%
UnCSS 13.5 MB
2,829 Files, 301 Folders
6.72 KB 75.71% 385 ms 638 / 9.3%
Purgecss 2.69 MB
560 Files, 119 Folders
8.01 KB 71.05% 88 ms 1,806 / 22.0%
PurifyCSS 3.46 MB
792 Files, 207 Folders
15.46 KB 44.34% 173 ms 9,440 / 59.6%

Notes

  • About 400 "unused bytes" are due to an explicit/shared whitelist, not an inability of the tools to detect/remove that CSS.
  • About 175 "unused bytes" are due to vendor-prefixed (-moz, -ms) properties & selectors that are inactive in Chrome, which is used for testing coverage.
  • Purgecss does not support attribute or complex selectors: Issue #110.

A full Stress Test is also available.


JavaScript Execution

DropCSS does not load external resources or execute <script> tags, so your HTML must be fully formed (or SSR'd). Alternatively, you can use Puppeteer and a local http server to get full <script> execution.

Here's a 35 line script which does exactly that:

const httpServer = require('http-server');
const puppeteer = require('puppeteer');
const fetch = require('node-fetch');
const dropcss = require('dropcss');

const server = httpServer.createServer({root: './www'});
server.listen(8080);

(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('http://127.0.0.1:8080/index.html');
    const html = await page.content();
    const styleHrefs = await page.$$eval('link[rel=stylesheet]', els => Array.from(els).map(s => s.href));
    await browser.close();

    await Promise.all(styleHrefs.map(href =>
        fetch(href).then(r => r.text()).then(css => {
            let start = +new Date();

            let clean = dropcss({
                css,
                html,
            });

            console.log({
                stylesheet: href,
                cleanCss: clean.css,
                elapsed: +new Date() - start,
            });
        })
    ));

    server.close();
})();

Accumulating a Whitelist

Perhaps you want to take one giant CSS file and purge it against multiple HTML sources, thus retaining any selectors that appear in any HTML source. This also applies when using Puppeteer to invoke different application states to ensure that DropCSS takes every state into account before cleaning the CSS. The idea is rather simple:

  1. Run DropCSS against each HTML source.
  2. Accumulate a whitelist from each result.
  3. Run DropCSS against an empty HTML string, relying only on the accumulated whitelist.

See /demos/accumulate.js:

const dropcss = require('dropcss');

// super mega-huge combined stylesheet
let css = `
    em {
        color: red;
    }

    p {
        font-weight: bold;
    }

    .foo {
        font-size: 10pt;
    }
`;

// html of page (or state) A
let htmlA = `
    <html>
        <head></head>
        <body>
            <em>Hello World!</em>
        </body>
    </html>
`;

// html of page (or state) B
let htmlB = `
    <html>
        <head></head>
        <body>
            <p>Soft Kitties!</p>
        </body>
    </html>
`;

// whitelist
let whitelist = new Set();

function didRetain(sel) {
    whitelist.add(sel);
}

let resA = dropcss({
    css,
    html: htmlA,
    didRetain,
});

let resB = dropcss({
    css,
    html: htmlB,
    didRetain,
});

// final purge relying only on accumulated whitelist
let cleaned = dropcss({
    html: '',
    css,
    shouldDrop: sel => !whitelist.has(sel),
});

console.log(cleaned.css);

Special / Escaped Sequences

DropCSS is stupid and will choke on unusual selectors, like the ones used by the popular Tailwind CSS framework:

class attributes can look like this:

<div class="px-6 pt-6 overflow-y-auto text-base lg:text-sm lg:py-12 lg:pl-6 lg:pr-8 sticky?lg:h-(screen-16)"></div>
<div class="px-2 -mx-2 py-1 transition-fast relative block hover:translate-r-2px hover:text-gray-900 text-gray-600 font-medium"></div>

...and the CSS looks like this:

.sticky\?lg\:h-\(screen-16\){...}
.lg\:text-sm{...}
.lg\:focus\:text-green-700:focus{...}

Ouch.

The solution is to temporarily replace the escaped characters in the HTML and CSS with some unique strings which match /[\w-]/. This allows DropCSS's tokenizer to consider the classname as one contiguous thing. After processing, we simply reverse the operation.

// remap
let css2 = css
    .replace(/\\\:/gm, '__0')
    .replace(/\\\//gm, '__1')
    .replace(/\\\?/gm, '__2')
    .replace(/\\\(/gm, '__3')
    .replace(/\\\)/gm, '__4');

let html2 = html.replace(/class=["'][^"']*["']/gm, m =>
    m
    .replace(/\:/gm, '__0')
    .replace(/\//gm, '__1')
    .replace(/\?/gm, '__2')
    .replace(/\(/gm, '__3')
    .replace(/\)/gm, '__4')
);

let res = dropcss({
    css: css2,
    html: html2,
});

// undo
res.css = res.css
    .replace(/__0/gm, '\\:')
    .replace(/__1/gm, '\\/')
    .replace(/__2/gm, '\\?')
    .replace(/__3/gm, '\\(')
    .replace(/__4/gm, '\\)');

This performant work-around allows DropCSS to process Tailwind without issues \o/ and is easily adaptable to support other "interesting" cases. One thing to keep in mind is that shouldDrop() will be called with selectors containing the temp replacements rather than original selectors, so make sure to account for this if shouldDrop() is used to test against some whitelist.


Caveats

  • Not tested against or designd to handle malformed HTML or CSS
  • Excessive escaping or reserved characters in your HTML or CSS can break DropCSS's parsers

Acknowledgements

  • Felix BΓΆhm's nth-check - it's not much code, but getting An+B expression testing exactly right is frustrating. I got part-way there before discovering this tiny solution.
  • Vadim Kiryukhin's vkbeautify - the benchmark and test code uses this tiny formatter to make it easier to spot differences in output diffs.

dropcss's People

Contributors

leeoniya avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dropcss's Issues

sourcemap support

i've personally not needed it, but it could be a quality of life improvement.

probably via https://github.com/Rich-Harris/magic-string but not sure how useful it would be without also ingesting a prior sourcemap, so also https://github.com/Rich-Harris/sorcery.

performance is somewhat a concern since we now need to also store indices in the token list.

perhaps the better way to keep this optional and out of the core is to optionally return a list of indices alongside the token list with indications of which were removed and leave the actual work of doing the sourcemapping to an external wrapper instead of bloating the core.

dropcss({trackLocs: true})

{
  css: ...,
  locs: [
    655,  // index
    12,   // chars removed
    928, // index
    533, // chars removed
  ] 
}

or better yet? add the location index as arg to shouldDrop(sel, loc) and let the consumer/wrapper accumulate the deleted array. (still behind an option flag).

Using DropCSS with a Ghost blog

Hello,

I'm working alone on my music blog (https://soundsirius.digitalpress.blog) and I don't have much time every day. I want to use your software to make my website faster. But here's what I need:

  • install: be able to use a script tag with a cdn to install it,
  • code: generic code I can copy/paste easily to benefit from the basic functionalities.

I installed Tailwind CSS which is very nice but I'm not using everything. I want to get rid of the bad css and css complexity and make my website load under 1 second.

PS: I also want to use CleanCSS before.

Crashes on font shorthand with custom properties

A recent fix (8e27afd) for short hand font properties will throw an error if one is using custom properties.

E.g. this rule causes the regex in said commit to not match.

.foo {
  font: var(--font-style) var(--font-weight) 1em/var(--line-height) var(--font-family);
}

Parent dropping (hard optimisation)

Simple example:

<!DOCTYPE html>
<body>
<p>Hello, world!
</body>
body p { margin: 1em; }

In this case, dropcss should notice that:

  1. All p are body p.
  2. Besides body p, all of the selectors on body p are either less specific than p or more specific than body p.

Therefore, it should just output:

p { margin: 1em; }

One potentially more applicable case of this might be styles which differentiate ul li and ol li on a page which only uses ul li. In this case, we could drop the ul and just output li to shrink the size of the CSS. This could also apply to, for example, .page-type .element, where only one variant of .page-type is used in the HTML samples given.

implement *-of-type selectors

  • :first-of-type
  • :last-of-type
  • :only-of-type
  • :nth-of-type()
  • :nth-last-of-type()
  • :nth-only-of-type()

ping me if you're interested in tackling this before i get to it.

:last-of-type error

HTML:

<div class="items">
    <div class="item">
        <div class="content"></div>
    </div>
    <div class="item">
        <div class="content"></div>
    </div>
</div>

CSS:

.items .item:last-of-type .content {}

Error:

TypeError: Cannot read property 'length' of null
    at find (dropcss.cjs.js?693)
    at find (dropcss.cjs.js?720)
    at eval (dropcss.cjs.js?765)
    at Array.some (<anonymous>)
    at some (dropcss.cjs.js?765)
    at _export_some_ (dropcss.cjs.js?772)
    at eval (dropcss.cjs.js?960)
    at Array.filter (<anonymous>)
    at dropcss (dropcss.cjs.js?947)
    at eval (index.js?80)
case 'last-of-type':
    tsibs = getSibsOfType(par, tag);
    res = n._typeIdx == tsibs.length - 1;
                              ^
    break;

didRetain() instead of result.sels

i never quite liked the naming of result.sels or that it gets accumulated regardless of whether the user asked for it or not. it also doesnt feel right that whitelist accumulation uses this return style but whitelist testing uses the shouldDrop() callback style. didRetain() which fires for every undropped selector feels more consistent.

this would be a breaking change but to a non-primary API, so i dont think a 2.0 is warranted; hopefully no-one's life depends on dropcss 😱

Infinite loop

Tried with the latest release. Still getting infinite loops. This time I was testing this page:

I suspect that the issue is again some weird selector that is not being parsed. It would probably be best to add back the bail outs for at least the CSS parser so that we can know when it gets stuck.

drop unused css variables?

because CSS optimization is a slippery slope, currently DropCSS draws a line in the sand for how it can mutate the provided CSS. right now it will only remove CSS selectors, and if all selectors are removed for a specific block, then it will also remove that block. that is to say, it does not touch the contents of the blocks.

dropping unused css variables should be a simple affair with just 2 regex passes, but would deviate from the current block modification abstinence. on the other hand, it still serves the core function of "removing unused css", so maybe i'm making a mountain out of a molehill.

Enable hooks to drop / retain individual css declarations

I want to optimize css file across multiple html files... those css files could be different so it would be nice if you could drop or retain individual declarations. Potentially via didRetainDeclaration and dropDeclaration.

it would allow doing something like this...

Screenshot 2021-02-09 at 00 32 58

if you would be interested in supporting this I could prepare a PR for it πŸ€—

let me know what you think πŸ€—


Some more details

I currently have it working in a fork and it usage looks like this

dropcss({
  html: page.html,
  css: page.css,
  didRetain: selector => {
    let sharedCount = sharedUsage.has(selector) ? sharedUsage.get(selector) : 0;
    sharedUsage.set(selector, sharedCount + 1);
  },
  didRetainDeclaration: ({ selector, property, value }) => {
    const key = `${selector} { ${property}: ${value} }`;
    let sharedCount = sharedDeclarations.has(key) ? sharedDeclarations.get(key) : 0;
    sharedDeclarations.set(key, sharedCount + 1);
  },
});

the code change itself is actually rather small and mostly in css.js

function generate(tokens, didRetain, didRetainDeclaration, shouldDropDeclaration) {
  let out = '',
    lastSelsLen = 0;

  for (let i = 0; i < tokens.length; i++) {
    let tok = tokens[i];

    switch (tok) {
      case SELECTORS:
        let sels = tokens[++i];
        lastSelsLen = sels.length;

        if (lastSelsLen > 0) {
          sels.forEach(didRetain);
          sels.forEach(selector => {
            const declarations = tokens[i + 2]
              .split(';')
              .map(line => line.trim())
              .filter(_ => !!_);

            let declarationIndex = 0;
            for (const declaration of declarations) {
              const [property, value] = declaration.split(':').map(value => value.trim());

              if (shouldDropDeclaration({ selector, declaration, property, value })) {
                declarations[declarationIndex] = null;
              } else {
                didRetainDeclaration({ selector, declaration, property, value });
              }
              tokens[i + 2] = declarations.filter(_ => !!_).join(';');
              if (tokens[i + 2] !== '') {
                tokens[i + 2] += ';';
              }
              declarationIndex += 1;
            }
          });
          out += sels.join();
        }
        break;

angle brackets in html attrs break tokenizer

Hi man I've been trying to compile some css, and what happened is that I accidentally had this:

const html = `
<body>
  <li heading="<em>your-signature-here</em>">
  </li>

  <h4 id="<em>your-signature-here</em>" class="mt-2"><em>Your Signature Here</em>:</h4>
</body>`

although it's probably wrong to put tags in attributes (it was autogenerated), the issue is that the error from dropcss does not really give much info

TypeError: Cannot read property 'parentNode' of null
    at build (/Users/zavr/adc/splendid/node_modules/dropcss/dist/dropcss.cjs.js:154:17)

is it possible to maybe give more debug info?
also maybe tags in attributes should result in some kind of warning? thanks.

dropping multiple font-faces

An error occurs when dropcss tries to handle and drop lots of font-faces (2 and more).

Error text:
(node:11700) UnhandledPromiseRejectionWarning: TypeError: Cannot read property '1' of null
at dropFontFaces (D:\web projects\test4\node_modules\dropcss\dist\dropcss.cjs.js:896:33)
at postProc$1 (D:\web projects\test4\node_modules\dropcss\dist\dropcss.cjs.js:935:8)
at dropcss (D:\web projects\test4\node_modules\dropcss\dist\dropcss.cjs.js:1062:8)
...

Demo:
Codepen

Gulp task

Hi.
Is there any build of dropcss to use in a Gulp task ?

Thanks you

applying to an entire site?

More a question: using this on a single html page is ok, but in reality most sites comprise many pages.

Is there a way to apply this to a range of pages, or an entire site?

This is the holy grail IMO.

attributes with :

Hey, attributes can have : in attributes, e.g., svgs such as <image xlink:href="example"/>, the regex needs to be updated.

Another infinite loop

Related #16

Here's an example:
https://repl.it/@heygrady/dropcss-example

It would be nice if it would throw instead of infinite looping. It doesn't matter much to me if this throws but infinite loops are a pretty big deal. Is it possible to put some runaway loop protection in place?

Regarding #16 (comment)

https://github.com/leeoniya/dropcss/blob/master/src/html.js#L66-L67
https://github.com/leeoniya/dropcss/blob/master/src/css.js#L159-L160

Would this basically fix it?

let prevPos = pos
while (pos < html.length) {
	next();
	// is the problem that we stop advancing in some cases?
	if (prevPos === pos) {
		throw new Error('tokenizer did not advance');
		break;
	}
	prevPos = pos;
}

`NASTIES` regex incorrectly matches custom elements

Our html includes <link-formatter>....</link-formatter>
DropCSS incorrrectly matches it with NASTIES ( this part: <link[^>]*>), and drops the opening tag. The resulting HTML becomes incorrect (unbalanced closing tag).

This probably also applies to the <meta part of the regex

"Yes, we're gonna have to go right to...ludicrous speed!"

https://www.youtube.com/watch?v=mk7VWcuVOf0

It should be possible to attain at least PurgeCSS speeds, and perhaps better.

Some ideas to give the old college try:

  • HTML parsing could push nodes, parents and children into flat Map()s, this would make it possible to very quickly query/filter leaf nodes (as would be necessary when matching selectors right to left). the current node-html-parser dependency outputs a tree and does not internally use flat structures, so is likely not terribly efficient to query after parsing (the parsing is fast, but most of the cost of CSSDrop is querying).
  • Possibly drop css-select in favor of letting CSSTree parse all selectors in its single pass or use https://github.com/fb55/css-what directly, but modify it to return bytecode sequences that do right-to-left matching against the Map objects instead of an intermediate CSS AST.

output a css structure object

personally, i author static stylesheets as js objects and use https://github.com/cssobj/cssobj to compile them (which handles a basic level of optimizations). since DropCSS already tokenizes the css (besides properties), it would be useful to expose the tokens to allow the build-out of a small post-processor which completes the parsing of props and creates a js object that can be fed into cssobj for final optimizations & compression.

probably the only actionable thing for this lib is to return the css tokens alongside the generated css.

Always assume implicit tags are present

Right now, rules are removed if the document lacks html, head, or body tags, even though these are implicitly added in HTML 5. The parser should also assume the presence of these tags so that these rules aren't removed.

Example input HTML/CSS:

<!DOCTYPE html>
<p>Hello, world!
html { background: black; color: white }
p { margin: 1em }

Currently outputs CSS:

p { margin: 1em }

Even though it should not remove the html selector.

Note that this also applies to head and body, e.g. head title will always be equivalent to title with extra specificity even if no <head> tag is present and body p will always be equivalent to p with extra specificity even if no <body> tag is present.

derive a dom selector lib

DropCSS already contains the vast majority of the machinery necessary to create something akin to these libs:

https://github.com/fb55/css-select
https://github.com/jquery/sizzle
https://github.com/dperini/nwmatcher

the amount of additional code/tweaks should be quite small to get this working and there's a reasonable chance that a selector engine built using DropCSS's innards would be very competative in speed and definitely size. Sizzle is 19.4KB (min) while DropCSS is 9.55KB (min) which includes an HTML parser [1] and post-processor [2] that can mostly be ditched.

[1] https://github.com/leeoniya/dropcss/blob/master/src/html.js
[2] https://github.com/leeoniya/dropcss/blob/master/src/postproc.js

Class names containing html/body/head do not get dropped

Any class name with specific tags in their names will not be dropped even if the html does not contain them.
I explained this second issue poorly in #57

.anyClassNameWithhtml{
  font-weight:400;
}
.anyClassNameWithbody{
  font-weight:400;
}
.anyClassNameWithhead{
  font-weight:400;
}

<html>
  <div class="nothing"></div>
</html>

Thanks for everything!

Trying to get in touch regarding a security issue

Hey there!

I'd like to report a security issue but cannot find contact instructions on your repository.

If not a hassle, might you kindly add a SECURITY.md file with an email, or another contact method? GitHub recommends this best practice to ensure security issues are responsibly disclosed, and it would serve as a simple instruction for security researchers in the future.

Thank you for your consideration, and I look forward to hearing from you!

(cc @huntr-helper)

dropcss-cli tool

I was wondering if you've considered a separate cli version of dropcss. I was thinking of developing it when I have time, so I can use it to lint on my CI, and exit with an error if there's any unused css. Kind of like how I use prettier on CI. Would you mind if I create dropcss-cli?

BEM + pseudo trips up custom var extraction: .a--b:hover{}

Seems like there's some trouble parsing BEM selectors, namely the --modifier bit.

const dropcss = require('dropcss');

let html = `
    <html>
        <head></head>
        <body>
          <div class="tab-table">
            <div class="tab-table__item">Tab 1</div>
            <div class="tab-table__item tab-table__item--new">Tab 2</div>
          </div>
        </body>
    </html>
`;

let css = `                                                                                           
.tab-table {
  display: flex;
  flex-flow: row nowrap;
  position: relative;
}

.tab-table__item {
  background: #494545;
  border-width: 0 .1rem;
  cursor: pointer;
  flex: 1 1 20%;
  flex-flow: column nowrap;
}

.tab-table__item--new::before {
  align-items: center;
  color: #fff;
  display: flex;
  justify-content: center;
}
`;

let cleaned = dropcss({
    html,
    css, 
});

console.log(cleaned);
/*
{ css: '.tab-table{display: flex;\n  flex-flow: row nowrap;\n  position: relative;}.tab-table__item{background: #494545;\n  border-width: 0 .1rem;\n  cursor: pointer;\n  flex: 1 1 20%;\n  flex-flow: column nowrap;}.tab-table__itemcolor: #fff;\n  display: flex;\n  justify-content: center;}',
  sels: 
   Set {
     '.tab-table',
     '.tab-table__item',
     '.tab-table__item--new::before' } }
*/

Formatted CSS to more clearly see what's going on:

.tab-table {
  display: flex;
  flex-flow: row nowrap;
  position: relative;
}

.tab-table__item {
  background: #494545;
  border-width: 0 .1rem;
  cursor: pointer;
  flex: 1 1 20%;
  flex-flow: column nowrap;
}

.tab-table__itemcolor: #fff; /* should be `.tab-table__item--new::before {` */
display: flex;
justify-content: center;
}

dropcss version: 1.0.12
node version: 10.16.0

Infinite loop

Don't want to paste in everything here but I ran into an issue where certain pages on my site would go into an infinite loop during the dropcss call. It's probably some weird css causing the issue but it's impossible to know because it gets stuck in the loop and never errors out.

duplicated rules with same content

Hi why does this tool doesn't merge these two rules?

.x-btn-group-tl,.x-btn-group-tr{background-image: url(../ext/default/button/group-cs.gif);}
.x-btn-group-bl,.x-btn-group-br{background-image: url(../ext/default/button/group-cs.gif);}

const dropcss = require('dropcss');

var html = "";

var css = ".x-btn-group-tl,.x-btn-group-tr{background-image: url(../ext/default/button/group-cs.gif);}.x-btn-group-bl,.x-btn-group-br{background-image: url(../ext/default/button/group-cs.gif);}"

let cleaned = dropcss({
html,
css,
shouldDrop: (sel) => {
return false;
},
});

console.log(cleaned.css);

unquoted last attr breaks html parser

Relatively trimmed example HTML: <!DOCTYPE html><meta charset=UTF-8><div class=dark><section><p>Paragraph</section></div>

Not 100% sure what the cause is. Commented on the line in the commit that fails.

Multiple blocks of HTML

My use case is that it's hard to have 1 optimized/cleaned/minimal/critical CSS stylesheet per page. What I want is the super set of the CSS for GET /, GET /about, GET /support, GET /search. Each page uses Bootstrap/Bulma/Whatever but they all each have their own slight differences.

E.g. The original CSS looks like this:

div.foo { }
div.bar { }
div.baz { } 
div.woo { }

Every page has <div class="foo">. The home page (only) has <div class="bar">. The /about page (only) has <div class="baz">.

So, the ideal outcome should be:

div.foo { }
div.bar { }
div.baz { } 

css selector parser cannot advance past unsupported pseudo classes with parenthesis

Was just testing this with the CSS on apple.com for fun. The library will freeze and crash if given this css:

.anything-dash-body{
  font-weight:400;
}
.anything-dash-body:lang(ar){
  line-height: 1;
}

The library mistakenly thinks .anything-dash-body is a body tag and does not remove it which is "ok", but I don't think it should freeze and crash when failing. All other selectors, etc seem to be ok.

Older browsers support

This lib is not working in IE11 and other browsers that don't support m and y regexp flags.
#18 makes possible to polyfill it.

I think that dropcss is an awesome tool for collecting CSS usage at production, directly from the user browser, in requestIdleCallback or WebWorker. It's small and relatively fast. For projects with the big codebase and a lot of legacy, it's impossible to use it at build time.

Pseudo-elements parsing error

Several errors occur while parsing pseudo-elements (::first-letter).
This happens when "shouldDrop" returns "false".

There are 2 demo:
https://codepen.io/babakhin/pen/xorYMP
https://codepen.io/babakhin/pen/orwqNM

Error text:
TypeError: Cannot read property '2' of null at next (D:\web projects\test11\node_modules\dropcss\dist\dropcss.cjs.js:523:10) at parse$1 (D:\web projects\test11\node_modules\dropcss\dist\dropcss.cjs.js:566:5) at _export_some_ (D:\web projects\test11\node_modules\dropcss\dist\dropcss.cjs.js:795:48) at D:\web projects\test11\node_modules\dropcss\dist\dropcss.cjs.js:1056:32

PostCSS plugin

Im very impressed by your work - need more testing to see if it really is as good as advertised in Tailwind projects.

If it is, im creating this issue, maybe even for myself if it wont be done when i finish testing, to create postcss plugin to make it easier to plug into existing asset pipelines.

.. Or maybe you will just merge your performance fixes to purgecss as you did with some of them, and then there will be no need for it :)

decide whether to support escaping in selectors

Tailwind CSS uses "fun" conventions like this:

  • .xl\:w-1\/5
  • <div class="lg:w-1/4 xl:w-1/5 pl-6 pr-6 lg:pr-8">

need to test perf impact and if it can be rolled into the IDENT regex:

// now
/([\w*-]+)/iy

// perhaps
/((?:\\.|[\w*-])+)/iy

stripPseudos regexes would also need to change, along with stripping the escapes in the caches.

not sure if all of it is worthwhile.

see also: tailwindlabs/tailwindcss.com#161

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.