@uniweb/build 0.80.3 → 1.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/build",
3
- "version": "0.80.3",
3
+ "version": "1.0.1",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -53,19 +53,19 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@citestyle/bibtex": "^1.1.0",
56
- "esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0",
56
+ "esbuild": "^0.21.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.27.0 || ^0.28.0",
57
57
  "js-yaml": "^4.1.0",
58
58
  "sharp": "^0.35.3",
59
59
  "yaml": "^2.5.0",
60
- "@uniweb/projections": "^0.7.15",
61
- "@uniweb/content-reader": "^1.2.8",
62
- "@uniweb/content-writer": "^0.3.4",
63
- "@uniweb/schemas": "^0.13.0",
64
- "@uniweb/semantic-parser": "^1.5.1",
65
- "@uniweb/theming": "^0.2.0"
60
+ "@uniweb/content-reader": "~1.2.8",
61
+ "@uniweb/content-writer": "~1.0.1",
62
+ "@uniweb/semantic-parser": "~1.5.1",
63
+ "@uniweb/theming": "~1.0.1",
64
+ "@uniweb/projections": "~1.0.1",
65
+ "@uniweb/schemas": "~1.0.1"
66
66
  },
67
67
  "optionalDependencies": {
68
- "@uniweb/runtime": "^0.29.4"
68
+ "@uniweb/runtime": "~1.0.1"
69
69
  },
70
70
  "peerDependencies": {
71
71
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -74,8 +74,8 @@
74
74
  "@tailwindcss/vite": "^4.0.0",
75
75
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
76
76
  "vite-plugin-svgr": "^4.0.0",
77
- "@uniweb/core": "^0.38.0",
78
- "@uniweb/runtime": "^0.29.4"
77
+ "@uniweb/runtime": "~1.0.1",
78
+ "@uniweb/core": "~1.0.1"
79
79
  },
