{"id":13727191,"url":"https://github.com/theKashey/used-styles","last_synced_at":"2025-05-07T22:30:54.313Z","repository":{"id":34092218,"uuid":"169651108","full_name":"theKashey/used-styles","owner":"theKashey","description":"📝All the critical styles you've used to render a page.","archived":false,"fork":false,"pushed_at":"2024-06-30T05:37:59.000Z","size":798,"stargazers_count":139,"open_issues_count":19,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-05-06T10:46:38.522Z","etag":null,"topics":["codesplitting","critical-css","css","inlines-critical-styles","server","ssr"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/theKashey.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-07T22:14:22.000Z","updated_at":"2025-04-13T07:42:01.000Z","dependencies_parsed_at":"2023-11-14T04:27:07.857Z","dependency_job_id":"f343b5a4-5385-488e-b1d4-299d093e1faf","html_url":"https://github.com/theKashey/used-styles","commit_stats":{"total_commits":143,"total_committers":8,"mean_commits":17.875,"dds":"0.23076923076923073","last_synced_commit":"270067caa13263c07d55c3dac5d99539c564cc94"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fused-styles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fused-styles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fused-styles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/theKashey%2Fused-styles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/theKashey","download_url":"https://codeload.github.com/theKashey/used-styles/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252965236,"owners_count":21832848,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codesplitting","critical-css","css","inlines-critical-styles","server","ssr"],"created_at":"2024-08-03T01:03:43.547Z","updated_at":"2025-05-07T22:30:53.274Z","avatar_url":"https://github.com/theKashey.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ch1\u003eused-styles\u003c/h1\u003e\n  \u003cbr/\u003e\n  Get all the styles, you have used to render a page.\u003cbr/\u003e\n  (without any puppeteer involved)\n  \u003cbr/\u003e\n  \u003cbr/\u003e\n\n[![Build Status](https://travis-ci.org/theKashey/used-styles.svg?branch=master)](https://travis-ci.org/theKashey/used-styles)\n[![NPM version](https://img.shields.io/npm/v/used-styles.svg)](https://www.npmjs.com/package/used-styles)\n\n\u003c/div\u003e\n\n\u003e 👋**Version 3** migration notice: `import { discoverProjectStyles } from 'used-styles/node'`. That's it\n\n---\n\n\u003e Bundler and framework independent CSS part of SSR-friendly code splitting\n\nDetects used `css` files from the given HTML, and/or **inlines critical styles**. Supports sync or **stream** rendering.\n\nRead more about critical style extraction and this library: https://dev.to/thekashey/optimising-css-delivery-57eh\n\n- 🚀 Super Fast - no browser, no jsdom, no runtime transformations\n- 💪 API - it's no more than an API - integrates with everything\n- 🤝 Works with `strings` and `streams`\n- ⏳ Helps preloading for the \"real\" style files\n\nWorks in two modes:\n\n- 🚙 inlines style **rules** required to render given HTML - ideal for the first time visitor\n- 🏋️‍♀️inlines style **files** required to render given HTML - ideal for the second time visitor (and code splitting)\n\nCritical style extraction:\n\n- 🧱 will _load_ all used styles at the beginning of your page in a **string** mode\n- 💉 will _interleave_ HTML and CSS in a **stream** mode. This is the best experience possible\n\n## How it works\n\n1. Scans all `.css` files, in your `build` directory, extracting all style rules names.\n2. Scans a given `html`, finding all the `classes` used.\n3. Here there are two options:\n   3a. Calculate all **style rules** you need to render a given HTML. 3b. Calculate all the style **files** you have\n   send to a client.\n4. Injects `\u003cstyles\u003e` or `\u003clinks\u003e`\n5. After the page load, hoist or removes critical styles replacing them by the \"real\" ones.\n\n## Limitation\n\nFor the performance sake `used-styles` inlines a bit more styles than it should - it inlines everything it would be \"not\nfast\" to remove.\n\n- inlines all `@keyframe` animations\n- inlines all `html, body` and other tag-based selectors (hello css-reset)\n- undefined behavior if `@layer a,b,c` is used multiple times\n\n### Speed\n\n\u003e Speed, I am speed!\n\nFor the 516kb page, which needs **80ms** to `renderToString`(React) resulting time for the `getCriticalRules`(very\nexpensive operation) would be around **4ms**.\n\n# API\n\n## Discovery API\n\nUse it to scan your `dist`/`build` folder to create a look up table between classNames and files they are described in.\n\n1. `discoverProjectStyles(buildDirrectory, [filter]): StyleDef` - generates class lookup table\n   \u003e you may use the second argument to control which files should be scanned\n\n`filter` is very important function here. It takes `fileName` as input, and returns\n`false`, `true`, or a `number` as result. `False` value would exclude this file from the set, `true` - add it,\nand `number`\nwould change **the order** of the chunk. Keeping chunk ordered \"as expected\" is required to preserve style declaration\norder, which is important for many existing styles.\n\n```js\n// with chunk format [chunkhash]_[id] lower ids are potentialy should be defined before higher\nconst styleData = discoverProjectStyles(resolve('build'), (name) =\u003e {\n  // get ID of a chunk and use it as order hint\n  const match = name.match(/(\\d)_c.css/);\n  return match \u0026\u0026 +match[1];\n});\n```\n\n\u003e ⚠️ generally speaking - this approach working only unless there are no order-sensive styles from different chunks applied to a single DOM Element.\n\u003e Quite often it never happen, but if you are looking for a better way - follow to [#26](https://github.com/theKashey/used-styles/issues/26) ☣️\n\n1. `loadStyleDefinitions` is a \"full control API\", and can used to feed `used-styles` with any custom data, for example\n   providing correct critical css extraction in dev mode (no files written on disk)\n\n```ts\nreturn loadStyleDefinitions(\n  /*list of files*/ async () =\u003e cssFiles,\n  /*data loader*/ (file) =\u003e fetchTxt(`http://localhost:${process.env.DEV_SERVER_PORT}/${file}`)\n  /*filter and order */ // (file) =\u003e order.indexOf(cssToChunk[file])\n);\n```\n\n## Scanners\n\nUse to get used styled from render result or a stream\n\n2. `getUsedStyles(html, StyleDef): string[]` - returns all used **files**, you will need to import them\n3. `getCriticalStyles(html, StyleDef) : string` - returns all used selectors and other applicable rules, wrapped\n   with `style`\n4. `getCriticalRules(html, StyleDef): string` - **the same**, but without `\u003cstyle\u003e` tag, letting you handle in a way you\n   want\n\n5. `createStyleStream(lookupTable, callback(fileName):void): TransformStream` - creates Transform stream - will\n   inject `\u003clinks`\n6. `createCriticalStyleStream(lookupTable, callback(fileName):void): TransformStream` - creates Transform stream - will\n   inject `\u003cstyles`.\n\n### React\n\nThere are only two things about react:\n\n1. to inline critical styles use another helper - `getCriticalRules` which does not wrap result with `style` letting you\n   do it\n\n```js\nimport { getCriticalRules } from 'used-styles';\n\nconst Header = () =\u003e (\n  \u003cstyle data-used-styles dangerouslySetInnerHTML={{ __html: getCriticalRules(markup, styleData) }} /\u003e\n);\n```\n\n2. React produces more _valid_ code, and you might enable optimistic optimization, making used-styles a bit faster.\n\n```js\nimport { enableReactOptimization } from 'used-styles';\n\nenableReactOptimization(); // just makes it a but faster\n```\n\n## Serialize API\n\nUse it to separate generation of styles lookup from your runtime.\n\nIt is useful in cases, where you can't directly use Discovery APIs on your client CSS bundles during app's runtime, e.g. various serverless runtimes.\nAlso it may be useful for you, if you want to save on the size of your container for the server app, since it allows you to only load styles lookup into it, without CSS bundles.\n\n1. `serializeStylesLookup(def: StyleDef): SerializedStyleDef` - creates a serializable object from original styles lookup. Result can be then stringified with `JSON.stringify`\n2. `loadSerializedLookup(def: SerializedStyleDef): StyleDef` - transforms serialized style definition back to normal `StyleDef`, which can be used with any Scanner API\n\n### Example\n\n#### During your build\n\n1. Add separate script to generate style lookup and store it as you like.\n\n```js\n// project/scripts/generate_styles_lookup.mjs\nimport { serializeStylesLookup, discoverProjectStyles } from 'used-styles';\nimport { writeFileSync } from 'fs';\n\nconst stylesLookup = discoverProjectStyles('./path/to/dist/client');\n\nawait stylesLookup;\n\nwriteFileSync('./path/to/dist/server/styles-lookup.json', JSON.stringify(serializeStylesLookup(lookup)));\n```\n\n2. Run this code after your build\n\n```sh\nyarn build\nnode ./scripts/generate_styles_lookup.mjs\n```\n\nNotice, that you can store serialized lookup in any way, that suits you and your case, example above is not the only valid option.\n\n#### During your runtime\n\n1. Access previously created and stored styles lookup, convert it to `StyleDef` with `loadSerializedLookup` and use it normally\n\n```js\nimport { loadSerializedLookup } from 'used-styles';\n\nconst stylesLookup = loadSerializedLookup(require('./dist/server/styles-lookup.json'));\n\n// ...\n\ngetCriticalStyles(markup, stylesLookup);\n```\n\n# Example\n\n## Demo\n\n- [React SSR](/example/ssr-react/README.md)\n- [React SSR + TS](/example/ssr-react-ts/README.md)\n- [React Streaming SSR](/example/ssr-react-streaming/README.md)\n- [React Streaming SSR + TS](/example/ssr-react-streaming-ts/README.md)\n\n## Static rendering\n\nThere is nothing interesting here - just render, just `getUsedStyles`.\n\n```js\nimport {getUsedStyles} from 'used-styles';\nimport {discoverProjectStyles} from 'used-styles/node';\n\n\n// generate lookup table on server start\nconst stylesLookup = discoverProjectStyles('./build');\n\nasync function MyRender() {\n  await stylesLookup;// it is \"thenable\"\n  // render App\n  const markup = ReactDOM.renderToString(\u003cApp/\u003e)\n  const usedStyles = getUsedStyles(markup, stylesLookup);\n\n  usedStyles.forEach(style =\u003e {\n    const link = `\u003clink  rel=\"stylesheet\" href=\"build/${style}\"\u003e\\n`;\n    // or\n    const link = `\u003clink rel=\"prefetch\" as=\"style\" href=\"build/${style}\"\u003e\\n`;\n    // append this link to the header output or to the body\n  });\n\n// or\n\n  const criticalCSS = getCriticalStyles(markup, stylesLookup);\n\n// append this link to the header output\n```\n\nAny _bulk_ CSS operations, both `getCriticalStyles` and `getUsedStyles` **are safe** and preserve the selector rule\norder. You **may combine** both methods, to prefetch full styles, and inline critical CSS.\n\n! Keep in mind - calling two functions is as fast, as calling a single one !\n\n### Stream rendering\n\nPlease keep in mind - stream rendering in **NOT SAFE** in terms of CSS, as long as **it might affect the ordering of\nselectors**. Only pure BEM and Atomic CSS are \"safe\", _just some random CSS_ might be not compatible. Please **test**\nresults before releasing into production.\n\n\u003e If you do not understand why and how selector order is important - please **do not use** stream transformer.\n\nStream rendering is much harder, and much more efficient, giving you the best Time-To-First-Byte. And the second byte.\n\nStream rendering could be interleaved(more efficient) or block(more predictable).\n\n### Interleaved Stream rendering\n\nIn case or React rendering you may use **interleaved streaming**, which would not delay TimeToFirstByte. It's quite\nsimilar how StyledComponents works\n\n```js\nimport express from 'express';\nimport { discoverProjectStyles } from 'used-styles/node';\nimport { loadStyleDefinitions, createCriticalStyleStream, createStyleStream, createLink } from 'used-styles';\n\nconst app = express();\n\n// generate lookup table on server start\nconst stylesLookup = isProduction\n  ? discoverProjectStyles('./dist/client')\n  : // load styles for development\n    loadStyleDefinitions(async () =\u003e []);\n\napp.use('*', async (req, res) =\u003e {\n  await stylesLookup;\n\n  try {\n    const renderApp = (await import('./dist/server/entry-server.js')).default;\n\n    // create a style steam\n    const styledStream = createStyleStream(stylesLookup, (style) =\u003e {\n      // _return_ link tag, and it will be appended to the stream output\n      return createLink(`dist/${style}`); // \u003clink href=\"dist/mystyle.css /\u003e\n    });\n\n    // or create critical CSS stream - it will inline all styles\n    const styledStream = createCriticalStyleStream(stylesLookup); // \u003cstyle\u003e.myClass {...\n\n    await renderApp({ res, styledStream });\n  } catch (err) {\n    res.sendStatus(500);\n  }\n});\n```\n\n```js\n// entry-server.js\nimport React from 'react';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport App from './App';\n\nconst ABORT_DELAY = 10000;\n\nasync function renderApp({ res, styledStream }) {\n  let didError = false;\n\n  const { pipe, abort } = renderToPipeableStream(\n    \u003cReact.StrictMode\u003e\n      \u003cApp /\u003e\n    \u003c/React.StrictMode\u003e,\n    {\n      onShellError() {\n        res.sendStatus(500);\n      },\n      onAllReady() {\n        res.status(didError ? 500 : 200);\n        res.set({ 'Content-Type': 'text/html' });\n\n        // allow client to start loading js bundle\n        res.write(`\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003cscript defer src=\"client.js\"\u003e\u003c/script\u003e\u003c/head\u003e\u003cbody\u003e\u003cdiv id=\"root\"\u003e`);\n\n        styledStream.pipe(res, { end: false });\n\n        // start by piping react and styled transform stream\n        pipe(styledStream);\n\n        styledStream.on('end', () =\u003e {\n          res.end('\u003c/div\u003e\u003c/body\u003e\u003c/html\u003e');\n        });\n      },\n      onError(error) {\n        didError = true;\n        console.error(error);\n      },\n    }\n  );\n\n  setTimeout(() =\u003e {\n    abort();\n  }, ABORT_DELAY);\n}\n\nexport default renderApp;\n```\n\n**!! THIS IS NOT THE END !!** Interleaving links and react output would break a client side rehydration, as long as _\ninjected_ links were not rendered by React, and not expected to present in the \"result\" HTML code.\n\nYou have to move injected styles out prior rehydration.\n\n```js\nimport React from 'react';\nimport { moveStyles } from 'used-styles/moveStyles';\nimport ReactDOM from 'react-dom/client';\nimport App from './App';\n\n// Call before `ReactDOM.hydrateRoot`\nmoveStyles();\n\nReactDOM.hydrateRoot(\n  document.getElementById('root'),\n  \u003cReact.StrictMode\u003e\n    \u003cApp /\u003e\n  \u003c/React.StrictMode\u003e\n);\n```\n\nYou might want to remove styles after rehydration to prevent duplication. Double check that corresponding _real_ CSS is\nloaded.\n\n```js\nimport { removeStyles } from 'used-styles/moveStyles';\n\nremoveStyles();\n```\n\n## Block rendering\n\n\u003e Not sure this is a good idea\n\nIdea is to:\n\n- push `initial line` to the browser, with `the-main-script` inside\n- push all used `styles`\n- push some `html` between `styles` and `content`\n- push `content`\n- push `closing` tags\n\nThat's all are streams, concatenated in a right order. It's possible to interleave them, but that's is not expected buy\na `hydrate`.\n\n```js\nimport { createStyleStream, createLink } from 'used-styles';\nimport { discoverProjectStyles } from 'used-styles/node';\nimport MultiStream from 'multistream';\n\n// .....\n// generate lookup table on server start\nconst lookup = await discoverProjectStyles('./build'); // __dirname usually\n\n// small utility for \"readable\" streams\nconst readableString = (string) =\u003e {\n  const s = new Readable();\n  s.push(string);\n  s.push(null);\n  s._read = () =\u003e true;\n  return s;\n};\n\n// render App\nconst htmlStream = ReactDOM.renderToNodeStream(\u003cApp /\u003e);\n\n// create a style steam\nconst styledStream = createStyleStream(lookup, (style) =\u003e {\n  // emit a line to header Stream\n  headerStream.push(createLink(`dist/${style}`));\n  // or\n  headerStream.push(`\u003clink href=\"dist/${style}\" rel=\"stylesheet\"\u003e\\n`);\n});\n\n// allow client to start loading js bundle\nres.write(`\u003c!DOCTYPE html\u003e\u003chtml\u003e\u003chead\u003e\u003cscript defer src=\"client.js\"\u003e\u003c/script\u003e`);\n\nconst middleStream = readableString('\u003c/head\u003e\u003cbody\u003e\u003cdiv id=\"root\"\u003e');\nconst endStream = readableString('\u003c/head\u003e\u003cbody\u003e');\n\n// concatenate all steams together\nconst streams = [\n  headerStream, // styles\n  middleStream, // end of a header, and start of a body\n  styledStream, // the main content\n  endStream, // closing tags\n];\n\nMultiStream(streams).pipe(res);\n\n// start by piping react and styled transform stream\nhtmlStream.pipe(styledStream, { end: false });\nhtmlStream.on('end', () =\u003e {\n  // kill header stream on the main stream end\n  headerStream.push(null);\n  styledStream.end();\n});\n```\n\n\u003e This example is taken from [Parcel-SSR-example](https://github.com/theKashey/react-imported-component/tree/master/examples/SSR/parcel-react-ssr)\n\u003e from **react-imported-component**.\n\n# Hybrid usage\n\nThe advanced pattern described in [Optimizing CSS Delivery](https://dev.to/thekashey/optimising-css-delivery-57eh)\narticle proposes to:\n\n- inline critical CSS for a first time customers\n- use cached `.css` files for recurring\n\nThis library does not provide a way to distinguish \"one\" cohort of customers from another, although, provides an API to\noptimize the delivery.\n\n- use `createCriticalStyleStream`/`getCriticalStyles` to **inline** critical CSS\n- use `createStyleStream`/`getUsedStyles` to use `.css` files\n- use `alterProjectStyles` with `filter` options to create two different sets of styles: not yet _cache_ set\n  for `critical` styles, and the _cached_ ones for `used`.\n- yes - you have to use or two transformers, or call two functions, one after another.\n\n\u003e Theoretically - all styles \"critical\" now, are \"cached\" ones next view.\n\n# Performance\n\nAlmost unmeasurable. It's a simple and single RegExp, which is not comparable to the React Render itself.\n\n# Comparison\n\n\u003e comparing with tools listed at [Google's Optimize CSS Delivery](https://developers.google.com/web/tools/lighthouse/audits/unused-css#inlining)\n\n- [penthouse](https://github.com/pocketjoso/penthouse) - a super slow puppetter based solution. No integration with a\n  real run time renderer is possible. Generates one big style block at the beginning of a file.\n- [critical](https://github.com/addyosmani/critical) - a super slow puppetter based solution. Able to extract critical\n  style \"above the fold\".\n- [inline-critical](https://github.com/bezoerb/inline-critical) - slow jsdom based solution. Generates one big style\n  block at the beginning of a file, and replaces all other `links` by async variants. However, it does not detect any\n  critical or used styles in provided HTML - HTML is used only as a output target. 👎\n\n- [critters-webpack-plugun](https://github.com/GoogleChromeLabs/critters) - is the nearest analog of used-styles, build\n  on almost same principles.\n\n`used-styles` is faster that libraries listed above, and optimized for multiple runs.\n\n# License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FtheKashey%2Fused-styles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FtheKashey%2Fused-styles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FtheKashey%2Fused-styles/lists"}