Security News
Weekly Downloads Now Available in npm Package Search Results
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
@joblift/used-styles
Advanced tools
Bundler and framework independent CSS part of SSR-friendly code splitting
Detects used css
files from the given HTML, and/or inlines critical styles. Supports sync or stream rendering.
Read more about critical style extraction and this library: https://dev.to/thekashey/optimising-css-delivery-57eh
strings
and streams
Works in two modes:
Critical style extraction:
.css
files, in your build
directory, extracting all style rules names.html
, finding all the classes
used.<styles>
or <links>
For the performance sake used-styles
inlines a bit more styles than it should - it inlines everything it would be "not
fast" to remove.
@keyframe
animationshtml, body
and other tag-based selectors (hello css-reset)@layer a,b,c
is used multiple timesSpeed, I am speed!
For the 516kb page, which needs 80ms to renderToString
(React) resulting time for the getCriticalRules
(very
expensive operation) would be around 4ms.
Use it to scan your dist
/build
folder to create a look up table between classNames and files they are described in.
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 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.
// 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/);
return match && +match[1];
});
⚠️ generally speaking - this approach working only unless there are no order-sensive styles from different chunks applied to a single DOM Element. Quite often it never happen, but if you are looking for a better way - follow to #26 ☣️
loadStyleDefinitions
is a "full control API", and can used to feed used-styles
with any custom data, for example
providing correct critical css extraction in dev mode (no files written on disk)return loadStyleDefinitions(
/*list of files*/ async () => cssFiles,
/*data loader*/ (file) => fetchTxt(`http://localhost:${process.env.DEV_SERVER_PORT}/${file}`)
/*filter and order */ // (file) => order.indexOf(cssToChunk[file])
);
Use to get used styled from render result or a stream
getUsedStyles(html, StyleDef): string[]
- returns all used files, you will need to import them
getCriticalStyles(html, StyleDef) : string
- returns all used selectors and other applicable rules, wrapped
with style
getCriticalRules(html, StyleDef): string
- the same, but without <style>
tag, letting you handle in a way you
want
createStyleStream(lookupTable, callback(fileName):void): TransformStream
- creates Transform stream - will
inject <links
createCriticalStyleStream(lookupTable, callback(fileName):void): TransformStream
- creates Transform stream - will
inject <styles
.
There are only two things about react:
getCriticalRules
which does not wrap result with style
letting you
do itimport { getCriticalRules } from 'used-styles';
const Header = () => (
<style data-used-styles dangerouslySetInnerHTML={{ __html: getCriticalRules(markup, styleData) }} />
);
import { enableReactOptimization } from 'used-styles';
enableReactOptimization(); // just makes it a but faster
There is nothing interesting here - just render, just getUsedStyles
.
import {discoverProjectStyles, getUsedStyles} from 'used-styles';
// generate lookup table on server start
const stylesLookup = discoverProjectStyles('./build');
async function MyRender() {
await stylesLookup;// it is "thenable"
// render App
const markup = ReactDOM.renderToString(<App/>)
const usedStyles = getUsedStyles(markup, stylesLookup);
usedStyles.forEach(style => {
const link = `<link rel="stylesheet" href="build/${style}">\n`;
// or
const link = `<link rel="prefetch" as="style" href="build/${style}">\n`;
// append this link to the header output or to the body
});
// or
const criticalCSS = getCriticalStyles(markup, stylesLookup);
// append this link to the header output
Any bulk CSS operations, both getCriticalStyles
and getUsedStyles
are safe and preserve the selector rule
order. You may combine both methods, to prefetch full styles, and inline critical CSS.
! Keep in mind - calling two functions is as fast, as calling a single one !
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, giving you the best Time-To-First-Byte. And the second byte.
Stream rendering could be interleaved(more efficient) or block(more predictable).
In case or React rendering you may use interleaved streaming, which would not delay TimeToFirstByte. It's quite similar how StyledComponents works
import {discoverProjectStyles, createLink, createStyleStream} from 'used-styles';
import MultiStream from 'multistream';
// generate lookup table on server start
const stylesLookup = discoverProjectStyles('./build'); // __dirname usually
// small utility for "readable" streams
const readableString = string => {
const s = new Readable();
s.push(string);
s.push(null);
s._read = () => true;
return s;
};
async function MyRender() {
// render App
const htmlStream = ReactDOM.renderToNodeStream(<App/>)
await stylesLookup;
// create a style steam
const styledStream = createStyleStream(stylesLookup, (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(stylesLookup); // <style>.myClass {...
// allow client to start loading js bundle
res.write(`<!DOCTYPE html><html><head><script defer src="client.js"></script>`);
const middleStream = readableString('</head><body><div id="root">');
const endStream = readableString('</head><body>');
// concatenate all steams together
const streams = [
middleStream, // end of a header, and start of a body
styledStream, // the main content
endStream, // closing tags
];
MultiStream(streams).pipe(res);
// start by piping react and styled transform stream
htmlStream.pipe(styledStream);
!! 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 out prior rehydration.
import { moveStyles } from 'used-styles/moveStyles';
moveStyles();
You might want to remove styles after rehydration to prevent duplication. Double check that corresponding real CSS is loaded.
import { removeStyles } from 'used-styles/moveStyles';
removeStyles();
Not sure this is a good idea
Idea is to:
initial line
to the browser, with the-main-script
insidestyles
html
between styles
and content
content
closing
tagsThat's all are streams, concatenated in a right order. It's possible to interleave them, but that's is not expected buy
a hydrate
.
import { discoverProjectStyles, createStyleStream, createLink } from 'used-styles';
import MultiStream from 'multistream';
// .....
// generate lookup table on server start
const lookup = await discoverProjectStyles('./build'); // __dirname usually
// small utility for "readable" streams
const readableString = (string) => {
const s = new Readable();
s.push(string);
s.push(null);
s._read = () => true;
return s;
};
// render App
const htmlStream = ReactDOM.renderToNodeStream(<App />);
// create a style steam
const styledStream = createStyleStream(lookup, (style) => {
// emit a line to header Stream
headerStream.push(createLink(`dist/${style}`));
// or
headerStream.push(`<link href="dist/${style}" rel="stylesheet">\n`);
});
// allow client to start loading js bundle
res.write(`<!DOCTYPE html><html><head><script defer src="client.js"></script>`);
const middleStream = readableString('</head><body><div id="root">');
const endStream = readableString('</head><body>');
// concatenate all steams together
const streams = [
headerStream, // styles
middleStream, // end of a header, and start of a body
styledStream, // the main content
endStream, // closing tags
];
MultiStream(streams).pipe(res);
// start by piping react and styled transform stream
htmlStream.pipe(styledStream, { end: false });
htmlStream.on('end', () => {
// kill header stream on the main stream end
headerStream.push(null);
styledStream.end();
});
This example is taken from Parcel-SSR-example from react-imported-component.
The advanced pattern described in Optimizing CSS Delivery article proposes to:
.css
files for recurringThis library does not provide a way to distinguish "one" cohort of customers from another, although, provides an API to optimize the delivery.
createCriticalStyleStream
/getCriticalStyles
to inline critical CSScreateStyleStream
/getUsedStyles
to use .css
filesalterProjectStyles
with filter
options to create two different sets of styles: not yet cache set
for critical
styles, and the cached ones for used
.Theoretically - all styles "critical" now, are "cached" ones next view.
Almost unmeasurable. It's a simple and single RegExp, which is not comparable to the React Render itself.
comparing with tools listed at Google's Optimize CSS Delivery
penthouse - a super slow puppetter based solution. No integration with a real run time renderer is possible. Generates one big style block at the beginning of a file.
critical - a super slow puppetter based solution. Able to extract critical style "above the fold".
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. 👎
critters-webpack-plugun - 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.
MIT
FAQs
Collect styles used on to create a page
We found that @joblift/used-styles demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Socket's package search now displays weekly downloads for npm packages, helping developers quickly assess popularity and make more informed decisions.
Security News
A Stanford study reveals 9.5% of engineers contribute almost nothing, costing tech $90B annually, with remote work fueling the rise of "ghost engineers."
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.