@wix/origami-site-runtime 1.0.7 → 1.0.9
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
|
@@ -30,7 +30,9 @@ cannot hide an illegally placed target. Diagnostics retain the original node sou
|
|
|
30
30
|
routes to `#id > .id-overflow-wrapper`. Names come from the pure
|
|
31
31
|
`@wix/origami-runtime/layout-contract` entry point. Root sizing, item placement, visibility
|
|
32
32
|
and ordinary positioning remain on the ID selector. Background, border and pinned-layer
|
|
33
|
-
CSS
|
|
33
|
+
CSS comes from `@wix/origami-system-components/InternalContainer.css`, imported by the maintained
|
|
34
|
+
`@wix/origami-runtime/components/InternalContainer` component. The component preserves focus and
|
|
35
|
+
wrapper flags. Ref support is deferred
|
|
34
36
|
(see the [runtime decision](../origami-runtime/README.md#decision-defer-ref-support-2026-09-24)).
|
|
35
37
|
|
|
36
38
|
Only explicit targets in site-owned layout.css carriers opt in. Sources of inlined nodes
|
|
@@ -60,11 +62,16 @@ half-pixel floor; fixed values retain fractional pixels; zero is zero. See the
|
|
|
60
62
|
|
|
61
63
|
## Release and limitations
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
Generated structures import Page, Section, HeaderSection and FooterSection from
|
|
66
|
+
`@wix/origami-system-components` subpaths, InternalContainer from `@wix/origami-runtime`,
|
|
67
|
+
and other editor components from fetched wrappers. The package dependency order is
|
|
68
|
+
`@wix/origami-system-components` → `@wix/origami-runtime` → `@wix/origami-site-runtime`;
|
|
69
|
+
publish the required entry points before generator/CLI releases that consume them. Existing dependency
|
|
70
|
+
entries, including local/file runtime overrides, are preserved when component dependencies are added.
|
|
71
|
+
Packed tests verify the compiler and pure layout contract outside the workspace with only
|
|
72
|
+
compiler dependencies installed, without React or component implementations. Producer publication
|
|
73
|
+
and first-published reader loadability require separate verification; the producer fixtures here
|
|
74
|
+
are static captures (see test/fixtures/css/README.md).
|
|
68
75
|
|
|
69
76
|
`--spx-stopper-max` is preserved but does not clamp scaling. The SPX plugin does not generate its runtime clamp formula. `position: stickyToHeader` is preserved
|
|
70
77
|
as authored but is not a browser position value and receives no lowering. These are release
|
|
@@ -73,7 +80,7 @@ limitations, not claims of Thunderbolt parity; they need a separate producer/run
|
|
|
73
80
|
## Verification
|
|
74
81
|
|
|
75
82
|
- `pnpm --filter @wix/origami-site-runtime run test`: parser/compiler, Vite and packed tests.
|
|
76
|
-
- `pnpm --filter @wix/origami-site-runtime run test:browser`:
|
|
83
|
+
- `pnpm --filter @wix/origami-site-runtime run test:browser`: maintained InternalContainer and system-component CSS in Chromium,
|
|
77
84
|
responsive SPX and grid/flex, nested fractional fixed values, max widths/alignment, overflow,
|
|
78
85
|
visibility, focus/pointer mechanics, HMR and production build. Install Playwright Chromium or
|
|
79
86
|
set `CHROME_CHANNEL=chrome` to use installed Chrome.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/origami-site-runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "Vite development and build helpers for wix-import static sites",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"@babel/types": "^7.25.2",
|
|
20
20
|
"@wix/builder-error-pages": "^1.1.6",
|
|
21
21
|
"@wix/builder-piler-components": "^1.6.7",
|
|
22
|
+
"@wix/origami-fonts": "1.0.0",
|
|
22
23
|
"@wix/origami-runtime": "0.11.0",
|
|
23
24
|
"postcss": "^8.5.3",
|
|
24
25
|
"postcss-custom-media": "^12.0.1",
|
|
@@ -64,5 +65,5 @@
|
|
|
64
65
|
"./default-error-404": "./src/default-error-404.jsx"
|
|
65
66
|
},
|
|
66
67
|
"type": "module",
|
|
67
|
-
"falconPackageHash": "
|
|
68
|
+
"falconPackageHash": "641cce00719388c9e1fbc0fd6cab982b52a23c4daf02f37771b70615"
|
|
68
69
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Checks the theme's font tokens (`--wst-heading-1-font: normal normal 700 --spx(88)/1em benzin`). It changes
|
|
2
|
+
// nothing. The editor reads the family at the end of each one as one Wix font id and fails the import otherwise, so
|
|
3
|
+
// the build fails, with every such token listed, unless the family is exactly the id of a font in the Wix font
|
|
4
|
+
// catalog bundled in @wix/origami-fonts: no quotes, commas or fallbacks, the same case. A legacy font, which the
|
|
5
|
+
// editor no longer offers, counts as not in the catalog. `origami search-font` finds the right id.
|
|
6
|
+
|
|
7
|
+
import { basename } from 'node:path'
|
|
8
|
+
import { fonts } from '@wix/origami-fonts'
|
|
9
|
+
import { LayoutError } from '../transform/layoutError.js'
|
|
10
|
+
|
|
11
|
+
const FONT_TOKEN = /^--wst-[\w-]+-font$/
|
|
12
|
+
// The size in a `font` shorthand, with its optional line height: the family follows it. The number's quantifiers
|
|
13
|
+
// never overlap, so a long run of digits that is not a size fails in linear time.
|
|
14
|
+
const SIZE = /^(?:--spx\(.*\)|calc\(.*\)|(?:\d+(?:\.\d*)?|\.\d+)(?:px|em|rem|%|vw|vh|pt)?)(?:\/\S+)?$/i
|
|
15
|
+
const FONT_IDS = new Set(fonts.filter(font => font.permissions !== 'legacy').map(font => font.id))
|
|
16
|
+
|
|
17
|
+
/** A value split on top-level whitespace, so `--spx(16)/1.3em` stays one token. */
|
|
18
|
+
const tokens = value => {
|
|
19
|
+
const result = []
|
|
20
|
+
let depth = 0
|
|
21
|
+
let current = ''
|
|
22
|
+
for (const char of value.trim()) {
|
|
23
|
+
if (char === '(') depth++
|
|
24
|
+
else if (char === ')') depth--
|
|
25
|
+
if (depth === 0 && /\s/.test(char)) {
|
|
26
|
+
if (current) result.push(current)
|
|
27
|
+
current = ''
|
|
28
|
+
} else current += char
|
|
29
|
+
}
|
|
30
|
+
if (current) result.push(current)
|
|
31
|
+
return result
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A font shorthand's family as written; undefined for `var(…)`. */
|
|
35
|
+
const familyOf = value => {
|
|
36
|
+
if (/var\(/.test(value)) return undefined
|
|
37
|
+
const parts = tokens(value)
|
|
38
|
+
// The last size: the repo's TS lib (ES2022) has no findLastIndex.
|
|
39
|
+
let size = parts.length - 1
|
|
40
|
+
while (size >= 0 && !SIZE.test(parts[size])) size--
|
|
41
|
+
if (size === -1 || size === parts.length - 1) return undefined
|
|
42
|
+
return parts.slice(size + 1).join(' ')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** ` (theme.css:12)`: the carrier the declaration was written in, which the bundler doesn't name. */
|
|
46
|
+
const where = declaration => {
|
|
47
|
+
const file = declaration.source?.input.file
|
|
48
|
+
const line = declaration.source?.start?.line
|
|
49
|
+
return file ? ` (${basename(file)}${line ? `:${line}` : ''})` : ''
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Where `source` starts, as LayoutError's location, leaving out what it doesn't know.
|
|
54
|
+
* @param {import('postcss').Declaration['source']} source
|
|
55
|
+
*/
|
|
56
|
+
const locationOf = source => {
|
|
57
|
+
/** @type {{ file?: string, line?: number, column?: number }} */
|
|
58
|
+
const location = {}
|
|
59
|
+
if (source?.input.file !== undefined) location.file = source.input.file
|
|
60
|
+
if (source?.start !== undefined) {
|
|
61
|
+
location.line = source.start.line
|
|
62
|
+
location.column = source.start.column
|
|
63
|
+
}
|
|
64
|
+
return location
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** @type {import('../transform/types.js').CssTransformPlugin} */
|
|
68
|
+
export const fontsPlugin = {
|
|
69
|
+
name: 'fonts',
|
|
70
|
+
// Only checks; it reads the tokens as authored, before spx rewrites the sizes the family follows.
|
|
71
|
+
order: 5,
|
|
72
|
+
transform(root) {
|
|
73
|
+
/** @type {import('postcss').Declaration[]} */
|
|
74
|
+
const declarations = []
|
|
75
|
+
root.walkDecls(FONT_TOKEN, declaration => {
|
|
76
|
+
declarations.push(declaration)
|
|
77
|
+
})
|
|
78
|
+
const violations = []
|
|
79
|
+
let first
|
|
80
|
+
for (const declaration of declarations) {
|
|
81
|
+
const family = familyOf(declaration.value)
|
|
82
|
+
if (family === undefined || FONT_IDS.has(family)) continue
|
|
83
|
+
first ??= declaration.source
|
|
84
|
+
violations.push(
|
|
85
|
+
`${declaration.prop}${where(declaration)} must name exactly one Wix font id from the catalog; found ` +
|
|
86
|
+
`${family}. Run \`origami search-font --font <name>\` for the id.`
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
if (violations.length) {
|
|
90
|
+
throw new LayoutError(violations, locationOf(first))
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
}
|
package/src/cssPlugins/index.js
CHANGED
|
@@ -7,15 +7,23 @@
|
|
|
7
7
|
// stage guarantees a plugin sees. The order matters once: mixin bodies are copied into their calling rules before
|
|
8
8
|
// layout routes declarations to wrappers and SPX converts calls using the browser's active settings. stack-children
|
|
9
9
|
// only checks, and runs between mixins and layout: it needs the mixins' declarations and the stack rules as authored,
|
|
10
|
-
// before layout compiles them away.
|
|
10
|
+
// before layout compiles them away. fonts only checks the theme's font tokens, as authored.
|
|
11
11
|
//
|
|
12
12
|
// `@layer` needs no plugin: cascade layers have been in every browser since March 2022.
|
|
13
13
|
|
|
14
14
|
import { customMediaPlugin } from './customMedia.js'
|
|
15
|
+
import { fontsPlugin } from './fonts.js'
|
|
15
16
|
import { mixinsPlugin } from './mixins.js'
|
|
16
17
|
import { layoutPlugin } from './layout/index.js'
|
|
17
18
|
import { spxPlugin } from './spx/index.js'
|
|
18
19
|
import { stackChildrenPlugin } from './stackChildren.js'
|
|
19
20
|
|
|
20
21
|
/** @type {readonly import('../transform/types.js').CssTransformPlugin[]} */
|
|
21
|
-
export const cssPlugins = Object.freeze([
|
|
22
|
+
export const cssPlugins = Object.freeze([
|
|
23
|
+
customMediaPlugin,
|
|
24
|
+
fontsPlugin,
|
|
25
|
+
mixinsPlugin,
|
|
26
|
+
stackChildrenPlugin,
|
|
27
|
+
layoutPlugin,
|
|
28
|
+
spxPlugin,
|
|
29
|
+
])
|
|
@@ -1,27 +1,24 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// with
|
|
1
|
+
// Resolve Origami component identity from named virtual-module imports or named/default imports
|
|
2
|
+
// from @wix/origami-system-components/<Name>. Local aliases keep that identity; unrelated local
|
|
3
|
+
// components with the same JSX tag name are left alone.
|
|
4
4
|
|
|
5
5
|
const ORIGAMI_MODULES = new Set(['document', 'document/components', 'wixEditorElements'])
|
|
6
|
-
const COMPONENTS_DIRECTORY = 'components'
|
|
7
6
|
|
|
8
7
|
const importedName = specifier => {
|
|
9
8
|
if (specifier.type !== 'ImportSpecifier') return undefined
|
|
10
9
|
return specifier.imported.type === 'Identifier' ? specifier.imported.name : specifier.imported.value
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
// A default import is an Origami component only when it comes from a bare `.../components/<Name>`
|
|
14
|
-
// specifier; relative paths are the site's own global components.
|
|
15
|
-
const defaultComponentName = source => {
|
|
16
|
-
if (source.startsWith('.') || source.startsWith('/')) return undefined
|
|
17
|
-
|
|
18
|
-
const segments = source.replace(/\/+$/u, '').split('/')
|
|
19
|
-
return segments.at(-2) === COMPONENTS_DIRECTORY ? segments.at(-1) : undefined
|
|
20
|
-
}
|
|
21
|
-
|
|
22
12
|
const componentName = (source, specifier) => {
|
|
23
|
-
if (
|
|
24
|
-
|
|
13
|
+
if (ORIGAMI_MODULES.has(source)) return importedName(specifier)
|
|
14
|
+
|
|
15
|
+
const match = /^@wix\/origami-system-components\/([^/]+)$/u.exec(source)
|
|
16
|
+
if (!match) return undefined
|
|
17
|
+
const name = match[1]
|
|
18
|
+
const imported = importedName(specifier)
|
|
19
|
+
return specifier.type === 'ImportDefaultSpecifier' || imported === name || imported === 'default'
|
|
20
|
+
? name
|
|
21
|
+
: undefined
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
/**
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
// Runs the selected CSS plugins over one shared Root, in order. Parsing, stringifying and source
|
|
2
2
|
// maps belong to PostCSS, which drives this stage — the only thing added here is attributing a
|
|
3
3
|
// failure to the plugin and the file that caused it. A LayoutError is the author's, so it passes through.
|
|
4
|
+
//
|
|
5
|
+
// A LayoutError comes from a plugin that only checks, so the Root is as it was and the later plugins still run:
|
|
6
|
+
// the author gets every plugin's violations in one error, at the first one's position, not one plugin's per build.
|
|
4
7
|
|
|
5
8
|
import { LayoutError } from '../layoutError.js'
|
|
6
9
|
|
|
7
10
|
const reason = cause => (cause instanceof Error ? cause.message : String(cause))
|
|
8
11
|
|
|
9
12
|
export const runCssPlugins = ({ plugins, root, context }) => {
|
|
13
|
+
/** @type {LayoutError[]} */
|
|
14
|
+
const layoutErrors = []
|
|
10
15
|
for (const phase of ['validate', 'transform']) {
|
|
11
16
|
for (const plugin of plugins) {
|
|
12
17
|
try {
|
|
13
18
|
plugin[phase]?.(root, context)
|
|
14
19
|
} catch (cause) {
|
|
15
|
-
if (cause instanceof LayoutError)
|
|
20
|
+
if (cause instanceof LayoutError) {
|
|
21
|
+
layoutErrors.push(cause)
|
|
22
|
+
continue
|
|
23
|
+
}
|
|
16
24
|
// Tooling such as Vite's error overlay prints only the message, so the reason goes in it as well. A
|
|
17
25
|
// plugin may throw any value, so it is read without assuming an Error.
|
|
18
26
|
throw new Error(
|
|
@@ -22,4 +30,11 @@ export const runCssPlugins = ({ plugins, root, context }) => {
|
|
|
22
30
|
}
|
|
23
31
|
}
|
|
24
32
|
}
|
|
33
|
+
if (layoutErrors.length === 1) throw layoutErrors[0]
|
|
34
|
+
if (layoutErrors.length > 1) {
|
|
35
|
+
throw new LayoutError(
|
|
36
|
+
layoutErrors.flatMap(error => error.violations),
|
|
37
|
+
layoutErrors[0].location
|
|
38
|
+
)
|
|
39
|
+
}
|
|
25
40
|
}
|
|
@@ -12,6 +12,8 @@ export class LayoutError extends Error {
|
|
|
12
12
|
super(violations.length === 1 ? violations[0] : `\n ${violations.join('\n ')}`)
|
|
13
13
|
this.name = 'LayoutError'
|
|
14
14
|
this.stack = ''
|
|
15
|
+
this.violations = violations
|
|
16
|
+
this.location = location
|
|
15
17
|
Object.assign(this, location)
|
|
16
18
|
}
|
|
17
19
|
}
|