Build tooling for the Uniweb Component Web Platform.
This package provides Vite plugins and utilities for building both Foundations (component libraries) and Sites (content-driven websites).
npm install @uniweb/build --save-devFor Foundations:
- Component Discovery - Discovers section types from
src/sections/(implicit at root) andsrc/components/(requiresmeta.js) - Entry Generation - Generates the foundation entry point with all exports
- Schema Building - Creates
schema.jsonwith full component metadata for editors - Image Processing - Converts preview images to WebP format
- Vite Plugin - Integrates seamlessly with Vite builds
For Sites:
- Content Collection - Collects pages from
pages/directory with YAML/Markdown - Dev Server Integration - Watches for content changes with hot reload
- Foundation Dev Server - Serves a local foundation during development
Add the foundation plugin to your foundation's vite.config.js:
import{defineConfig}from'vite'importreactfrom'@vitejs/plugin-react'import{foundationPlugin}from'@uniweb/build'exportdefaultdefineConfig({plugins: [react(),foundationPlugin()],build: {lib: {entry: 'src/_entry.generated.js',formats: ['es'],fileName: 'foundation'},rollupOptions: {external: ['react','react-dom','react/jsx-runtime']}}})For sites, use the content and dev plugins in your site's vite.config.js:
import{defineConfig}from'vite'importreactfrom'@vitejs/plugin-react'import{siteContentPlugin}from'@uniweb/build/site'import{foundationDevPlugin}from'@uniweb/build/dev'exportdefaultdefineConfig({plugins: [react(),// Collect content from pages/ directorysiteContentPlugin({sitePath: './',inject: true,// Inject into HTML}),// Serve local foundation during developmentfoundationDevPlugin({path: '../foundation',serve: '/foundation',}),]})siteContentPlugin({sitePath: './',// Path to site directorypagesDir: 'pages',// Pages subdirectory nameinject: true,// Inject content into HTMLfilename: 'site-content.json',// Output filenamewatch: true,// Watch for changes (dev mode)seo: {// SEO configuration (optional)baseUrl: 'https://example.com',defaultImage: '/og-image.png',twitterHandle: '@example',locales: [{code: 'en',default: true},{code: 'es'}],robots: {disallow: ['/admin','/api'],crawlDelay: 1}},assets: {// Asset processing (optional)process: true,// Process assets in production (default: true)convertToWebp: true,// Convert images to WebP (default: true)quality: 80,// WebP quality 1-100 (default: 80)outputDir: 'assets',// Output subdirectory (default: 'assets')videoPosters: true,// Extract poster from videos (default: true, requires ffmpeg)pdfThumbnails: true// Generate PDF thumbnails (default: true, requires pdf-lib)}})When seo.baseUrl is provided, the plugin generates:
sitemap.xml - Auto-generated from collected pages with:
- Last modified dates from file timestamps
- Per-page
changefreqandpriorityfrom page frontmatter - Hreflang entries for multi-locale sites
robots.txt - Generated with sitemap reference and optional rules
Meta Tags - Injected into HTML <head>:
- Open Graph tags (
og:title,og:description,og:image, etc.) - Twitter Card tags (
twitter:card,twitter:site, etc.) - Canonical URL
- Hreflang links for multi-locale sites
Page-level SEO - Configure in page.yml:
title: About Usdescription: Learn about our companyseo:
noindex: false # Exclude from sitemapimage: /about-og.png # Page-specific OG imagechangefreq: monthly # Sitemap changefreqpriority: 0.8# Sitemap priorityThe plugin automatically discovers and processes assets referenced in your content. In content-driven sites, markdown acts as "code" - local asset references are like implicit imports and get optimized during build.
Supported path formats:
./image.png- Relative to the markdown file../shared/logo.png- Relative paths with parent traversal/images/hero.png- Absolute paths (resolved frompublic/orassets/folder)
What gets processed:
- Images in markdown content:
 - Media in frontmatter fields:
background,image,thumbnail,poster,avatar,logo,icon,video,pdf, etc.
Image processing:
- PNG, JPG, JPEG, GIF → Converted to WebP for smaller file sizes
- SVG, WebP, AVIF → Copied as-is (already optimized formats)
- All processed assets get content-hashed filenames for cache busting
Video poster extraction (requires ffmpeg on system):
- MP4, WebM, MOV, AVI, MKV → Poster frame extracted at 1 second
- Poster images converted to WebP and added to
_assetMeta.posters - Skipped if an explicit
posterattribute is provided in markdown
PDF thumbnail generation (requires pdf-lib package):
- PDF files → Placeholder thumbnail with page count
- Thumbnails added to
_assetMeta.thumbnails - Skipped if an explicit
previewattribute is provided in markdown
Explicit poster/preview images:
When you provide explicit poster or preview attributes in your markdown, those images are collected and optimized alongside other assets:
{role=video poster=./custom-poster.jpg}
{role=pdf preview=./guide-preview.png}- The explicit images (
./custom-poster.jpg,./guide-preview.png) are processed and optimized - Auto-generation via ffmpeg/pdf-lib is skipped for these files
- This gives you full control over preview images while still benefiting from optimization
Build output:
dist/
├── assets/
│ ├── hero-a1b2c3d4.webp # Converted from hero.jpg
│ ├── logo-e5f6g7h8.svg # Copied as-is
│ ├── intro-poster-9i0j1k2l.webp # Video poster frame
│ └── guide-thumb-3m4n5o6p.webp # PDF thumbnail
└── site-content.json # Paths rewritten, _assetMeta included
Graceful degradation:
- If
ffmpegis not installed, video posters are silently skipped - If
pdf-libis not installed, PDF thumbnails are silently skipped - Missing assets are logged as warnings but don't fail the build
foundationDevPlugin({name: 'foundation',// Name for loggingpath: '../foundation',// Path to foundation packageserve: '/foundation',// URL path to serve fromwatch: true,// Watch for source changesbuildOnStart: true// Build when dev server starts})import{discoverComponents,buildSchema,generateEntryPoint,processAllPreviews}from'@uniweb/build'// Discover components in a foundationconstcomponents=awaitdiscoverComponents('./src')// => { Hero: { title: 'Hero Banner', ... }, Features: { ... }}// Build complete schemaconstschema=awaitbuildSchema('./src')// => { _self: { name: 'My Foundation' }, Hero: {...}, Features: {...}}// Generate entry pointawaitgenerateEntryPoint('./src','./src/_entry.generated.js')// Process preview imagesconst{schema: withImages, totalImages }=awaitprocessAllPreviews('./src','./dist',schema,true// production mode - converts to webp)Foundations use a folder-based component structure:
src/
├── meta.js # Foundation-level metadata
├── index.css # Global styles (Tailwind)
├── components/
│ └── Hero/
│ ├── index.jsx # Component implementation
│ ├── meta.js # Component metadata
│ └── previews/ # Preset preview images
│ └── default.png
// src/sections/Hero/meta.jsexportdefault{title: 'Hero Banner',description: 'A prominent header section',category: 'Headers',elements: {title: {label: 'Headline',required: true},subtitle: {label: 'Subtitle'},},properties: {alignment: {type: 'select',label: 'Text Alignment',options: [{value: 'center',label: 'Center'},{value: 'left',label: 'Left'},],default: 'center',},},presets: {default: {label: 'Default',properties: {}},dark: {label: 'Dark Theme',properties: {theme: 'dark'}},},}// src/meta.jsexportdefault{name: 'My Foundation',description: 'Components for marketing websites',// Runtime props available to all componentsprops: {themeToggleEnabled: true,},// Foundation-wide style configurationstyleFields: [{id: 'primary-color',type: 'color',label: 'Primary Color',default: '#3b82f6',},],}After building, your foundation will contain:
dist/
├── foundation.js # Bundled components (~6KB typical)
├── foundation.js.map # Source map
└── meta/ # Editor metadata (not needed at runtime)
├── schema.json # Full component metadata for editors
└── previews/ # Preset preview images
└── Hero/
└── default.webp
The generated schema.json contains:
{
"_self": {
"name": "foundation",
"version": "0.1.0",
"description": "My foundation description",
"vars": { ... }
},
"Hero": { ... },
"Features": { ... }
}The _self object contains foundation-level metadata:
| Field | Source | Description |
|---|---|---|
name | package.json | Foundation package name |
version | package.json | Foundation version |
description | package.json | Foundation description |
vars | foundation.js | CSS custom properties sites can override |
Identity fields (name, version, description) come from the foundation's package.json. Configuration fields (vars, etc.) come from src/foundation.js.
| Function | Description |
|---|---|
discoverComponents(srcDir) | Discover all section types (folders with meta.js) |
loadComponentMeta(componentDir) | Load meta file for a component |
loadPackageJson(srcDir) | Load identity from package.json |
loadFoundationConfig(srcDir) | Load foundation.js configuration |
buildSchema(srcDir) | Build complete schema object |
| Function | Description |
|---|---|
generateEntryPoint(srcDir, outputPath) | Generate foundation entry file |
The generated _entry.generated.js file exports:
| Export | Description |
|---|---|
components | Object map of component name → React component |
| Named exports | Each component exported by name (e.g., Hero, Features) |
capabilities | Custom Layout and props from src/foundation.js (or null) |
meta | Runtime metadata extracted from component meta.js files |
Some properties in meta.js are needed at runtime, not just editor-time. These are extracted into the meta export to keep them available without loading the full schema.json.
Currently extracted properties:
input- Form input schemas (for components that accept user input)
Example meta.js with form schema:
exportdefault{title: 'Contact Form',// ... editor-only properties ...// This gets extracted to the runtime `meta` exportinput: {name: {type: 'text',label: 'Name',required: true},email: {type: 'email',label: 'Email',required: true},message: {type: 'textarea',label: 'Message'}}}Generated entry will include:
exportconstmeta={"ContactForm": {"input": {"name": {"type": "text","label": "Name","required": true},// ...}}}To add more runtime properties, update RUNTIME_META_KEYS in src/generate-entry.js.
| Function | Description |
|---|---|
processComponentPreviews(componentDir, name, outputDir, isProduction) | Process one component's previews |
processAllPreviews(srcDir, outputDir, schema, isProduction) | Process all preview images |
Foundation plugins (@uniweb/build):
| Plugin | Description |
|---|---|
foundationPlugin(options) | Combined dev + build plugin |
foundationBuildPlugin(options) | Build-only plugin |
foundationDevPlugin(options) | Dev-only plugin with HMR |
Site plugins (@uniweb/build/site and @uniweb/build/dev):
| Plugin | Description |
|---|---|
siteContentPlugin(options) | Collect and inject site content |
collectSiteContent(sitePath) | Programmatic content collection |
foundationDevPlugin(options) | Serve foundation during site dev |
@uniweb/core- Core classes (Uniweb, Website, Block)@uniweb/kit- Component library for foundations@uniweb/runtime- Browser runtime for sitesuniweb- CLI for creating projects
Apache 2.0