Skip to content

Latest commit

History

897 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

@uniweb/build

Build tooling for the Uniweb Component Web Platform.

Overview

This package provides Vite plugins and utilities for building both Foundations (component libraries) and Sites (content-driven websites).

Installation

npm install @uniweb/build --save-dev

Features

For Foundations:

  • Component Discovery - Discovers section types from src/sections/ (implicit at root) and src/components/ (requires meta.js)
  • Entry Generation - Generates the foundation entry point with all exports
  • Schema Building - Creates schema.json with 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

Usage

Foundation Plugin

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']}}})

Site Plugins

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',}),]})

Site Content Plugin Options

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)}})

SEO Features

When seo.baseUrl is provided, the plugin generates:

sitemap.xml - Auto-generated from collected pages with:

  • Last modified dates from file timestamps
  • Per-page changefreq and priority from 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 priority

Asset Processing

The 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 from public/ or assets/ folder)

What gets processed:

  • Images in markdown content: ![Alt](./photo.jpg)
  • 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 poster attribute 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 preview attribute 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:

![Video](./intro.mp4){role=video poster=./custom-poster.jpg}
![PDF](./guide.pdf){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 ffmpeg is not installed, video posters are silently skipped
  • If pdf-lib is not installed, PDF thumbnails are silently skipped
  • Missing assets are logged as warnings but don't fail the build

Foundation Dev Plugin Options

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})

Programmatic API

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)

Foundation Structure

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

Component Meta File

// 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'}},},}

Foundation Meta File

// 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',},],}

Build Output

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

Schema.json Structure

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:

FieldSourceDescription
namepackage.jsonFoundation package name
versionpackage.jsonFoundation version
descriptionpackage.jsonFoundation description
varsfoundation.jsCSS 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.

API Reference

Schema Functions

FunctionDescription
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

Entry Generation

FunctionDescription
generateEntryPoint(srcDir, outputPath)Generate foundation entry file

Generated Entry Exports

The generated _entry.generated.js file exports:

ExportDescription
componentsObject map of component name → React component
Named exportsEach component exported by name (e.g., Hero, Features)
capabilitiesCustom Layout and props from src/foundation.js (or null)
metaRuntime metadata extracted from component meta.js files

Runtime Metadata (meta export)

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.

Image Processing

FunctionDescription
processComponentPreviews(componentDir, name, outputDir, isProduction)Process one component's previews
processAllPreviews(srcDir, outputDir, schema, isProduction)Process all preview images

Vite Plugins

Foundation plugins (@uniweb/build):

PluginDescription
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):

PluginDescription
siteContentPlugin(options)Collect and inject site content
collectSiteContent(sitePath)Programmatic content collection
foundationDevPlugin(options)Serve foundation during site dev

Related Packages

License

Apache 2.0

About

Foundation build tooling (schema, images, entry gen)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages