Skip to content

Commit

Permalink
Merge branch 'master' of github.com:theKashey/used-styles
Browse files Browse the repository at this point in the history
  • Loading branch information
theKashey committed Mar 22, 2020
2 parents dc7939d + 738f8f6 commit 5c61ccb
Showing 1 changed file with 46 additions and 58 deletions.
104 changes: 46 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<div align="center">
<h1>used-style</h1>
<br/>
Get all the styles, you have used to render a page.
Get all the styles, you have used to render a page.<br/>
(without any puppeteer involved)
<br/>
<br/>

Expand All @@ -16,70 +17,57 @@
Detects used `css` files from the given HTML, and/or __inlines critical styles__.
Supports sync or __stream__ rendering.

## Code splitting
This is all about code splitting, Server Side Rendering and React, even if React has nothing to do with this library.
Read more about critical style extraction and this library: https://dev.to/thekashey/optimising-css-delivery-57eh

Code splitting is a good feature, and SSR is also awesome, but then you have
to load all the used `scripts` on the client, before making a page alive.
Everybody is talking not about `.css`, but only about `.js`.
- 🚀 Super Fast - no browser, no jsdom, no runtime transformations
- 💪 API - it's no more than an API - integrates with everything
- 🤝 Works with `strings` and `streams`
- ⏳ Helps preloading for the "real" style files

That's done, in a different ways. That's not a big task, as long as the _usage_ of code splitted blocks
is _trackable_ - you are using it, and components defined inside.
Works in two modes:
- 🚙 inlines style __rules__ required to render given HTML - ideal for the first time visitor
- 🏋️‍♀️inlines style __files__ required to render given HTML - ideal for the second time visitor (and code splitting)

CSS is harder - you might just use random classes and what next? You are just importing CSS here and where,
sometimes indirectly, and there is no way to understand what's happening.
Critical style extraction:
- 🧱 will all all used styles at the beginning of your page in a __string__ mode
- 💉 will _interleave_ HTML and CSS in a __stream__ mode. This is the best experience possible

> While it's possible for webpack to add a `Link` to document header once some `Component` uses some `Style`,
you can't do the same in the __concurrent__ server environment - there is no <head/> to add a Link.

Code splitting libraries solved it straight forward - by building resource graph, and fetching all
bound resources to that graph, but tracking is hard, and quite bound to the bundler, and could delay content sending.

## Solution
1. Scan all `.css` files, extracting all the style names.
2. Scan resulting `html`, finding all the `classNames` used.
3a. Calculate all styles you need to render a given HTML.
3b. Calculate all the files you shall send to a client.
4. Inject styles or links
5. Hoist or remove styles on clientside startup

> Bonus: Do the same for streams.
> Bonus: Do it only for really `used styled`, not just imported somewhere.
## How it works
1. Scans all `.css` files, in your `build` directory, extracting all style rules names.
2. Scans a given `html`, finding all the `classes` used.
3. Here there are two options:
3a. Calculate all __style rules__ you need to render a given HTML.
3b. Calculate all the style __files__ you have send to a client.
4. Injects `<styles>` or `<links>`
5. After the page load, hoist or removes critical styles replacing them by the "real" ones.

## Limitation
In the performance sake `used-styles` inlines a bit more styles than it should -
it's just harder to understand what shall be done.
For the performance sake `used-styles` inlines a bit more styles than it should - it inlines everything it would be "not fast" to remove.
- inlines all `@keyframe` animations
- inlines all `html, body` and other simple selectors (aka css-reset)
- inlines all rules matching last part of a selector

> And, hopefully
- __inlines all classes used in HTML code__

- inlines all `html, body` and other tag-based selectors (hello css-reset)
- inlines all rules matching last part of a selector (`.a .b` would be included if `.b` was used but `.a` was not)

### Speed
>Speed, I am speed!
For the 516kb page, which needs 80ms to renderToString resulting time for `getCriticalRules`(very expensive operation)
would be around __4ms__.
For the 516kb page, which needs __80ms__ to `renderToString`(React) resulting time for the `getCriticalRules`(very expensive operation) would be around __4ms__.

# API
## Discovery API
Use to scan your `dist` folder to create a look up table between classNames and files they are described in.
Use to scan your `dist`/`build` folder to create a look up table between classNames and files they are described in.

1. `discoverProjectStyles(buildDirrectory, [filter]): StyleDef` - generates class lookup table
> you may use the second argument to control which files should be scanned
`filter` is very important function here. It takes `fileName` as input, and returns
`false`, `true`, or a `number` as result. `False` value would exclude chunk from the set, while `number`
would change the order of the chunk.
Keeping chunk ordering "as expected" is required to preserve style declaration order, which is important for many
`false`, `true`, or a `number` as result. `False` value would exclude this file from the set, `true` - add it, and `number`
would change __the order__ of the chunk.
Keeping chunk ordered "as expected" is required to preserve style declaration order, which is important for many
existing styles.

