@jay-framework/jay-stack-cli 0.19.4 → 0.19.6
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.
|
@@ -21,6 +21,11 @@ A `.jay-html` file is standard HTML with jay-specific extensions.
|
|
|
21
21
|
<!-- Headfull component imports -->
|
|
22
22
|
<script type="application/jay-headfull" src="..." names="..." contract="..."></script>
|
|
23
23
|
|
|
24
|
+
<!-- SEO head tags (support {binding} syntax) -->
|
|
25
|
+
<title>{productPage.name} | My Store</title>
|
|
26
|
+
<meta name="description" content="{productPage.description}" />
|
|
27
|
+
<link rel="canonical" href="https://mystore.com/products/{productPage.slug}" />
|
|
28
|
+
|
|
24
29
|
<!-- Styles -->
|
|
25
30
|
<style>
|
|
26
31
|
/* inline CSS */
|
|
@@ -34,6 +39,22 @@ A `.jay-html` file is standard HTML with jay-specific extensions.
|
|
|
34
39
|
</html>
|
|
35
40
|
```
|
|
36
41
|
|
|
42
|
+
## Head Tag Bindings
|
|
43
|
+
|
|
44
|
+
`<title>`, `<meta>`, and `<link>` in `<head>` support `{binding}` syntax — the same expressions used in `<body>`. Bindings resolve against the merged ViewState at SSR time.
|
|
45
|
+
|
|
46
|
+
```html
|
|
47
|
+
<head>
|
|
48
|
+
<title>{productPage.name} | My Store</title>
|
|
49
|
+
<meta name="description" content="{productPage.description}" />
|
|
50
|
+
<link rel="canonical" href="https://mystore.com/products/{productPage.slug}" />
|
|
51
|
+
</head>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
If a headless component also provides head tags via `phaseOutput({ headTags })`, the **template wins** — template head tags override component-provided ones. This lets you customize the head while components provide defaults.
|
|
55
|
+
|
|
56
|
+
Canonical URLs must be absolute (`https://...`). The `{binding}` syntax can be used for the dynamic part (e.g., slug).
|
|
57
|
+
|
|
37
58
|
## Data Binding
|
|
38
59
|
|
|
39
60
|
Use `{expression}` to bind contract data:
|
|
@@ -71,11 +71,51 @@ const headTags = seoData.tags.map((tag) => ({
|
|
|
71
71
|
return phaseOutput(viewState, carryForward, { headTags });
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
## Declaring Head Tags in plugin.yaml
|
|
75
|
+
|
|
76
|
+
If your component provides head tags dynamically via `phaseOutput`, declare them in `plugin.yaml` so the SEO validator knows not to warn about missing title/description on pages using your component:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
name: my-plugin
|
|
80
|
+
contracts:
|
|
81
|
+
- name: product-page
|
|
82
|
+
contract: product-page.jay-contract
|
|
83
|
+
component: productPage
|
|
84
|
+
headTags:
|
|
85
|
+
- title
|
|
86
|
+
- meta:description
|
|
87
|
+
- link:canonical
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Values: `title`, `meta:<name>` (e.g., `meta:description`, `meta:og:title`), `link:<rel>` (e.g., `link:canonical`).
|
|
91
|
+
|
|
92
|
+
## Priority: Template Wins
|
|
93
|
+
|
|
94
|
+
When both the jay-html template and a component provide the same head tag, the **template wins**:
|
|
95
|
+
|
|
96
|
+
1. Component `phaseOutput({ headTags })` — defaults
|
|
97
|
+
2. Jay-html `<head>` tags — **highest priority, overrides component**
|
|
98
|
+
|
|
99
|
+
This lets designers customize head content in the template while components provide sensible defaults.
|
|
100
|
+
|
|
101
|
+
The jay-html `<head>` supports `{binding}` syntax for dynamic values:
|
|
102
|
+
|
|
103
|
+
```html
|
|
104
|
+
<head>
|
|
105
|
+
<title>{productPage.name} | My Store</title>
|
|
106
|
+
<meta name="description" content="{productPage.description}" />
|
|
107
|
+
<link rel="canonical" href="https://mystore.com/products/{productPage.slug}" />
|
|
108
|
+
</head>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Bindings are resolved against the merged ViewState at SSR time.
|
|
112
|
+
|
|
74
113
|
## Phase Rules
|
|
75
114
|
|
|
76
115
|
- Return headTags from **slow** phase for build-time SEO data (product name, description)
|
|
77
116
|
- Return headTags from **fast** phase for per-request data (pricing, availability)
|
|
78
117
|
- Fast phase headTags **replace** slow phase entirely (no merge)
|
|
118
|
+
- Template head tags override both (if present)
|
|
79
119
|
- No interactive phase — head tags are SSR-only
|
|
80
120
|
|
|
81
121
|
## Collision Rules
|
|
@@ -91,3 +131,4 @@ return phaseOutput(viewState, carryForward, { headTags });
|
|
|
91
131
|
|
|
92
132
|
- Head tags from components inside `forEach` are ignored
|
|
93
133
|
- The framework handles HTML escaping automatically
|
|
134
|
+
- Canonical URLs must be absolute (`https://...`)
|
|
@@ -100,6 +100,25 @@ component: ./lib/components/product-page.ts
|
|
|
100
100
|
component: productPage
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
+
## Head Tags Declaration
|
|
104
|
+
|
|
105
|
+
If your component provides head tags dynamically (via `phaseOutput({ headTags })`), declare them in `plugin.yaml` so validators don't warn about missing tags on pages using your component:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
contracts:
|
|
109
|
+
- name: product-page
|
|
110
|
+
contract: product-page.jay-contract
|
|
111
|
+
component: productPage
|
|
112
|
+
headTags:
|
|
113
|
+
- title
|
|
114
|
+
- meta:description
|
|
115
|
+
- link:canonical
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Values: `title`, `meta:<name>` (e.g., `meta:description`), `link:<rel>` (e.g., `link:canonical`).
|
|
119
|
+
|
|
120
|
+
Validators access this via `ctx.headlessImports[].providedHeadTags`.
|
|
121
|
+
|
|
103
122
|
## Plugin Validators
|
|
104
123
|
|
|
105
124
|
Plugins can provide custom jay-html validation rules that run during `jay-stack validate`. Declare validators in `plugin.yaml`:
|
|
@@ -122,9 +141,11 @@ export const validate: JayHtmlValidatorFn = (ctx) => {
|
|
|
122
141
|
const findings: JayHtmlValidationFinding[] = [];
|
|
123
142
|
|
|
124
143
|
// ctx.body — parsed DOM tree (HTMLElement from node-html-parser)
|
|
144
|
+
// ctx.head — parsed <head> metadata (title, meta tags, link tags)
|
|
125
145
|
// ctx.filePath — relative path to the jay-html file
|
|
126
146
|
// ctx.contract — page contract (if any), with tags including meta
|
|
127
147
|
// ctx.headlessImports — headless components used in this file
|
|
148
|
+
// .providedHeadTags — head tags the component declares in plugin.yaml
|
|
128
149
|
// ctx.projectRoot — absolute project root path
|
|
129
150
|
|
|
130
151
|
return findings;
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import fs, { promises } from "fs";
|
|
|
9
9
|
import YAML from "yaml";
|
|
10
10
|
import { getLogger, setDevLogger, createDevLogger } from "@jay-framework/logger";
|
|
11
11
|
import { parseJayFile, JAY_IMPORT_RESOLVER, generateElementDefinitionFile, ContractTagType, parseContract, generateElementFile, generateServerElementFile, htmlElementTagNameMap, loadLinkedContract, getLinkedContractDir } from "@jay-framework/compiler-jay-html";
|
|
12
|
-
import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, resolvePluginManifest, LOCAL_PLUGIN_PATH, JayAtomicType, JayEnumType, loadPluginManifest, RuntimeMode, GenerateTarget } from "@jay-framework/compiler-shared";
|
|
12
|
+
import { JAY_CONTRACT_EXTENSION, JAY_EXTENSION, resolvePluginManifest, LOCAL_PLUGIN_PATH, JayAtomicType, JayEnumType, loadPluginManifest, RuntimeMode, GenerateTarget, findDynamicContract } from "@jay-framework/compiler-shared";
|
|
13
13
|
import { scanPlugins as scanPlugins$1, listContracts, materializeContracts } from "@jay-framework/stack-server-runtime";
|
|
14
14
|
import { listContracts as listContracts2, materializeContracts as materializeContracts2 } from "@jay-framework/stack-server-runtime";
|
|
15
15
|
import { Command } from "commander";
|
|
@@ -4340,6 +4340,21 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
4340
4340
|
} : void 0,
|
|
4341
4341
|
headlessImports: parsed.headlessImports.map((imp) => {
|
|
4342
4342
|
const resolvedContract = imp.contract ? resolveContractLinks(imp.contract, imp.contractPath) : void 0;
|
|
4343
|
+
let providedHeadTags;
|
|
4344
|
+
for (const [, p] of scannedPlugins) {
|
|
4345
|
+
const entry = p.manifest.contracts?.find(
|
|
4346
|
+
(c) => c.name === imp.contractName
|
|
4347
|
+
);
|
|
4348
|
+
if (entry?.headTags) {
|
|
4349
|
+
providedHeadTags = entry.headTags;
|
|
4350
|
+
break;
|
|
4351
|
+
}
|
|
4352
|
+
const dynEntry = findDynamicContract(p.manifest, imp.contractName);
|
|
4353
|
+
if (dynEntry?.headTags) {
|
|
4354
|
+
providedHeadTags = dynEntry.headTags;
|
|
4355
|
+
break;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4343
4358
|
return {
|
|
4344
4359
|
key: imp.key,
|
|
4345
4360
|
contractName: imp.contractName,
|
|
@@ -4348,7 +4363,8 @@ async function runPluginValidators(projectRoot, parsedFiles, errors, warnings) {
|
|
|
4348
4363
|
tags: resolvedContract.tags,
|
|
4349
4364
|
props: resolvedContract.props,
|
|
4350
4365
|
params: resolvedContract.params
|
|
4351
|
-
} : void 0
|
|
4366
|
+
} : void 0,
|
|
4367
|
+
providedHeadTags
|
|
4352
4368
|
};
|
|
4353
4369
|
}),
|
|
4354
4370
|
projectRoot
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jay-framework/jay-stack-cli",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.6",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"test:watch": "vitest"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@jay-framework/compiler-jay-html": "^0.19.
|
|
28
|
-
"@jay-framework/compiler-shared": "^0.19.
|
|
29
|
-
"@jay-framework/dev-server": "^0.19.
|
|
30
|
-
"@jay-framework/editor-server": "^0.19.
|
|
31
|
-
"@jay-framework/fullstack-component": "^0.19.
|
|
32
|
-
"@jay-framework/logger": "^0.19.
|
|
33
|
-
"@jay-framework/plugin-validator": "^0.19.
|
|
34
|
-
"@jay-framework/production-server": "^0.19.
|
|
35
|
-
"@jay-framework/stack-server-runtime": "^0.19.
|
|
27
|
+
"@jay-framework/compiler-jay-html": "^0.19.6",
|
|
28
|
+
"@jay-framework/compiler-shared": "^0.19.6",
|
|
29
|
+
"@jay-framework/dev-server": "^0.19.6",
|
|
30
|
+
"@jay-framework/editor-server": "^0.19.6",
|
|
31
|
+
"@jay-framework/fullstack-component": "^0.19.6",
|
|
32
|
+
"@jay-framework/logger": "^0.19.6",
|
|
33
|
+
"@jay-framework/plugin-validator": "^0.19.6",
|
|
34
|
+
"@jay-framework/production-server": "^0.19.6",
|
|
35
|
+
"@jay-framework/stack-server-runtime": "^0.19.6",
|
|
36
36
|
"chalk": "^4.1.2",
|
|
37
37
|
"commander": "^14.0.0",
|
|
38
38
|
"express": "^5.0.1",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"yaml": "^2.3.4"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@jay-framework/dev-environment": "^0.19.
|
|
46
|
+
"@jay-framework/dev-environment": "^0.19.6",
|
|
47
47
|
"@types/express": "^5.0.2",
|
|
48
48
|
"@types/node": "^22.15.21",
|
|
49
49
|
"nodemon": "^3.0.3",
|