{"_id":"@anoyomoose/q2-css-shake","name":"@anoyomoose/q2-css-shake","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@anoyomoose/q2-css-shake","version":"0.1.0","description":"CSS tree-shaking for Quasar — strips unused component styles and icons from build output","license":"MIT","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","bin":{"q2-css-shake":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","shake":"node dist/cli.js"},"peerDependencies":{"vite":"^5.4.3 || ^6.0.0 || ^7.0.0 || ^8.0.0"},"peerDependenciesMeta":{"vite":{"optional":true}},"devDependencies":{"@types/node":"^22.0.0","tsup":"^8.0.0","typescript":"^5.0.0","vite":"^6.0.0","vitest":"^3.0.0"},"_id":"@anoyomoose/q2-css-shake@0.1.0","gitHead":"6b2bd33ba9d9a08ca82b6d21ab03fb901e75af05","_nodeVersion":"25.6.1","_npmVersion":"11.3.0","dist":{"integrity":"sha512-SE1zQDrtim8Q/sUvaOx54Ms4D2WrYnchTrjLIcbBaqYejT1SdfPigppCmI9xEwHI1lEIEeFyy8Wk1ZQVLnHQfg==","shasum":"577f5cf9e27f7218fb74c0fef56d88ab5bb73dfc","tarball":"https://registry.npmjs.org/@anoyomoose/q2-css-shake/-/q2-css-shake-0.1.0.tgz","fileCount":15,"unpackedSize":206848,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDZUqRZHNDGXLXvvJJXkizO0sWbylP9SM9UosMD98OXhAIhAPNKjIPA5nvb1gZvxjyyOjYhu+9Y8Yu6ZQrVp3vgjpQg"}]},"_npmUser":{"name":"anoyomoose","email":"anoyomoose@proton.me"},"directories":{},"maintainers":[{"name":"anoyomoose","email":"anoyomoose@proton.me"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/q2-css-shake_0.1.0_1774854989130_0.020998738353560364"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-30T07:16:29.027Z","0.1.0":"2026-03-30T07:16:29.293Z","modified":"2026-03-30T07:16:29.554Z"},"maintainers":[{"name":"anoyomoose","email":"anoyomoose@proton.me"}],"description":"CSS tree-shaking for Quasar — strips unused component styles and icons from build output","license":"MIT","readme":"# @anoyomoose/q2-css-shake\n\nCSS tree-shaking for Quasar Framework. Strips unused component styles and icon font glyphs from production builds.\n\nQuasar ships CSS for all 100+ components, and icon packs like MDI v7 include 7,000+ glyph definitions. Most applications use a fraction of these. This plugin analyzes your production bundle to determine which components and icons are actually used, then removes the rest from the CSS output.\n\n## Results\n\nTypical reductions on real applications (uncompressed):\n\n| Target                    | Before | After  | Reduction |\n|---------------------------|--------|--------|-----------|\n| Component CSS             | 197 kB | 106 kB | 46%       |\n| MDI v7 icon CSS           | 418 kB | 7 kB   | 98%       |\n| Quasar's UI Playground    | 836 kB | 355 kB | 58%       |\n| One of our own dashboards | 522 kB | 161 kB | 69%       |\n\nUtility classes are *not* stripped as their usage is hard to detect, and Quasar adds them dynamically. This is about half of Quasar's built-in CSS. Perhaps something for a future version.\n\nNote that the actual utility of this is debatable as CSS files are mostly served gzipped, and while the savings are similar expressed in percentages, in absolute terms it saves perhaps 100 kB on a large project or 30 kB on a small one.\n\nThis was built mostly to see if it could be done and what the result would be on our own projects, your mileage may vary. This is not\nan officially supported Quasar plugin, and may break with future Quasar versions.\n\n## Installation\n\n```bash\npnpm add -D @anoyomoose/q2-css-shake\n```\n\n## Vite Plugin\n\n### Quasar CLI (`quasar.config.js` / `quasar.config.ts`)\n\n```js\nimport { cssShakePlugin } from '@anoyomoose/q2-css-shake'\n\nexport default defineConfig(() => ({\n  build: {\n    vitePlugins: [\n      cssShakePlugin({\n        scan: [\n          'quasar/src/components',\n        ],\n        icons: true,\n        debug: true,\n      })\n    ],\n  },\n}))\n```\n\n### Plain Vite (`vite.config.ts`)\n\n```ts\nimport { cssShakePlugin } from '@anoyomoose/q2-css-shake'\n\nexport default defineConfig({\n  plugins: [\n    cssShakePlugin({\n      scan: ['quasar/src/components'],\n      icons: true,\n    })\n  ],\n})\n```\n\n## Options\n\n### `scan` (required)\n\nArray of package subpaths to scan for component files. Each entry is resolved from your project root using Node module resolution.\n\n```js\nscan: [\n  'quasar/src/components',  // always include this\n  '@anoyomoose/q2-fresh-paint-md3e/dist/components',  // if you're using it\n]\n```\n\nThe plugin recursively scans these directories for files matching the PascalCase component naming convention (e.g. `QBtn.js`, `Md3eBtn.js`), converts them to BEM format (`q-btn`, `md3e-btn`), and builds the \"known components\" list.\n\nDuring the production build, the plugin checks which of these component modules actually appear in the bundled chunks. CSS rules targeting components that were tree-shaken from the JS are stripped from the CSS output.\n\n**IMPORTANT** Barrel imports for third-party components are not tracked. If you import components from an `index.js` which in turn imports all individual components from their source files, all of them will be seen as used. Tracking only works if you directly import from the file that defines the target component **only**. This is not an issue for Quasar's own included components in the default setup if you import from `'quasar'` or don't explicitly import at all. Quasar's Vite plugin rewrites the import statements in the supported format automatically.   \n\n### `icons` (default: `false`)\n\nEnable icon glyph CSS shaking. When `true`, the plugin:\n\n1. Resolves `@quasar/extras` from your project and discovers all installed icon packs (any directory containing an `icons.json` file).\n2. Parses the icon CSS files to identify glyph rules -- rules whose body contains only a `content` or `--fa` property with a string value.\n3. During bundling, scans the JS output for icon name strings (e.g. `\"mdi-account\"`, `\"fa-check\"`).\n4. Strips glyph rules for icons that don't appear anywhere in the JS.\n\nUtility classes (sizing, animation, transforms) are never stripped -- only glyph definitions.\n\nSupported icon packs: MDI (v3--v7), FontAwesome (v5, v6), Bootstrap Icons, Eva Icons, Ionicons v4, Line Awesome, Themify. Material Icons and Material Symbols use font ligatures and have no per-icon CSS, so they are unaffected.\n\n**IMPORTANT** This cannot detect dynamic icon names (`mdi-${name}`) and is therefore disabled by default. Any icons used dynamically need to be added to `keep`. Probably not an issue for many projects, but you should definitely do a build and check if you're missing any icons before using this in production. \n\n### `keep` (default: `[]`)\n\nArray of component or icon names to exclude from shaking. Accepts PascalCase (`QBtn`) or BEM format (`q-btn`). Use this for components or icons referenced dynamically in ways the plugin cannot detect.\n\n```js\nkeep: ['QFormChildMixin', 'mdi-loading']\n```\n\n### `debug` (default: `false`)\n\nLog detailed information to the console during builds:\n\n- Known and used component lists\n- Icon pack discovery results and prefix detection\n- Per-file size before and after, with percentage reduction\n\n## How It Works\n\nThe plugin operates in Vite's `generateBundle` hook, after tree-shaking and code splitting but before the final output is written to disk.\n\n**Component shaking:** The plugin inspects `chunk.modules` in each JS chunk to determine which component source files survived tree-shaking. It then runs a CSS transform pass that parses every CSS asset, evaluates each rule's selector against the known/used component lists, and strips rules for unused components. Selectors containing functional pseudo-classes (`:not()`, `:has()`), attribute selectors, or other complex syntax are left untouched to avoid false positives.\n\n**Icon shaking:** The plugin uses a `renderChunk` hook to scan the unminified JS for icon name strings before minification obscures them. It uses an efficient prefix-based search (one `indexOf` scan per icon prefix, not per icon name). In `generateBundle`, it runs a second CSS transform pass targeting glyph rules specifically.\n\nBoth passes use the same underlying CSS parser, which handles minified and unminified CSS, quoted strings, comments, nested braces, and all the edge cases of real-world stylesheets.\n\n## CLI\n\nA command-line tool is included for debugging and testing the CSS transform outside of a build.\n\n### Scan a directory for components\n\n```bash\nq2-css-shake node_modules/quasar/src/components\n# Output: q-ajax-bar,q-avatar,q-badge,...\n```\n\n### Strip unused component CSS\n\n```bash\nq2-css-shake input.css --known q-btn,q-card,q-field --used q-btn > output.css\n```\n\n- `--known` -- all component BEM prefixes the tool should reason about\n- `--used` -- the subset that are actually used\n\n### Strip unused icon CSS\n\n```bash\nq2-css-shake mdi-v7.css --js bundle.js > output.css\n```\n\nThe tool extracts glyph names from the CSS, scans the JS file for matching strings, and strips unused glyphs.\n\n### Combined\n\n```bash\nq2-css-shake quasar.css --known q-btn,q-card --used q-btn --js bundle.js > output.css\n```\n\nDebug information is written to stderr in all modes, so stdout contains only the transformed CSS.\n\n## Icons Without CSS\n\nAn alternative to icon CSS shaking is to avoid icon font CSS entirely. Quasar supports SVG icons, which are tree-shaken by the JS bundler automatically -- no CSS involved.\n\n### SVG icon imports\n\nInstead of using icon name strings (which require the font CSS):\n\n```vue\n<!-- Font approach: requires full icon CSS loaded -->\n<q-icon name=\"mdi-account\" />\n```\n\nImport the SVG constant directly:\n\n```vue\n<template>\n  <q-icon :name=\"mdiAccount\" />\n</template>\n\n<script setup>\nimport { mdiAccount } from '@quasar/extras/mdi-v7'\n</script>\n```\n\nThe SVG constant is a path string that QIcon renders as an inline `<svg>` element. The JS bundler tree-shakes unused icon imports, so only the icons you actually reference are included in the build. No icon CSS is loaded at all.\n\nEvery `@quasar/extras` icon pack provides these SVG exports. The import path follows the pattern `@quasar/extras/<pack-name>`:\n\n```ts\nimport { mdiAccount, mdiHome } from '@quasar/extras/mdi-v7'\nimport { fabGithub } from '@quasar/extras/fontawesome-v6'\nimport { ionHome } from '@quasar/extras/ionicons-v7'\n```\n\n### Material Icons and Material Symbols\n\nThe `material-icons` and `material-symbols-*` packs use font ligatures rather than CSS class definitions. Their CSS is small (just the `@font-face` declaration and base class) and contains no per-icon rules, so there is nothing to shake. These packs work efficiently without any special configuration.\n\n### When to use which approach\n\n| Approach | Pros | Cons |\n|----------|------|------|\n| SVG imports | Zero icon CSS, perfect tree-shaking | Requires explicit imports, no string-based icon names |\n| Font + `icons: true` | String-based names, no code changes | Requires this plugin, string scanning has edge cases with dynamic names |\n| Material Icons (ligatures) | String-based names, tiny CSS | Limited to Google's icon sets |\n\nFor new projects, SVG imports give the smallest builds with zero configuration. For existing projects that already use string-based icon names throughout, enabling `icons: true` in this plugin avoids a large refactor.\n\n## Requirements\n\n- Vite 5.4+ / 6.x / 7.x / 8.x (optional peer dependency -- only needed for the plugin, not the CLI or API)\n- Node 20+\n- Quasar 2.x\n\n## Limitations\n\n- **CSS inside `@media`, `@keyframes`, and other at-rules** is not processed -- the entire at-rule block passes through unchanged.\n- **Selectors with functional pseudo-classes** (`:not()`, `:is()`, `:has()`, `:where()`), attribute selectors, or quoted strings are skipped to avoid false positives.\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-c6fe4720c3abc9475cfed5d15d5d4372"}