{"id":16229230,"url":"https://github.com/morris/cbst","last_synced_at":"2026-02-08T20:12:26.153Z","repository":{"id":65064296,"uuid":"581642512","full_name":"morris/cbst","owner":"morris","description":"A cache buster that versions a website's files for immutable cache control.","archived":false,"fork":false,"pushed_at":"2025-06-07T17:01:56.000Z","size":769,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-29T03:26:30.032Z","etag":null,"topics":["cache-busting","cache-control"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/morris.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","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":"2022-12-23T20:11:21.000Z","updated_at":"2025-06-07T17:02:00.000Z","dependencies_parsed_at":"2024-08-27T09:58:45.290Z","dependency_job_id":null,"html_url":"https://github.com/morris/cbst","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/morris/cbst","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fcbst","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fcbst/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fcbst/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fcbst/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/morris","download_url":"https://codeload.github.com/morris/cbst/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/morris%2Fcbst/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29241869,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-08T19:36:48.828Z","status":"ssl_error","status_checked_at":"2026-02-08T19:27:12.336Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["cache-busting","cache-control"],"created_at":"2024-10-10T12:57:44.738Z","updated_at":"2026-02-08T20:12:26.148Z","avatar_url":"https://github.com/morris.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# cbst\n\nA cache buster that versions a website's files for immutable cache control.\n\n- Takes a directory containing a website (i.e. HTML, CSS, JS and other files),\n- appends version tags to static files,\n- rewrites references to versioned files,\n- and writes the result into an output directory.\n\n## Installation\n\n```\nnpm install cbst --save-dev # project installation (recommended)\nnpm install cbst --global   # global installation\n```\n\n## Usage\n\n```\ncbst \u003cinput dir\u003e \u003coutput dir\u003e [\u003cconfig file\u003e]\n```\n\n## Example\n\nThis diff demonstrates how versioning is applied to a simple website:\n\n```diff\n# /index.html\n\n  \u003c!DOCTYPE html\u003e\n  \u003chtml\u003e\n    \u003chead\u003e\n      \u003ctitle\u003eHello, World!\u003c/title\u003e\n-     \u003clink rel=\"stylesheet\" href=\"/css/main.css\"\u003e\n+     \u003clink rel=\"stylesheet\" href=\"/css/main.v-ed1e118152.css\"\u003e\n    \u003c/head\u003e\n    \u003cbody\u003e\n      \u003ch1\u003eHello, World!\u003c/h1\u003e\n      \u003cp\u003e\n-       \u003cimg id=\"welcome\" src=\"/img/loading.svg\"\u003e\n+       \u003cimg id=\"welcome\" src=\"/img/loading.v-834ab3df39.svg\"\u003e\n      \u003c/p\u003e\n      \u003cscript type=\"module\"\u003e\n-       import { main } from './js/main.js';\n+       import { main } from './js/main.v-f39e95e656.js';\n\n        setTimeout(main, 1000);\n      \u003c/script\u003e\n    \u003c/body\u003e\n  \u003c/html\u003e\n\n# /js/main.js -\u003e /js/main.v-f39e95e656.js\n\n- import { hello } from './hello.js';\n+ import { hello } from './hello.v-93724d33b5.js';\n\n  export function main() {\n    hello('world');\n-   document.getElementById('welcome').src = '/img/welcome.svg';\n+   document.getElementById('welcome').src = '/img/welcome.v-afe45bb832.svg';\n  }\n\n# /css/main.css -\u003e /css/main.v-ed1e118152.css\n# /js/hello.js -\u003e /js/hello.v-93724d33b5.js\n# etc.\n```\n\n## Configuration\n\nThe configuration file (JSON) supports the following properties:\n\n- `exclude`\n  - List of file patterns to exclude.\n  - Defaults to `[]`.\n- `source`\n  - List of file patterns to treat as sources with references to other files.\n  - Defaults to `[\"*.html\", \"*.css\", \"*.js\", \"*.svg\", \"*.json\"]`.\n- `html`\n  - List of file patterns to treat as HTML.\n  - Defaults to `[\"*.html\"]`.\n- `dynamic`\n  - List of file patterns to treat as dynamic.\n  - Matching files will not be versioned.\n  - References found in matching source files will still be rewritten.\n  - Defaults to `[\"*.html\"]`.\n- `metadata`\n  - Metadata filename (JSON).\n  - If omitted, no metadata file is written.\n- `hashLength`\n  - Length of hashes in versioned filenames.\n  - Defaults to `10`.\n\nFile patterns support `*` (slash-free wildcards) and `/**/` (directory\nwildcards).\n\n## Versioning and References\n\nAs `cbst` transforms files, it also rewrites any references to other versioned\nfiles. References are detected by scanning source files for **quoted strings**\nthat\n\n- are double- or single-quoted,\n- contain no whitespace or quotes,\n- and have a file extension.\n\nIf a reference can be resolved to a file, and that file is not `dynamic`, the\nreference is rewritten with the versioned filename.\n\nIf a reference cannot be resolved to a file, the reference is not modified.\n\n## Static vs. Dynamic Files\n\n**Static files** (not listed in `dynamic`) will be versioned based on a hash of\ntheir contents, as well as the contents of any referenced files. Circular\nreferences are resolved deterministically.\n\nStatic files should be delivered with a `Cache-Control` header that allows\nbrowsers and proxies to cache them indefinitely. For example:\n\n```\nCache-Control: public, max-age=31536000, immutable\n```\n\nVersion tags generated by `cbst` are prefixed with `v-`. You may configure your\nweb server to deliver files containing `.v-` in their names with the above\nimmutable cache control header.\n\n**Dynamic files** (those listed in `dynamic`) will not be versioned, but any\nreferences detected inside these files will be rewritten if necessary. For\nexample, `*.html` files are usually marked dynamic because they are directly\nvisited, not indirectly requested like other files.\n\nDynamic files should be delivered with a `Cache-Control` header that requires\nbrowsers and proxies to revalidate the file in a timely fashion. Additional\n`E-Tag` or `Last-Modified` headers should be provided to allow conditional\nrequests. For example:\n\n```\nCache-Control: public, max-age=604800, stale-while-revalidate=86400\nETag: 0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33\n```\n\nSee also\n\n- [Cache-Control (MDN)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)\n- [Conditional requests (MDN)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests)\n\n## Discussion\n\n\u003e I can't use my favorite libraries from npm with this because they require\n\u003e Node.js resolution and/or bundling.\n\nChicken and egg problem here. The industry got used to bundlers and started\nbuilding libraries for the web assuming that everyone used a bundler.\n\nAsk library authors to ship native ES modules. That will make all our lives\neasier.\n\n\u003e Isn't bundling still more performant for larger websites?\n\nYou should be fine if your website is delivered via HTTP/2 with compression and\nsolid cache control.\n\nFor recurring users, bundled websites that are modified regularly require\ndownloading/parsing the entire bundle after every modification.\n\nWith fine-grained file versioning and long-term caching, the first page visit\nmay suffer, but recurring users will have a better experience.\n\nFinally, avoiding frameworks and large dependencies is a much more effective\noptimization in the first place.\n\n\u003e What about minification?\n\nIt's out of scope for `cbst`. There are too many options to consider, and any\nintegration with minifiers will probably result in config/plug-in hell.\n\nInstead, websites should be minified in a separate pass during a production\nbuild, usually before cache busting.\n\n\u003e Why is this implemented with regular expressions? Everyone knows you can't\n\u003e parse HTML, CSS, and JS like that!!1\n\nIt's a trade-off. The RegExp-based implementation is simple, fast, and will only\nfail in obscure cases (hopefully).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorris%2Fcbst","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmorris%2Fcbst","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmorris%2Fcbst/lists"}