@uniweb/build 0.1.32 → 0.2.0

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
@@ -333,6 +333,34 @@ dist/
333
333
  └── default.webp
334
334
  ```
335
335
 
336
+ ### Schema.json Structure
337
+
338
+ The generated `schema.json` contains:
339
+
340
+ ```json
341
+ {
342
+ "_self": {
343
+ "name": "foundation",
344
+ "version": "0.1.0",
345
+ "description": "My foundation description",
346
+ "vars": { ... }
347
+ },
348
+ "Hero": { ... },
349
+ "Features": { ... }
350
+ }
351
+ ```
352
+
353
+ The `_self` object contains foundation-level metadata:
354
+
355
+ | Field | Source | Description |
356
+ |-------|--------|-------------|
357
+ | `name` | `package.json` | Foundation package name |
358
+ | `version` | `package.json` | Foundation version |
359
+ | `description` | `package.json` | Foundation description |
360
+ | `vars` | `foundation.js` | CSS custom properties sites can override |
361
+
362
+ Identity fields (`name`, `version`, `description`) come from the foundation's `package.json`. Configuration fields (`vars`, etc.) come from `src/foundation.js`.
363
+
336
364
  ## API Reference
337
365
 
338
366
  ### Schema Functions
@@ -341,7 +369,8 @@ dist/
341
369
  |----------|-------------|
342
370
  | `discoverComponents(srcDir)` | Discover all exposed components |
343
371
  | `loadComponentMeta(componentDir)` | Load meta file for a component |
344
- | `loadFoundationMeta(srcDir)` | Load foundation-level meta |
372
+ | `loadPackageJson(srcDir)` | Load identity from package.json |
373
+ | `loadFoundationConfig(srcDir)` | Load foundation.js configuration |
345
374
  | `buildSchema(srcDir)` | Build complete schema object |
346
375
 
347
376
  ### Entry Generation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "0.1.32",
3
+ "version": "0.2.0",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -51,7 +51,7 @@
51
51
  },
52
52
  "optionalDependencies": {
53
53
  "@uniweb/content-reader": "1.0.4",
54
- "@uniweb/runtime": "0.2.18"
54
+ "@uniweb/runtime": "0.2.20"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -60,7 +60,7 @@
60
60
  "@tailwindcss/vite": "^4.0.0",
61
61
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
62
62
  "vite-plugin-svgr": "^4.0.0",
63
- "@uniweb/core": "0.1.15"
63
+ "@uniweb/core": "0.2.0"
64
64
  },
65
65
  "peerDependenciesMeta": {
66
66
  "vite": {
package/src/docs.js CHANGED
@@ -165,8 +165,8 @@ export async function generateDocs(foundationDir, options = {}) {
165
165
 
166
166
  let schema
167
167
 
168
- // Try to load schema.json from dist
169
- const schemaPath = join(foundationDir, 'dist', 'schema.json')
168
+ // Try to load schema.json from dist/meta (where foundation build outputs it)
169
+ const schemaPath = join(foundationDir, 'dist', 'meta', 'schema.json')
170
170
 
171
171
  if (!fromSource && existsSync(schemaPath)) {
172
172
  // Load from existing schema.json
@@ -26,20 +26,41 @@ import { discoverComponents } from './schema.js'
26
26
  import { extractAllRuntimeSchemas } from './runtime-schema.js'
27
27
 
28
28
  /**
29
- * Detect foundation exports file (for custom Layout, props, etc.)
30
- * Looks for: src/exports.js, src/exports.jsx, src/exports/index.js, src/exports/index.jsx
29
+ * Detect foundation config/exports file (for custom Layout, props, vars, etc.)
30
+ *
31
+ * Looks for (in order of preference):
32
+ * 1. foundation.js - New consolidated format
33
+ * 2. exports.js - Legacy format (for backward compatibility)
34
+ *
35
+ * The file should export:
36
+ * - Layout (optional) - Custom page layout component
37
+ * - props (optional) - Foundation-wide props
38
+ * - vars (optional) - CSS custom properties (also read by schema builder)
31
39
  */
32
40
  function detectFoundationExports(srcDir) {
33
- const candidates = [
41
+ // Prefer foundation.js (new consolidated format)
42
+ const foundationCandidates = [
43
+ { path: 'foundation.js', ext: 'js' },
44
+ { path: 'foundation.jsx', ext: 'jsx' },
45
+ ]
46
+
47
+ for (const { path, ext } of foundationCandidates) {
48
+ if (existsSync(join(srcDir, path))) {
49
+ return { path: `./${path}`, ext, isFoundationJs: true }
50
+ }
51
+ }
52
+
53
+ // Fall back to exports.js (legacy format)
54
+ const legacyCandidates = [
34
55
  { path: 'exports.js', ext: 'js' },
35
56
  { path: 'exports.jsx', ext: 'jsx' },
36
57
  { path: 'exports/index.js', ext: 'js' },
37
58
  { path: 'exports/index.jsx', ext: 'jsx' },
38
59
  ]
39
60
 
40
- for (const { path, ext } of candidates) {
61
+ for (const { path, ext } of legacyCandidates) {
41
62
  if (existsSync(join(srcDir, path))) {
42
- return { path: `./${path.replace(/\/index\.(js|jsx)$/, '')}`, ext }
63
+ return { path: `./${path.replace(/\/index\.(js|jsx)$/, '')}`, ext, isFoundationJs: false }
43
64
  }
44
65
  }
45
66
  return null
package/src/index.js CHANGED
@@ -7,7 +7,8 @@
7
7
  // Schema discovery and loading
8
8
  export {
9
9
  loadComponentMeta,
10
- loadFoundationMeta,
10
+ loadFoundationConfig,
11
+ loadFoundationMeta, // @deprecated - use loadFoundationConfig
11
12
  discoverComponents,
12
13
  buildSchema,
13
14
  getExposedComponents,
package/src/prerender.js CHANGED
@@ -306,9 +306,11 @@ function renderBlock(block) {
306
306
  }
307
307
 
308
308
  // Wrapper props
309
+ // Use stableId for DOM ID if available (stable across reordering)
309
310
  const theme = block.themeName
311
+ const sectionId = block.stableId || block.id
310
312
  const wrapperProps = {
311
- id: `Section${block.id}`,
313
+ id: `section-${sectionId}`,
312
314
  className: theme || ''
313
315
  }
314
316
 
@@ -516,6 +518,14 @@ export async function prerenderSite(siteDir, options = {}) {
516
518
  function injectContent(shell, renderedContent, page, siteContent) {
517
519
  let html = shell
518
520
 
521
+ // Inject theme CSS if not already present
522
+ if (siteContent?.theme?.css && !html.includes('id="uniweb-theme"')) {
523
+ html = html.replace(
524
+ '</head>',
525
+ ` <style id="uniweb-theme">\n${siteContent.theme.css}\n </style>\n </head>`
526
+ )
527
+ }
528
+
519
529
  // Replace the empty root div with pre-rendered content
520
530
  html = html.replace(
521
531
  /<div id="root">[\s\S]*?<\/div>/,
@@ -548,7 +558,13 @@ function injectContent(shell, renderedContent, page, siteContent) {
548
558
 
549
559
  // Inject site content as JSON for hydration
550
560
  // Replace existing content if present, otherwise add it
551
- const contentScript = `<script id="__SITE_CONTENT__" type="application/json">${JSON.stringify(siteContent)}</script>`
561
+ // Strip CSS from theme (it's already in a <style> tag)
562
+ const contentForJson = { ...siteContent }
563
+ if (contentForJson.theme?.css) {
564
+ contentForJson.theme = { ...contentForJson.theme }
565
+ delete contentForJson.theme.css
566
+ }
567
+ const contentScript = `<script id="__SITE_CONTENT__" type="application/json">${JSON.stringify(contentForJson)}</script>`
552
568
  if (html.includes('__SITE_CONTENT__')) {
553
569
  // Replace existing site content with updated version (includes expanded dynamic routes)
554
570
  // Match script tag with attributes in any order
package/src/schema.js CHANGED
@@ -5,14 +5,17 @@
5
5
  * Schema data is for editor-time only, not runtime.
6
6
  */
7
7
 
8
- import { readdir } from 'node:fs/promises'
8
+ import { readdir, readFile } from 'node:fs/promises'
9
9
  import { existsSync } from 'node:fs'
10
- import { join, basename } from 'node:path'
10
+ import { join, dirname } from 'node:path'
11
11
  import { pathToFileURL } from 'node:url'
12
12
 
13
- // Meta file name (standardized to meta.js)
13
+ // Component meta file name
14
14
  const META_FILE_NAME = 'meta.js'
15
15
 
16
+ // Foundation config file name
17
+ const FOUNDATION_FILE_NAME = 'foundation.js'
18
+
16
19
  // Default component paths (relative to srcDir)
17
20
  const DEFAULT_COMPONENT_PATHS = ['components']
18
21
 
@@ -44,21 +47,71 @@ export async function loadComponentMeta(componentDir) {
44
47
  }
45
48
 
46
49
  /**
47
- * Load foundation-level meta file
50
+ * Load package.json from foundation root
51
+ * Extracts identity fields: name, version, description
52
+ *
53
+ * @param {string} srcDir - Source directory (e.g., 'src')
54
+ * @returns {Object} Identity fields from package.json
48
55
  */
49
- export async function loadFoundationMeta(srcDir) {
50
- const filePath = join(srcDir, META_FILE_NAME)
56
+ export async function loadPackageJson(srcDir) {
57
+ // package.json is in the foundation root (parent of srcDir)
58
+ const foundationRoot = dirname(srcDir)
59
+ const packagePath = join(foundationRoot, 'package.json')
60
+
61
+ if (!existsSync(packagePath)) {
62
+ return {}
63
+ }
64
+
65
+ try {
66
+ const content = await readFile(packagePath, 'utf-8')
67
+ const pkg = JSON.parse(content)
68
+
69
+ // Extract only identity fields for schema
70
+ return {
71
+ name: pkg.name,
72
+ version: pkg.version,
73
+ description: pkg.description,
74
+ }
75
+ } catch (error) {
76
+ console.warn(`Warning: Failed to load package.json:`, error.message)
77
+ return {}
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Load foundation-level config file (foundation.js)
83
+ *
84
+ * Contains foundation-wide configuration:
85
+ * - vars: CSS custom properties sites can override
86
+ * - Layout: Custom layout component
87
+ * - Future: providers, middleware, etc.
88
+ */
89
+ export async function loadFoundationConfig(srcDir) {
90
+ const filePath = join(srcDir, FOUNDATION_FILE_NAME)
51
91
  if (!existsSync(filePath)) {
52
92
  return {}
53
93
  }
54
94
  try {
55
- return await loadMetaFile(filePath)
95
+ const module = await import(pathToFileURL(filePath).href)
96
+ // Support both default export and named exports
97
+ return {
98
+ ...module.default,
99
+ vars: module.vars || module.default?.vars,
100
+ Layout: module.Layout || module.default?.Layout,
101
+ }
56
102
  } catch (error) {
57
- console.warn(`Warning: Failed to load foundation meta ${filePath}:`, error.message)
103
+ console.warn(`Warning: Failed to load foundation config ${filePath}:`, error.message)
58
104
  return {}
59
105
  }
60
106
  }
61
107
 
108
+ /**
109
+ * @deprecated Use loadFoundationConfig instead
110
+ */
111
+ export async function loadFoundationMeta(srcDir) {
112
+ return loadFoundationConfig(srcDir)
113
+ }
114
+
62
115
  /**
63
116
  * Discover components in a single path
64
117
  * @param {string} srcDir - Source directory (e.g., 'src')
@@ -127,17 +180,31 @@ export async function discoverComponents(srcDir, componentPaths = DEFAULT_COMPON
127
180
 
128
181
  /**
129
182
  * Build complete schema for a foundation
130
- * Returns { _self: foundationMeta, ComponentName: componentMeta, ... }
183
+ * Returns { _self: { identity + config }, ComponentName: componentMeta, ... }
184
+ *
185
+ * The _self object contains:
186
+ * - Identity from package.json (name, version, description)
187
+ * - Configuration from foundation.js (vars, Layout, etc.)
131
188
  *
132
189
  * @param {string} srcDir - Source directory
133
190
  * @param {string[]} [componentPaths] - Paths to search for components
134
191
  */
135
192
  export async function buildSchema(srcDir, componentPaths) {
136
- const foundationMeta = await loadFoundationMeta(srcDir)
193
+ // Load identity from package.json
194
+ const identity = await loadPackageJson(srcDir)
195
+
196
+ // Load configuration from foundation.js
197
+ const foundationConfig = await loadFoundationConfig(srcDir)
198
+
199
+ // Discover components
137
200
  const components = await discoverComponents(srcDir, componentPaths)
138
201
 
139
202
  return {
140
- _self: foundationMeta,
203
+ // Merge identity and config - identity fields take precedence
204
+ _self: {
205
+ ...foundationConfig,
206
+ ...identity,
207
+ },
141
208
  ...components,
142
209
  }
143
210
  }
@@ -235,7 +235,8 @@ export async function defineSiteConfig(options = {}) {
235
235
  inject: true,
236
236
  seo,
237
237
  assets,
238
- search
238
+ search,
239
+ foundationPath: foundationInfo.path // For loading foundation theme vars
239
240
  }),
240
241
 
241
242
  // Foundation dev server (only in runtime mode with local foundation)