80
80
  "peerDependenciesMeta": {
81
81
  "vite": {
@@ -25,6 +25,7 @@
25
25
 
26
26
  import { resolve } from 'node:path'
27
27
  import { foundationPlugin } from '../vite-foundation-plugin.js'
28
+ import { jsxExtensionGuard } from '../jsx-extension.js'
28
29
  import { resolveFoundationSrcDir } from '../utils/foundation-source-root.js'
29
30
  // The ONE definition. This list used to be re-typed here, and again in
30
31
  // vite-foundation-plugin.js — three hand-synced copies of "what a foundation
@@ -107,6 +108,8 @@ export async function defineFoundationConfig(options = {}) {
107
108
  // foundationPlugin handles entry generation and schema building
108
109
  const plugins = [
109
110
  foundationPlugin({ srcDir, sections: sectionPaths }),
111
+ // JSX compiles only in .jsx files: a .js file holding it fails with that said.
112
+ jsxExtensionGuard(),
110
113
  tailwind && tailwindcss(),
111
114
  react(),
112
115
  svgr(),
@@ -0,0 +1,68 @@
1
+ /**
2
+ * JSX compiles only in `.jsx` and `.tsx` files — in a foundation's Vite build, in a
3
+ * site build that bundles its foundation, and in the step that reads `main.js`
4
+ * (`schema.js`, `importFoundationConfig`).
5
+ *
6
+ * ⭐ THE RULE, SAID WHERE IT FAILS. A `.js` file holding JSX fails each of those with a
7
+ * parser's message that never says why — Vite's "Expression expected", esbuild's "The JSX
8
+ * syntax extension is not currently enabled" — and section discovery accepts `.js`
9
+ * components, which build only without JSX. Ruled 2026-10-11: JSX stays in `.jsx` files,
10
+ * and the failure names the file and the fix.
11
+ */
12
+
13
+ import { basename, relative } from 'node:path'
14
+
15
+ const JS_FILE = /\.m?js$/
16
+
17
+ /**
18
+ * What to tell an author whose `.js` file holds JSX — the rule and the fix, without the
19
+ * location, which each caller states its own way (Vite prefixes `file (line:col):`).
20
+ *
21
+ * @param {string} file - the file holding JSX
22
+ * @returns {string}
23
+ */
24
+ export function jsxInJsMessage(file) {
25
+ const name = basename(file)
26
+ // `main.js` is the one name a foundation's declarations are read from, so it cannot
27
+ // become `main.jsx`: its components move out instead.
28
+ if (name === 'main.js') {
29
+ return 'JSX compiles only in .jsx and .tsx files, and main.js is plain JavaScript: move the component into a .jsx file and import it.'
30
+ }
31
+ return `JSX compiles only in .jsx and .tsx files — rename ${name} to ${name.replace(JS_FILE, '.jsx')}.`
32
+ }
33
+
34
+ /**
35
+ * A Vite plugin that turns a parse failure on JSX in a `.js` file into
36
+ * {@link jsxInJsMessage}. Silent otherwise: a file that parses, or fails for any other
37
+ * reason, is left to Vite. A file under `node_modules` is skipped — the advice is for files
38
+ * the author can rename.
39
+ *
40
+ * It parses only a project `.js` file that contains a `<`, and the failure it reports is the
41
+ * one the build would hit next: the parser stopped at that `<`.
42
+ *
43
+ * @returns {import('vite').Plugin}
44
+ */
45
+ export function jsxExtensionGuard() {
46
+ let root = process.cwd()
47
+ return {
48
+ name: 'uniweb-jsx-extension',
49
+ enforce: 'pre',
50
+ configResolved(config) {
51
+ root = config.root
52
+ },
53
+ transform(code, id) {
54
+ const file = id.split('?')[0]
55
+ if (!JS_FILE.test(file) || file.includes('/node_modules/') || !code.includes('<')) return null
56
+ if (typeof this.parse !== 'function') return null
57
+ try {
58
+ this.parse(code)
59
+ } catch (error) {
60
+ const pos = error?.pos
61
+ if (typeof pos === 'number' && code[pos] === '<') {
62
+ this.error(jsxInJsMessage(relative(root, file)), pos)
63
+ }
64
+ }
65
+ return null
66
+ },
67
+ }
68
+ }
package/src/schema.js CHANGED
@@ -14,8 +14,9 @@ import { readdir, readFile, rm } from 'node:fs/promises'
14
14
  import { existsSync } from 'node:fs'
15
15
  import { createHash } from 'node:crypto'
16
16
  import { isFontVar } from '@uniweb/theming'
17
- import { join, dirname, extname, basename } from 'node:path'
17
+ import { join, dirname, extname, basename, relative, resolve } from 'node:path'
18
18
  import { pathToFileURL } from 'node:url'
19
+ import { jsxInJsMessage } from './jsx-extension.js'
19
20
  import { describeChildren, describeVisuals, lowerChildren } from '@uniweb/schemas/component'
20
21
  import { describeContent, lowerData } from '@uniweb/schemas/content'
21
22
  import { normalizeData, schemaDeclarationOf } from '@uniweb/schemas/foundation'
@@ -433,6 +434,16 @@ async function importFoundationConfig(filePath) {
433
434
  logLevel: 'silent',
434
435
  })
435
436
  return await import(`${pathToFileURL(outfile).href}?content=${content}`)
437
+ } catch (bundleError) {
438
+ // JSX in a `.js` file — `main.js` itself or a module it imports — compiles nowhere
439
+ // (jsx-extension.js): say so, rather than esbuild's "The JSX syntax extension is not
440
+ // currently enabled".
441
+ const jsx = bundleError?.errors?.find(
442
+ (e) => /JSX syntax extension/.test(e.text) && /\.m?js$/.test(e.location?.file ?? ''),
443
+ )
444
+ if (!jsx) throw bundleError
445
+ const file = relative(dirname(filePath), resolve(jsx.location.file))
446
+ throw new Error(`${file} (line ${jsx.location.line}): ${jsxInJsMessage(file)}`, { cause: bundleError })
436
447
  } finally {
437
448
  await rm(outfile, { force: true }).catch(() => {})
438
449
  }
@@ -482,13 +493,15 @@ export async function loadFoundationConfig(srcDir) {
482
493
  )
483
494
  }
484
495
  // ⛔ The same for `main.jsx`, which the entry used to import while this read nothing — the
485
- // editor saw none of its declarations, the runtime all of them (until 2026-10-01). A main.js
486
- // may hold JSX: `importFoundationConfig` transpiles it.
496
+ // editor saw none of its declarations, the runtime all of them (until 2026-10-01).
497
+ // ⛔ This said "rename it to main.js, which may hold JSX" until 2026-10-11, and main.js never
498
+ // could: JSX compiles only in .jsx files, here and in Vite (jsx-extension.js). A main.jsx's
499
+ // components move into a .jsx file the new main.js imports.
487
500
  const jsx = join(srcDir, 'main.jsx')
488
501
  if (existsSync(jsx)) {
489
502
  throw new Error(
490
- `${jsx} is not read — rename it to main.js, which may hold JSX.\n` +
491
- ` A foundation's declarations live in main.js; left as main.jsx, the build would continue with none of them.`,
503
+ `${jsx} is not read — rename it to main.js, and move any JSX it holds into a .jsx file it imports.\n` +
504
+ ` A foundation's declarations live in main.js, which is plain JavaScript; left as main.jsx, the build would continue with none of them.`,
492
505
  )
493
506
  }
494
507
  return {}
@@ -30,6 +30,7 @@ import {
30
30
  getStructuralWatchPaths
31
31
  } from '../generate-entry.js'
32
32
  import { importMapPlugin } from '../import-map-plugin.js'
33
+ import { jsxExtensionGuard } from '../jsx-extension.js'
33
34
  import { resolveModuleUrl, resolveExtensionUrls } from './extension-urls.js'
34
35
  import { resolveFoundationSrcPath } from '../utils/foundation-source-root.js'
35
36
  import { checkFoundationResolution } from '../utils/foundation-resolution-check.js'
@@ -317,6 +318,8 @@ export async function defineSiteConfig(options = {}) {
317
318
  ensureFoundationEntryPlugin,
318
319
 
319
320
  // Standard plugins
321
+ // JSX compiles only in .jsx files — a bundled foundation's too.
322
+ jsxExtensionGuard(),
320
323
  tailwind && tailwindcss(),
321
324
  react(),
322
325
  svgr(),