@bagelink/blox
@bagelink/blox
Blox provides Vue components, routing, previews, and static generation for Bagelink websites.
Installation
pnpm add @bagelink/blox @bagelink/vue vue vue-router
CSS
Every Blox website must import these two stylesheets once, from its client
entry (src/main.ts), before project styles:
import '@bagelink/vue/styles/core.css'
import '@bagelink/blox/style.css'
import './styles/main.css'
Use @bagelink/vue/styles/admin.css instead of core.css only for admin
applications that need the Bagel font pack.
Do not import @bagelink/vue/dist/style.css or
@bagelink/blox/dist/style.css; those internal paths are retained only for
existing projects. Import the same entries in local development and CI so a
source alias cannot hide a package-export problem.
Production Website Setup
// main.ts
import { createBloxApp } from '@bagelink/blox'
import { BagelVue } from '@bagelink/vue'
import '@bagelink/vue/styles/core.css'
import '@bagelink/blox/style.css'
import App from './App.vue'
import router from './router'
const blocks = import.meta.glob('./components/blocks/*.vue', { eager: true })
createBloxApp({
rootComponent: App,
router,
modules: blocks,
websiteName: 'my-site',
store: 'my-site',
locale: 'en',
supportedLocales: ['en'],
plugins: [BagelVue],
navigation: 'document',
})
// main.server.ts
import { createBloxSSREntry } from '@bagelink/blox/ssg'
import { BagelVue } from '@bagelink/vue'
import App from './App.vue'
import { createRouter } from './router'
const blocks = import.meta.glob('./components/blocks/*.vue', { eager: true })
export const { render, prefetchResolve } = createBloxSSREntry({
rootComponent: App,
createRouter,
modules: blocks,
websiteName: 'my-site',
store: 'my-site',
locale: 'en',
supportedLocales: ['en'],
plugins: [BagelVue],
})
The client hydrates prerendered HTML directly. Components can use normal Vue
primitives including Suspense, Teleport, Pinia, and onServerPrefetch.
Use useData(key, loader) for data that must be present in static HTML and in
the hydration payload. Do not install fetch or Axios cache interceptors.
Blox owns the preview route and one named CMS fallback route. Consumer routers should declare app-owned routes and redirects only.
With Custom Components
import { createBlox, createComponentConfig, ButtonConfig } from '@bagelink/blox'
import MyHeroSection from './components/MyHeroSection.vue'
const MyHeroConfig = createComponentConfig({
id: 'MyHero',
label: 'Hero Section',
icon: 'hero_section',
component: MyHeroSection,
content: [
{
type: 'text',
key: 'title',
label: 'Hero Title',
defaultValue: 'Welcome!',
},
],
})
const blox = createBlox()
blox.registerComponents([ButtonConfig, MyHeroConfig]).registerRoutes(router)
app.use(blox)
Available Built-in Component Configs
Import these configs to register built-in components:
ButtonConfig- Customizable button/linkTextConfig- Rich text contentTitleConfig- Heading with optional subtitleImageConfig- Responsive imageSpacerConfig- Vertical spacingContainerConfig- Content container with max-width
For more examples, see SETUP_EXAMPLE.md.
Low-level Setup
createBlox() remains available for the editor and specialized embedded
renderers. New production websites should use createBloxApp() and
createBloxSSREntry() so client and server assembly cannot drift.
1. Register Base Components
import { createApp } from 'vue'
import { registerBaseComponents } from '@bagelink/blox'
const app = createApp(App)
// Register all base components
registerBaseComponents(app)
app.mount('#app')
2. Use the RenderPage Component
<template>
<RenderPage :page-data="pageData" />
</template>
<script setup lang="ts">
import { RenderPage } from '@bagelink/blox'
const pageData = {
components: [...],
pageSettings: {...}
}
</script>
3. Use ExternalPreview for Page Builder
<template>
<ExternalPreview :origin="editorOrigin" />
</template>
<script setup lang="ts">
import { ExternalPreview } from '@bagelink/blox'
const editorOrigin = 'https://your-editor.com'
</script>
Core Concepts
Component Registry
Register your custom components to make them available in the page builder:
import { registerComponent, registerComponents } from '@bagelink/blox'
import MyButton from './MyButton.vue'
// Register a single component
registerComponent('Button', MyButton)
// Or register multiple components at once
registerComponents({
Button: MyButton,
Hero: MyHero,
Text: MyText,
})
Communication System
Blox includes a built-in communication system for coordinating between the builder and preview:
import { createCommunicationBridge } from '@bagelink/blox'
// Create a communication bridge
const bridge = createCommunicationBridge({
origin: 'https://editor.com',
targetWindow: window.parent,
})
// Listen for messages
bridge.on('update', ({ data }) => {
console.log('Received update:', data)
})
// Send messages
bridge.send('ready', { components: ['Button', 'Hero'] })
Styling Utilities
Use the built-in styling utilities to apply consistent styles:
import { generateBlockStyles, getResponsiveClasses } from '@bagelink/blox'
const styles = generateBlockStyles(blockData, false)
const classes = getResponsiveClasses(blockData)
Base Components
The library includes these pre-built components:
- Button - Customizable button with variants
- Container - Layout container with spacing
- Image - Responsive image component
- Spacer - Vertical/horizontal spacing
- Text - Rich text content
- Title - Heading component
API Reference
Registry API
registerComponent(type: string, component: ComponentConstructor)
Register a single component.
import { registerComponent } from '@bagelink/blox'
import MyButton from './MyButton.vue'
registerComponent('Button', MyButton)
Parameters:
type- The block type identifier (e.g., 'Button', 'Hero')component- Vue component constructor or async import
registerComponents(components: ComponentRegistry)
Register multiple components at once.
import { registerComponents } from '@bagelink/blox'
registerComponents({
Button: MyButton,
Hero: MyHero,
Text: MyText,
})
Parameters:
components- Object mapping type names to components
getComponent(type: string): ComponentConstructor | undefined
Get a registered component by type.
const ButtonComponent = getComponent('Button')
hasComponent(type: string): boolean
Check if a component type is registered.
if (hasComponent('Button')) {
// Button is registered
}
getAllComponents(): ComponentRegistry
Get all registered components.
const allComponents = getAllComponents()
unregisterComponent(type: string)
Remove a component from the registry.
unregisterComponent('Button')
clearRegistry()
Remove all registered components.
clearRegistry()
getRegisteredTypes(): string[]
Get array of all registered type names.
const types = getRegisteredTypes()
// ['Button', 'Hero', 'Text']
createNamespacedRegistry(namespace: string)
Create a namespaced registry for multi-tenant scenarios.
const siteA = createNamespacedRegistry('site-a')
siteA.register('Button', ButtonA)
const siteB = createNamespacedRegistry('site-b')
siteB.register('Button', ButtonB)
Returns: Object with methods:
register(type, component)- Register to namespaceget(type)- Get from namespacegetAll()- Get all from namespace
Communication API
createCommunicationBridge(config: CommunicationConfig): CommunicationBridge
Create a communication bridge for postMessage communication.
import { createCommunicationBridge } from '@bagelink/blox'
const bridge = createCommunicationBridge({
origin: 'https://editor.com',
targetWindow: window.parent,
})
Parameters:
config.origin- Allowed origin (string or '*')config.targetWindow- Target window (default: window.parent)config.onMessage- Optional global message handler
CommunicationBridge.on(type: MessageType, handler: MessageHandler): () => void
Register a message handler.
const unsubscribe = bridge.on('update', ({ data }) => {
console.log('Received update:', data)
})
// Later: unsubscribe()
Parameters:
type- Message type or '*' for all messageshandler- Handler function
Returns: Unsubscribe function
CommunicationBridge.off(type: MessageType, handler: MessageHandler)
Unregister a message handler.
bridge.off('update', myHandler)
CommunicationBridge.send(type: MessageType, message?: any, data?: any)
Send a message to the target window.
bridge.send('focus', 'block-123')
bridge.send('update', null, { components: [...] })
Parameters:
type- Message typemessage- Optional message payloaddata- Optional data payload
CommunicationBridge.destroy()
Clean up and remove all listeners.
bridge.destroy()
sendMessage(type, message?, targetWindow?, origin?)
Send a one-off message without creating a bridge.
import { sendMessage } from '@bagelink/blox'
sendMessage('ready', { components: ['Button', 'Hero'] })
Renderer API
initializePage(pageData: PageData): Promise<void>
Initialize a page with all settings and assets.
import { initializePage } from '@bagelink/blox'
await initializePage(pageData)
What it does:
- Injects responsive CSS
- Applies page settings
- Loads Google Fonts
- Injects custom code
injectResponsiveCSS()
Inject the responsive CSS system.
import { injectResponsiveCSS } from '@bagelink/blox'
injectResponsiveCSS()
injectCode(code: string | undefined, target: 'head' | 'body')
Inject custom code into head or body.
injectCode('<script>console.log("Hello")</script>', 'head')
loadGoogleFont(fontName: string): Promise<void>
Load a Google Font.
await loadGoogleFont('Roboto')
loadComponentFonts(components: ComponentData[]): Promise<void>
Load all fonts used in components.
await loadComponentFonts(pageData.components)
applyGlobalFont(fontName: string)
Apply a font globally to the page.
applyGlobalFont('Inter')
applyPageSettings(pageData: PageData)
Apply all page settings to the document.
applyPageSettings(pageData)
Styles API
generateBlockStyles(data: Record<string, any>, isMobile?: boolean): Record<string, string>
Generate CSS styles from block data.
import { generateBlockStyles } from '@bagelink/blox'
const styles = generateBlockStyles(blockData, false)
// { 'margin-top': '2rem', 'padding': '1rem', ... }
Parameters:
data- Block data objectisMobile- Whether to use mobile overrides (default: false)
Returns: CSS styles object
getResponsiveClasses(data: Record<string, any>): string[]
Get responsive CSS classes for a block.
const classes = getResponsiveClasses(blockData)
// ['blox-block', 'responsive-colors', 'full-width-mobile']
getResponsiveCSS(): string
Get the responsive CSS to inject.
const css = getResponsiveCSS()
Normalizer API
normalizeComponentData(data: Record<string, any>): Record<string, any>
Normalize component data (convert string booleans, numbers, etc.).
import { normalizeComponentData } from '@bagelink/blox'
const normalized = normalizeComponentData({
width: '800',
fullWidth: 'true',
title: 'Hello',
})
// { width: 800, fullWidth: true, title: 'Hello' }
deepClone<T>(obj: T): T
Deep clone an object.
const cloned = deepClone(originalData)
deepMerge(target: any, source: any): any
Deep merge two objects.
const merged = deepMerge(defaults, customSettings)
Components
ExternalPreview
Preview component for external sites.
<ExternalPreview :origin="editorOrigin" :initial-page-data="pageData" />
Props:
origin?: string- Allowed editor origin (default: '*')initialPageData?: PageData- Optional initial page data
Events: Communicates via postMessage with these message types:
ready- Sent when preview is readyfocus- Sent when block is focusedhighlight- Sent when block is highlighted
RenderPage
Production render component.
<RenderPage :page-data="pageData" :mobile-breakpoint="910" />
Props:
pageData: PageData- Required page data to rendermobileBreakpoint?: number- Window width for mobile (default: 910)
TypeScript Types
ComponentData
interface ComponentData {
id: string
type: string
data: Record<string, any>
}
PageData
interface PageData {
id?: string
components: ComponentData[]
pageSettings?: PageSettings
header_code?: string
body_code?: string
language?: Record<string, any>
}
PageSettings
interface PageSettings {
selectedGoogleFont?: string
pageLanguage?: string
pageDirection?: 'ltr' | 'rtl'
additionalHeadCode?: string
additionalBodyCode?: string
customFavicon?: string
customOgImage?: string
customKeywords?: string
customRobotsMeta?: string
customAnalyticsCode?: string
disableGlobalHeadCode?: boolean
disableGlobalBodyCode?: boolean
disableGoogleAnalytics?: boolean
disableFacebookPixel?: boolean
}
GlobalSettings
interface GlobalSettings {
selectedGoogleFont?: string
pageLanguage?: string
pageDirection?: 'ltr' | 'rtl'
globalHeadCode?: string
globalBodyCode?: string
favicon?: string
ogImage?: string
siteKeywords?: string
googleAnalyticsId?: string
facebookPixelId?: string
}
MessageType
type MessageType =
| 'ready'
| 'update'
| 'focus'
| 'highlight'
| 'preview'
| 'meta'
| 'delete'
| 'disableLinks'
| 'newBlock'
EditorMessage
interface EditorMessage {
type: MessageType
message?: any
data?: any
isMobile?: boolean
}
BlockProps
interface BlockProps {
isMobile?: boolean
[key: string]: https://github.com/bageldb/bagelink/blob/HEAD/any
}
StyleConfig
interface StyleConfig {
// Spacing
marginTop?: number
marginBottom?: number
marginTopMobile?: number
marginBottomMobile?: number
padding?: number
paddingMobile?: number
// Width
width?: number
widthMobile?: number
widthPercent?: number
widthPercentMobile?: number
fullWidth?: boolean
fullWidthMobile?: boolean
// Colors
backgroundColor?: string
backgroundColorMobile?: string
textColor?: string
textColorMobile?: string
// Border
borderWidth?: number
borderWidthMobile?: number
borderStyle?: string
borderStyleMobile?: string
borderColor?: string
borderColorMobile?: string
borderRadius?: number
borderRadiusMobile?: number
// Typography
fontFamily?: string
fontFamilyMobile?: string
center?: boolean
centerMobile?: boolean
// Effects
shadowType?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'custom'
shadowTypeMobile?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'custom'
zIndex?: number
zIndexMobile?: number
// Visibility
showDesktop?: boolean
showMobile?: boolean
// Custom
customId?: string
customCSS?: string
}
Message Protocol
Editor → Preview
update
Update components data.
{
type: 'update',
data: ComponentData[],
isMobile: boolean
}
focus
Focus a specific block.
{
type: 'focus',
message: 'block-id-123'
}
highlight
Highlight a specific block.
{
type: 'highlight',
message: 'block-id-123'
}
preview
Toggle preview mode.
{
type: 'preview',
message: true | false
}
disableLinks
Disable link navigation.
{
type: 'disableLinks',
message: true | false
}
Preview → Editor
ready
Preview is ready.
{
type: 'ready',
message: {
registeredTypes: string[]
}
}
focus
User focused a block.
{
type: 'focus',
message: 'block-id-123'
}
highlight
User highlighted a block.
{
type: 'highlight',
message: 'block-id-123'
}
delete
User wants to delete a block.
{
type: 'delete',
message: 'block-id-123'
}
Contributing
Contributions are welcome! Please read our Contributing Guide for details.
License
MIT © Bagel Studio
Links
Related Packages
revenexx hard fork of blökkli — interactive page building experience for Nuxt. I...
Constructor de páginas visual basado en Vue.js que permite crear y diseñar págin...
Visual page editor and renderer for Vue. Register the components you already shi...
Version History
| Version | Released | License |
|---|---|---|
| 1.18.3 | MIT | |
| 1.17.6 | MIT | |
| 1.17.4 | MIT | |
| 1.17.3 | MIT | |
| 1.17.2 | MIT | |
| 1.16.2 | MIT | |
| 1.16.1 | MIT | |
| 1.16.0 | MIT | |
| 1.15.678 | MIT | |
| 1.15.677 | MIT | |
| 1.15.676 | MIT | |
| 1.15.675 | MIT | |
| 1.15.674 | MIT | |
| 1.15.673 | MIT | |
| 1.15.672 | MIT | |
| 1.15.671 | MIT | |
| 1.15.670 | MIT | |
| 1.15.669 | MIT | |
| 1.15.668 | MIT | |
| 1.15.667 | MIT | |
| 1.15.666 | MIT | |
| 1.15.665 | MIT | |
| 1.15.663 | MIT | |
| 1.15.661 | MIT | |
| 1.15.659 | MIT | |
| 1.15.657 | MIT | |
| 1.15.655 | MIT | |
| 1.15.653 | MIT | |
| 1.15.651 | MIT | |
| 1.15.649 | MIT |
Showing the latest 30 of 571. See every release on npm