```js
// with chunk format [chunkhash]_[id]
// with chunk format [chunkhash]_[id] lower ids are potentialy should be defined before higher
const styleData = discoverProjectStyles(resolve('build'), name => {
// get ID of a chunk and use it as order hint
const match = name.match(/(\d)_c.css/);
Expand Down Expand Up @@ -113,7 +101,7 @@ const Header = () => (
```js
import {enableReactOptimization} from 'used-styles';

enableReactOptimization(); //
enableReactOptimization(); // just makes it a but faster
```

# Example
Expand Down Expand Up @@ -150,24 +138,22 @@ You __may combine__ both methods, to prefetch full styles, and inline critical C
! Keep in mind - calling two functions is as fast, as calling a single one !
### Stream rendering
Please keep in mind - stream rendering in `NOT SAFE` in terms of CSS, as long as __it might affect the ordering of selectors__.
Only pure BEM and Atomic CSS are "safe", _just some CSS_ would not be compatible.
Please keep in mind - stream rendering in __NOT SAFE__ in terms of CSS, as long as __it might affect the ordering of selectors__.
Only pure BEM and Atomic CSS are "safe", _just some random CSS_ might be not compatible.
Please __test__ results before releasing into production.
> If you do not understand why and how selector order is important - please __do not use__ stream transformer.
Stream rendering is much harder, and much more efficient.
The idea is to make it efficient, and not delay Time-To-First-Byte. And the second byte.
Stream rendering is much harder, and much more efficient, giving you the best Time-To-First-Byte. And the second byte.
Stream rendering could be interleaved(more efficient) or block(more predictable).
### Interleaved Stream rendering
In case or React rendering you may use __interleaved streaming__, which would not delay TimeToFirstByte.
It's quite similar how StyledComponents works
```js
import {discoverProjectStyles, createLink} from 'used-styles';
import {createStyleStream} from 'used-styles/react';
import {discoverProjectStyles, createLink, createStyleStream} from 'used-styles';
import MultiStream from 'multistream';

// generate lookup table on server start
Expand All @@ -187,12 +173,12 @@ async function MyRender() {
const lookup = await stylesLookup;
// create a style steam
const styledStream = createStyleStream(lookup, (style) => {
// _return_ link tag, and it will be appended to the stream output
return createLink(`dist/${style}`)
});
// _return_ link tag, and it will be appended to the stream output
return createLink(`dist/${style}`) // <link href="dist/mystyle.css />
});

// or create critical CSS stream - it will inline all styles
const styledStream = createCriticalStyleStream(projectStyles);
// or create critical CSS stream - it will inline all styles
const styledStream = createCriticalStyleStream(projectStyles); // <style>.myClass {...

// allow client to start loading js bundle
res.write(`<!DOCTYPE html><html><head><script defer src="client.js"></script>`);
Expand All @@ -213,10 +199,10 @@ MultiStream(streams).pipe(res);
htmlStream.pipe(styledStream);
```
__!! THIS IS NOT THE END !!__ Interleaving links and react output would produce break client side rehydration,
as long as _injected_ links are not rendered by React, and not expected to present in the "result" HTML code.
__!! THIS IS NOT THE END !!__ Interleaving links and react output would break a client side rehydration,
as long as _injected_ links were not rendered by React, and not expected to present in the "result" HTML code.
You have to move injected styles prior rehydration.
You have to move injected styles out prior rehydration.
```js
import { moveStyles } from 'used-styles/moveStyles';
moveStyles();
Expand Down Expand Up @@ -318,7 +304,9 @@ Almost unmeasurable. It's a simple and single RegExp, which is not comparable to
- [critical](https://github.com/addyosmani/critical) - a super slow puppetter based solution. Able to extract critical style "above the fold".
- [inline-critical](https://github.com/bezoerb/inline-critical) - slow jsdom based solution. Generates one big style block at the beginning of a file, and replaces all other `links` by async variants. However, it does not detect any critical or used styles in provided HTML - HTML is used only as a output target. 👎
`used-styles` is faster that libraries listed above, optimized for multiple runs, as well as
- [critters-webpack-plugun](https://github.com/GoogleChromeLabs/critters) - is the nearest analog of used-styles, build on almost same principles.
`used-styles` is faster that libraries listed above, and optimized for multiple runs.
# License
MIT

0 comments on commit 5c61ccb

Please sign in to comment.