@uniweb/build 0.24.5 → 0.25.1

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.
Files changed (2) hide show
  1. package/package.json +9 -9
  2. package/src/schema.js +89 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "0.24.5",
3
+ "version": "0.25.1",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -60,16 +60,16 @@
60
60
  "sharp": "^0.35.3",
61
61
  "yaml": "^2.5.0",
62
62
  "@uniweb/schemas": "^0.2.10",
63
- "@uniweb/content-writer": "^0.3.3",
64
- "@uniweb/projections": "^0.3.3",
65
- "@uniweb/semantic-parser": "^1.2.3",
66
- "@uniweb/theming": "^0.1.15"
63
+ "@uniweb/theming": "^0.1.15",
64
+ "@uniweb/projections": "^0.3.4",
65
+ "@uniweb/content-writer": "^0.3.4",
66
+ "@uniweb/semantic-parser": "^1.3.0"
67
67
  },
68
68
  "optionalDependencies": {
69
- "@uniweb/content-reader": "^1.2.3",
69
+ "@uniweb/semantic-parser": "^1.3.0",
70
70
  "@uniweb/schemas": "^0.2.10",
71
- "@uniweb/runtime": "^0.12.2",
72
- "@uniweb/semantic-parser": "^1.2.3"
71
+ "@uniweb/runtime": "^0.12.9",
72
+ "@uniweb/content-reader": "^1.2.4"
73
73
  },
74
74
  "peerDependencies": {
75
75
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -78,7 +78,7 @@
78
78
  "@tailwindcss/vite": "^4.0.0",
79
79
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
80
80
  "vite-plugin-svgr": "^4.0.0",
81
- "@uniweb/core": "^0.10.1"
81
+ "@uniweb/core": "^0.11.2"
82
82
  },
83
83
  "peerDependenciesMeta": {
84
84
  "vite": {
package/src/schema.js CHANGED
@@ -10,7 +10,7 @@
10
10
  * - Additional paths (via config): meta.js required for addressability
11
11
  */
12
12
 
13
- import { readdir, readFile } from 'node:fs/promises'
13
+ import { readdir, readFile, rm } from 'node:fs/promises'
14
14
  import { existsSync } from 'node:fs'
15
15
  import { isFontVar } from '@uniweb/theming'
16
16
  import { join, dirname, extname, basename } from 'node:path'
@@ -177,6 +177,70 @@ function warnMisplacedCapabilities(module, filePath) {
177
177
  )
178
178
  }
179
179
 
180
+ /**
181
+ * Import a foundation config, transpiling it when Node alone cannot.
182
+ *
183
+ * ⛔ A foundation config routinely imports JSX, and Node cannot parse it.
184
+ * `defaultInsets` and `xref` take REACT COMPONENTS — that is what they are
185
+ * for — so `main.js` legitimately reads:
186
+ *
187
+ * import { buildXrefRegistry, Ref } from '@uniweb/kit/xref'
188
+ *
189
+ * kit ships source, so that resolves to `Ref.jsx` and a bare `import()`
190
+ * throws `Unknown file extension ".jsx"`. The config is fine; the loader was
191
+ * the problem.
192
+ *
193
+ * Fast path first: a plain `import()`, which is free and covers the common
194
+ * case of a config that is pure data. Only when Node rejects the SYNTAX do we
195
+ * pay for a bundle — the same fallback shape Vite uses for `vite.config.ts`.
196
+ *
197
+ * React stays external because Node imports it happily and it is the bulk of
198
+ * the graph; everything else is inlined so no `.jsx` survives to be resolved
199
+ * at run time. The temp file is written beside the config so bare specifiers
200
+ * still resolve from the project's own `node_modules`.
201
+ */
202
+ async function importFoundationConfig(filePath) {
203
+ const href = pathToFileURL(filePath).href
204
+ try {
205
+ return await import(href)
206
+ } catch (error) {
207
+ if (!isUnparseableByNode(error)) throw error
208
+
209
+ const esbuild = (await import('esbuild')).default ?? (await import('esbuild'))
210
+ const outfile = join(
211
+ dirname(filePath),
212
+ `.${basename(filePath)}.uniweb-config.${process.pid}.mjs`,
213
+ )
214
+ try {
215
+ await esbuild.build({
216
+ entryPoints: [filePath],
217
+ outfile,
218
+ bundle: true,
219
+ format: 'esm',
220
+ platform: 'node',
221
+ jsx: 'automatic',
222
+ // Node can load these as-is, and they dominate the graph.
223
+ external: ['react', 'react/*', 'react-dom', 'react-dom/*'],
224
+ logLevel: 'silent',
225
+ })
226
+ return await import(pathToFileURL(outfile).href)
227
+ } finally {
228
+ await rm(outfile, { force: true }).catch(() => {})
229
+ }
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Does this error mean "Node cannot read this file", as opposed to "the
235
+ * config threw"? Only the former is worth re-trying through a bundler —
236
+ * re-running a config that threw on its own would just throw again.
237
+ */
238
+ function isUnparseableByNode(error) {
239
+ if (error instanceof SyntaxError) return true
240
+ const code = error?.code
241
+ return code === 'ERR_UNKNOWN_FILE_EXTENSION' || code === 'ERR_UNSUPPORTED_DIR_IMPORT'
242
+ }
243
+
180
244
  export async function loadFoundationConfig(srcDir) {
181
245
  let filePath = null
182
246
  for (const name of FOUNDATION_FILE_NAMES) {
@@ -188,18 +252,32 @@ export async function loadFoundationConfig(srcDir) {
188
252
  }
189
253
  if (!filePath) return {}
190
254
 
255
+ let module
191
256
  try {
192
- const module = await import(pathToFileURL(filePath).href)
193
- warnMisplacedCapabilities(module, filePath)
194
- // Support both default export and named exports
195
- return {
196
- ...module.default,
197
- vars: inferFontVarTypes(module.vars || module.default?.vars),
198
- defaultLayout: module.default?.defaultLayout,
199
- }
257
+ module = await importFoundationConfig(filePath)
200
258
  } catch (error) {
201
- console.warn(`Warning: Failed to load foundation config ${filePath}:`, error.message)
202
- return {}
259
+ // NEVER degrade to `{}` here. Everything a foundation declares `vars`,
260
+ // `xref`, `defaultInsets`, `name` — arrives through this one call, so an
261
+ // empty return silently produces a foundation with NO THEME VARIABLES. The
262
+ // symptom is a site whose `px-[var(--section-padding-x)]` resolves to 0 and
263
+ // whose `max-w-[var(--width-content)]` resolves to `none`: content sprawls
264
+ // edge to edge and every layout token is gone, with nothing in the output
265
+ // naming a cause. Measured 2026-08-23 on a real site that had been shipping
266
+ // that way for three weeks behind a single `console.warn`.
267
+ throw new Error(
268
+ `Failed to load foundation config ${filePath}: ${error.message}\n` +
269
+ ` Everything the foundation declares (vars, xref, defaultInsets, name) comes from this file,\n` +
270
+ ` so the build cannot continue without it — a partial load would emit a site with no theme variables.`,
271
+ { cause: error },
272
+ )
273
+ }
274
+
275
+ warnMisplacedCapabilities(module, filePath)
276
+ // Support both default export and named exports
277
+ return {
278
+ ...module.default,
279
+ vars: inferFontVarTypes(module.vars || module.default?.vars),
280
+ defaultLayout: module.default?.defaultLayout,
203
281
  }
204
282
  }
205
283