Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9070a1ef9c | ||
|
|
4230de7065 | ||
|
|
0006aef2c4 | ||
|
|
7dfca4a190 | ||
|
|
29bbeefe92 | ||
|
|
98445c2ec7 | ||
|
|
17a535d4dc | ||
|
|
afb14c2fbf | ||
|
|
2c6e6f6de3 | ||
|
|
cade812569 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ai",
|
"name": "@wrnexus/ai",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/authz",
|
"name": "@wrnexus/authz",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
"description": "@wrnexus/authz — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
+11
-11
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
"description": "@wrnexus/cli — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -28,16 +28,16 @@
|
|||||||
"wrnexus": "./dist/index.js"
|
"wrnexus": "./dist/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33",
|
"@wrnexus/core": "^0.2.42",
|
||||||
"@wrnexus/router": "^0.2.33",
|
"@wrnexus/router": "^0.2.42",
|
||||||
"@wrnexus/csr": "^0.2.33",
|
"@wrnexus/csr": "^0.2.42",
|
||||||
"@wrnexus/compiler": "^0.2.33",
|
"@wrnexus/compiler": "^0.2.42",
|
||||||
"@wrnexus/styles": "^0.2.33",
|
"@wrnexus/styles": "^0.2.42",
|
||||||
"@wrnexus/dev-server": "^0.2.33",
|
"@wrnexus/dev-server": "^0.2.42",
|
||||||
"@wrnexus/ui": "^0.2.33",
|
"@wrnexus/ui": "^0.2.42",
|
||||||
"@wrnexus/validation": "^0.2.33",
|
"@wrnexus/validation": "^0.2.42",
|
||||||
"@wrnexus/i18n": "^0.2.33",
|
"@wrnexus/i18n": "^0.2.42",
|
||||||
"@wrnexus/db": "^0.2.33"
|
"@wrnexus/db": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
"description": "@wrnexus/compiler — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/core",
|
"name": "@wrnexus/core",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/core — part of the WrNexus framework.",
|
"description": "@wrnexus/core — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
"description": "@wrnexus/csr — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33"
|
"@wrnexus/core": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/db",
|
"name": "@wrnexus/db",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/db — part of the WrNexus framework.",
|
"description": "@wrnexus/db — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
"description": "@wrnexus/dev-server — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -25,18 +25,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33",
|
"@wrnexus/core": "^0.2.42",
|
||||||
"@wrnexus/router": "^0.2.33",
|
"@wrnexus/router": "^0.2.42",
|
||||||
"@wrnexus/ssr": "^0.2.33",
|
"@wrnexus/ssr": "^0.2.42",
|
||||||
"@wrnexus/csr": "^0.2.33",
|
"@wrnexus/csr": "^0.2.42",
|
||||||
"@wrnexus/compiler": "^0.2.33",
|
"@wrnexus/compiler": "^0.2.42",
|
||||||
"@wrnexus/styles": "^0.2.33",
|
"@wrnexus/styles": "^0.2.42",
|
||||||
"@wrnexus/ui": "^0.2.33",
|
"@wrnexus/ui": "^0.2.42",
|
||||||
"@wrnexus/validation": "^0.2.33",
|
"@wrnexus/validation": "^0.2.42",
|
||||||
"@wrnexus/i18n": "^0.2.33",
|
"@wrnexus/i18n": "^0.2.42",
|
||||||
"@wrnexus/db": "^0.2.33",
|
"@wrnexus/db": "^0.2.42",
|
||||||
"@wrnexus/pubsub": "^0.2.33",
|
"@wrnexus/pubsub": "^0.2.42",
|
||||||
"@wrnexus/uploader": "^0.2.33"
|
"@wrnexus/uploader": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/encryption",
|
"name": "@wrnexus/encryption",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
"description": "@wrnexus/encryption — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/helpers",
|
"name": "@wrnexus/helpers",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33"
|
"@wrnexus/core": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/i18n",
|
"name": "@wrnexus/i18n",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
"description": "@wrnexus/i18n — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33"
|
"@wrnexus/core": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/jwt",
|
"name": "@wrnexus/jwt",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
"description": "@wrnexus/jwt — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
"description": "@wrnexus/mobile — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/native": "^0.2.33"
|
"@wrnexus/native": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/native",
|
"name": "@wrnexus/native",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/native — part of the WrNexus framework.",
|
"description": "@wrnexus/native — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/oauth",
|
"name": "@wrnexus/oauth",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
"description": "@wrnexus/oauth — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/pubsub",
|
"name": "@wrnexus/pubsub",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
"description": "@wrnexus/pubsub — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/queue",
|
"name": "@wrnexus/queue",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
"description": "@wrnexus/queue — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/reactive",
|
"name": "@wrnexus/reactive",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
"description": "@wrnexus/reactive — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/router",
|
"name": "@wrnexus/router",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/router — part of the WrNexus framework.",
|
"description": "@wrnexus/router — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/compiler": "^0.2.33",
|
"@wrnexus/compiler": "^0.2.42",
|
||||||
"@wrnexus/core": "^0.2.33"
|
"@wrnexus/core": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ssr",
|
"name": "@wrnexus/ssr",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
"description": "@wrnexus/ssr — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33"
|
"@wrnexus/core": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
"description": "@wrnexus/styles — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/uploader": "^0.2.33"
|
"@wrnexus/uploader": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/test — part of the WrNexus framework.",
|
"description": "@wrnexus/test — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/tracking",
|
"name": "@wrnexus/tracking",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
"description": "@wrnexus/tracking — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
"description": "@wrnexus/ui — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"./ui.css": "./ui.css"
|
"./ui.css": "./ui.css"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33"
|
"@wrnexus/core": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
"description": "@wrnexus/uploader — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@wrnexus/core": "^0.2.33"
|
"@wrnexus/core": "^0.2.42"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/validation",
|
"name": "@wrnexus/validation",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
"description": "@wrnexus/validation — part of the WrNexus framework.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
Vendored
+17
-1
@@ -42,5 +42,21 @@
|
|||||||
"editors/vscode/src/compiler.cjs": true
|
"editors/vscode/src/compiler.cjs": true
|
||||||
},
|
},
|
||||||
|
|
||||||
"wrnexus.diagnostics.enable": true
|
"wrnexus.diagnostics.enable": true,
|
||||||
|
|
||||||
|
"editor.semanticHighlighting.enabled": true,
|
||||||
|
"editor.semanticTokenColorCustomizations": {
|
||||||
|
"rules": {
|
||||||
|
"variable:wrn": {
|
||||||
|
"foreground": "#8B5CF6"
|
||||||
|
},
|
||||||
|
"variable.declaration:wrn": {
|
||||||
|
"foreground": "#A78BFA",
|
||||||
|
"fontStyle": "bold"
|
||||||
|
},
|
||||||
|
"variable.modification:wrn": {
|
||||||
|
"foreground": "#F59E0B"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"name": "wrnexus",
|
"name": "wrnexus",
|
||||||
"displayName": "WRNexus Language Support",
|
"displayName": "WRNexus Language Support",
|
||||||
"description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.",
|
"description": "Complete language support for WRNexus .wrn files, including highlighting, formatting, diagnostics, snippets, lifecycle hooks, state watchers, component functions, completions, and definition navigation.",
|
||||||
"version": "0.2.7",
|
"version": "0.2.10",
|
||||||
"publisher": "wrnexus",
|
"publisher": "wrnexus",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "SEE LICENSE IN LICENSE",
|
"license": "SEE LICENSE IN LICENSE",
|
||||||
@@ -225,7 +225,30 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"semanticTokenScopes": [
|
||||||
|
{
|
||||||
|
"language": "wrn",
|
||||||
|
"scopes": {
|
||||||
|
"variable.declaration": [
|
||||||
|
"variable.other.readwrite.wrn",
|
||||||
|
"variable.other.readwrite.declaration.wrn"
|
||||||
|
],
|
||||||
|
"variable": [
|
||||||
|
"variable.other.readwrite.wrn"
|
||||||
|
],
|
||||||
|
"parameter": [
|
||||||
|
"variable.parameter.wrn"
|
||||||
|
],
|
||||||
|
"function": [
|
||||||
|
"entity.name.function.wrn"
|
||||||
|
],
|
||||||
|
"property": [
|
||||||
|
"variable.other.property.wrn"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:compiler": "bun build ../../packages/compiler/src/index.ts --target=node --format=cjs --outfile=src/compiler.cjs",
|
"build:compiler": "bun build ../../packages/compiler/src/index.ts --target=node --format=cjs --outfile=src/compiler.cjs",
|
||||||
|
|||||||
+201
-29
@@ -493,12 +493,6 @@ function parseHtmlView(src, pos) {
|
|||||||
let i = pos;
|
let i = pos;
|
||||||
const isNameStart = (c) => /[A-Za-z_]/.test(c);
|
const isNameStart = (c) => /[A-Za-z_]/.test(c);
|
||||||
const isTagNamePart = (c) => /[A-Za-z0-9_$:.-]/.test(c);
|
const isTagNamePart = (c) => /[A-Za-z0-9_$:.-]/.test(c);
|
||||||
const isAttributeNamePart = (c, next) => {
|
|
||||||
if (c === "/") {
|
|
||||||
return next !== ">";
|
|
||||||
}
|
|
||||||
return /[A-Za-z0-9_$:.[\]%-]/.test(c);
|
|
||||||
};
|
|
||||||
const isWs2 = (c) => c === " " || c === "\t" || c === `
|
const isWs2 = (c) => c === " " || c === "\t" || c === `
|
||||||
` || c === "\r";
|
` || c === "\r";
|
||||||
const fail = (msg) => {
|
const fail = (msg) => {
|
||||||
@@ -546,14 +540,22 @@ function parseHtmlView(src, pos) {
|
|||||||
return src.slice(start, i);
|
return src.slice(start, i);
|
||||||
};
|
};
|
||||||
const readAttributeName = () => {
|
const readAttributeName = () => {
|
||||||
const first = src[i];
|
if (i >= src.length) {
|
||||||
if (i >= src.length || !isNameStart(first) && first !== ":" && first !== "$") {
|
|
||||||
return fail("Expected an attribute name");
|
return fail("Expected an attribute name");
|
||||||
}
|
}
|
||||||
const start = i++;
|
const start = i;
|
||||||
while (i < src.length && isAttributeNamePart(src[i], src[i + 1])) {
|
while (i < src.length) {
|
||||||
|
const char = src[i];
|
||||||
|
const next = src[i + 1];
|
||||||
|
if (char === "=" || char === ">" || char === '"' || char === "'" || char === " " || char === "\t" || char === `
|
||||||
|
` || char === "\r" || char === "/" && next === ">") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
if (i === start) {
|
||||||
|
return fail("Expected an attribute name");
|
||||||
|
}
|
||||||
return src.slice(start, i);
|
return src.slice(start, i);
|
||||||
};
|
};
|
||||||
const parseTag = () => {
|
const parseTag = () => {
|
||||||
@@ -730,6 +732,7 @@ function parseHtmlView(src, pos) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ../../packages/compiler/src/codegen.ts
|
// ../../packages/compiler/src/codegen.ts
|
||||||
|
var import_node_buffer = require("node:buffer");
|
||||||
function isComponentTag(tag) {
|
function isComponentTag(tag) {
|
||||||
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
|
return /^[A-Z][A-Za-z0-9_$]*$/.test(tag);
|
||||||
}
|
}
|
||||||
@@ -968,9 +971,9 @@ function renderNode(node, ssrBindings, csrBindings, apiBindings, loops, reactive
|
|||||||
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>${inner}</${node.tag}>`;
|
return `<${node.tag}${renderAttrs(node.attrs, csrId, reactive)}>${inner}</${node.tag}>`;
|
||||||
}
|
}
|
||||||
function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) {
|
function renderPageComponentInvocation(node, ssrBindings, csrBindings, apiBindings, loops, reactive) {
|
||||||
const attrs = node.attrs.filter((attr) => attr.name !== "data-component");
|
const attrs = node.attrs.filter((attr) => attr.name !== "data-component").map((attr) => renderPageComponentAttr(attr, loops)).join("");
|
||||||
const inner = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
|
const inner = node.children.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive)).join("");
|
||||||
return `<div data-component="${attrEscape(node.tag)}"${renderAttrs(attrs, undefined, reactive)}>${inner}</div>`;
|
return `<div data-component="${attrEscape(node.tag)}"` + `${attrs}>${inner}</div>`;
|
||||||
}
|
}
|
||||||
function renderNestedComponentInvocation(node, ctx) {
|
function renderNestedComponentInvocation(node, ctx) {
|
||||||
let bindIndex = 0;
|
let bindIndex = 0;
|
||||||
@@ -981,8 +984,10 @@ function renderNestedComponentInvocation(node, ctx) {
|
|||||||
if (attr.boolean) {
|
if (attr.boolean) {
|
||||||
return ` ${attr.name}`;
|
return ` ${attr.name}`;
|
||||||
}
|
}
|
||||||
const rendered = ` ${attr.name}="` + `${compileAttrValue(attr.value, ctx)}"`;
|
const wholeExpression = wholeAttributeExpression(attr.value);
|
||||||
if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
|
const compiledValue = wholeExpression ? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}` : compileAttrValue(attr.value, ctx);
|
||||||
|
const rendered = ` ${attr.name}="${compiledValue}"`;
|
||||||
|
if (wholeExpression || !attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
|
||||||
return rendered;
|
return rendered;
|
||||||
}
|
}
|
||||||
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
|
const marker = attrEscape(JSON.stringify([attr.name, attr.value]));
|
||||||
@@ -1083,6 +1088,28 @@ function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any):
|
|||||||
return new Function("$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nwith ($data ?? {}) {\\n" + helpers + "\\n" + body + "\\n}")(data, adapters);
|
return new Function("$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nwith ($data ?? {}) {\\n" + helpers + "\\n" + body + "\\n}")(data, adapters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function __wrnexusPropAttr(
|
||||||
|
value: unknown,
|
||||||
|
): string {
|
||||||
|
const serialized =
|
||||||
|
value !== null &&
|
||||||
|
typeof value === "object"
|
||||||
|
? JSON.stringify(value)
|
||||||
|
: String(value == null ? "" : value);
|
||||||
|
|
||||||
|
return serialized.replace(
|
||||||
|
/[&<>"]/g,
|
||||||
|
(character) =>
|
||||||
|
character === "&"
|
||||||
|
? "&"
|
||||||
|
: character === "<"
|
||||||
|
? "<"
|
||||||
|
: character === ">"
|
||||||
|
? ">"
|
||||||
|
: """,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
|
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
|
||||||
if (typeof ctx.__wrnexusCallApi === "function") {
|
if (typeof ctx.__wrnexusCallApi === "function") {
|
||||||
return await ctx.__wrnexusCallApi(path, method);
|
return await ctx.__wrnexusCallApi(path, method);
|
||||||
@@ -1340,7 +1367,8 @@ function behaviorAttribute(behavior) {
|
|||||||
if (!behavior) {
|
if (!behavior) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return ` data-wrn-behavior="${attrEscape(JSON.stringify(behavior))}"`;
|
const encoded = import_node_buffer.Buffer.from(JSON.stringify(behavior), "utf8").toString("base64");
|
||||||
|
return ` data-wrn-behavior="${encoded}"`;
|
||||||
}
|
}
|
||||||
var INTERP_RE = /\{([^{}]+)\}/g;
|
var INTERP_RE = /\{([^{}]+)\}/g;
|
||||||
function exprRefsState(expr, stateNames) {
|
function exprRefsState(expr, stateNames) {
|
||||||
@@ -1351,7 +1379,17 @@ function exprRefsState(expr, stateNames) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
function viewHasEvents(nodes) {
|
function viewHasEvents(nodes) {
|
||||||
return nodes.some((n) => n.type === "element" && (n.attrs.some((a) => a.event) || viewHasEvents(n.children)));
|
return nodes.some((node) => {
|
||||||
|
if (node.type === "text")
|
||||||
|
return false;
|
||||||
|
if (node.type === "each") {
|
||||||
|
return viewHasEvents(node.body) || viewHasEvents(node.empty);
|
||||||
|
}
|
||||||
|
if (node.type === "if") {
|
||||||
|
return node.branches.some((branch) => viewHasEvents(branch.body));
|
||||||
|
}
|
||||||
|
return node.attrs.some((attr) => attr.event) || viewHasEvents(node.children);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
function compileText(raw, ctx) {
|
function compileText(raw, ctx) {
|
||||||
let out = "";
|
let out = "";
|
||||||
@@ -1395,11 +1433,36 @@ function compileAttrValue(raw, ctx) {
|
|||||||
}
|
}
|
||||||
return out + escLit(attrEscape(raw.slice(last)));
|
return out + escLit(attrEscape(raw.slice(last)));
|
||||||
}
|
}
|
||||||
|
function renderComponentIfNode(node, ctx) {
|
||||||
|
let expression = "``";
|
||||||
|
for (let index = node.branches.length - 1;index >= 0; index--) {
|
||||||
|
const branch = node.branches[index];
|
||||||
|
const body = branch.body.map((child) => renderComponentNode(child, ctx)).join("");
|
||||||
|
const bodyExpression = "`" + body + "`";
|
||||||
|
expression = branch.cond === null ? bodyExpression : `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`;
|
||||||
|
}
|
||||||
|
return "${" + expression + "}";
|
||||||
|
}
|
||||||
|
function renderComponentEachNode(node, ctx) {
|
||||||
|
const item = node.item;
|
||||||
|
const index = node.index ?? "__wi";
|
||||||
|
const list = ctx.resolveExpr(node.list);
|
||||||
|
const childCtx = {
|
||||||
|
...ctx,
|
||||||
|
serverLocals: new Set([...ctx.serverLocals ?? [], item, index])
|
||||||
|
};
|
||||||
|
const body = node.body.map((child) => renderComponentNode(child, childCtx)).join("");
|
||||||
|
const empty = node.empty.map((child) => renderComponentNode(child, ctx)).join("");
|
||||||
|
return "${(() => { const __wl = Array.isArray(" + list + ") ? (" + list + ") : []; return __wl.length ? __wl.map((" + item + ", " + index + ") => `" + body + '`).join("") : `' + empty + "`; })()}";
|
||||||
|
}
|
||||||
function renderComponentNode(node, ctx) {
|
function renderComponentNode(node, ctx) {
|
||||||
if (node.type === "text")
|
if (node.type === "text")
|
||||||
return compileText(node.value, ctx);
|
return compileText(node.value, ctx);
|
||||||
if (node.type === "each" || node.type === "if") {
|
if (node.type === "each") {
|
||||||
throw new Error("Server `{#each}` / `{#if}` blocks are supported in pages, not components. Move them into a page (or use data-for / data-show on the client).");
|
return renderComponentEachNode(node, ctx);
|
||||||
|
}
|
||||||
|
if (node.type === "if") {
|
||||||
|
return renderComponentIfNode(node, ctx);
|
||||||
}
|
}
|
||||||
if (isComponentTag(node.tag)) {
|
if (isComponentTag(node.tag)) {
|
||||||
return renderNestedComponentInvocation(node, ctx);
|
return renderNestedComponentInvocation(node, ctx);
|
||||||
@@ -1435,14 +1498,19 @@ function renderComponentNode(node, ctx) {
|
|||||||
const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => {
|
const initialConditionalClasses = conditionalClasses.map(({ className, expression }) => {
|
||||||
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
|
return `\${(${ctx.resolveExpr(expression)}) ? ${JSON.stringify(` ${className}`)} : ""}`;
|
||||||
}).join("");
|
}).join("");
|
||||||
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClasses.join(" "), ctx)}${initialConditionalClasses}"` : "";
|
const staticClassValue = staticClasses.join(" ");
|
||||||
const classBindings = conditionalClasses.map(({ className, expression }, index) => {
|
const classHasReactiveExpression = staticClassValue.includes("{") && exprRefsState(staticClassValue, ctx.stateNames);
|
||||||
|
const classAttribute = staticClasses.length > 0 || conditionalClasses.length > 0 ? ` class="${compileAttrValue(staticClassValue, ctx)}${initialConditionalClasses}"` : "";
|
||||||
|
const classReactiveBinding = classHasReactiveExpression ? ` data-wrn-bind-class="${escLit(attrEscape(JSON.stringify(["class", staticClassValue])))}"` : "";
|
||||||
|
const classBindings = conditionalClasses.filter(({ expression }) => {
|
||||||
|
return !ctx.serverLocals || !exprRefsState(expression, ctx.serverLocals);
|
||||||
|
}).map(({ className, expression }, index) => {
|
||||||
const marker = attrEscape(JSON.stringify([className, expression]));
|
const marker = attrEscape(JSON.stringify([className, expression]));
|
||||||
return ` data-wrn-class-${index}="${escLit(marker)}"`;
|
return ` data-wrn-class-${index}="${escLit(marker)}"`;
|
||||||
}).join("");
|
}).join("");
|
||||||
const loops = loopVarsOf(node);
|
const loops = loopVarsOf(node);
|
||||||
const childCtx = loops.length > 0 ? { ...ctx, loopVars: new Set([...ctx.loopVars ?? [], ...loops]) } : ctx;
|
const childCtx = loops.length > 0 ? { ...ctx, loopVars: new Set([...ctx.loopVars ?? [], ...loops]) } : ctx;
|
||||||
const allAttrs = `${classAttribute}${classBindings}${attrs}`;
|
const allAttrs = `${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
|
||||||
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
||||||
return `<${node.tag}${allAttrs}>`;
|
return `<${node.tag}${allAttrs}>`;
|
||||||
}
|
}
|
||||||
@@ -1507,18 +1575,103 @@ ${styles.map(styleEscape).join(`
|
|||||||
out.push(`export const __wrnexusBehavior = ${JSON.stringify(behavior, null, 2)};`);
|
out.push(`export const __wrnexusBehavior = ${JSON.stringify(behavior, null, 2)};`);
|
||||||
}
|
}
|
||||||
out.push(`function __coerce(v: any, def: any): any {
|
out.push(`function __coerce(v: any, def: any): any {
|
||||||
if (v === undefined || v === null) return def;
|
if (v === undefined || v === null) {
|
||||||
if (typeof def === "number") return Number(v);
|
return def;
|
||||||
if (typeof def === "boolean") return v === true || v === "" || v === "true";
|
}
|
||||||
|
|
||||||
|
if (typeof def === "number") {
|
||||||
|
return Number(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof def === "boolean") {
|
||||||
|
return v === true || v === "" || v === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(def)) {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === "string") {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(v);
|
||||||
|
return Array.isArray(parsed) ? parsed : def;
|
||||||
|
} catch {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (def !== null && typeof def === "object") {
|
||||||
|
if (
|
||||||
|
v !== null &&
|
||||||
|
typeof v === "object" &&
|
||||||
|
!Array.isArray(v)
|
||||||
|
) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === "string") {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(v);
|
||||||
|
|
||||||
|
return (
|
||||||
|
parsed !== null &&
|
||||||
|
typeof parsed === "object" &&
|
||||||
|
!Array.isArray(parsed)
|
||||||
|
)
|
||||||
|
? parsed
|
||||||
|
: def;
|
||||||
|
} catch {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
return String(v);
|
return String(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
function __wireHtml(v: any): string {
|
function __wireHtml(v: any): string {
|
||||||
return String(v == null ? "" : v).replace(/[&<>]/g, (c) => (c === "&" ? "&" : c === "<" ? "<" : ">"));
|
return String(v == null ? "" : v).replace(
|
||||||
}
|
/[&<>]/g,
|
||||||
function __wireAttr(v: any): string {
|
(c) =>
|
||||||
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
|
c === "&"
|
||||||
c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """,
|
? "&"
|
||||||
|
: c === "<"
|
||||||
|
? "<"
|
||||||
|
: ">",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __wireAttr(v: any): string {
|
||||||
|
return String(v == null ? "" : v).replace(
|
||||||
|
/[&<>"]/g,
|
||||||
|
(c) =>
|
||||||
|
c === "&"
|
||||||
|
? "&"
|
||||||
|
: c === "<"
|
||||||
|
? "<"
|
||||||
|
: c === ">"
|
||||||
|
? ">"
|
||||||
|
: """,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __wireProp(v: any): string {
|
||||||
|
const value =
|
||||||
|
v !== null && typeof v === "object"
|
||||||
|
? JSON.stringify(v)
|
||||||
|
: String(v == null ? "" : v);
|
||||||
|
|
||||||
|
return __wireAttr(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __wireRaw(v: any): string {
|
||||||
|
return String(v == null ? "" : v);
|
||||||
}`);
|
}`);
|
||||||
if (needsScope) {
|
if (needsScope) {
|
||||||
out.push(`function __wrnexusScopeDecl(obj: Record<string, any>): string {
|
out.push(`function __wrnexusScopeDecl(obj: Record<string, any>): string {
|
||||||
@@ -1546,6 +1699,25 @@ function __wireAttr(v: any): string {
|
|||||||
`) + `
|
`) + `
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
function wholeAttributeExpression(value) {
|
||||||
|
const match = /^\s*\{([\s\S]+)\}\s*$/.exec(value);
|
||||||
|
return match?.[1]?.trim() || null;
|
||||||
|
}
|
||||||
|
function renderPageComponentAttr(attr, dynamicExpressions) {
|
||||||
|
if (attr.event) {
|
||||||
|
return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
|
||||||
|
}
|
||||||
|
if (attr.boolean) {
|
||||||
|
return ` ${attr.name}`;
|
||||||
|
}
|
||||||
|
const expression = wholeAttributeExpression(attr.value);
|
||||||
|
if (!expression) {
|
||||||
|
return ` ${attr.name}="${attrEscape(attr.value)}"`;
|
||||||
|
}
|
||||||
|
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
|
||||||
|
const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
|
||||||
|
return ` ${attr.name}="${marker}"`;
|
||||||
|
}
|
||||||
|
|
||||||
// ../../packages/compiler/src/native-codegen.ts
|
// ../../packages/compiler/src/native-codegen.ts
|
||||||
class NativeCompileError extends Error {
|
class NativeCompileError extends Error {
|
||||||
|
|||||||
+356
-22
@@ -40,41 +40,60 @@ const COMPILER_DIAGNOSTIC_COLLECTION = "wrnexus-compiler";
|
|||||||
* @param {vscode.ExtensionContext} context
|
* @param {vscode.ExtensionContext} context
|
||||||
*/
|
*/
|
||||||
function registerFormatter(context) {
|
function registerFormatter(context) {
|
||||||
const provider = vscode.languages.registerDocumentFormattingEditProvider(
|
const selector = {
|
||||||
{
|
language: WRN_LANGUAGE_ID,
|
||||||
language: WRN_LANGUAGE_ID,
|
};
|
||||||
scheme: "file",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provideDocumentFormattingEdits(document, options) {
|
|
||||||
const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);
|
|
||||||
|
|
||||||
const enabled = configuration.get("format.enable", true);
|
const provider = vscode.languages.registerDocumentFormattingEditProvider(selector, {
|
||||||
|
provideDocumentFormattingEdits(document, options, token) {
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
if (!enabled) {
|
const configuration = vscode.workspace.getConfiguration("wrnexus", document.uri);
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const source = document.getText();
|
const enabled = configuration.get("format.enable", true);
|
||||||
|
|
||||||
const printWidth = configuration.get("formatting.printWidth", 100);
|
if (!enabled) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const source = document.getText();
|
||||||
|
|
||||||
|
if (!source.trim()) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const printWidth = configuration.get("formatting.printWidth", 100);
|
||||||
|
|
||||||
|
try {
|
||||||
const formatted = formatWrn(source, {
|
const formatted = formatWrn(source, {
|
||||||
tabSize: options.tabSize,
|
tabSize: options.tabSize || 2,
|
||||||
insertSpaces: options.insertSpaces,
|
insertSpaces: options.insertSpaces !== false,
|
||||||
printWidth,
|
printWidth,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (formatted === source) {
|
if (typeof formatted !== "string" || formatted === source) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const range = new vscode.Range(document.positionAt(0), document.positionAt(source.length));
|
const fullRange = new vscode.Range(
|
||||||
|
document.positionAt(0),
|
||||||
|
document.positionAt(source.length),
|
||||||
|
);
|
||||||
|
|
||||||
return [vscode.TextEdit.replace(range, formatted)];
|
return [vscode.TextEdit.replace(fullRange, formatted)];
|
||||||
},
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
console.error("[wrnexus] formatting failed:", message);
|
||||||
|
|
||||||
|
vscode.window.showErrorMessage(`WRNexus formatting failed: ${message}`);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
context.subscriptions.push(provider);
|
context.subscriptions.push(provider);
|
||||||
}
|
}
|
||||||
@@ -259,6 +278,103 @@ function registerCompilerDiagnostics(context) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register semantic highlighting for WRN state declarations and references.
|
||||||
|
*
|
||||||
|
* @param {vscode.ExtensionContext} context
|
||||||
|
*/
|
||||||
|
function registerSemanticTokens(context) {
|
||||||
|
const semanticTokenLegend = new vscode.SemanticTokensLegend(
|
||||||
|
["variable"],
|
||||||
|
["declaration", "modification"],
|
||||||
|
);
|
||||||
|
|
||||||
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
|
||||||
|
const collectStateVariables = (text) => {
|
||||||
|
const states = new Map();
|
||||||
|
|
||||||
|
const pattern = /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = pattern.exec(text)) !== null) {
|
||||||
|
const name = match[1];
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = match.index + match[0].lastIndexOf(name);
|
||||||
|
|
||||||
|
const declarations = states.get(name) ?? new Set();
|
||||||
|
|
||||||
|
declarations.add(offset);
|
||||||
|
states.set(name, declarations);
|
||||||
|
}
|
||||||
|
|
||||||
|
return states;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isModification = (text, offset, length) => {
|
||||||
|
const after = text.slice(offset + length).match(/^\s*(=|\+=|-=|\*=|\/=|%=|\+\+|--)/);
|
||||||
|
|
||||||
|
if (after) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = text.slice(Math.max(0, offset - 8), offset).match(/(\+\+|--)\s*$/);
|
||||||
|
|
||||||
|
return Boolean(before);
|
||||||
|
};
|
||||||
|
|
||||||
|
const provider = {
|
||||||
|
provideDocumentSemanticTokens(document, token) {
|
||||||
|
const builder = new vscode.SemanticTokensBuilder(semanticTokenLegend);
|
||||||
|
|
||||||
|
const text = document.getText();
|
||||||
|
const states = collectStateVariables(text);
|
||||||
|
|
||||||
|
for (const [name, declarationOffsets] of states) {
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(name)}(?![A-Za-z0-9_$])`, "g");
|
||||||
|
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = pattern.exec(text)) !== null) {
|
||||||
|
const offset = match.index;
|
||||||
|
const position = document.positionAt(offset);
|
||||||
|
|
||||||
|
let modifiers = [];
|
||||||
|
|
||||||
|
if (declarationOffsets.has(offset)) {
|
||||||
|
modifiers = ["declaration"];
|
||||||
|
} else if (isModification(text, offset, name.length)) {
|
||||||
|
modifiers = ["modification"];
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.push(position.line, position.character, name.length, "variable", modifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const registration = vscode.languages.registerDocumentSemanticTokensProvider(
|
||||||
|
{
|
||||||
|
language: WRN_LANGUAGE_ID,
|
||||||
|
},
|
||||||
|
provider,
|
||||||
|
semanticTokenLegend,
|
||||||
|
);
|
||||||
|
|
||||||
|
context.subscriptions.push(registration);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activate the WRNexus VS Code extension.
|
* Activate the WRNexus VS Code extension.
|
||||||
*
|
*
|
||||||
@@ -267,18 +383,236 @@ function registerCompilerDiagnostics(context) {
|
|||||||
function activate(context) {
|
function activate(context) {
|
||||||
registerDiagnostics(context);
|
registerDiagnostics(context);
|
||||||
registerCompilerDiagnostics(context);
|
registerCompilerDiagnostics(context);
|
||||||
|
|
||||||
registerCompletionProvider(context);
|
registerCompletionProvider(context);
|
||||||
registerDefinitionProvider(context);
|
registerDefinitionProvider(context);
|
||||||
registerFormatter(context);
|
registerFormatter(context);
|
||||||
|
registerSemanticTokens(context);
|
||||||
|
|
||||||
|
context.subscriptions.push(
|
||||||
|
vscode.languages.registerDocumentSemanticTokensProvider(
|
||||||
|
{
|
||||||
|
language: "wrn",
|
||||||
|
},
|
||||||
|
wrnSemanticTokensProvider,
|
||||||
|
semanticTokenLegend,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function deactivate() {}
|
function deactivate() {}
|
||||||
|
|
||||||
|
const semanticTokenLegend = new vscode.SemanticTokensLegend(
|
||||||
|
["variable"],
|
||||||
|
["declaration", "modification"],
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} value
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function escapeRegExp(value) {
|
||||||
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {Map<string, Set<number>>}
|
||||||
|
*/
|
||||||
|
function collectStateVariables(text) {
|
||||||
|
const states = new Map();
|
||||||
|
const statePattern = /\bstate\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*(?==|;|\r?$)/gm;
|
||||||
|
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = statePattern.exec(text)) !== null) {
|
||||||
|
const name = match[1];
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = match.index + match[0].lastIndexOf(name);
|
||||||
|
const declarations = states.get(name) || new Set();
|
||||||
|
|
||||||
|
declarations.add(offset);
|
||||||
|
states.set(name, declarations);
|
||||||
|
}
|
||||||
|
|
||||||
|
return states;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comments are excluded, but quoted attribute expressions are deliberately
|
||||||
|
* included because WRN state references commonly appear in values such as
|
||||||
|
* class:flex="centered" and @click="enabled = !enabled".
|
||||||
|
*
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {Array<{start: number, end: number}>}
|
||||||
|
*/
|
||||||
|
function collectCommentRanges(text) {
|
||||||
|
const ranges = [];
|
||||||
|
let index = 0;
|
||||||
|
let quote = null;
|
||||||
|
let escaped = false;
|
||||||
|
|
||||||
|
while (index < text.length) {
|
||||||
|
const current = text[index];
|
||||||
|
const next = text[index + 1];
|
||||||
|
|
||||||
|
if (quote) {
|
||||||
|
if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current === "\\") {
|
||||||
|
escaped = true;
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current === quote) {
|
||||||
|
quote = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current === '"' || current === "'" || current === "`") {
|
||||||
|
quote = current;
|
||||||
|
index++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current === "/" && next === "/") {
|
||||||
|
const start = index;
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
while (index < text.length && text[index] !== "\n") {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.push({ start, end: index });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current === "/" && next === "*") {
|
||||||
|
const start = index;
|
||||||
|
index += 2;
|
||||||
|
|
||||||
|
while (index < text.length && !(text[index] === "*" && text[index + 1] === "/")) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = Math.min(text.length, index + 2);
|
||||||
|
ranges.push({ start, end: index });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} offset
|
||||||
|
* @param {Array<{start: number, end: number}>} ranges
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isInsideComment(offset, ranges) {
|
||||||
|
let low = 0;
|
||||||
|
let high = ranges.length - 1;
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
const middle = Math.floor((low + high) / 2);
|
||||||
|
const range = ranges[middle];
|
||||||
|
|
||||||
|
if (!range) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < range.start) {
|
||||||
|
high = middle - 1;
|
||||||
|
} else if (offset >= range.end) {
|
||||||
|
low = middle + 1;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @param {number} offset
|
||||||
|
* @param {number} length
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isStateModification(text, offset, length) {
|
||||||
|
const after = text.slice(offset + length).match(/^\s*(=|\+=|-=|\*=|\/=|%=|\+\+|--)/);
|
||||||
|
|
||||||
|
if (after) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = text.slice(Math.max(0, offset - 8), offset).match(/(\+\+|--)\s*$/);
|
||||||
|
|
||||||
|
return Boolean(before);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrnSemanticTokensProvider = {
|
||||||
|
/**
|
||||||
|
* @param {vscode.TextDocument} document
|
||||||
|
* @param {vscode.CancellationToken} token
|
||||||
|
*/
|
||||||
|
provideDocumentSemanticTokens(document, token) {
|
||||||
|
const builder = new vscode.SemanticTokensBuilder(semanticTokenLegend);
|
||||||
|
const text = document.getText();
|
||||||
|
const states = collectStateVariables(text);
|
||||||
|
const commentRanges = collectCommentRanges(text);
|
||||||
|
|
||||||
|
for (const [name, declarationOffsets] of states) {
|
||||||
|
if (token.isCancellationRequested) {
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = new RegExp(`(?<![A-Za-z0-9_$])${escapeRegExp(name)}(?![A-Za-z0-9_$])`, "g");
|
||||||
|
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = pattern.exec(text)) !== null) {
|
||||||
|
const offset = match.index;
|
||||||
|
|
||||||
|
if (isInsideComment(offset, commentRanges)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = document.positionAt(offset);
|
||||||
|
let modifiers = [];
|
||||||
|
|
||||||
|
if (declarationOffsets.has(offset)) {
|
||||||
|
modifiers = ["declaration"];
|
||||||
|
} else if (isStateModification(text, offset, name.length)) {
|
||||||
|
modifiers = ["modification"];
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.push(position.line, position.character, name.length, "variable", modifiers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
activate,
|
activate,
|
||||||
deactivate,
|
deactivate,
|
||||||
registerCompilerDiagnostics,
|
registerCompilerDiagnostics,
|
||||||
registerFormatter,
|
registerFormatter,
|
||||||
|
registerSemanticTokens,
|
||||||
toCompilerDiagnostic,
|
toCompilerDiagnostic,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -534,6 +534,9 @@
|
|||||||
{
|
{
|
||||||
"include": "#conditional-class"
|
"include": "#conditional-class"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"include": "#conditional-class-single"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"include": "text.html.basic"
|
"include": "text.html.basic"
|
||||||
}
|
}
|
||||||
@@ -560,8 +563,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"conditional-class": {
|
"conditional-class": {
|
||||||
"match": "\\b(class)(:)([^\\s=]+)(\\s*=)",
|
"name": "meta.attribute.class-directive.wrn",
|
||||||
"captures": {
|
"begin": "(?<=\\s)(class)(:)((?:\\[[^\\]\\r\\n]*\\]|[^\\s=])+)(\\s*=\\s*)(\")",
|
||||||
|
"beginCaptures": {
|
||||||
"1": {
|
"1": {
|
||||||
"name": "entity.other.attribute-name.class.wrn"
|
"name": "entity.other.attribute-name.class.wrn"
|
||||||
},
|
},
|
||||||
@@ -573,8 +577,56 @@
|
|||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
"name": "keyword.operator.assignment.wrn"
|
"name": "keyword.operator.assignment.wrn"
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"name": "punctuation.definition.string.begin.wrn"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"conditional-class-single": {
|
||||||
|
"name": "meta.attribute.class-directive.wrn",
|
||||||
|
"begin": "(?<=\\s)(class)(:)((?:\\[[^\\]\\r\\n]*\\]|[^\\s=])+)(\\s*=\\s*)(')",
|
||||||
|
"beginCaptures": {
|
||||||
|
"1": {
|
||||||
|
"name": "entity.other.attribute-name.class.wrn"
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"name": "punctuation.separator.key-value.wrn"
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"name": "string.unquoted.class-name.wrn"
|
||||||
|
},
|
||||||
|
"4": {
|
||||||
|
"name": "keyword.operator.assignment.wrn"
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"name": "punctuation.definition.string.begin.wrn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"end": "'",
|
||||||
|
"endCaptures": {
|
||||||
|
"0": {
|
||||||
|
"name": "punctuation.definition.string.end.wrn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentName": "meta.embedded.expression.wrn",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "source.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"end": "\"",
|
||||||
|
"endCaptures": {
|
||||||
|
"0": {
|
||||||
|
"name": "punctuation.definition.string.end.wrn"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contentName": "meta.embedded.expression.wrn",
|
||||||
|
"patterns": [
|
||||||
|
{
|
||||||
|
"include": "source.ts"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"interpolation": {
|
"interpolation": {
|
||||||
"patterns": [
|
"patterns": [
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ai",
|
"name": "@wrnexus/ai",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
"description": "Zero-dependency Claude (Anthropic) client for WrNexus apps.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/authz",
|
"name": "@wrnexus/authz",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/cli",
|
"name": "@wrnexus/cli",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -375,6 +375,87 @@ const MIGRATIONS: Migration[] = [
|
|||||||
// No generated application files require automatic migration.
|
// No generated application files require automatic migration.
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.34",
|
||||||
|
id: "reactive-bug",
|
||||||
|
description:
|
||||||
|
"Fixes component behavior encoding, lifecycle and watcher metadata parsing, CSR cleanup, and reactive form control property updates.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.35",
|
||||||
|
id: "class-bug",
|
||||||
|
description:
|
||||||
|
"Fixes component behavior encoding, lifecycle and watcher metadata parsing, CSR cleanup, and reactive form control property updates.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.36",
|
||||||
|
id: "attribute-bug",
|
||||||
|
description:
|
||||||
|
"Adds support for Tailwind arbitrary-value class directives and improves reactive class bindings.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.37",
|
||||||
|
id: "loop-condition-fix",
|
||||||
|
description:
|
||||||
|
"Adds server-rendered component each/if blocks and fixes dynamic component rendering.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.38",
|
||||||
|
id: "loop-condition-bug",
|
||||||
|
description:
|
||||||
|
"Adds server-rendered component each/if blocks and fixes dynamic component rendering.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.39",
|
||||||
|
id: "server-function-bug",
|
||||||
|
description:
|
||||||
|
"Adds server-rendered component each/if blocks and fixes dynamic component rendering.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.40",
|
||||||
|
id: "conditions-bug",
|
||||||
|
description:
|
||||||
|
"Adds server-rendered component each/if blocks and fixes dynamic component rendering.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.41",
|
||||||
|
id: "conditions-bug",
|
||||||
|
description:
|
||||||
|
"Adds server-rendered component each/if blocks and fixes dynamic component rendering.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "0.2.42",
|
||||||
|
id: "conditions-bug",
|
||||||
|
description:
|
||||||
|
"Adds server-rendered component each/if blocks and fixes dynamic component rendering.",
|
||||||
|
apply() {
|
||||||
|
// No generated application files require automatic migration.
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/** Release tooling uses this to require an explicit migration entry per version. */
|
/** Release tooling uses this to require an explicit migration entry per version. */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/compiler",
|
"name": "@wrnexus/compiler",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -15,6 +15,8 @@
|
|||||||
* realtime {..} -> export const websocket = { evt(ws, ...args) { b } }
|
* realtime {..} -> export const websocket = { evt(ws, ...args) { b } }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Buffer } from "node:buffer";
|
||||||
|
|
||||||
import { VOID_ELEMENTS, type Attr, type DataMode, type PageAst, type ViewNode } from "./parser.ts";
|
import { VOID_ELEMENTS, type Attr, type DataMode, type PageAst, type ViewNode } from "./parser.ts";
|
||||||
|
|
||||||
interface RenderBinding {
|
interface RenderBinding {
|
||||||
@@ -428,17 +430,16 @@ function renderPageComponentInvocation(
|
|||||||
loops: string[],
|
loops: string[],
|
||||||
reactive: PageReactive | null,
|
reactive: PageReactive | null,
|
||||||
): string {
|
): string {
|
||||||
const attrs = node.attrs.filter((attr) => attr.name !== "data-component");
|
const attrs = node.attrs
|
||||||
|
.filter((attr) => attr.name !== "data-component")
|
||||||
|
.map((attr) => renderPageComponentAttr(attr, loops))
|
||||||
|
.join("");
|
||||||
|
|
||||||
const inner = node.children
|
const inner = node.children
|
||||||
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
|
.map((child) => renderNode(child, ssrBindings, csrBindings, apiBindings, loops, reactive))
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
return `<div data-component="${attrEscape(node.tag)}"${renderAttrs(
|
return `<div data-component="${attrEscape(node.tag)}"` + `${attrs}>${inner}</div>`;
|
||||||
attrs,
|
|
||||||
undefined,
|
|
||||||
reactive,
|
|
||||||
)}>${inner}</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderNestedComponentInvocation(
|
function renderNestedComponentInvocation(
|
||||||
@@ -460,9 +461,19 @@ function renderNestedComponentInvocation(
|
|||||||
return ` ${attr.name}`;
|
return ` ${attr.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rendered = ` ${attr.name}="` + `${compileAttrValue(attr.value, ctx)}"`;
|
const wholeExpression = wholeAttributeExpression(attr.value);
|
||||||
|
|
||||||
if (!attr.value.includes("{") || !exprRefsState(attr.value, ctx.stateNames)) {
|
const compiledValue = wholeExpression
|
||||||
|
? `\${__wireProp(${ctx.resolveExpr(wholeExpression)})}`
|
||||||
|
: compileAttrValue(attr.value, ctx);
|
||||||
|
|
||||||
|
const rendered = ` ${attr.name}="${compiledValue}"`;
|
||||||
|
|
||||||
|
if (
|
||||||
|
wholeExpression ||
|
||||||
|
!attr.value.includes("{") ||
|
||||||
|
!exprRefsState(attr.value, ctx.stateNames)
|
||||||
|
) {
|
||||||
return rendered;
|
return rendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -589,6 +600,28 @@ function __wrnexusEvalData(data: unknown, body: string, helpers = "", ctx: any):
|
|||||||
return new Function("$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nwith ($data ?? {}) {\\n" + helpers + "\\n" + body + "\\n}")(data, adapters);
|
return new Function("$data", "$adapters", "const cookies = $adapters.cookies;\\nconst session = $adapters.session;\\nconst localStorage = $adapters.localStorage;\\nwith ($data ?? {}) {\\n" + helpers + "\\n" + body + "\\n}")(data, adapters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function __wrnexusPropAttr(
|
||||||
|
value: unknown,
|
||||||
|
): string {
|
||||||
|
const serialized =
|
||||||
|
value !== null &&
|
||||||
|
typeof value === "object"
|
||||||
|
? JSON.stringify(value)
|
||||||
|
: String(value == null ? "" : value);
|
||||||
|
|
||||||
|
return serialized.replace(
|
||||||
|
/[&<>"]/g,
|
||||||
|
(character) =>
|
||||||
|
character === "&"
|
||||||
|
? "&"
|
||||||
|
: character === "<"
|
||||||
|
? "<"
|
||||||
|
: character === ">"
|
||||||
|
? ">"
|
||||||
|
: """,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
|
async function __wrnexusCallApi(path: string, method: string, ctx: any): Promise<unknown> {
|
||||||
if (typeof ctx.__wrnexusCallApi === "function") {
|
if (typeof ctx.__wrnexusCallApi === "function") {
|
||||||
return await ctx.__wrnexusCallApi(path, method);
|
return await ctx.__wrnexusCallApi(path, method);
|
||||||
@@ -804,6 +837,8 @@ interface CompCtx {
|
|||||||
/** `data-for` loop variables in scope — their mustaches stay literal for the
|
/** `data-for` loop variables in scope — their mustaches stay literal for the
|
||||||
* client's list renderer (never baked server-side, since they have no value). */
|
* client's list renderer (never baked server-side, since they have no value). */
|
||||||
loopVars?: Set<string>;
|
loopVars?: Set<string>;
|
||||||
|
/** Local identifiers introduced by server-rendered `{#each}` blocks. */
|
||||||
|
serverLocals?: Set<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ComponentBehavior {
|
interface ComponentBehavior {
|
||||||
@@ -940,9 +975,19 @@ function exprRefsState(expr: string, stateNames: Set<string>): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function viewHasEvents(nodes: ViewNode[]): boolean {
|
function viewHasEvents(nodes: ViewNode[]): boolean {
|
||||||
return nodes.some(
|
return nodes.some((node) => {
|
||||||
(n) => n.type === "element" && (n.attrs.some((a) => a.event) || viewHasEvents(n.children)),
|
if (node.type === "text") return false;
|
||||||
);
|
|
||||||
|
if (node.type === "each") {
|
||||||
|
return viewHasEvents(node.body) || viewHasEvents(node.empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === "if") {
|
||||||
|
return node.branches.some((branch) => viewHasEvents(branch.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.attrs.some((attr) => attr.event) || viewHasEvents(node.children);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1004,14 +1049,65 @@ function compileAttrValue(raw: string, ctx: CompCtx): string {
|
|||||||
return out + escLit(attrEscape(raw.slice(last)));
|
return out + escLit(attrEscape(raw.slice(last)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderComponentIfNode(node: IfNode, ctx: CompCtx): string {
|
||||||
|
let expression = "``";
|
||||||
|
|
||||||
|
for (let index = node.branches.length - 1; index >= 0; index--) {
|
||||||
|
const branch = node.branches[index]!;
|
||||||
|
const body = branch.body.map((child) => renderComponentNode(child, ctx)).join("");
|
||||||
|
const bodyExpression = "`" + body + "`";
|
||||||
|
|
||||||
|
expression =
|
||||||
|
branch.cond === null
|
||||||
|
? bodyExpression
|
||||||
|
: `(${ctx.resolveExpr(branch.cond)}) ? ${bodyExpression} : ${expression}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "${" + expression + "}";
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderComponentEachNode(node: EachNode, ctx: CompCtx): string {
|
||||||
|
const item = node.item;
|
||||||
|
const index = node.index ?? "__wi";
|
||||||
|
const list = ctx.resolveExpr(node.list);
|
||||||
|
|
||||||
|
const childCtx: CompCtx = {
|
||||||
|
...ctx,
|
||||||
|
serverLocals: new Set([...(ctx.serverLocals ?? []), item, index]),
|
||||||
|
};
|
||||||
|
|
||||||
|
const body = node.body.map((child) => renderComponentNode(child, childCtx)).join("");
|
||||||
|
const empty = node.empty.map((child) => renderComponentNode(child, ctx)).join("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
"${(() => { const __wl = Array.isArray(" +
|
||||||
|
list +
|
||||||
|
") ? (" +
|
||||||
|
list +
|
||||||
|
") : []; return __wl.length ? __wl.map((" +
|
||||||
|
item +
|
||||||
|
", " +
|
||||||
|
index +
|
||||||
|
") => `" +
|
||||||
|
body +
|
||||||
|
'`).join("") : `' +
|
||||||
|
empty +
|
||||||
|
"`; })()}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** Render a component view node into template-literal-ready source. */
|
/** Render a component view node into template-literal-ready source. */
|
||||||
function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
||||||
if (node.type === "text") return compileText(node.value, ctx);
|
if (node.type === "text") return compileText(node.value, ctx);
|
||||||
if (node.type === "each" || node.type === "if") {
|
|
||||||
throw new Error(
|
if (node.type === "each") {
|
||||||
"Server `{#each}` / `{#if}` blocks are supported in pages, not components. Move them into a page (or use data-for / data-show on the client).",
|
return renderComponentEachNode(node, ctx);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.type === "if") {
|
||||||
|
return renderComponentIfNode(node, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
if (isComponentTag(node.tag)) {
|
if (isComponentTag(node.tag)) {
|
||||||
return renderNestedComponentInvocation(node, ctx);
|
return renderNestedComponentInvocation(node, ctx);
|
||||||
}
|
}
|
||||||
@@ -1065,12 +1161,24 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
|||||||
})
|
})
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
|
const staticClassValue = staticClasses.join(" ");
|
||||||
|
|
||||||
|
const classHasReactiveExpression =
|
||||||
|
staticClassValue.includes("{") && exprRefsState(staticClassValue, ctx.stateNames);
|
||||||
|
|
||||||
const classAttribute =
|
const classAttribute =
|
||||||
staticClasses.length > 0 || conditionalClasses.length > 0
|
staticClasses.length > 0 || conditionalClasses.length > 0
|
||||||
? ` class="${compileAttrValue(staticClasses.join(" "), ctx)}${initialConditionalClasses}"`
|
? ` class="${compileAttrValue(staticClassValue, ctx)}${initialConditionalClasses}"`
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
|
const classReactiveBinding = classHasReactiveExpression
|
||||||
|
? ` data-wrn-bind-class="${escLit(attrEscape(JSON.stringify(["class", staticClassValue])))}"`
|
||||||
|
: "";
|
||||||
|
|
||||||
const classBindings = conditionalClasses
|
const classBindings = conditionalClasses
|
||||||
|
.filter(({ expression }) => {
|
||||||
|
return !ctx.serverLocals || !exprRefsState(expression, ctx.serverLocals);
|
||||||
|
})
|
||||||
.map(({ className, expression }, index) => {
|
.map(({ className, expression }, index) => {
|
||||||
const marker = attrEscape(JSON.stringify([className, expression]));
|
const marker = attrEscape(JSON.stringify([className, expression]));
|
||||||
|
|
||||||
@@ -1083,7 +1191,8 @@ function renderComponentNode(node: ViewNode, ctx: CompCtx): string {
|
|||||||
const childCtx =
|
const childCtx =
|
||||||
loops.length > 0 ? { ...ctx, loopVars: new Set([...(ctx.loopVars ?? []), ...loops]) } : ctx;
|
loops.length > 0 ? { ...ctx, loopVars: new Set([...(ctx.loopVars ?? []), ...loops]) } : ctx;
|
||||||
|
|
||||||
const allAttrs = `${classAttribute}${classBindings}${attrs}`;
|
const allAttrs =
|
||||||
|
`${classAttribute}` + `${classReactiveBinding}` + `${classBindings}` + `${attrs}`;
|
||||||
|
|
||||||
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
if (VOID_ELEMENTS.has(node.tag.toLowerCase())) {
|
||||||
return `<${node.tag}${allAttrs}>`;
|
return `<${node.tag}${allAttrs}>`;
|
||||||
@@ -1123,6 +1232,11 @@ function generateComponent(ast: PageAst): string {
|
|||||||
};
|
};
|
||||||
const ctx: CompCtx = { stateNames, resolveExpr };
|
const ctx: CompCtx = { stateNames, resolveExpr };
|
||||||
|
|
||||||
|
const serverFunctions = ast.functions
|
||||||
|
.map((body) => body.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.join("\n\n");
|
||||||
|
|
||||||
const viewCode = ast.view.map((node) => renderComponentNode(node, ctx)).join("");
|
const viewCode = ast.view.map((node) => renderComponentNode(node, ctx)).join("");
|
||||||
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
|
const styles = ast.styles.map((body) => body.trim()).filter(Boolean);
|
||||||
const styleTag =
|
const styleTag =
|
||||||
@@ -1153,7 +1267,7 @@ function generateComponent(ast: PageAst): string {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const state of ast.states) {
|
for (const state of ast.states) {
|
||||||
decls.push(` const ${nameRefs.get(state.name)} = (${resolveExpr(state.expr)});`);
|
decls.push(` let ${nameRefs.get(state.name)} = (${resolveExpr(state.expr)});`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const returnExpr = needsScope
|
const returnExpr = needsScope
|
||||||
@@ -1178,18 +1292,103 @@ function generateComponent(ast: PageAst): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
out.push(`function __coerce(v: any, def: any): any {
|
out.push(`function __coerce(v: any, def: any): any {
|
||||||
if (v === undefined || v === null) return def;
|
if (v === undefined || v === null) {
|
||||||
if (typeof def === "number") return Number(v);
|
return def;
|
||||||
if (typeof def === "boolean") return v === true || v === "" || v === "true";
|
}
|
||||||
|
|
||||||
|
if (typeof def === "number") {
|
||||||
|
return Number(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof def === "boolean") {
|
||||||
|
return v === true || v === "" || v === "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(def)) {
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === "string") {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(v);
|
||||||
|
return Array.isArray(parsed) ? parsed : def;
|
||||||
|
} catch {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (def !== null && typeof def === "object") {
|
||||||
|
if (
|
||||||
|
v !== null &&
|
||||||
|
typeof v === "object" &&
|
||||||
|
!Array.isArray(v)
|
||||||
|
) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof v === "string") {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(v);
|
||||||
|
|
||||||
|
return (
|
||||||
|
parsed !== null &&
|
||||||
|
typeof parsed === "object" &&
|
||||||
|
!Array.isArray(parsed)
|
||||||
|
)
|
||||||
|
? parsed
|
||||||
|
: def;
|
||||||
|
} catch {
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
return String(v);
|
return String(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
function __wireHtml(v: any): string {
|
function __wireHtml(v: any): string {
|
||||||
return String(v == null ? "" : v).replace(/[&<>]/g, (c) => (c === "&" ? "&" : c === "<" ? "<" : ">"));
|
return String(v == null ? "" : v).replace(
|
||||||
}
|
/[&<>]/g,
|
||||||
function __wireAttr(v: any): string {
|
(c) =>
|
||||||
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
|
c === "&"
|
||||||
c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """,
|
? "&"
|
||||||
|
: c === "<"
|
||||||
|
? "<"
|
||||||
|
: ">",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __wireAttr(v: any): string {
|
||||||
|
return String(v == null ? "" : v).replace(
|
||||||
|
/[&<>"]/g,
|
||||||
|
(c) =>
|
||||||
|
c === "&"
|
||||||
|
? "&"
|
||||||
|
: c === "<"
|
||||||
|
? "<"
|
||||||
|
: c === ">"
|
||||||
|
? ">"
|
||||||
|
: """,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __wireProp(v: any): string {
|
||||||
|
const value =
|
||||||
|
v !== null && typeof v === "object"
|
||||||
|
? JSON.stringify(v)
|
||||||
|
: String(v == null ? "" : v);
|
||||||
|
|
||||||
|
return __wireAttr(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __wireRaw(v: any): string {
|
||||||
|
return String(v == null ? "" : v);
|
||||||
}`);
|
}`);
|
||||||
|
|
||||||
if (needsScope) {
|
if (needsScope) {
|
||||||
@@ -1208,10 +1407,13 @@ function __wireAttr(v: any): string {
|
|||||||
}`);
|
}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const serverFunctionSource = serverFunctions ? `${serverFunctions}\n` : "";
|
||||||
|
|
||||||
out.push(
|
out.push(
|
||||||
`export function render(props: Record<string, any> = {}): string {\n` +
|
`export function render(props: Record<string, any> = {}): string {\n` +
|
||||||
` const __p = props || {};\n` +
|
` const __p = props || {};\n` +
|
||||||
(decls.length > 0 ? decls.join("\n") + "\n" : "") +
|
(decls.length > 0 ? decls.join("\n") + "\n" : "") +
|
||||||
|
serverFunctionSource +
|
||||||
scopeLine +
|
scopeLine +
|
||||||
` return ${returnExpr};\n` +
|
` return ${returnExpr};\n` +
|
||||||
`}`,
|
`}`,
|
||||||
@@ -1220,12 +1422,51 @@ function __wireAttr(v: any): string {
|
|||||||
return out.join("\n\n") + "\n";
|
return out.join("\n\n") + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function __wireRaw(v: any): string {
|
||||||
|
return String(v == null ? "" : v);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wholeAttributeExpression(value: string): string | null {
|
||||||
|
const match = /^\s*\{([\s\S]+)\}\s*$/.exec(value);
|
||||||
|
|
||||||
|
return match?.[1]?.trim() || null;
|
||||||
|
}
|
||||||
function __wireHtml(v: any): string {
|
function __wireHtml(v: any): string {
|
||||||
return String(v == null ? "" : v).replace(/[&<>]/g, (c) =>
|
return String(v == null ? "" : v).replace(/[&<>]/g, (c) =>
|
||||||
c === "&" ? "&" : c === "<" ? "<" : ">",
|
c === "&" ? "&" : c === "<" ? "<" : ">",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function __wireRaw(v: any): string {
|
function __wireAttr(v: any): string {
|
||||||
return String(v == null ? "" : v);
|
return String(v == null ? "" : v).replace(/[&<>"]/g, (c) =>
|
||||||
|
c === "&" ? "&" : c === "<" ? "<" : c === ">" ? ">" : """,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function __wireProp(v: any): string {
|
||||||
|
const value =
|
||||||
|
v !== null && typeof v === "object" ? JSON.stringify(v) : String(v == null ? "" : v);
|
||||||
|
|
||||||
|
return __wireAttr(value);
|
||||||
|
}
|
||||||
|
function renderPageComponentAttr(attr: Attr, dynamicExpressions: string[]): string {
|
||||||
|
if (attr.event) {
|
||||||
|
return ` ${eventAttribute(attr.name)}="${attrEscape(attr.value)}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr.boolean) {
|
||||||
|
return ` ${attr.name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expression = wholeAttributeExpression(attr.value);
|
||||||
|
|
||||||
|
if (!expression) {
|
||||||
|
return ` ${attr.name}="${attrEscape(attr.value)}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`);
|
||||||
|
|
||||||
|
const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`;
|
||||||
|
|
||||||
|
return ` ${attr.name}="${marker}"`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -469,13 +469,6 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en
|
|||||||
|
|
||||||
const isTagNamePart = (c: string): boolean => /[A-Za-z0-9_$:.-]/.test(c);
|
const isTagNamePart = (c: string): boolean => /[A-Za-z0-9_$:.-]/.test(c);
|
||||||
|
|
||||||
const isAttributeNamePart = (c: string, next: string | undefined): boolean => {
|
|
||||||
if (c === "/") {
|
|
||||||
return next !== ">";
|
|
||||||
}
|
|
||||||
|
|
||||||
return /[A-Za-z0-9_$:.[\]%-]/.test(c);
|
|
||||||
};
|
|
||||||
const isWs = (c: string): boolean => c === " " || c === "\t" || c === "\n" || c === "\r";
|
const isWs = (c: string): boolean => c === " " || c === "\t" || c === "\n" || c === "\r";
|
||||||
|
|
||||||
const fail = (msg: string): never => {
|
const fail = (msg: string): never => {
|
||||||
@@ -526,18 +519,37 @@ export function parseHtmlView(src: string, pos: number): { nodes: ViewNode[]; en
|
|||||||
};
|
};
|
||||||
|
|
||||||
const readAttributeName = (): string => {
|
const readAttributeName = (): string => {
|
||||||
const first = src[i];
|
if (i >= src.length) {
|
||||||
|
|
||||||
if (i >= src.length || (!isNameStart(first!) && first !== ":" && first !== "$")) {
|
|
||||||
return fail("Expected an attribute name");
|
return fail("Expected an attribute name");
|
||||||
}
|
}
|
||||||
|
|
||||||
const start = i++;
|
const start = i;
|
||||||
|
|
||||||
|
while (i < src.length) {
|
||||||
|
const char = src[i];
|
||||||
|
const next = src[i + 1];
|
||||||
|
|
||||||
|
if (
|
||||||
|
char === "=" ||
|
||||||
|
char === ">" ||
|
||||||
|
char === '"' ||
|
||||||
|
char === "'" ||
|
||||||
|
char === " " ||
|
||||||
|
char === "\t" ||
|
||||||
|
char === "\n" ||
|
||||||
|
char === "\r" ||
|
||||||
|
(char === "/" && next === ">")
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
while (i < src.length && isAttributeNamePart(src[i]!, src[i + 1])) {
|
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (i === start) {
|
||||||
|
return fail("Expected an attribute name");
|
||||||
|
}
|
||||||
|
|
||||||
return src.slice(start, i);
|
return src.slice(start, i);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { join } from "node:path";
|
|||||||
import { pathToFileURL } from "node:url";
|
import { pathToFileURL } from "node:url";
|
||||||
import { generate, parse } from "../src/index.ts";
|
import { generate, parse } from "../src/index.ts";
|
||||||
import { compileWireFile } from "../src/index.ts";
|
import { compileWireFile } from "../src/index.ts";
|
||||||
|
import { mountHtml } from "@wrnexus/test";
|
||||||
|
|
||||||
let seq = 0;
|
let seq = 0;
|
||||||
/** Compile a `.wrn` source and import the resulting module. */
|
/** Compile a `.wrn` source and import the resulting module. */
|
||||||
@@ -243,7 +244,9 @@ component ParentCard {
|
|||||||
|
|
||||||
expect(output).toContain('data-component="ChildCard"');
|
expect(output).toContain('data-component="ChildCard"');
|
||||||
|
|
||||||
expect(output).toContain('title="${__wireAttr(title)}"');
|
expect(output).toContain('title="${__wireProp(title)}"');
|
||||||
|
|
||||||
|
expect(output).toContain("function __wireProp");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("parses a layout block", () => {
|
test("parses a layout block", () => {
|
||||||
@@ -610,3 +613,229 @@ function extractBehavior(output: string): {
|
|||||||
|
|
||||||
return JSON.parse(Buffer.from(match![1], "base64").toString("utf8"));
|
return JSON.parse(Buffer.from(match![1], "base64").toString("utf8"));
|
||||||
}
|
}
|
||||||
|
test("component interpolated class attributes remain reactive", () => {
|
||||||
|
const output = generate(
|
||||||
|
parse(`
|
||||||
|
component CookieToggle {
|
||||||
|
state personalizationCookies = false
|
||||||
|
|
||||||
|
view {
|
||||||
|
<span
|
||||||
|
class="base {personalizationCookies ? 'enabled' : 'disabled'}"
|
||||||
|
>
|
||||||
|
Cookie
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
|
||||||
|
const match = output.match(/data-wrn-bind-class="([^"]+)"/);
|
||||||
|
|
||||||
|
expect(match).not.toBeNull();
|
||||||
|
|
||||||
|
const encodedBinding = match?.[1] ?? "";
|
||||||
|
|
||||||
|
const decodedBinding = encodedBinding
|
||||||
|
.replaceAll(""", '"')
|
||||||
|
.replaceAll("'", "'")
|
||||||
|
.replaceAll("&", "&")
|
||||||
|
.replaceAll("<", "<")
|
||||||
|
.replaceAll(">", ">");
|
||||||
|
|
||||||
|
const binding = JSON.parse(decodedBinding) as [string, string];
|
||||||
|
|
||||||
|
expect(binding[0]).toBe("class");
|
||||||
|
|
||||||
|
expect(binding[1]).toBe("base {personalizationCookies ? 'enabled' : 'disabled'}");
|
||||||
|
});
|
||||||
|
test("parses class directives containing Tailwind arbitrary values", () => {
|
||||||
|
const ast = parse(`
|
||||||
|
component PageHeader {
|
||||||
|
state centered = false
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div
|
||||||
|
class="py-12"
|
||||||
|
class:lg:grid-cols-[minmax(0,1fr)_auto]="!centered"
|
||||||
|
class:dark:bg-white/10="centered"
|
||||||
|
class:w-[calc(100%-2rem)]="centered"
|
||||||
|
>
|
||||||
|
Header
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(ast).toBeDefined();
|
||||||
|
});
|
||||||
|
test("compiles Tailwind arbitrary values in class directives", () => {
|
||||||
|
const output = generate(
|
||||||
|
parse(`
|
||||||
|
component PageHeader {
|
||||||
|
state centered = false
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div
|
||||||
|
class="py-12"
|
||||||
|
class:lg:grid-cols-[minmax(0,1fr)_auto]="!centered"
|
||||||
|
class:dark:bg-white/10="centered"
|
||||||
|
>
|
||||||
|
Header
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).toContain("lg:grid-cols-[minmax(0,1fr)_auto]");
|
||||||
|
|
||||||
|
expect(output).toContain("dark:bg-white/10");
|
||||||
|
});
|
||||||
|
test("components support each blocks", () => {
|
||||||
|
const output = generate(
|
||||||
|
parse(`
|
||||||
|
component PageHeader {
|
||||||
|
props {
|
||||||
|
highlights = []
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
<div>
|
||||||
|
{#each highlights as highlight}
|
||||||
|
<span>{highlight.title}</span>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).toContain("highlights");
|
||||||
|
|
||||||
|
expect(output).toContain("highlight.title");
|
||||||
|
|
||||||
|
expect(output).not.toContain("supported in pages, not components");
|
||||||
|
});
|
||||||
|
test("components support if blocks", () => {
|
||||||
|
const output = generate(
|
||||||
|
parse(`
|
||||||
|
component Banner {
|
||||||
|
props {
|
||||||
|
visible = true
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
{#if visible}
|
||||||
|
<div>Visible</div>
|
||||||
|
{:else}
|
||||||
|
<div>Hidden</div>
|
||||||
|
{/if}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).toContain("visible");
|
||||||
|
expect(output).toContain("Visible");
|
||||||
|
expect(output).toContain("Hidden");
|
||||||
|
});
|
||||||
|
test("component array props support each blocks", () => {
|
||||||
|
const output = generate(
|
||||||
|
parse(`
|
||||||
|
component HighlightList {
|
||||||
|
props {
|
||||||
|
highlights = []
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
{#each highlights as highlight}
|
||||||
|
<p>{highlight.title}</p>
|
||||||
|
{/each}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).toContain("Array.isArray(def)");
|
||||||
|
|
||||||
|
expect(output).toContain("JSON.parse(v)");
|
||||||
|
|
||||||
|
expect(output).toContain("highlights");
|
||||||
|
});
|
||||||
|
test("page component props evaluate whole expressions", () => {
|
||||||
|
const output = generate(
|
||||||
|
parse(`
|
||||||
|
page Home {
|
||||||
|
view {
|
||||||
|
<HighlightList
|
||||||
|
enabled="{true}"
|
||||||
|
highlights="{[
|
||||||
|
{ title: 'First' },
|
||||||
|
{ title: 'Second' }
|
||||||
|
]}"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).toContain("__wrnexusPropAttr(true)");
|
||||||
|
|
||||||
|
expect(output).toContain("__wrnexusPropAttr([");
|
||||||
|
});
|
||||||
|
test("component functions are available during server rendering", () => {
|
||||||
|
const output = generate(
|
||||||
|
parse(`
|
||||||
|
component FAQAccordion {
|
||||||
|
props {
|
||||||
|
allowMultiple = false
|
||||||
|
items = []
|
||||||
|
}
|
||||||
|
|
||||||
|
state openIndexes = []
|
||||||
|
|
||||||
|
functions {
|
||||||
|
function isOpen(index) {
|
||||||
|
return openIndexes.includes(index)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleItem(index) {
|
||||||
|
if (isOpen(index)) {
|
||||||
|
openIndexes = openIndexes.filter(
|
||||||
|
(itemIndex) =>
|
||||||
|
itemIndex !== index
|
||||||
|
)
|
||||||
|
} else if (allowMultiple) {
|
||||||
|
openIndexes = [
|
||||||
|
...openIndexes,
|
||||||
|
index
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
openIndexes = [index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view {
|
||||||
|
{#each items as item, index}
|
||||||
|
<button
|
||||||
|
class:hidden="!isOpen(index)"
|
||||||
|
@click="toggleItem(index)"
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).toContain("function isOpen(index)");
|
||||||
|
|
||||||
|
expect(output).toContain("function toggleItem(index)");
|
||||||
|
|
||||||
|
expect(output).toContain("let openIndexes =");
|
||||||
|
|
||||||
|
expect(output).toContain("isOpen(index)");
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/core",
|
"name": "@wrnexus/core",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/csr",
|
"name": "@wrnexus/csr",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -164,6 +164,119 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
subscribe: function (f) { subs.add(f); return function () { subs.delete(f); }; }
|
subscribe: function (f) { subs.add(f); return function () { subs.delete(f); }; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
function applyReactiveAttribute(node, name, value) {
|
||||||
|
var lowerName = String(name || "").toLowerCase();
|
||||||
|
|
||||||
|
if (
|
||||||
|
lowerName === "value" &&
|
||||||
|
(
|
||||||
|
node instanceof HTMLInputElement ||
|
||||||
|
node instanceof HTMLTextAreaElement ||
|
||||||
|
node instanceof HTMLSelectElement
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
var nextValue =
|
||||||
|
value == null ? "" : String(value);
|
||||||
|
|
||||||
|
if (node.value !== nextValue) {
|
||||||
|
node.value = nextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
node.removeAttribute("value");
|
||||||
|
} else {
|
||||||
|
node.setAttribute(
|
||||||
|
"value",
|
||||||
|
nextValue,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lowerName === "checked" &&
|
||||||
|
node instanceof HTMLInputElement
|
||||||
|
) {
|
||||||
|
var checked = !!value;
|
||||||
|
|
||||||
|
if (node.checked !== checked) {
|
||||||
|
node.checked = checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checked) {
|
||||||
|
node.setAttribute(
|
||||||
|
"checked",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
node.removeAttribute(
|
||||||
|
"checked",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lowerName === "selected" &&
|
||||||
|
node instanceof HTMLOptionElement
|
||||||
|
) {
|
||||||
|
var selected = !!value;
|
||||||
|
|
||||||
|
node.selected = selected;
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
node.setAttribute(
|
||||||
|
"selected",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
node.removeAttribute(
|
||||||
|
"selected",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
lowerName === "disabled" ||
|
||||||
|
lowerName === "required" ||
|
||||||
|
lowerName === "readonly" ||
|
||||||
|
lowerName === "multiple"
|
||||||
|
) {
|
||||||
|
var enabled = !!value;
|
||||||
|
|
||||||
|
if (lowerName in node) {
|
||||||
|
try {
|
||||||
|
node[lowerName] = enabled;
|
||||||
|
} catch (_) {
|
||||||
|
// Fall through to attribute handling.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled) {
|
||||||
|
node.setAttribute(
|
||||||
|
name,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
node.removeAttribute(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === false || value == null) {
|
||||||
|
node.removeAttribute(name);
|
||||||
|
} else {
|
||||||
|
node.setAttribute(
|
||||||
|
name,
|
||||||
|
value === true ? "" : String(value),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setupScope(el) {
|
function setupScope(el) {
|
||||||
if (el.__wrnexusScope) return; // idempotent: safe to call again after an HMR morph
|
if (el.__wrnexusScope) return; // idempotent: safe to call again after an HMR morph
|
||||||
@@ -486,8 +599,7 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
} catch (e) { return ""; }
|
} catch (e) { return ""; }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (raw === false || raw == null) node.removeAttribute(name);
|
applyReactiveAttribute(node, name, raw);
|
||||||
else node.setAttribute(name, raw === true ? "" : String(raw));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -696,12 +808,269 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
write(name, next);
|
write(name, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function evaluateSpecialExpression(
|
||||||
|
expression,
|
||||||
|
read,
|
||||||
|
) {
|
||||||
|
var source = String(
|
||||||
|
expression || "",
|
||||||
|
).trim();
|
||||||
|
|
||||||
|
// Remove an optional trailing semicolon.
|
||||||
|
if (
|
||||||
|
source.charAt(
|
||||||
|
source.length - 1,
|
||||||
|
) === ";"
|
||||||
|
) {
|
||||||
|
source = source
|
||||||
|
.slice(0, -1)
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Array literals containing spreads:
|
||||||
|
*
|
||||||
|
* [...openIndexes, index]
|
||||||
|
* [first, ...otherItems]
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
source.charAt(0) === "[" &&
|
||||||
|
source.charAt(
|
||||||
|
source.length - 1,
|
||||||
|
) === "]" &&
|
||||||
|
source.indexOf("...") !== -1
|
||||||
|
) {
|
||||||
|
var arraySource = source.slice(
|
||||||
|
1,
|
||||||
|
-1,
|
||||||
|
);
|
||||||
|
|
||||||
|
var arrayParts = splitTopLevel(
|
||||||
|
arraySource,
|
||||||
|
",",
|
||||||
|
);
|
||||||
|
|
||||||
|
var arrayResult = [];
|
||||||
|
|
||||||
|
arrayParts.forEach(function (
|
||||||
|
part,
|
||||||
|
) {
|
||||||
|
var partSource =
|
||||||
|
part.trim();
|
||||||
|
|
||||||
|
if (!partSource) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
partSource.slice(0, 3) ===
|
||||||
|
"..."
|
||||||
|
) {
|
||||||
|
var spreadResult =
|
||||||
|
evaluateExpression(
|
||||||
|
partSource
|
||||||
|
.slice(3)
|
||||||
|
.trim(),
|
||||||
|
read,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(
|
||||||
|
spreadResult,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Array.prototype.push.apply(
|
||||||
|
arrayResult,
|
||||||
|
spreadResult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayResult.push(
|
||||||
|
evaluateExpression(
|
||||||
|
partSource,
|
||||||
|
read,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
matched: true,
|
||||||
|
value: arrayResult,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* First recognize the array method call without
|
||||||
|
* trying to parse the callback in the same regex.
|
||||||
|
*/
|
||||||
|
var methodMatch =
|
||||||
|
/^([\s\S]+)\.(filter|map|some|every|find|findIndex)\(\s*([\s\S]*)\s*\)$/.exec(
|
||||||
|
source,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!methodMatch) {
|
||||||
|
return {
|
||||||
|
matched: false,
|
||||||
|
value: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var collectionSource =
|
||||||
|
methodMatch[1].trim();
|
||||||
|
|
||||||
|
var method =
|
||||||
|
methodMatch[2];
|
||||||
|
|
||||||
|
var callbackSource =
|
||||||
|
methodMatch[3].trim();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Supported callbacks:
|
||||||
|
*
|
||||||
|
* item => item.active
|
||||||
|
* (item) => item.active
|
||||||
|
* (item, index) => index > 0
|
||||||
|
*/
|
||||||
|
var callbackMatch =
|
||||||
|
/^(?:\(\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*(?:,\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*)?\)|([A-Za-z_$][A-Za-z0-9_$]*))\s*=>\s*([\s\S]+)$/.exec(
|
||||||
|
callbackSource,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!callbackMatch) {
|
||||||
|
return {
|
||||||
|
matched: false,
|
||||||
|
value: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var itemName =
|
||||||
|
callbackMatch[1] ||
|
||||||
|
callbackMatch[3];
|
||||||
|
|
||||||
|
var indexName =
|
||||||
|
callbackMatch[2];
|
||||||
|
|
||||||
|
var callbackBody =
|
||||||
|
callbackMatch[4].trim();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Support expression bodies wrapped in parentheses:
|
||||||
|
*
|
||||||
|
* item => (
|
||||||
|
* item.active
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
callbackBody.charAt(0) ===
|
||||||
|
"(" &&
|
||||||
|
callbackBody.charAt(
|
||||||
|
callbackBody.length - 1,
|
||||||
|
) === ")"
|
||||||
|
) {
|
||||||
|
callbackBody = callbackBody
|
||||||
|
.slice(1, -1)
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var collection =
|
||||||
|
evaluateExpression(
|
||||||
|
collectionSource,
|
||||||
|
read,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Array.isArray(collection)) {
|
||||||
|
collection = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var callback = function (
|
||||||
|
itemValue,
|
||||||
|
itemIndex,
|
||||||
|
) {
|
||||||
|
return evaluateExpression(
|
||||||
|
callbackBody,
|
||||||
|
function (name) {
|
||||||
|
if (name === itemName) {
|
||||||
|
return itemValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
indexName &&
|
||||||
|
name === indexName
|
||||||
|
) {
|
||||||
|
return itemIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return read(name);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
var result;
|
||||||
|
|
||||||
|
if (method === "filter") {
|
||||||
|
result =
|
||||||
|
collection.filter(callback);
|
||||||
|
} else if (
|
||||||
|
method === "map"
|
||||||
|
) {
|
||||||
|
result =
|
||||||
|
collection.map(callback);
|
||||||
|
} else if (
|
||||||
|
method === "some"
|
||||||
|
) {
|
||||||
|
result =
|
||||||
|
collection.some(callback);
|
||||||
|
} else if (
|
||||||
|
method === "every"
|
||||||
|
) {
|
||||||
|
result =
|
||||||
|
collection.every(callback);
|
||||||
|
} else if (
|
||||||
|
method === "find"
|
||||||
|
) {
|
||||||
|
result =
|
||||||
|
collection.find(callback);
|
||||||
|
} else {
|
||||||
|
result =
|
||||||
|
collection.findIndex(
|
||||||
|
callback,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
matched: true,
|
||||||
|
value: result,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// A small, eval-free expression evaluator (so a strict CSP needs no
|
// A small, eval-free expression evaluator (so a strict CSP needs no
|
||||||
// 'unsafe-eval'). Supports: literals, identifiers, member access (a.b, a[b]),
|
// 'unsafe-eval'). Supports: literals, identifiers, member access (a.b, a[b]),
|
||||||
// function/method calls, arrays, objects, arithmetic, comparison, equality,
|
// function/method calls, arrays, objects, arithmetic, comparison, equality,
|
||||||
// logical (&& ||), unary (! - +), and the ternary operator.
|
// logical (&& ||), unary (! - +), and the ternary operator.
|
||||||
function evaluateExpression(expr, read) {
|
function evaluateExpression(
|
||||||
var tokens = tokenizeExpression(String(expr || ""));
|
expr,
|
||||||
|
read,
|
||||||
|
) {
|
||||||
|
expr = String(
|
||||||
|
expr || "",
|
||||||
|
).trim();
|
||||||
|
|
||||||
|
var special =
|
||||||
|
evaluateSpecialExpression(
|
||||||
|
expr,
|
||||||
|
read,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (special.matched) {
|
||||||
|
return special.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokens =
|
||||||
|
tokenizeExpression(expr);
|
||||||
|
|
||||||
var index = 0;
|
var index = 0;
|
||||||
function peek() { return tokens[index]; }
|
function peek() { return tokens[index]; }
|
||||||
function next() { return tokens[index++]; }
|
function next() { return tokens[index++]; }
|
||||||
@@ -856,11 +1225,36 @@ export const REACTIVE_RUNTIME = String.raw`
|
|||||||
tokens.push({ type: "ident", value: input.slice(s, i) });
|
tokens.push({ type: "ident", value: input.slice(s, i) });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var three = input.substr(i, 3);
|
var three = input.slice(i, i + 3);
|
||||||
if (three === "===" || three === "!==") { tokens.push({ type: "op", value: three }); i += 3; continue; }
|
|
||||||
var two = input.substr(i, 2);
|
if (
|
||||||
if (["==", "!=", "<=", ">=", "&&", "||"].indexOf(two) !== -1) {
|
three === "===" ||
|
||||||
tokens.push({ type: "op", value: two });
|
three === "!=="
|
||||||
|
) {
|
||||||
|
tokens.push({
|
||||||
|
type: "operator",
|
||||||
|
value: three,
|
||||||
|
});
|
||||||
|
|
||||||
|
i += 3;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var two = input.slice(i, i + 2);
|
||||||
|
|
||||||
|
if (
|
||||||
|
two === "==" ||
|
||||||
|
two === "!=" ||
|
||||||
|
two === ">=" ||
|
||||||
|
two === "<=" ||
|
||||||
|
two === "&&" ||
|
||||||
|
two === "||"
|
||||||
|
) {
|
||||||
|
tokens.push({
|
||||||
|
type: "operator",
|
||||||
|
value: two,
|
||||||
|
});
|
||||||
|
|
||||||
i += 2;
|
i += 2;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/db",
|
"name": "@wrnexus/db",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/dev-server",
|
"name": "@wrnexus/dev-server",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { existsSync, rmSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
|
export interface ResetDevCacheOptions {
|
||||||
|
rootDir?: string;
|
||||||
|
cacheDir?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetDevCache(options: ResetDevCacheOptions = {}): string {
|
||||||
|
const rootDir = resolve(options.rootDir ?? process.cwd());
|
||||||
|
|
||||||
|
const cacheDir = resolve(rootDir, options.cacheDir ?? ".wrnexus");
|
||||||
|
|
||||||
|
if (options.enabled === false) {
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety: never allow deleting the project root.
|
||||||
|
if (cacheDir === rootDir) {
|
||||||
|
throw new Error("Refusing to remove the project root as the WRNexus cache directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existsSync(cacheDir)) {
|
||||||
|
rmSync(cacheDir, {
|
||||||
|
recursive: true,
|
||||||
|
force: true,
|
||||||
|
maxRetries: 3,
|
||||||
|
retryDelay: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`[wrnexus] cleared generated cache: ${cacheDir}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheDir;
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import { createDevAssetServer } from "./assets.ts";
|
|||||||
import { HmrHub } from "./hmr.ts";
|
import { HmrHub } from "./hmr.ts";
|
||||||
import { startWatcher } from "./watch.ts";
|
import { startWatcher } from "./watch.ts";
|
||||||
export { RESTART_EXIT_CODE } from "./restart.ts";
|
export { RESTART_EXIT_CODE } from "./restart.ts";
|
||||||
|
import { resetDevCache } from "./cache.ts";
|
||||||
|
|
||||||
export interface ServeOptions {
|
export interface ServeOptions {
|
||||||
appDir: string;
|
appDir: string;
|
||||||
@@ -122,6 +123,12 @@ export async function startServer(opts: ServeOptions): Promise<RunningServer> {
|
|||||||
const router = buildRouter(appDir, { componentDirs: [uiComponentsDir()] });
|
const router = buildRouter(appDir, { componentDirs: [uiComponentsDir()] });
|
||||||
const styleEntry = opts.styleEntry ?? null;
|
const styleEntry = opts.styleEntry ?? null;
|
||||||
const appRoot = dirname(appDir);
|
const appRoot = dirname(appDir);
|
||||||
|
|
||||||
|
resetDevCache({
|
||||||
|
rootDir: appRoot,
|
||||||
|
enabled: process.env.WRNEXUS_PRESERVE_CACHE !== "1",
|
||||||
|
});
|
||||||
|
|
||||||
// Compile every `.wrn` into ONE cache dir at the project root, instead of a
|
// Compile every `.wrn` into ONE cache dir at the project root, instead of a
|
||||||
// `.wrnexus/` next to each source file (and inside node_modules UI dirs).
|
// `.wrnexus/` next to each source file (and inside node_modules UI dirs).
|
||||||
setCompileCacheDir(join(appRoot, ".wrnexus"));
|
setCompileCacheDir(join(appRoot, ".wrnexus"));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/encryption",
|
"name": "@wrnexus/encryption",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/helpers",
|
"name": "@wrnexus/helpers",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
"description": "Safe convenience helpers for WrNexus request contexts and common application flows.",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/i18n",
|
"name": "@wrnexus/i18n",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/jwt",
|
"name": "@wrnexus/jwt",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/mobile",
|
"name": "@wrnexus/mobile",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/native",
|
"name": "@wrnexus/native",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/oauth",
|
"name": "@wrnexus/oauth",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/pubsub",
|
"name": "@wrnexus/pubsub",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/queue",
|
"name": "@wrnexus/queue",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/reactive",
|
"name": "@wrnexus/reactive",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/router",
|
"name": "@wrnexus/router",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ssr",
|
"name": "@wrnexus/ssr",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/styles",
|
"name": "@wrnexus/styles",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/test",
|
"name": "@wrnexus/test",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/tracking",
|
"name": "@wrnexus/tracking",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/ui",
|
"name": "@wrnexus/ui",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/uploader",
|
"name": "@wrnexus/uploader",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@wrnexus/validation",
|
"name": "@wrnexus/validation",
|
||||||
"version": "0.2.33",
|
"version": "0.2.42",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
const version = "0.2.42";
|
||||||
|
|
||||||
|
const dependencyGroups = [
|
||||||
|
"dependencies",
|
||||||
|
"devDependencies",
|
||||||
|
"peerDependencies",
|
||||||
|
"optionalDependencies",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const entry of readdirSync("packages", {
|
||||||
|
withFileTypes: true,
|
||||||
|
})) {
|
||||||
|
if (!entry.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageFile = join("packages", entry.name, "package.json");
|
||||||
|
|
||||||
|
if (!existsSync(packageFile)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const packageJson = JSON.parse(readFileSync(packageFile, "utf8"));
|
||||||
|
|
||||||
|
packageJson.version = version;
|
||||||
|
|
||||||
|
for (const group of dependencyGroups) {
|
||||||
|
const dependencies = packageJson[group];
|
||||||
|
|
||||||
|
if (!dependencies || typeof dependencies !== "object") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dependencyName of Object.keys(dependencies)) {
|
||||||
|
if (!dependencyName.startsWith("@wrnexus/")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentVersion = dependencies[dependencyName];
|
||||||
|
|
||||||
|
if (typeof currentVersion !== "string") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVersion.startsWith("workspace:")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentVersion.startsWith("^")) {
|
||||||
|
dependencies[dependencyName] = `^${version}`;
|
||||||
|
} else if (currentVersion.startsWith("~")) {
|
||||||
|
dependencies[dependencyName] = `~${version}`;
|
||||||
|
} else {
|
||||||
|
dependencies[dependencyName] = version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writeFileSync(packageFile, `${JSON.stringify(packageJson, null, 2)}\n`);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user