@uniweb/runtime 0.1.2 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # @uniweb/runtime
2
2
 
3
- Runtime loader and Vite plugins for Uniweb sites.
3
+ Minimal runtime for loading Uniweb foundations and orchestrating rendering.
4
4
 
5
5
  ## Overview
6
6
 
7
- This package provides the runtime environment for Uniweb sites—content-driven websites that load Foundation components dynamically. It bridges site content with Foundation components and provides Vite plugins for development.
7
+ This package provides the browser-side runtime for Uniweb sites. It loads foundations dynamically and renders content using React Router.
8
+
9
+ For Vite build plugins, see [`@uniweb/build`](https://github.com/uniweb/build).
8
10
 
9
11
  ## Installation
10
12
 
@@ -12,13 +14,6 @@ This package provides the runtime environment for Uniweb sites—content-driven
12
14
  npm install @uniweb/runtime
13
15
  ```
14
16
 
15
- ## Features
16
-
17
- - **Foundation Loading** - Load Foundations via dynamic import or import maps
18
- - **Content Rendering** - Render pages from structured content (JSON/YAML)
19
- - **Vite Plugins** - Collect site content and serve foundations in development
20
- - **React Integration** - Built on React with React Router for navigation
21
-
22
17
  ## Usage
23
18
 
24
19
  ### Runtime Loading
@@ -26,7 +21,7 @@ npm install @uniweb/runtime
26
21
  Initialize the runtime with a Foundation URL:
27
22
 
28
23
  ```jsx
29
- import { initRuntime } from '@uniweb/runtime'
24
+ import initRuntime from '@uniweb/runtime'
30
25
 
31
26
  // Load foundation from URL
32
27
  initRuntime('/foundation/foundation.js', {
@@ -40,194 +35,76 @@ initRuntime({
40
35
  })
41
36
  ```
42
37
 
43
- ### Vite Plugins for Sites
44
-
45
- Use the Vite plugins to collect content and optionally serve a foundation:
46
-
47
- ```js
48
- // vite.config.js
49
- import { defineConfig } from 'vite'
50
- import react from '@vitejs/plugin-react'
51
- import { siteContentPlugin, foundationPlugin } from '@uniweb/runtime/vite'
52
-
53
- export default defineConfig({
54
- plugins: [
55
- react(),
56
-
57
- // Collect content from pages/ directory
58
- siteContentPlugin({
59
- sitePath: './',
60
- inject: true, // Inject into HTML
61
- }),
62
-
63
- // Optional: Serve foundation in dev mode
64
- foundationPlugin({
65
- name: 'my-foundation',
66
- path: '../my-foundation',
67
- serve: '/foundation',
68
- }),
69
- ]
70
- })
71
- ```
72
-
73
- ### Site Content Structure
74
-
75
- Sites use a pages/ directory structure:
76
-
77
- ```
78
- site/
79
- ├── site.yml # Site configuration
80
- ├── theme.yml # Theme configuration
81
- └── pages/
82
- ├── @header/ # Special: header section
83
- │ └── 1.md
84
- ├── @footer/ # Special: footer section
85
- │ └── 1.md
86
- ├── home/ # Home page (route: /)
87
- │ ├── page.yml # Page metadata
88
- │ ├── 1.md # First section
89
- │ └── 2.md # Second section
90
- └── about/ # About page (route: /about)
91
- ├── page.yml
92
- └── 1.md
93
- ```
94
-
95
- ### Section Markdown Format
96
-
97
- Each `.md` file is a section with YAML frontmatter for configuration and markdown body for content:
98
-
99
- ```markdown
100
- ---
101
- component: Hero
102
- preset: default
103
- theme: dark
104
- ---
38
+ ### Static Bundling
105
39
 
106
- # Welcome to Our Site
40
+ Foundation imported directly and bundled with the site:
107
41
 
108
- Building the future together.
42
+ ```jsx
43
+ import * as Foundation from '@my-org/my-foundation'
44
+ import initRuntime from '@uniweb/runtime'
109
45
 
110
- [Get Started](#features)
46
+ initRuntime(Foundation)
111
47
  ```
112
48
 
113
- The frontmatter specifies the component and configuration parameters. The markdown body contains the actual content, which is semantically parsed into structured data (headings → titles, paragraphs → descriptions, links → CTAs).
114
-
115
- ## API Reference
116
-
117
- ### Runtime Functions
118
-
119
- | Function | Description |
120
- |----------|-------------|
121
- | `initRuntime(source, options)` | Initialize runtime with foundation |
122
- | `initRTE(source, options)` | Alias for initRuntime |
123
-
124
- ### Exported Components
125
-
126
- | Component | Description |
127
- |-----------|-------------|
128
- | `Link` | Router-aware link component |
129
- | `SafeHtml` | Safe HTML rendering |
130
- | `ChildBlocks` | Render child sections |
131
- | `ErrorBoundary` | Error boundary wrapper |
132
-
133
- ### Vite Plugins
49
+ ## API
134
50
 
135
- | Plugin | Description |
136
- |--------|-------------|
137
- | `siteContentPlugin(options)` | Collect and inject site content |
138
- | `foundationPlugin(options)` | Build and serve foundation in dev |
139
- | `collectSiteContent(sitePath)` | Programmatic content collection |
51
+ ### initRuntime(source, options)
140
52
 
141
- ### Plugin Options
53
+ Initialize the runtime with a foundation.
142
54
 
143
- **siteContentPlugin:**
144
- ```js
145
- {
146
- sitePath: './', // Path to site directory
147
- pagesDir: 'pages', // Pages subdirectory
148
- inject: true, // Inject into HTML
149
- filename: 'site-content.json', // Output filename
150
- watch: true // Watch for changes (dev)
151
- }
152
- ```
55
+ **source** - One of:
56
+ - URL string to foundation module
57
+ - Object with `{ url, cssUrl }`
58
+ - Foundation module object (for static bundling)
153
59
 
154
- **foundationPlugin:**
60
+ **options:**
155
61
  ```js
156
62
  {
157
- name: 'foundation', // Foundation name (for logs)
158
- path: '../foundation', // Path to foundation package
159
- serve: '/foundation', // URL path to serve from
160
- watch: true, // Watch for changes
161
- buildOnStart: true // Build when dev server starts
63
+ development: false, // Enable dev mode (StrictMode, verbose errors)
64
+ configData: null, // Site content (or reads from DOM)
65
+ basename: undefined // React Router basename
162
66
  }
163
67
  ```
164
68
 
165
- ## Core Classes
166
-
167
- ### Uniweb
168
-
169
- The main runtime instance (available as `globalThis.uniweb`):
170
-
171
- ```js
172
- uniweb.getComponent(name) // Get component from foundation
173
- uniweb.listComponents() // List available components
174
- uniweb.activeWebsite // Current website instance
175
- ```
176
-
177
- ### Website
69
+ For core classes (`Uniweb`, `Website`, `Block`, etc.), import from [`@uniweb/core`](https://github.com/uniweb/core).
178
70
 
179
- Manages pages, theme, and localization:
71
+ ## Architecture
180
72
 
181
- ```js
182
- website.getPage(route) // Get page by route
183
- website.setActivePage(route) // Navigate to page
184
- website.localize(value) // Localize a value
185
- website.getLanguage() // Get current language
186
73
  ```
187
-
188
- ### Block
189
-
190
- Represents a section on a page:
191
-
192
- ```js
193
- block.initComponent() // Initialize foundation component
194
- block.getBlockContent() // Get structured content
195
- block.getBlockProperties() // Get configuration properties
196
- block.getBlockLinks() // Get links from content
74
+ @uniweb/runtime (browser)
75
+
76
+ ├── Loads foundation dynamically
77
+ ├── Creates Uniweb instance via @uniweb/core
78
+ └── Orchestrates React/Router rendering
197
79
  ```
198
80
 
199
- ## Two Loading Modes
81
+ Foundations should:
82
+ - Import components from `@uniweb/kit` (bundled)
83
+ - Mark `@uniweb/core` as external (provided by runtime)
200
84
 
201
- ### Runtime Loading (Import Maps)
85
+ ## Build Plugins
202
86
 
203
- Foundation loaded at runtime via dynamic import. React provided by host via import map.
87
+ Vite plugins have moved to `@uniweb/build`:
204
88
 
205
- ```html
206
- <script type="importmap">
207
- {
208
- "imports": {
209
- "react": "https://esm.sh/react@18",
210
- "react-dom": "https://esm.sh/react-dom@18"
211
- }
212
- }
213
- </script>
214
- ```
215
-
216
- ### Static Bundling
217
-
218
- Foundation imported directly and bundled with the site.
219
-
220
- ```jsx
221
- import * as Foundation from '@my-org/my-foundation'
222
- import { initRuntime } from '@uniweb/runtime'
89
+ ```js
90
+ // vite.config.js
91
+ import { siteContentPlugin } from '@uniweb/build/site'
92
+ import { foundationDevPlugin } from '@uniweb/build/dev'
223
93
 
224
- initRuntime(Foundation)
94
+ export default defineConfig({
95
+ plugins: [
96
+ siteContentPlugin({ sitePath: './', inject: true }),
97
+ foundationDevPlugin({ path: '../foundation', serve: '/foundation' }),
98
+ ]
99
+ })
225
100
  ```
226
101
 
227
102
  ## Related Packages
228
103
 
229
- - [`uniweb`](https://github.com/uniweb/cli) - CLI for creating Uniweb projects
230
- - [`@uniweb/build`](https://github.com/uniweb/build) - Foundation build tooling
104
+ - [`@uniweb/core`](https://github.com/uniweb/core) - Core classes (Uniweb, Website, Block)
105
+ - [`@uniweb/kit`](https://github.com/uniweb/kit) - Component library for foundations
106
+ - [`@uniweb/build`](https://github.com/uniweb/build) - Vite build plugins
107
+ - [`uniweb`](https://github.com/uniweb/cli) - CLI for creating projects
231
108
 
232
109
  ## License
233
110
 
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@uniweb/runtime",
3
- "version": "0.1.2",
4
- "description": "Runtime loader and Vite plugins for Uniweb sites",
3
+ "version": "0.2.2",
4
+ "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
7
- ".": "./src/index.jsx",
8
- "./vite": "./src/vite/index.js"
7
+ ".": "./src/index.jsx"
9
8
  },
10
9
  "files": [
11
10
  "src"
@@ -13,9 +12,8 @@
13
12
  "keywords": [
14
13
  "uniweb",
15
14
  "runtime",
16
- "vite",
17
15
  "react",
18
- "components"
16
+ "foundation"
19
17
  ],
20
18
  "author": "Proximify",
21
19
  "license": "Apache-2.0",
@@ -31,21 +29,11 @@
31
29
  "node": ">=20.19"
32
30
  },
33
31
  "dependencies": {
34
- "js-yaml": "^4.1.0",
35
- "@uniweb/semantic-parser": "1.0.1"
32
+ "@uniweb/core": "0.1.5"
36
33
  },
37
34
  "peerDependencies": {
38
- "react": "^18.0.0",
39
- "react-dom": "^18.0.0",
40
- "react-router-dom": "^6.0.0 || ^7.0.0",
41
- "vite": "^5.0.0 || ^6.0.0 || ^7.0.0"
42
- },
43
- "peerDependenciesMeta": {
44
- "vite": {
45
- "optional": true
46
- }
47
- },
48
- "optionalDependencies": {
49
- "@uniweb/content-reader": "1.0.1"
35
+ "react": "^18.0.0 || ^19.0.0",
36
+ "react-dom": "^18.0.0 || ^19.0.0",
37
+ "react-router-dom": "^6.0.0 || ^7.0.0"
50
38
  }
51
39
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Blocks
3
+ *
4
+ * Renders an array of blocks for a layout area (header, body, footer, panels).
5
+ * Used by the Layout component to pre-render each area.
6
+ */
7
+
8
+ import React from 'react'
9
+ import BlockRenderer from './BlockRenderer.jsx'
10
+
11
+ /**
12
+ * Render a list of blocks
13
+ *
14
+ * @param {Object} props
15
+ * @param {Block[]} props.blocks - Array of Block instances to render
16
+ * @param {Object} [props.extra] - Extra props to pass to each block
17
+ */
18
+ export default function Blocks({ blocks, extra = {} }) {
19
+ if (!blocks || blocks.length === 0) return null
20
+
21
+ return blocks.map((block, index) => (
22
+ <React.Fragment key={block.id || index}>
23
+ <BlockRenderer block={block} extra={extra} />
24
+ </React.Fragment>
25
+ ))
26
+ }
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Layout
3
+ *
4
+ * Orchestrates page rendering by assembling layout areas (header, body, footer, panels).
5
+ * Supports foundation-provided custom Layout components via website.getRemoteLayout().
6
+ *
7
+ * Layout Areas:
8
+ * - header: Top navigation, branding (from @header page)
9
+ * - body: Main page content (from page sections)
10
+ * - footer: Bottom navigation, copyright (from @footer page)
11
+ * - left: Left sidebar/panel (from @left page)
12
+ * - right: Right sidebar/panel (from @right page)
13
+ *
14
+ * Custom Layouts:
15
+ * Foundations can export a Layout component that receives pre-rendered areas as props:
16
+ *
17
+ * ```jsx
18
+ * export const site = {
19
+ * Layout: ({ page, website, header, body, footer, left, right }) => (
20
+ * <div className="my-layout">
21
+ * <header>{header}</header>
22
+ * <aside>{left}</aside>
23
+ * <main>{body}</main>
24
+ * <aside>{right}</aside>
25
+ * <footer>{footer}</footer>
26
+ * </div>
27
+ * )
28
+ * }
29
+ * ```
30
+ */
31
+
32
+ import React from 'react'
33
+ import Blocks from './Blocks.jsx'
34
+
35
+ /**
36
+ * Default layout - renders header, body, footer in sequence
37
+ * (no panels in default layout)
38
+ */
39
+ function DefaultLayout({ header, body, footer }) {
40
+ return (
41
+ <>
42
+ {header}
43
+ {body}
44
+ {footer}
45
+ </>
46
+ )
47
+ }
48
+
49
+ /**
50
+ * Layout component
51
+ *
52
+ * @param {Object} props
53
+ * @param {Page} props.page - Current page instance
54
+ * @param {Website} props.website - Website instance
55
+ */
56
+ export default function Layout({ page, website }) {
57
+ // Check if foundation provides a custom Layout
58
+ const RemoteLayout = website.getRemoteLayout()
59
+
60
+ // Get block groups from page (respects layout preferences)
61
+ const headerBlocks = page.getHeaderBlocks()
62
+ const bodyBlocks = page.getBodyBlocks()
63
+ const footerBlocks = page.getFooterBlocks()
64
+ const leftBlocks = page.getLeftBlocks()
65
+ const rightBlocks = page.getRightBlocks()
66
+
67
+ // Pre-render each area as React elements
68
+ const headerElement = headerBlocks ? <Blocks blocks={headerBlocks} /> : null
69
+ const bodyElement = bodyBlocks ? <Blocks blocks={bodyBlocks} /> : null
70
+ const footerElement = footerBlocks ? <Blocks blocks={footerBlocks} /> : null
71
+ const leftElement = leftBlocks ? <Blocks blocks={leftBlocks} /> : null
72
+ const rightElement = rightBlocks ? <Blocks blocks={rightBlocks} /> : null
73
+
74
+ // Use foundation's custom Layout if provided
75
+ if (RemoteLayout) {
76
+ return (
77
+ <RemoteLayout
78
+ page={page}
79
+ website={website}
80
+ header={headerElement}
81
+ body={bodyElement}
82
+ footer={footerElement}
83
+ left={leftElement}
84
+ right={rightElement}
85
+ // Aliases for backwards compatibility
86
+ leftPanel={leftElement}
87
+ rightPanel={rightElement}
88
+ />
89
+ )
90
+ }
91
+
92
+ // Default layout
93
+ return (
94
+ <DefaultLayout
95
+ header={headerElement}
96
+ body={bodyElement}
97
+ footer={footerElement}
98
+ />
99
+ )
100
+ }
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * PageRenderer
3
3
  *
4
- * Renders a page by iterating through its blocks.
4
+ * Renders a page using the Layout component for proper orchestration
5
+ * of header, body, footer, and panel areas.
6
+ * Manages head meta tags for SEO and social sharing.
5
7
  */
6
8
 
7
- import React, { useEffect } from 'react'
9
+ import React from 'react'
8
10
  import BlockRenderer from './BlockRenderer.jsx'
11
+ import Layout from './Layout.jsx'
12
+ import { useHeadMeta } from '../hooks/useHeadMeta.js'
9
13
 
10
14
  /**
11
15
  * ChildBlocks - renders child blocks of a block
@@ -23,17 +27,27 @@ export function ChildBlocks({ block, childBlocks, pure = false, extra = {} }) {
23
27
 
24
28
  /**
25
29
  * PageRenderer component
30
+ *
31
+ * Renders the current page using the Layout system which supports:
32
+ * - Header, body, footer areas
33
+ * - Left and right panels
34
+ * - Foundation-provided custom layouts
35
+ * - Per-page layout preferences
26
36
  */
27
37
  export default function PageRenderer() {
28
- const page = globalThis.uniweb?.activeWebsite?.activePage
29
- const pageTitle = page?.title || 'Website'
38
+ const uniweb = globalThis.uniweb
39
+ const website = uniweb?.activeWebsite
40
+ const page = website?.activePage
41
+ const siteName = website?.name || ''
42
+
43
+ // Get head metadata from page (uses Page.getHeadMeta() if available)
44
+ const headMeta = page?.getHeadMeta?.() || {
45
+ title: page?.title || 'Website',
46
+ description: page?.description || ''
47
+ }
30
48
 
31
- useEffect(() => {
32
- document.title = pageTitle
33
- return () => {
34
- document.title = 'Website'
35
- }
36
- }, [pageTitle])
49
+ // Manage head meta tags
50
+ useHeadMeta(headMeta, { siteName })
37
51
 
38
52
  if (!page) {
39
53
  return (
@@ -43,15 +57,6 @@ export default function PageRenderer() {
43
57
  )
44
58
  }
45
59
 
46
- const blocks = page.getPageBlocks()
47
-
48
- return (
49
- <>
50
- {blocks.map((block, index) => (
51
- <React.Fragment key={block.id || index}>
52
- <BlockRenderer block={block} />
53
- </React.Fragment>
54
- ))}
55
- </>
56
- )
60
+ // Use Layout component for proper orchestration
61
+ return <Layout page={page} website={website} />
57
62
  }
@@ -2,10 +2,12 @@
2
2
  * WebsiteRenderer
3
3
  *
4
4
  * Top-level renderer that sets up theme styles and renders pages.
5
+ * Manages scroll memory for navigation and optional analytics.
5
6
  */
6
7
 
7
8
  import React from 'react'
8
9
  import PageRenderer from './PageRenderer.jsx'
10
+ import { useRememberScroll } from '../hooks/useRememberScroll.js'
9
11
 
10
12
  /**
11
13
  * Build CSS custom properties from theme data
@@ -57,6 +59,9 @@ function Fonts({ fontsData }) {
57
59
  export default function WebsiteRenderer() {
58
60
  const website = globalThis.uniweb?.activeWebsite
59
61
 
62
+ // Enable scroll memory for navigation
63
+ useRememberScroll({ enabled: true })
64
+
60
65
  if (!website) {
61
66
  return (
62
67
  <div className="website-loading" style={{ padding: '2rem', textAlign: 'center', color: '#64748b' }}>