{"id":29618660,"url":"https://github.com/wernerglinka/metalsmith-bundled-components","last_synced_at":"2026-05-19T03:24:00.482Z","repository":{"id":298589302,"uuid":"1000473439","full_name":"wernerglinka/metalsmith-bundled-components","owner":"wernerglinka","description":"A Metalsmith plugin that automatically discovers, orders, and bundles CSS and JavaScript files from component-based architectures","archived":false,"fork":false,"pushed_at":"2026-02-18T22:50:38.000Z","size":1488,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-19T04:34:45.594Z","etag":null,"topics":["content","metalsmith","plugin","structured"],"latest_commit_sha":null,"homepage":"https://github.com/wernerglinka/metalsmith-bundled-components","language":"JavaScript","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/wernerglinka.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-11T20:56:54.000Z","updated_at":"2026-02-18T22:50:42.000Z","dependencies_parsed_at":"2025-06-11T22:37:25.431Z","dependency_job_id":null,"html_url":"https://github.com/wernerglinka/metalsmith-bundled-components","commit_stats":null,"previous_names":["wernerglinka/metalsmith-bundled-components"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/wernerglinka/metalsmith-bundled-components","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wernerglinka%2Fmetalsmith-bundled-components","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wernerglinka%2Fmetalsmith-bundled-components/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wernerglinka%2Fmetalsmith-bundled-components/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wernerglinka%2Fmetalsmith-bundled-components/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wernerglinka","download_url":"https://codeload.github.com/wernerglinka/metalsmith-bundled-components/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wernerglinka%2Fmetalsmith-bundled-components/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29637925,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T22:32:43.237Z","status":"ssl_error","status_checked_at":"2026-02-19T22:32:38.330Z","response_time":117,"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":["content","metalsmith","plugin","structured"],"created_at":"2025-07-21T02:02:59.014Z","updated_at":"2026-05-19T03:24:00.473Z","avatar_url":"https://github.com/wernerglinka.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# metalsmith-bundled-components\n\nA Metalsmith plugin that automatically discovers and bundles CSS and JavaScript files from component-based architectures using esbuild\n\n[![metalsmith:plugin][metalsmith-badge]][metalsmith-url]\n[![npm: version][npm-badge]][npm-url]\n[![license: MIT][license-badge]][license-url]\n[![coverage][coverage-badge]][coverage-url]\n[![AI-assisted development](https://img.shields.io/badge/AI-assisted-blue)](https://github.com/wernerglinka/metalsmith-bundled-components/blob/main/CLAUDE.md)\n\n\u003e This Metalsmith plugin is under active development. The API is stable, but breaking changes may occur before reaching 1.0.0.\n\n## Features\n\n- **Automatic component discovery** - Scans directories for components and their assets\n- **Requirement validation** - Validates that component requirements exist (no complex dependency ordering)\n- **esbuild-powered bundling** - Modern, fast bundling with tree shaking and minification\n- **CSS @import resolution** - Automatically resolves @import statements in main CSS files\n- **Complete minification** - All CSS and JS (main + components) properly minified in production\n- **Main entry points** - Bundle your main CSS/JS files alongside components\n- **PostCSS integration** - PostCSS support via esbuild plugins\n- **Simple, predictable ordering** - Main entries → base components → sections (filesystem order)\n- **Component validation** - Validates component properties to prevent silent failures\n- **Tree shaking** - Removes unused code for smaller bundles\n- **Convention over configuration** - Sensible defaults with minimal required setup\n\n## Installation\n\n```bash\nnpm install metalsmith-bundled-components\n```\n\nThis plugin is published as ESM only and requires Node.js 22 or newer. CommonJS\nconsumers should pin to the 0.10.x line.\n\n## Usage\n\nPass `metalsmith-bundled-components` to `metalsmith.use`:\n\n### Basic Usage\n\n```js\nimport Metalsmith from 'metalsmith';\nimport bundledComponents from 'metalsmith-bundled-components';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nMetalsmith(__dirname)\n  .use(bundledComponents()) // default options\n  .build((err) =\u003e {\n    if (err) throw err;\n  });\n```\n\n### With Custom Component Paths\n\n```js\nimport Metalsmith from 'metalsmith';\nimport bundledComponents from 'metalsmith-bundled-components';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nMetalsmith(__dirname)\n  .use(\n    bundledComponents({\n      basePath: 'components/base',\n      sectionsPath: 'components/sections',\n      cssDest: 'assets/bundle.css',\n      jsDest: 'assets/bundle.js'\n    })\n  )\n  .build((err) =\u003e {\n    if (err) throw err;\n  });\n```\n\n### With Main Entry Points (New!)\n\n```js\nimport Metalsmith from 'metalsmith';\nimport bundledComponents from 'metalsmith-bundled-components';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nMetalsmith(__dirname)\n  .use(\n    bundledComponents({\n      // Bundle main app files along with components\n      mainCSSEntry: 'src/styles/main.css',\n      mainJSEntry: 'src/scripts/main.js',\n      // Component paths\n      basePath: 'components/base',\n      sectionsPath: 'components/sections'\n    })\n  )\n  .build((err) =\u003e {\n    if (err) throw err;\n  });\n```\n\n### Real-World Example with PostCSS Processing\n\n```js\nimport Metalsmith from 'metalsmith';\nimport bundledComponents from 'metalsmith-bundled-components';\nimport autoprefixer from 'autoprefixer';\nimport cssnano from 'cssnano';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nMetalsmith(__dirname)\n  .use(\n    bundledComponents({\n      basePath: 'lib/layouts/components/_partials',\n      sectionsPath: 'lib/layouts/components/sections',\n      postcss: {\n        enabled: true,\n        plugins: [autoprefixer(), cssnano({ preset: 'default' })],\n        options: {\n          // Additional PostCSS options if needed\n        }\n      }\n    })\n  )\n  .build((err) =\u003e {\n    if (err) throw err;\n  });\n```\n\nThis configuration:\n\n1. Uses the default component paths in the `lib/layouts` directory structure\n2. Enables PostCSS processing\n3. Applies autoprefixer to add vendor prefixes for better browser compatibility\n4. Minifies the CSS output using cssnano with default settings\n\nThe resulting bundled CSS will be properly ordered by dependencies, prefixed for browser compatibility, and minified for production use.\n\n### Options\n\n| Option         | Description                                              | Type      | Default                                                   |\n| -------------- | -------------------------------------------------------- | --------- | --------------------------------------------------------- |\n| `basePath`     | Path to base/atomic components directory                 | `String`  | `'lib/layouts/components/_partials'`                      |\n| `sectionsPath` | Path to section/composite components directory           | `String`  | `'lib/layouts/components/sections'`                       |\n| `layoutsPath`  | Path to layouts directory for scanning template includes | `String`  | `'lib/layouts'`                                           |\n| `cssDest`      | Destination path for bundled CSS                         | `String`  | `'assets/main.css'`                                       |\n| `jsDest`       | Destination path for bundled JavaScript                  | `String`  | `'assets/main.js'`                                        |\n| `mainCSSEntry` | Main CSS entry point (design tokens, base styles)        | `String`  | `'lib/assets/main.css'`                                   |\n| `mainJSEntry`  | Main JS entry point (app initialization code)            | `String`  | `'lib/assets/main.js'`                                    |\n| `minifyOutput` | Enable esbuild minification for production builds        | `Boolean` | `false`                                                   |\n| `postcss`      | PostCSS configuration (enabled, plugins, options)        | `Object`  | `{ enabled: false, plugins: [], options: {} }`            |\n| `validation`   | Section validation configuration                         | `Object`  | `{ enabled: true, strict: false, reportAllErrors: true }` |\n\n## Component Structure\n\nThe plugin expects components to be organized in a specific structure:\n\n```\nlib/\n└─ layouts/\n   ├─ components/\n   │  ├─ _partials/          # Atomic/base components\n   │  │  ├─ button/\n   │  │  │  ├─ button.njk\n   │  │  │  ├─ button.css\n   │  │  │  ├─ button.js\n   │  │  │  └─ manifest.json (optional)\n   │  │  └─ image/\n   │  │     ├─ image.njk\n   │  │     └─ image.css\n   │  └─ sections/           # Composite components\n   │      ├─ banner/\n   │      │   ├─ banner.njk\n   │      │   ├─ banner.css\n   │      │   ├─ banner.js\n   │      │   └─ manifest.json\n   │      └─ media/\n   │          ├─ media.njk\n   │          ├─ media.css\n   │          └─ manifest.json\n   └─ pages/\n      ├─ default.njk\n      └─ home.njk\n```\n\n### Component Manifest\n\nEach component can include an optional `manifest.json` file:\n\n```json\n{\n  \"name\": \"banner\",\n  \"type\": \"section\",\n  \"description\": \"banner section with background image\",\n  \"styles\": [\"banner.css\", \"banner-responsive.css\"],\n  \"scripts\": [\"banner.js\"],\n  \"requires\": [\"button\", \"image\"]\n}\n```\n\nIf no manifest file is present, the plugin will auto-generate one based on the component name:\n\n- It will look for `\u003ccomponent-name\u003e.css` and `\u003ccomponent-name\u003e.js` files\n- Requirements must be explicitly defined in a manifest file if component depends on others\n\n## Section Validation\n\nThe plugin includes validation capabilities to catch common configuration errors in your frontmatter/YAML that would otherwise result in \"silent failures\" - where the site builds successfully but renders incorrectly.\n\n### Common Problems Solved\n\n- **Type coercion issues**: `isAnimated: \"false\"` (string) always evaluates to `true` in templates\n- **Invalid enum values**: `buttonStyle: \"blue\"` when CSS only supports `primary`, `secondary`, `ghost`\n- **Misspelled properties**: `titleTag: \"header\"` instead of valid HTML heading tags\n\n### Manifest with Validation Rules\n\nAdd a `validation` object to your component's `manifest.json`:\n\n```json\n{\n  \"name\": \"hero\",\n  \"type\": \"section\",\n  \"styles\": [\"hero.css\"],\n  \"scripts\": [],\n  \"requires\": [\"button\", \"image\"],\n  \"validation\": {\n    \"required\": [\"sectionType\"],\n    \"properties\": {\n      \"sectionType\": {\n        \"type\": \"string\",\n        \"const\": \"hero\"\n      },\n      \"isReverse\": {\n        \"type\": \"boolean\"\n      },\n      \"containerFields.isAnimated\": {\n        \"type\": \"boolean\"\n      },\n      \"containerFields.background.imageScreen\": {\n        \"type\": \"string\",\n        \"enum\": [\"light\", \"dark\", \"none\"]\n      },\n      \"text.titleTag\": {\n        \"type\": \"string\",\n        \"enum\": [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"]\n      },\n      \"ctas\": {\n        \"type\": \"array\",\n        \"items\": {\n          \"properties\": {\n            \"isButton\": {\n              \"type\": \"boolean\"\n            },\n            \"buttonStyle\": {\n              \"type\": \"string\",\n              \"pattern\": \"^(primary|secondary|ghost|none)( small)?$\"\n            }\n          }\n        }\n      }\n    }\n  }\n}\n```\n\n### Validation Features\n\n**Type Validation**: Ensure fields are actual booleans, strings, numbers, or arrays - not string representations.\n\n**Enum Validation**: Restrict values to predefined options (e.g., `titleTag: [\"h1\", \"h2\", \"h3\"]`).\n\n**Pattern Validation**: Match values against a regex pattern. Use this instead of `enum` when values support compound forms, e.g., `buttonStyle` accepts a base style optionally followed by `small`:\n\n```json\n\"buttonStyle\": {\n  \"type\": \"string\",\n  \"pattern\": \"^(primary|secondary|tertiary|inverted)( small)?$\"\n}\n```\n\nThis accepts `\"primary\"`, `\"tertiary small\"`, `\"inverted small\"`, etc.\n\n**Nested Properties**: Use dot notation for nested validation (`containerFields.isAnimated`).\n\n**Array Items**: Validate properties within array elements.\n\n**Helpful Error Messages**: Get error messages with file context and helpful tips.\n\n### Error Message Example\n\n```\n❌ Section Validation Errors:\n\nSection 0 (hero) in src/index.md:\n  - containerFields.isAnimated: expected boolean, got string \"false\"\n  - text.titleTag: \"header\" is invalid. Must be one of: h1, h2, h3, h4, h5, h6\n  - ctas[0].buttonStyle: \"blue\" is invalid. Must be one of: primary, secondary, ghost, none\n\nTip: String \"false\" evaluates to true in templates. Use boolean false instead.\n```\n\n### Validation Configuration\n\nConfigure validation behavior in plugin options:\n\n```js\nMetalsmith(__dirname)\n  .use(\n    bundledComponents({\n      validation: {\n        enabled: true, // Enable/disable validation\n        strict: false, // Fail build on errors vs warnings only\n        reportAllErrors: true // Report all errors vs stop on first\n      }\n    })\n  )\n  .build((err) =\u003e {\n    if (err) throw err;\n  });\n```\n\n## Additional PostCSS Examples\n\n### Adding Custom Media Queries Support\n\n```js\nimport Metalsmith from 'metalsmith';\nimport bundledComponents from 'metalsmith-bundled-components';\nimport autoprefixer from 'autoprefixer';\nimport cssnano from 'cssnano';\nimport postcssCustomMedia from 'postcss-custom-media';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nMetalsmith(__dirname)\n  .use(\n    bundledComponents({\n      postcss: {\n        enabled: true,\n        plugins: [postcssCustomMedia(), autoprefixer(), cssnano({ preset: 'default' })]\n      }\n    })\n  )\n  .build((err) =\u003e {\n    if (err) throw err;\n  });\n```\n\n### Adding Nested Rules Support\n\n```js\nimport Metalsmith from 'metalsmith';\nimport bundledComponents from 'metalsmith-bundled-components';\nimport postcssNested from 'postcss-nested';\nimport autoprefixer from 'autoprefixer';\nimport { fileURLToPath } from 'url';\nimport { dirname } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nMetalsmith(__dirname)\n  .use(\n    bundledComponents({\n      postcss: {\n        enabled: true,\n        plugins: [postcssNested(), autoprefixer()]\n      }\n    })\n  )\n  .build((err) =\u003e {\n    if (err) throw err;\n  });\n```\n\n## CSS Processing \u0026 @import Resolution\n\nThe plugin provides CSS processing with automatic @import resolution:\n\n### How CSS Processing Works\n\n1. **Concatenation**: Main CSS entry + all component CSS files are combined\n2. **Temp Directory Setup**: Combined CSS and @import dependencies copied to temporary directory\n3. **@import Resolution**: esbuild processes the combined CSS to resolve all @import statements\n4. **Minification**: When `minifyOutput: true`, all CSS (main + components) is minified together\n5. **Output**: Final processed CSS written to build directory\n6. **Cleanup**: Temporary files automatically cleaned up\n\n### @import Support\n\nYour main CSS file can use @import statements with the following supported directory structure:\n\n```css\n/* main.css */\n@import './styles/_design-tokens.css';\n@import './styles/_base.css';\n@import './_utilities.css'; /* Files in same directory */\n\n/* Your main application styles */\nbody {\n  font-family: var(--font-primary);\n  line-height: var(--line-height);\n}\n```\n\n**Expected Directory Structure:**\n\n```\nsrc/assets/\n├── main.css               /* Main CSS entry point */\n├── _utilities.css         /* CSS files in same directory */\n└── styles/                /* Subdirectory for @imports */\n    ├── _design-tokens.css\n    ├── _base.css\n    └── _components.css\n```\n\nThe plugin automatically:\n\n- ✅ **Copies imported files** to temp directory preserving relative paths\n- ✅ **Resolves @import statements** using esbuild bundling\n- ✅ **Combines with component CSS** for a single output file\n- ✅ **Applies minification** to the entire combined CSS when enabled\n\n### Production Minification\n\nWhen `minifyOutput: true` is set:\n\n```js\nMetalsmith(__dirname).use(\n  bundledComponents({\n    mainCSSEntry: 'lib/assets/main.css',\n    minifyOutput: process.env.NODE_ENV === 'production' // Enable in production\n  })\n);\n```\n\n**Result**: All CSS (main entry + imported files + component styles) is fully minified into a single optimized file.\n\n## Test Coverage\n\nThis plugin is tested with Node's native `node:test` runner and `--experimental-test-coverage`.\n\n## Debug\n\nTo enable debug logs, set the `DEBUG` environment variable to `metalsmith-bundled-components*`:\n\n```js\nmetalsmith.env('DEBUG', 'metalsmith-bundled-components*');\n```\n\nAlternatively, you can set `DEBUG` to `metalsmith:*` to debug all Metalsmith plugins.\n\n## CLI Usage\n\nTo use this plugin with the Metalsmith CLI, add `metalsmith-bundled-components` to the `plugins` key in your `metalsmith.json` file:\n\n```json\n{\n  \"plugins\": [\n    {\n      \"metalsmith-bundled-components\": {\n        \"basePath\": \"lib/layouts/components/_partials\",\n        \"sectionsPath\": \"lib/layouts/components/sections\",\n        \"postcss\": {\n          \"enabled\": true,\n          \"plugins\": [\"autoprefixer\", \"cssnano\"]\n        }\n      }\n    }\n  ]\n}\n```\n\n## License\n\nMIT\n\n## Development transparency\n\nPortions of this project were developed with the assistance of AI tools including Claude and Claude Code. These tools were used to:\n\n- Generate or refactor code\n- Assist with documentation\n- Troubleshoot bugs and explore alternative approaches\n\nAll AI-assisted code has been reviewed and tested to ensure it meets project standards. See the included [CLAUDE.md](CLAUDE.md) and [PROMPT-TEMPLATE.md](PROMPT-TEMPLATE.md) files for more details.\n\n[npm-badge]: https://img.shields.io/npm/v/metalsmith-bundled-components.svg\n[npm-url]: https://www.npmjs.com/package/metalsmith-bundled-components\n[metalsmith-badge]: https://img.shields.io/badge/metalsmith-plugin-green.svg?longCache=true\n[metalsmith-url]: https://metalsmith.io\n[license-badge]: https://img.shields.io/github/license/wernerglinka/metalsmith-bundled-components\n[license-url]: LICENSE\n[coverage-badge]: https://img.shields.io/badge/test%20coverage-97%25-brightgreen\n[coverage-url]: #test-coverage\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwernerglinka%2Fmetalsmith-bundled-components","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwernerglinka%2Fmetalsmith-bundled-components","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwernerglinka%2Fmetalsmith-bundled-components/lists"}