@bagelink/blox

中文文档
1.20.64发布于 2026/11/07MITTypeScriptESM + CJS近 30 天下载 1.8万

Blox page builder library for drag-and-drop page building and static data management

原始使用文档
文档目录38 节
  1. Installation
  2. CSS
  3. Production Website Setup
  4. With Custom Components
  5. Available Built-in Component Configs
  6. Low-level Setup
  7. 1. Register Base Components
  8. 2. Use the RenderPage Component
  9. 3. Use ExternalPreview for Page Builder
  10. Core Concepts
  11. Component Registry
  12. Communication System
  13. Styling Utilities
  14. Base Components
  15. API Reference
  16. Registry API
  17. Communication API
  18. Renderer API
  19. Styles API
  20. Normalizer API
  21. Components
  22. ExternalPreview
  23. RenderPage
  24. TypeScript Types
  25. ComponentData
  26. PageData
  27. PageSettings
  28. GlobalSettings
  29. MessageType
  30. EditorMessage
  31. BlockProps
  32. StyleConfig
  33. Message Protocol
  34. Editor → Preview
  35. Preview → Editor
  36. Contributing
  37. License
  38. Links

@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/link
  • TextConfig - Rich text content
  • TitleConfig - Heading with optional subtitle
  • ImageConfig - Responsive image
  • SpacerConfig - Vertical spacing
  • ContainerConfig - 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 namespace
  • get(type) - Get from namespace
  • getAll() - 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 messages
  • handler - 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 type
  • message - Optional message payload
  • data - 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 object
  • isMobile - 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 ready
  • focus - Sent when block is focused
  • highlight - Sent when block is highlighted

RenderPage

Production render component.

<RenderPage :page-data="pageData" :mobile-breakpoint="910" />

Props:

  • pageData: PageData - Required page data to render
  • mobileBreakpoint?: 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]: 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
}

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

版本与发布记录

@bagelink/blox 共发布 607 个版本,首次发布于 2025/11/19;过去 12 个月发布了 607 个版本。

版本发布日期距今
1.20.64latest2026/11/071 天前
1.20.582026/11/062 天前
1.20.562026/11/062 天前
1.20.542026/11/062 天前
1.20.522026/11/052 天前
1.20.502026/11/052 天前

关于 @bagelink/blox 的常见问题

@bagelink/blox 是什么?

Blox page builder library for drag-and-drop page building and static data management

如何安装 @bagelink/blox?

在项目目录运行 npm install @bagelink/blox,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:vue(^3.3.0)、vue-router(^4.0.0)、@bagelink/vue(*)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 blox-ssg,可以用 npx blox-ssg 直接运行。

@bagelink/blox 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bagelink/blox 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@bagelink/blox 可以商用吗?

@bagelink/blox 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bagelink/blox 的下载量有多少?

根据 npm 官方下载统计,@bagelink/blox 在 2026/10/08 – 2026/11/06 这 30 天内被下载 18,312 次,日均约 610 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bagelink/blox 还在维护吗?

最新版本 1.20.64 发布于 2026/11/07(1 天前);至今共发布 607 个版本,其中过去 12 个月发布了 607 个;首次发布于 2025/11/19。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。