@ape-egg/vibe 1.7.0 → 1.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -1
- package/README.md +0 -2
- package/compiler/native/vibe-compiler-darwin-arm64 +0 -0
- package/compiler/native/vibe-compiler-linux-x64 +0 -0
- package/compiler/src/Cargo.lock +67 -197
- package/compiler/src/Cargo.toml +2 -2
- package/compiler/src/compiler/compile.rs +102 -30
- package/compiler/src/compiler/component_tagger.rs +46 -37
- package/compiler/src/config.rs +0 -5
- package/compiler/src/main.rs +0 -11
- package/compiler/src/parser/html.rs +144 -79
- package/package.json +1 -1
- package/runtime/component.js +4 -2
- package/runtime/index.js +7 -0
- package/runtime/pre-compiled-manifest.js +6 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.7.2] - 2026-02-27
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Component inlining — hyphenated custom elements inside slot content** (`find_matching_close`)
|
|
8
|
+
- `<component-card>`, `<component-grid>`, etc. inside slot content incorrectly matched `<component\b`, incrementing the depth counter without a corresponding close
|
|
9
|
+
- The real `</component>` could never bring the depth back to 0, so `find_matching_close` returned `None` and the outer component was left un-inlined (causing `Cannot GET /components/Layout.html` at runtime)
|
|
10
|
+
- Fixed by replacing `\b` with `(?:\s|>|/>)` — only matches actual `<component` tags followed by whitespace or `>`, not hyphenated variants
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## [1.7.1] - 2026-02-27
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Linux x64 binary** (`vibe-compiler-linux-x64`) — cross-compiled from macOS ARM using `musl-cross` and the `x86_64-unknown-linux-musl` Rust target
|
|
19
|
+
- Enables the compiler to run on Linux environments (e.g. Vercel, Docker, CI) without a local Rust toolchain
|
|
20
|
+
- New `compiler:build:linux` npm script builds the musl binary
|
|
21
|
+
- `publish:vibe:prepare` now builds both macOS ARM and Linux x64 binaries before publishing
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- **`reqwest` TLS backend** — switched from `native-tls` to `rustls-tls` with `default-features = false`
|
|
26
|
+
- Eliminates the OpenSSL dependency that prevented cross-compilation to `x86_64-unknown-linux-musl`
|
|
27
|
+
- Pure-Rust TLS implementation; no system libraries required
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
3
31
|
## [1.7.0] - 2026-02-19
|
|
4
32
|
|
|
5
33
|
### Fixed
|
|
@@ -430,7 +458,7 @@
|
|
|
430
458
|
- Helps explain total compilation time vs individual phase times
|
|
431
459
|
- Displayed as: `* Validated components in XXms`
|
|
432
460
|
- **Component tree output**: Improved verbose logging for component compilation
|
|
433
|
-
- Tree structure with nested dependencies (e.g., nav.html →
|
|
461
|
+
- Tree structure with nested dependencies (e.g., nav.html → menu.html)
|
|
434
462
|
- Usage counts for each unique component
|
|
435
463
|
- Color-coded: internal (green) vs external (magenta)
|
|
436
464
|
- Summary format: `Compiled components (X internal, Y external)`
|
package/README.md
CHANGED
|
@@ -291,7 +291,6 @@ bunx vibe compile --minify --source-maps
|
|
|
291
291
|
bunx vibe compile --verbose # Step-by-step logging
|
|
292
292
|
bunx vibe compile --minify # Minify output
|
|
293
293
|
bunx vibe compile --elements-as-is # Keep custom elements as-is
|
|
294
|
-
bunx vibe compile --validate # Validate HTML syntax
|
|
295
294
|
bunx vibe compile --source-maps # Generate source maps
|
|
296
295
|
bunx vibe compile --node-modules-as-is # Copy node_modules as-is
|
|
297
296
|
bunx vibe compile --components-as-is # Skip component inlining
|
|
@@ -329,7 +328,6 @@ Add to your `package.json`:
|
|
|
329
328
|
"elementsAsIs": false,
|
|
330
329
|
"reservedElements": [],
|
|
331
330
|
"sourceMaps": false,
|
|
332
|
-
"validate": false,
|
|
333
331
|
"nodeModulesAsIs": false,
|
|
334
332
|
"componentsAsIs": false,
|
|
335
333
|
"runtimeAsIs": false,
|
|
Binary file
|
|
Binary file
|
package/compiler/src/Cargo.lock
CHANGED
|
@@ -302,22 +302,6 @@ version = "1.0.2"
|
|
|
302
302
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
303
303
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
304
304
|
|
|
305
|
-
[[package]]
|
|
306
|
-
name = "errno"
|
|
307
|
-
version = "0.3.14"
|
|
308
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
309
|
-
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
|
310
|
-
dependencies = [
|
|
311
|
-
"libc",
|
|
312
|
-
"windows-sys 0.61.2",
|
|
313
|
-
]
|
|
314
|
-
|
|
315
|
-
[[package]]
|
|
316
|
-
name = "fastrand"
|
|
317
|
-
version = "2.3.0"
|
|
318
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
319
|
-
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
|
320
|
-
|
|
321
305
|
[[package]]
|
|
322
306
|
name = "file-id"
|
|
323
307
|
version = "0.2.3"
|
|
@@ -350,21 +334,6 @@ version = "1.0.7"
|
|
|
350
334
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
351
335
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
|
352
336
|
|
|
353
|
-
[[package]]
|
|
354
|
-
name = "foreign-types"
|
|
355
|
-
version = "0.3.2"
|
|
356
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
357
|
-
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
|
358
|
-
dependencies = [
|
|
359
|
-
"foreign-types-shared",
|
|
360
|
-
]
|
|
361
|
-
|
|
362
|
-
[[package]]
|
|
363
|
-
name = "foreign-types-shared"
|
|
364
|
-
version = "0.1.1"
|
|
365
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
366
|
-
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
|
367
|
-
|
|
368
337
|
[[package]]
|
|
369
338
|
name = "form_urlencoded"
|
|
370
339
|
version = "1.2.2"
|
|
@@ -454,14 +423,13 @@ dependencies = [
|
|
|
454
423
|
|
|
455
424
|
[[package]]
|
|
456
425
|
name = "getrandom"
|
|
457
|
-
version = "0.
|
|
426
|
+
version = "0.2.17"
|
|
458
427
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
459
|
-
checksum = "
|
|
428
|
+
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
|
460
429
|
dependencies = [
|
|
461
430
|
"cfg-if",
|
|
462
431
|
"libc",
|
|
463
|
-
"
|
|
464
|
-
"wasip2",
|
|
432
|
+
"wasi",
|
|
465
433
|
]
|
|
466
434
|
|
|
467
435
|
[[package]]
|
|
@@ -598,16 +566,17 @@ dependencies = [
|
|
|
598
566
|
]
|
|
599
567
|
|
|
600
568
|
[[package]]
|
|
601
|
-
name = "hyper-
|
|
602
|
-
version = "0.
|
|
569
|
+
name = "hyper-rustls"
|
|
570
|
+
version = "0.24.2"
|
|
603
571
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
604
|
-
checksum = "
|
|
572
|
+
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
|
605
573
|
dependencies = [
|
|
606
|
-
"
|
|
574
|
+
"futures-util",
|
|
575
|
+
"http",
|
|
607
576
|
"hyper",
|
|
608
|
-
"
|
|
577
|
+
"rustls",
|
|
609
578
|
"tokio",
|
|
610
|
-
"tokio-
|
|
579
|
+
"tokio-rustls",
|
|
611
580
|
]
|
|
612
581
|
|
|
613
582
|
[[package]]
|
|
@@ -825,12 +794,6 @@ dependencies = [
|
|
|
825
794
|
"redox_syscall 0.7.1",
|
|
826
795
|
]
|
|
827
796
|
|
|
828
|
-
[[package]]
|
|
829
|
-
name = "linux-raw-sys"
|
|
830
|
-
version = "0.11.0"
|
|
831
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
832
|
-
checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
|
|
833
|
-
|
|
834
797
|
[[package]]
|
|
835
798
|
name = "litemap"
|
|
836
799
|
version = "0.8.1"
|
|
@@ -919,23 +882,6 @@ dependencies = [
|
|
|
919
882
|
"windows-sys 0.61.2",
|
|
920
883
|
]
|
|
921
884
|
|
|
922
|
-
[[package]]
|
|
923
|
-
name = "native-tls"
|
|
924
|
-
version = "0.2.14"
|
|
925
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
926
|
-
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
|
|
927
|
-
dependencies = [
|
|
928
|
-
"libc",
|
|
929
|
-
"log",
|
|
930
|
-
"openssl",
|
|
931
|
-
"openssl-probe",
|
|
932
|
-
"openssl-sys",
|
|
933
|
-
"schannel",
|
|
934
|
-
"security-framework",
|
|
935
|
-
"security-framework-sys",
|
|
936
|
-
"tempfile",
|
|
937
|
-
]
|
|
938
|
-
|
|
939
885
|
[[package]]
|
|
940
886
|
name = "new_debug_unreachable"
|
|
941
887
|
version = "1.0.6"
|
|
@@ -1025,50 +971,6 @@ version = "1.70.2"
|
|
|
1025
971
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1026
972
|
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
|
1027
973
|
|
|
1028
|
-
[[package]]
|
|
1029
|
-
name = "openssl"
|
|
1030
|
-
version = "0.10.75"
|
|
1031
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1032
|
-
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
|
|
1033
|
-
dependencies = [
|
|
1034
|
-
"bitflags 2.10.0",
|
|
1035
|
-
"cfg-if",
|
|
1036
|
-
"foreign-types",
|
|
1037
|
-
"libc",
|
|
1038
|
-
"once_cell",
|
|
1039
|
-
"openssl-macros",
|
|
1040
|
-
"openssl-sys",
|
|
1041
|
-
]
|
|
1042
|
-
|
|
1043
|
-
[[package]]
|
|
1044
|
-
name = "openssl-macros"
|
|
1045
|
-
version = "0.1.1"
|
|
1046
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1047
|
-
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|
1048
|
-
dependencies = [
|
|
1049
|
-
"proc-macro2",
|
|
1050
|
-
"quote",
|
|
1051
|
-
"syn 2.0.114",
|
|
1052
|
-
]
|
|
1053
|
-
|
|
1054
|
-
[[package]]
|
|
1055
|
-
name = "openssl-probe"
|
|
1056
|
-
version = "0.1.6"
|
|
1057
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1058
|
-
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
|
1059
|
-
|
|
1060
|
-
[[package]]
|
|
1061
|
-
name = "openssl-sys"
|
|
1062
|
-
version = "0.9.111"
|
|
1063
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1064
|
-
checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321"
|
|
1065
|
-
dependencies = [
|
|
1066
|
-
"cc",
|
|
1067
|
-
"libc",
|
|
1068
|
-
"pkg-config",
|
|
1069
|
-
"vcpkg",
|
|
1070
|
-
]
|
|
1071
|
-
|
|
1072
974
|
[[package]]
|
|
1073
975
|
name = "parking_lot"
|
|
1074
976
|
version = "0.12.5"
|
|
@@ -1162,12 +1064,6 @@ version = "0.1.0"
|
|
|
1162
1064
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1163
1065
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|
1164
1066
|
|
|
1165
|
-
[[package]]
|
|
1166
|
-
name = "pkg-config"
|
|
1167
|
-
version = "0.3.32"
|
|
1168
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1169
|
-
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
|
1170
|
-
|
|
1171
1067
|
[[package]]
|
|
1172
1068
|
name = "potential_utf"
|
|
1173
1069
|
version = "0.1.4"
|
|
@@ -1231,12 +1127,6 @@ dependencies = [
|
|
|
1231
1127
|
"proc-macro2",
|
|
1232
1128
|
]
|
|
1233
1129
|
|
|
1234
|
-
[[package]]
|
|
1235
|
-
name = "r-efi"
|
|
1236
|
-
version = "5.3.0"
|
|
1237
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1238
|
-
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
|
1239
|
-
|
|
1240
1130
|
[[package]]
|
|
1241
1131
|
name = "rand"
|
|
1242
1132
|
version = "0.8.5"
|
|
@@ -1334,15 +1224,15 @@ dependencies = [
|
|
|
1334
1224
|
"http",
|
|
1335
1225
|
"http-body",
|
|
1336
1226
|
"hyper",
|
|
1337
|
-
"hyper-
|
|
1227
|
+
"hyper-rustls",
|
|
1338
1228
|
"ipnet",
|
|
1339
1229
|
"js-sys",
|
|
1340
1230
|
"log",
|
|
1341
1231
|
"mime",
|
|
1342
|
-
"native-tls",
|
|
1343
1232
|
"once_cell",
|
|
1344
1233
|
"percent-encoding",
|
|
1345
1234
|
"pin-project-lite",
|
|
1235
|
+
"rustls",
|
|
1346
1236
|
"rustls-pemfile",
|
|
1347
1237
|
"serde",
|
|
1348
1238
|
"serde_json",
|
|
@@ -1350,15 +1240,30 @@ dependencies = [
|
|
|
1350
1240
|
"sync_wrapper",
|
|
1351
1241
|
"system-configuration",
|
|
1352
1242
|
"tokio",
|
|
1353
|
-
"tokio-
|
|
1243
|
+
"tokio-rustls",
|
|
1354
1244
|
"tower-service",
|
|
1355
1245
|
"url",
|
|
1356
1246
|
"wasm-bindgen",
|
|
1357
1247
|
"wasm-bindgen-futures",
|
|
1358
1248
|
"web-sys",
|
|
1249
|
+
"webpki-roots",
|
|
1359
1250
|
"winreg",
|
|
1360
1251
|
]
|
|
1361
1252
|
|
|
1253
|
+
[[package]]
|
|
1254
|
+
name = "ring"
|
|
1255
|
+
version = "0.17.14"
|
|
1256
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1257
|
+
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
|
1258
|
+
dependencies = [
|
|
1259
|
+
"cc",
|
|
1260
|
+
"cfg-if",
|
|
1261
|
+
"getrandom",
|
|
1262
|
+
"libc",
|
|
1263
|
+
"untrusted",
|
|
1264
|
+
"windows-sys 0.52.0",
|
|
1265
|
+
]
|
|
1266
|
+
|
|
1362
1267
|
[[package]]
|
|
1363
1268
|
name = "rquickjs"
|
|
1364
1269
|
version = "0.6.2"
|
|
@@ -1393,16 +1298,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1393
1298
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
|
1394
1299
|
|
|
1395
1300
|
[[package]]
|
|
1396
|
-
name = "
|
|
1397
|
-
version = "
|
|
1301
|
+
name = "rustls"
|
|
1302
|
+
version = "0.21.12"
|
|
1398
1303
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1399
|
-
checksum = "
|
|
1304
|
+
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
|
1400
1305
|
dependencies = [
|
|
1401
|
-
"
|
|
1402
|
-
"
|
|
1403
|
-
"
|
|
1404
|
-
"
|
|
1405
|
-
"windows-sys 0.61.2",
|
|
1306
|
+
"log",
|
|
1307
|
+
"ring",
|
|
1308
|
+
"rustls-webpki",
|
|
1309
|
+
"sct",
|
|
1406
1310
|
]
|
|
1407
1311
|
|
|
1408
1312
|
[[package]]
|
|
@@ -1414,6 +1318,16 @@ dependencies = [
|
|
|
1414
1318
|
"base64",
|
|
1415
1319
|
]
|
|
1416
1320
|
|
|
1321
|
+
[[package]]
|
|
1322
|
+
name = "rustls-webpki"
|
|
1323
|
+
version = "0.101.7"
|
|
1324
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1325
|
+
checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765"
|
|
1326
|
+
dependencies = [
|
|
1327
|
+
"ring",
|
|
1328
|
+
"untrusted",
|
|
1329
|
+
]
|
|
1330
|
+
|
|
1417
1331
|
[[package]]
|
|
1418
1332
|
name = "rustversion"
|
|
1419
1333
|
version = "1.0.22"
|
|
@@ -1435,15 +1349,6 @@ dependencies = [
|
|
|
1435
1349
|
"winapi-util",
|
|
1436
1350
|
]
|
|
1437
1351
|
|
|
1438
|
-
[[package]]
|
|
1439
|
-
name = "schannel"
|
|
1440
|
-
version = "0.1.28"
|
|
1441
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1442
|
-
checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1"
|
|
1443
|
-
dependencies = [
|
|
1444
|
-
"windows-sys 0.61.2",
|
|
1445
|
-
]
|
|
1446
|
-
|
|
1447
1352
|
[[package]]
|
|
1448
1353
|
name = "scoped-tls"
|
|
1449
1354
|
version = "1.0.1"
|
|
@@ -1457,26 +1362,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
|
1457
1362
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|
1458
1363
|
|
|
1459
1364
|
[[package]]
|
|
1460
|
-
name = "
|
|
1461
|
-
version = "
|
|
1462
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1463
|
-
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
|
1464
|
-
dependencies = [
|
|
1465
|
-
"bitflags 2.10.0",
|
|
1466
|
-
"core-foundation",
|
|
1467
|
-
"core-foundation-sys",
|
|
1468
|
-
"libc",
|
|
1469
|
-
"security-framework-sys",
|
|
1470
|
-
]
|
|
1471
|
-
|
|
1472
|
-
[[package]]
|
|
1473
|
-
name = "security-framework-sys"
|
|
1474
|
-
version = "2.15.0"
|
|
1365
|
+
name = "sct"
|
|
1366
|
+
version = "0.7.1"
|
|
1475
1367
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1476
|
-
checksum = "
|
|
1368
|
+
checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414"
|
|
1477
1369
|
dependencies = [
|
|
1478
|
-
"
|
|
1479
|
-
"
|
|
1370
|
+
"ring",
|
|
1371
|
+
"untrusted",
|
|
1480
1372
|
]
|
|
1481
1373
|
|
|
1482
1374
|
[[package]]
|
|
@@ -1849,19 +1741,6 @@ dependencies = [
|
|
|
1849
1741
|
"libc",
|
|
1850
1742
|
]
|
|
1851
1743
|
|
|
1852
|
-
[[package]]
|
|
1853
|
-
name = "tempfile"
|
|
1854
|
-
version = "3.24.0"
|
|
1855
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1856
|
-
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
|
|
1857
|
-
dependencies = [
|
|
1858
|
-
"fastrand",
|
|
1859
|
-
"getrandom",
|
|
1860
|
-
"once_cell",
|
|
1861
|
-
"rustix",
|
|
1862
|
-
"windows-sys 0.61.2",
|
|
1863
|
-
]
|
|
1864
|
-
|
|
1865
1744
|
[[package]]
|
|
1866
1745
|
name = "tendril"
|
|
1867
1746
|
version = "0.4.3"
|
|
@@ -1918,12 +1797,12 @@ dependencies = [
|
|
|
1918
1797
|
]
|
|
1919
1798
|
|
|
1920
1799
|
[[package]]
|
|
1921
|
-
name = "tokio-
|
|
1922
|
-
version = "0.
|
|
1800
|
+
name = "tokio-rustls"
|
|
1801
|
+
version = "0.24.1"
|
|
1923
1802
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1924
|
-
checksum = "
|
|
1803
|
+
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
|
1925
1804
|
dependencies = [
|
|
1926
|
-
"
|
|
1805
|
+
"rustls",
|
|
1927
1806
|
"tokio",
|
|
1928
1807
|
]
|
|
1929
1808
|
|
|
@@ -2017,6 +1896,12 @@ version = "0.1.14"
|
|
|
2017
1896
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2018
1897
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
|
2019
1898
|
|
|
1899
|
+
[[package]]
|
|
1900
|
+
name = "untrusted"
|
|
1901
|
+
version = "0.9.0"
|
|
1902
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1903
|
+
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
|
1904
|
+
|
|
2020
1905
|
[[package]]
|
|
2021
1906
|
name = "url"
|
|
2022
1907
|
version = "2.5.8"
|
|
@@ -2047,12 +1932,6 @@ version = "0.2.2"
|
|
|
2047
1932
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2048
1933
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
|
2049
1934
|
|
|
2050
|
-
[[package]]
|
|
2051
|
-
name = "vcpkg"
|
|
2052
|
-
version = "0.2.15"
|
|
2053
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2054
|
-
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
|
2055
|
-
|
|
2056
1935
|
[[package]]
|
|
2057
1936
|
name = "version_check"
|
|
2058
1937
|
version = "0.9.5"
|
|
@@ -2061,7 +1940,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
|
|
2061
1940
|
|
|
2062
1941
|
[[package]]
|
|
2063
1942
|
name = "vibe-compiler"
|
|
2064
|
-
version = "1.7.
|
|
1943
|
+
version = "1.7.2"
|
|
2065
1944
|
dependencies = [
|
|
2066
1945
|
"clap",
|
|
2067
1946
|
"colored",
|
|
@@ -2109,15 +1988,6 @@ version = "0.11.1+wasi-snapshot-preview1"
|
|
|
2109
1988
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2110
1989
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
|
2111
1990
|
|
|
2112
|
-
[[package]]
|
|
2113
|
-
name = "wasip2"
|
|
2114
|
-
version = "1.0.2+wasi-0.2.9"
|
|
2115
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2116
|
-
checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
|
|
2117
|
-
dependencies = [
|
|
2118
|
-
"wit-bindgen",
|
|
2119
|
-
]
|
|
2120
|
-
|
|
2121
1991
|
[[package]]
|
|
2122
1992
|
name = "wasm-bindgen"
|
|
2123
1993
|
version = "0.2.108"
|
|
@@ -2187,6 +2057,12 @@ dependencies = [
|
|
|
2187
2057
|
"wasm-bindgen",
|
|
2188
2058
|
]
|
|
2189
2059
|
|
|
2060
|
+
[[package]]
|
|
2061
|
+
name = "webpki-roots"
|
|
2062
|
+
version = "0.25.4"
|
|
2063
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2064
|
+
checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
|
|
2065
|
+
|
|
2190
2066
|
[[package]]
|
|
2191
2067
|
name = "winapi-util"
|
|
2192
2068
|
version = "0.1.11"
|
|
@@ -2443,12 +2319,6 @@ dependencies = [
|
|
|
2443
2319
|
"windows-sys 0.48.0",
|
|
2444
2320
|
]
|
|
2445
2321
|
|
|
2446
|
-
[[package]]
|
|
2447
|
-
name = "wit-bindgen"
|
|
2448
|
-
version = "0.51.0"
|
|
2449
|
-
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
2450
|
-
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
|
2451
|
-
|
|
2452
2322
|
[[package]]
|
|
2453
2323
|
name = "writeable"
|
|
2454
2324
|
version = "0.6.2"
|
package/compiler/src/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "vibe-compiler"
|
|
3
|
-
version = "1.7.
|
|
3
|
+
version = "1.7.2"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Vibe framework compiler - compiles Vibe source files into optimized output"
|
|
6
6
|
authors = ["Kim Korte"]
|
|
@@ -20,7 +20,7 @@ markup5ever = "0.12"
|
|
|
20
20
|
thiserror = "1.0"
|
|
21
21
|
regex = "1.10"
|
|
22
22
|
colored = "2.1"
|
|
23
|
-
reqwest = { version = "0.11", features = ["blocking"] }
|
|
23
|
+
reqwest = { version = "0.11", features = ["blocking", "rustls-tls"], default-features = false }
|
|
24
24
|
glob = "0.3"
|
|
25
25
|
rquickjs = "0.6"
|
|
26
26
|
swc_common = "=0.40.1"
|
|
@@ -488,17 +488,15 @@ impl Compiler {
|
|
|
488
488
|
}
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
// Validate HTML syntax
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
println!(" HTML validation completed successfully");
|
|
501
|
-
}
|
|
491
|
+
// Validate HTML syntax (always runs)
|
|
492
|
+
let validation_start = Instant::now();
|
|
493
|
+
if self.verbose {
|
|
494
|
+
println!("\nValidating HTML syntax...");
|
|
495
|
+
}
|
|
496
|
+
self.validate_html_syntax()?;
|
|
497
|
+
stats.validation_time_ms = Some(validation_start.elapsed().as_secs_f64() * 1000.0);
|
|
498
|
+
if self.verbose {
|
|
499
|
+
println!(" HTML validation completed successfully");
|
|
502
500
|
}
|
|
503
501
|
|
|
504
502
|
// Clean output directory if not in no-clean mode
|
|
@@ -644,6 +642,13 @@ impl Compiler {
|
|
|
644
642
|
}
|
|
645
643
|
|
|
646
644
|
for file_path in files {
|
|
645
|
+
// Validate HTML before compiling (same as full compile path)
|
|
646
|
+
let content = fs::read_to_string(file_path).map_err(|e| CompileError::ReadError {
|
|
647
|
+
path: file_path.display().to_string(),
|
|
648
|
+
source: e,
|
|
649
|
+
})?;
|
|
650
|
+
self.validate_html(&content, file_path)?;
|
|
651
|
+
|
|
647
652
|
// Calculate relative path
|
|
648
653
|
let relative_path = file_path
|
|
649
654
|
.strip_prefix(&self.config.source)
|
|
@@ -807,29 +812,19 @@ impl Compiler {
|
|
|
807
812
|
source_root: &Path,
|
|
808
813
|
) -> Result<(), String> {
|
|
809
814
|
use crate::compiler::manifest_builder::ManifestBuilder;
|
|
810
|
-
use crate::compiler::value_stamper::ValueStamper;
|
|
811
815
|
use crate::compiler::component_tagger::ComponentTagger;
|
|
816
|
+
use crate::compiler::value_stamper::ValueStamper;
|
|
812
817
|
|
|
813
818
|
// Tag components with deterministic IDs and structure state
|
|
814
819
|
let tagged = ComponentTagger::tag_components(html, &source_root.to_path_buf())?;
|
|
815
820
|
let html = &tagged.html; // Use modified HTML with data-vibe-component-id attributes
|
|
816
821
|
let state = tagged.state;
|
|
817
822
|
|
|
818
|
-
// Build manifest from
|
|
823
|
+
// Build manifest from pre-stamp HTML (both conditional branches present, @[...] markers intact)
|
|
824
|
+
// This ensures restoration.template has both branches so runtime can switch between them
|
|
819
825
|
let manifest_builder = ManifestBuilder::new();
|
|
820
826
|
let manifest = manifest_builder.build_from_html(html, &state, iterations_as_is)?;
|
|
821
827
|
|
|
822
|
-
// Stamp values (pre-render) AFTER building manifest
|
|
823
|
-
// When components_as_is is true, skip stamping inside component elements (runtime will handle them)
|
|
824
|
-
let stamper = ValueStamper::new(&state, components_as_is)?;
|
|
825
|
-
let pre_rendered = stamper.stamp_html(html.to_string())?;
|
|
826
|
-
|
|
827
|
-
// Note: FOUC removal happens in compile_html_file(), not here
|
|
828
|
-
|
|
829
|
-
// Write pre-rendered HTML
|
|
830
|
-
fs::write(html_path, pre_rendered)
|
|
831
|
-
.map_err(|e| format!("Failed to write HTML: {}", e))?;
|
|
832
|
-
|
|
833
828
|
// Write manifest
|
|
834
829
|
let manifest_path = output_dir
|
|
835
830
|
.join("vibe-hyperspeed")
|
|
@@ -853,6 +848,20 @@ impl Compiler {
|
|
|
853
848
|
fs::write(&manifest_path, manifest_js)
|
|
854
849
|
.map_err(|e| format!("Failed to write manifest: {}", e))?;
|
|
855
850
|
|
|
851
|
+
// Stamp the compiled HTML with initial state values for FOUC prevention:
|
|
852
|
+
// - @[...] bindings replaced with their initial values
|
|
853
|
+
// - Only the active conditional branch is kept (inactive branch stripped)
|
|
854
|
+
// - Iterations are pre-rendered with initial array data
|
|
855
|
+
// The manifest (written above) preserves BOTH branches so the runtime can
|
|
856
|
+
// restore them and switch between branches reactively.
|
|
857
|
+
let stamper = ValueStamper::new(&state, components_as_is)
|
|
858
|
+
.map_err(|e| format!("Failed to create value stamper: {}", e))?;
|
|
859
|
+
let stamped = stamper.stamp_html(html.to_string())
|
|
860
|
+
.map_err(|e| format!("Failed to stamp HTML: {}", e))?;
|
|
861
|
+
|
|
862
|
+
fs::write(html_path, stamped)
|
|
863
|
+
.map_err(|e| format!("Failed to write stamped HTML: {}", e))?;
|
|
864
|
+
|
|
856
865
|
Ok(())
|
|
857
866
|
}
|
|
858
867
|
|
|
@@ -1097,11 +1106,6 @@ impl Compiler {
|
|
|
1097
1106
|
source: e,
|
|
1098
1107
|
})?;
|
|
1099
1108
|
|
|
1100
|
-
// Validate if requested
|
|
1101
|
-
if self.config.validate {
|
|
1102
|
-
self.validate_html(&content, path)?;
|
|
1103
|
-
}
|
|
1104
|
-
|
|
1105
1109
|
// Count <component src="..."> instances before processing (only if not components_as_is)
|
|
1106
1110
|
let (internal_count, external_count, component_srcs) = if !self.config.components_as_is {
|
|
1107
1111
|
self.extract_components(&content)
|
|
@@ -1531,8 +1535,8 @@ impl Compiler {
|
|
|
1531
1535
|
}
|
|
1532
1536
|
|
|
1533
1537
|
fn validate_html(&self, content: &str, path: &Path) -> Result<(), CompileError> {
|
|
1538
|
+
// Check 1: Quote balance per line
|
|
1534
1539
|
let mut line_num = 1;
|
|
1535
|
-
|
|
1536
1540
|
for line in content.lines() {
|
|
1537
1541
|
let quote_count = line.matches('"').count();
|
|
1538
1542
|
if quote_count % 2 != 0 {
|
|
@@ -1545,9 +1549,77 @@ impl Compiler {
|
|
|
1545
1549
|
line_num += 1;
|
|
1546
1550
|
}
|
|
1547
1551
|
|
|
1552
|
+
// Check 2: Tag balance (unclosed elements break slot extraction)
|
|
1553
|
+
self.validate_tag_balance(content, path)
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
fn validate_tag_balance(&self, content: &str, path: &Path) -> Result<(), CompileError> {
|
|
1557
|
+
const VOID_ELEMENTS: &[&str] = &[
|
|
1558
|
+
"area", "base", "br", "col", "embed", "hr", "img", "input",
|
|
1559
|
+
"link", "meta", "param", "source", "track", "wbr",
|
|
1560
|
+
];
|
|
1561
|
+
// These are implicitly closed by the parser — don't require explicit close tags
|
|
1562
|
+
const IMPLICIT_CLOSE: &[&str] = &[
|
|
1563
|
+
"html", "head", "body", "p", "li", "dt", "dd", "option",
|
|
1564
|
+
"optgroup", "tr", "td", "th", "thead", "tbody", "tfoot", "colgroup",
|
|
1565
|
+
];
|
|
1566
|
+
|
|
1567
|
+
// Strip comments, raw content blocks, and @[...] bindings before tag scanning
|
|
1568
|
+
let stripped = Self::strip_for_tag_validation(content);
|
|
1569
|
+
|
|
1570
|
+
// Match any tag: opening, self-closing, or closing
|
|
1571
|
+
let tag_re = Regex::new(r"</?([a-zA-Z][a-zA-Z0-9-]*)(?:\s[^>]*)?>").unwrap();
|
|
1572
|
+
|
|
1573
|
+
// Count opens vs closes per tag name; track line of first open for error reporting
|
|
1574
|
+
let mut counts: HashMap<String, (i32, usize)> = HashMap::new();
|
|
1575
|
+
for cap in tag_re.captures_iter(&stripped) {
|
|
1576
|
+
let full = cap.get(0).unwrap().as_str();
|
|
1577
|
+
let name = cap[1].to_lowercase();
|
|
1578
|
+
if VOID_ELEMENTS.contains(&name.as_str()) || IMPLICIT_CLOSE.contains(&name.as_str()) {
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
if full.starts_with("</") {
|
|
1582
|
+
counts.entry(name).and_modify(|(c, _)| *c -= 1);
|
|
1583
|
+
} else if full.ends_with("/>") {
|
|
1584
|
+
// self-closing — no balance change
|
|
1585
|
+
} else {
|
|
1586
|
+
let offset = cap.get(0).unwrap().start();
|
|
1587
|
+
let line = stripped[..offset].matches('\n').count() + 1;
|
|
1588
|
+
let entry = counts.entry(name).or_insert((0, line));
|
|
1589
|
+
entry.0 += 1;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// Report the first unclosed tag (sorted by line for deterministic output)
|
|
1594
|
+
let mut unclosed: Vec<(String, usize)> = counts.into_iter()
|
|
1595
|
+
.filter(|(_, (count, _))| *count > 0)
|
|
1596
|
+
.map(|(name, (_, line))| (name, line))
|
|
1597
|
+
.collect();
|
|
1598
|
+
unclosed.sort_by_key(|(_, line)| *line);
|
|
1599
|
+
|
|
1600
|
+
if let Some((name, line)) = unclosed.first() {
|
|
1601
|
+
return Err(CompileError::ValidationError {
|
|
1602
|
+
file: path.display().to_string(),
|
|
1603
|
+
line: *line,
|
|
1604
|
+
message: format!("Unclosed tag <{}>", name),
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1548
1608
|
Ok(())
|
|
1549
1609
|
}
|
|
1550
1610
|
|
|
1611
|
+
fn strip_for_tag_validation(content: &str) -> String {
|
|
1612
|
+
// Strip HTML comments <!-- ... --> (includes Vibe syntax: each, if, else, /if, /each)
|
|
1613
|
+
let comment_re = Regex::new(r"(?s)<!--.*?-->").unwrap();
|
|
1614
|
+
let s = comment_re.replace_all(content, "");
|
|
1615
|
+
// Strip @[...] reactive bindings (can contain < and > characters)
|
|
1616
|
+
let binding_re = Regex::new(r"@\[[^\]]*\]").unwrap();
|
|
1617
|
+
let s = binding_re.replace_all(&s, "");
|
|
1618
|
+
// Strip content inside <script>, <style>, <pre> to avoid parsing embedded code as HTML
|
|
1619
|
+
let raw_re = Regex::new(r"(?si)(<(?:script|style|pre)(?:\s[^>]*)?>).*?(</(?:script|style|pre)>)").unwrap();
|
|
1620
|
+
raw_re.replace_all(&s, "$1$2").into_owned()
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1551
1623
|
/// Copy an entire directory recursively
|
|
1552
1624
|
fn copy_directory(
|
|
1553
1625
|
&mut self,
|
|
@@ -127,46 +127,55 @@ impl ComponentTagger {
|
|
|
127
127
|
if is_component || is_div_component {
|
|
128
128
|
drop(borrowed_attrs); // Release borrow before checking innerHTML
|
|
129
129
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
130
|
+
// Only extract state from DIRECT <script> children — not the full subtree.
|
|
131
|
+
// Using the full subtree caused the Layout wrapper (a <component> created by
|
|
132
|
+
// inline_component_elements that contains all nested component scripts) to be
|
|
133
|
+
// treated as a stateful component with the merged state of all its descendants.
|
|
134
|
+
// That made rewrite_this_to_component_id rewrite every @[this.xxx] in the page
|
|
135
|
+
// to @[_c0.xxx] before child components could claim their own bindings.
|
|
136
|
+
let mut direct_scripts_html = String::new();
|
|
133
137
|
for child in node.children.borrow().iter() {
|
|
134
|
-
let
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
if let NodeData::Element { name: ref child_name, .. } = child.data {
|
|
139
|
+
if child_name.local.as_ref() == "script" {
|
|
140
|
+
let mut script_bytes = Vec::new();
|
|
141
|
+
let _ = serialize(
|
|
142
|
+
&mut script_bytes,
|
|
143
|
+
&SerializableHandle::from(child.clone()),
|
|
144
|
+
SerializeOpts::default()
|
|
145
|
+
);
|
|
146
|
+
if let Ok(script_str) = String::from_utf8(script_bytes) {
|
|
147
|
+
direct_scripts_html.push_str(&script_str);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
139
151
|
}
|
|
140
152
|
|
|
141
|
-
if let Ok(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
component_states.borrow_mut().push((component_id, Value::Object(comp_state)));
|
|
169
|
-
}
|
|
153
|
+
if let Ok(Value::Object(comp_state)) = StateExtractor::extract_from_html(
|
|
154
|
+
&direct_scripts_html,
|
|
155
|
+
&PathBuf::from(".")
|
|
156
|
+
) {
|
|
157
|
+
// Only tag and register components that have state
|
|
158
|
+
if !comp_state.is_empty() {
|
|
159
|
+
let component_id = format!("_c{}", *counter.borrow());
|
|
160
|
+
*counter.borrow_mut() += 1;
|
|
161
|
+
|
|
162
|
+
// Add data-vibe-component-id attribute
|
|
163
|
+
let mut attrs_mut = attrs.borrow_mut();
|
|
164
|
+
attrs_mut.push(markup5ever::Attribute {
|
|
165
|
+
name: QualName::new(
|
|
166
|
+
None,
|
|
167
|
+
Namespace::from(""),
|
|
168
|
+
LocalName::from("data-vibe-component-id"),
|
|
169
|
+
),
|
|
170
|
+
value: component_id.clone().into(),
|
|
171
|
+
});
|
|
172
|
+
drop(attrs_mut);
|
|
173
|
+
|
|
174
|
+
// Rewrite this.property to componentId.property in the node's children
|
|
175
|
+
// This allows ValueStamper to properly evaluate component-scoped expressions
|
|
176
|
+
Self::rewrite_this_to_component_id(node, &component_id);
|
|
177
|
+
|
|
178
|
+
component_states.borrow_mut().push((component_id, Value::Object(comp_state)));
|
|
170
179
|
}
|
|
171
180
|
}
|
|
172
181
|
}
|
package/compiler/src/config.rs
CHANGED
|
@@ -31,8 +31,6 @@ pub struct VibeCompilerConfig {
|
|
|
31
31
|
#[serde(default)]
|
|
32
32
|
pub elements_as_is: bool,
|
|
33
33
|
#[serde(default)]
|
|
34
|
-
pub validate: bool,
|
|
35
|
-
#[serde(default)]
|
|
36
34
|
pub source_maps: bool,
|
|
37
35
|
#[serde(default)]
|
|
38
36
|
pub reserved_elements: Vec<String>,
|
|
@@ -96,7 +94,6 @@ impl Default for VibeCompilerConfig {
|
|
|
96
94
|
root: None,
|
|
97
95
|
minify: false,
|
|
98
96
|
elements_as_is: false,
|
|
99
|
-
validate: false,
|
|
100
97
|
source_maps: false,
|
|
101
98
|
reserved_elements: vec![],
|
|
102
99
|
node_modules_as_is: false,
|
|
@@ -121,7 +118,6 @@ pub struct Config {
|
|
|
121
118
|
pub _root: Option<String>,
|
|
122
119
|
pub minify: bool,
|
|
123
120
|
pub elements_as_is: bool,
|
|
124
|
-
pub validate: bool,
|
|
125
121
|
pub source_maps: bool,
|
|
126
122
|
pub reserved_elements: Vec<String>,
|
|
127
123
|
pub node_modules_as_is: bool,
|
|
@@ -169,7 +165,6 @@ impl Config {
|
|
|
169
165
|
_root: config.root,
|
|
170
166
|
minify: config.minify,
|
|
171
167
|
elements_as_is: config.elements_as_is,
|
|
172
|
-
validate: config.validate,
|
|
173
168
|
source_maps: config.source_maps,
|
|
174
169
|
reserved_elements,
|
|
175
170
|
node_modules_as_is: config.node_modules_as_is,
|
package/compiler/src/main.rs
CHANGED
|
@@ -14,7 +14,6 @@ use compiler::Compiler;
|
|
|
14
14
|
struct ConfigOverrides {
|
|
15
15
|
minify: bool,
|
|
16
16
|
elements_as_is: bool,
|
|
17
|
-
validate: bool,
|
|
18
17
|
source_maps: bool,
|
|
19
18
|
node_modules_as_is: bool,
|
|
20
19
|
components_as_is: bool,
|
|
@@ -53,10 +52,6 @@ struct Args {
|
|
|
53
52
|
#[arg(long, name = "source-maps")]
|
|
54
53
|
source_maps: bool,
|
|
55
54
|
|
|
56
|
-
/// Validate HTML syntax
|
|
57
|
-
#[arg(long)]
|
|
58
|
-
validate: bool,
|
|
59
|
-
|
|
60
55
|
/// Initialize configuration in package.json
|
|
61
56
|
#[arg(long)]
|
|
62
57
|
init: bool,
|
|
@@ -127,7 +122,6 @@ fn main() {
|
|
|
127
122
|
// Store original config values before applying flag overrides
|
|
128
123
|
let original_minify = config.minify;
|
|
129
124
|
let original_elements_as_is = config.elements_as_is;
|
|
130
|
-
let original_validate = config.validate;
|
|
131
125
|
let original_source_maps = config.source_maps;
|
|
132
126
|
let original_node_modules_as_is = config.node_modules_as_is;
|
|
133
127
|
let original_components_as_is = config.components_as_is;
|
|
@@ -145,10 +139,6 @@ fn main() {
|
|
|
145
139
|
overrides.elements_as_is = !original_elements_as_is;
|
|
146
140
|
config.elements_as_is = true;
|
|
147
141
|
}
|
|
148
|
-
if args.validate {
|
|
149
|
-
overrides.validate = !original_validate;
|
|
150
|
-
config.validate = true;
|
|
151
|
-
}
|
|
152
142
|
if args.source_maps {
|
|
153
143
|
overrides.source_maps = !original_source_maps;
|
|
154
144
|
config.source_maps = true;
|
|
@@ -229,7 +219,6 @@ fn main() {
|
|
|
229
219
|
println!(" {}: {}", format!("{:<16}", "runtimeAsIs").cyan(), format_bool_with_flag(config.runtime_as_is, overrides.runtime_as_is, original_runtime_as_is));
|
|
230
220
|
println!(" {}: {}", format!("{:<16}", "source").cyan(), format_value_no_flag(&config._source_str));
|
|
231
221
|
println!(" {}: {}", format!("{:<16}", "sourceMaps").cyan(), format_bool_with_flag(config.source_maps, overrides.source_maps, original_source_maps));
|
|
232
|
-
println!(" {}: {}", format!("{:<16}", "validate").cyan(), format_bool_with_flag(config.validate, overrides.validate, original_validate));
|
|
233
222
|
println!();
|
|
234
223
|
} else {
|
|
235
224
|
// Show version in non-verbose mode
|
|
@@ -255,6 +255,65 @@ impl HtmlParser {
|
|
|
255
255
|
/// Recursively inline all <component> elements with their HTML content
|
|
256
256
|
/// Inline a single component (one pass, no looping)
|
|
257
257
|
/// Used during recursive component fetching to resolve nested components
|
|
258
|
+
/// Find the start position of the matching close tag, handling nesting of the same tag.
|
|
259
|
+
/// `after_open`: byte position immediately after the `>` of the opening tag.
|
|
260
|
+
fn find_matching_close(content: &str, after_open: usize, tag_name: &str) -> Option<usize> {
|
|
261
|
+
// (?:\s|>) ensures <component> matches but not <component-foo> (hyphen is not \s or >)
|
|
262
|
+
let open_re = regex::Regex::new(&format!(r"<{}(?:\s|>|/>)", regex::escape(tag_name))).unwrap();
|
|
263
|
+
let close_re = regex::Regex::new(&format!(r"</{}>", regex::escape(tag_name))).unwrap();
|
|
264
|
+
|
|
265
|
+
let mut depth = 1i32;
|
|
266
|
+
let mut cursor = after_open;
|
|
267
|
+
|
|
268
|
+
while cursor < content.len() {
|
|
269
|
+
let slice = &content[cursor..];
|
|
270
|
+
let next_open = open_re.find(slice).map(|m| (m.start(), m.end()));
|
|
271
|
+
let next_close = close_re.find(slice).map(|m| (m.start(), m.end()));
|
|
272
|
+
|
|
273
|
+
match (next_open, next_close) {
|
|
274
|
+
(None, None) => return None,
|
|
275
|
+
(None, Some((cs, ce))) => {
|
|
276
|
+
depth -= 1;
|
|
277
|
+
if depth == 0 { return Some(cursor + cs); }
|
|
278
|
+
cursor += ce;
|
|
279
|
+
}
|
|
280
|
+
(Some((os, oe)), None) => {
|
|
281
|
+
// Check if self-closing by finding the end of this tag
|
|
282
|
+
if let Some(end) = slice[os..].find('>') {
|
|
283
|
+
if slice[os..os + end + 1].ends_with("/>") {
|
|
284
|
+
cursor += os + end + 1;
|
|
285
|
+
} else {
|
|
286
|
+
depth += 1;
|
|
287
|
+
cursor += oe;
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
return None;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
(Some((os, oe)), Some((cs, ce))) => {
|
|
294
|
+
if os < cs {
|
|
295
|
+
if let Some(end) = slice[os..].find('>') {
|
|
296
|
+
if slice[os..os + end + 1].ends_with("/>") {
|
|
297
|
+
cursor += os + end + 1;
|
|
298
|
+
} else {
|
|
299
|
+
depth += 1;
|
|
300
|
+
cursor += oe;
|
|
301
|
+
}
|
|
302
|
+
} else {
|
|
303
|
+
return None;
|
|
304
|
+
}
|
|
305
|
+
} else {
|
|
306
|
+
depth -= 1;
|
|
307
|
+
if depth == 0 { return Some(cursor + cs); }
|
|
308
|
+
cursor += ce;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
None
|
|
315
|
+
}
|
|
316
|
+
|
|
258
317
|
pub fn inline_single_component(&self, content: &str, component_src: &str, component_content: &str) -> String {
|
|
259
318
|
let mut result = content.to_string();
|
|
260
319
|
|
|
@@ -270,55 +329,53 @@ impl HtmlParser {
|
|
|
270
329
|
}
|
|
271
330
|
};
|
|
272
331
|
|
|
273
|
-
// Match
|
|
274
|
-
//
|
|
275
|
-
let pattern = format!(r#"
|
|
276
|
-
let
|
|
332
|
+
// Match only the opening tag — slot content is extracted via depth-counting close search
|
|
333
|
+
// to correctly handle slot content that contains </div> or nested <component> elements.
|
|
334
|
+
let pattern = format!(r#"<(component|div)\s+([^>]*)\bsrc="{}"\s*([^>]*)>"#, regex::escape(&normalized_src));
|
|
335
|
+
let open_re = regex::Regex::new(&pattern).unwrap();
|
|
277
336
|
|
|
278
|
-
let matches: Vec<_> =
|
|
279
|
-
let
|
|
337
|
+
let matches: Vec<_> = open_re.captures_iter(&result).filter_map(|cap| {
|
|
338
|
+
let open_tag = cap.get(0).unwrap();
|
|
280
339
|
let tag_name = cap.get(1).unwrap().as_str();
|
|
281
340
|
let attrs_before = cap.get(2).map(|m| m.as_str()).unwrap_or("");
|
|
282
341
|
let attrs_after = cap.get(3).map(|m| m.as_str()).unwrap_or("");
|
|
283
|
-
let slot_content = cap.get(4).map(|m| m.as_str().to_string());
|
|
284
342
|
|
|
285
343
|
// For <div>, verify it has class="component"
|
|
286
344
|
if tag_name == "div" {
|
|
287
345
|
let combined_attrs = format!("{} {}", attrs_before, attrs_after);
|
|
288
346
|
if !combined_attrs.contains("class=") || !combined_attrs.contains("component") {
|
|
289
|
-
return None;
|
|
347
|
+
return None;
|
|
290
348
|
}
|
|
291
349
|
}
|
|
292
350
|
|
|
293
|
-
|
|
351
|
+
let open_end = open_tag.end();
|
|
352
|
+
let close_start = Self::find_matching_close(&result, open_end, tag_name)?;
|
|
353
|
+
let close_end = close_start + format!("</{}>", tag_name).len();
|
|
354
|
+
let slot_content = result[open_end..close_start].to_string();
|
|
355
|
+
|
|
294
356
|
let attrs_str = format!("{} {}", attrs_before, attrs_after);
|
|
295
357
|
let props = Self::parse_props(&attrs_str);
|
|
296
358
|
|
|
297
|
-
Some((
|
|
298
|
-
}).
|
|
359
|
+
Some((open_tag.start(), close_end, props, slot_content))
|
|
360
|
+
}).collect();
|
|
361
|
+
|
|
362
|
+
// Replace from end to start (sorted descending by start position)
|
|
363
|
+
let mut sorted = matches;
|
|
364
|
+
sorted.sort_by(|a, b| b.0.cmp(&a.0));
|
|
299
365
|
|
|
300
|
-
|
|
301
|
-
for (start, end, props, slot_content) in matches.iter().rev() {
|
|
366
|
+
for (start, end, props, slot_content) in &sorted {
|
|
302
367
|
let mut replacement = component_content.to_string();
|
|
303
368
|
|
|
304
|
-
// Replace props
|
|
305
369
|
for (prop_name, prop_value) in props {
|
|
306
370
|
let prop_binding = format!("@[{}]", prop_name);
|
|
307
371
|
replacement = replacement.replace(&prop_binding, prop_value);
|
|
308
372
|
}
|
|
309
373
|
|
|
310
|
-
|
|
311
|
-
let slot_replacement = slot_content
|
|
312
|
-
.as_ref()
|
|
313
|
-
.filter(|s| !s.trim().is_empty())
|
|
314
|
-
.map(|s| s.as_str())
|
|
315
|
-
.unwrap_or("");
|
|
316
|
-
|
|
374
|
+
let slot_replacement = if slot_content.trim().is_empty() { "" } else { slot_content.as_str() };
|
|
317
375
|
replacement = replacement.replace("<slot></slot>", slot_replacement);
|
|
318
376
|
replacement = replacement.replace("<slot/>", slot_replacement);
|
|
319
377
|
replacement = replacement.replace("<slot />", slot_replacement);
|
|
320
378
|
|
|
321
|
-
// Keep the <component> wrapper
|
|
322
379
|
let wrapper = format!("<component>{}</component>", replacement);
|
|
323
380
|
result.replace_range(*start..*end, &wrapper);
|
|
324
381
|
}
|
|
@@ -329,73 +386,81 @@ impl HtmlParser {
|
|
|
329
386
|
fn inline_component_elements(&self, content: &str, external_cache: &HashMap<String, String>) -> String {
|
|
330
387
|
let mut result = content.to_string();
|
|
331
388
|
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
let component_re = regex::Regex::new(
|
|
337
|
-
r#"(?s)<(component|div)\s+([^>]*)\bsrc="([^"]+)"([^>]*)>(.*?)</(component|div)>"#
|
|
389
|
+
// Match only the opening component tag — slot content is extracted via depth-counting
|
|
390
|
+
// to correctly handle slot content containing </div> or nested <component> elements.
|
|
391
|
+
let open_re = regex::Regex::new(
|
|
392
|
+
r#"<(component|div)\s+([^>]*)\bsrc="([^"]+)"([^>]*)>"#
|
|
338
393
|
).unwrap();
|
|
339
394
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
let
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
let
|
|
351
|
-
|
|
352
|
-
|
|
395
|
+
// Process one match at a time with re-scanning after each replacement.
|
|
396
|
+
// This is required for nested components (e.g. Layout.html wrapping inner components):
|
|
397
|
+
// collecting all byte positions at once and applying them in reverse fails because
|
|
398
|
+
// replacing an inner component changes the string length, invalidating the outer
|
|
399
|
+
// component's end byte position and causing a char boundary panic.
|
|
400
|
+
loop {
|
|
401
|
+
let matches: Vec<_> = open_re.captures_iter(&result).filter_map(|cap| {
|
|
402
|
+
let open_tag = cap.get(0).unwrap();
|
|
403
|
+
let tag_name = cap.get(1).unwrap().as_str();
|
|
404
|
+
let attrs_before = cap.get(2).map(|m| m.as_str()).unwrap_or("");
|
|
405
|
+
let src = cap.get(3).unwrap().as_str();
|
|
406
|
+
let attrs_after = cap.get(4).map(|m| m.as_str()).unwrap_or("");
|
|
407
|
+
|
|
408
|
+
// For <div>, verify it has class="component"
|
|
409
|
+
if tag_name == "div" {
|
|
410
|
+
let combined_attrs = format!("{} {}", attrs_before, attrs_after);
|
|
411
|
+
if !combined_attrs.contains("class=") || !combined_attrs.contains("component") {
|
|
412
|
+
return None;
|
|
413
|
+
}
|
|
353
414
|
}
|
|
354
|
-
}
|
|
355
415
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
416
|
+
let open_end = open_tag.end();
|
|
417
|
+
let close_start = Self::find_matching_close(&result, open_end, tag_name)?;
|
|
418
|
+
let close_end = close_start + format!("</{}>", tag_name).len();
|
|
419
|
+
let slot_content = result[open_end..close_start].to_string();
|
|
359
420
|
|
|
360
|
-
|
|
361
|
-
|
|
421
|
+
let attrs_str = format!("{} {}", attrs_before, attrs_after);
|
|
422
|
+
let props = Self::parse_props(&attrs_str);
|
|
362
423
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
424
|
+
Some((open_tag.start(), close_end, src.to_string(), props, slot_content))
|
|
425
|
+
}).collect();
|
|
426
|
+
|
|
427
|
+
// Pick the rightmost match that has a cached component and apply it, then re-scan.
|
|
428
|
+
let mut sorted = matches;
|
|
429
|
+
sorted.sort_by(|a, b| b.0.cmp(&a.0));
|
|
430
|
+
|
|
431
|
+
let mut applied = false;
|
|
432
|
+
for (start, end, src, props, slot_content) in sorted {
|
|
433
|
+
let normalized_src = if src.starts_with("http://") || src.starts_with("https://") {
|
|
434
|
+
src.to_string()
|
|
372
435
|
} else {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
for (prop_name, prop_value) in props {
|
|
381
|
-
let prop_binding = format!("@[{}]", prop_name);
|
|
382
|
-
replacement = replacement.replace(&prop_binding, prop_value);
|
|
383
|
-
}
|
|
436
|
+
let without_prefix = src.trim_start_matches("./");
|
|
437
|
+
if without_prefix.starts_with('/') {
|
|
438
|
+
without_prefix.to_string()
|
|
439
|
+
} else {
|
|
440
|
+
format!("/{}", without_prefix)
|
|
441
|
+
}
|
|
442
|
+
};
|
|
384
443
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
444
|
+
if let Some(mut replacement) = external_cache.get(&normalized_src).cloned() {
|
|
445
|
+
for (prop_name, prop_value) in &props {
|
|
446
|
+
let prop_binding = format!("@[{}]", prop_name);
|
|
447
|
+
replacement = replacement.replace(&prop_binding, prop_value);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
let slot_replacement = if slot_content.trim().is_empty() { "" } else { slot_content.as_str() };
|
|
451
|
+
replacement = replacement.replace("<slot></slot>", slot_replacement);
|
|
452
|
+
replacement = replacement.replace("<slot/>", slot_replacement);
|
|
453
|
+
replacement = replacement.replace("<slot />", slot_replacement);
|
|
391
454
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
455
|
+
let wrapper = format!("<component>{}</component>", replacement);
|
|
456
|
+
result.replace_range(start..end, &wrapper);
|
|
457
|
+
applied = true;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
395
461
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
result.replace_range(*start..*end, &wrapper);
|
|
462
|
+
if !applied {
|
|
463
|
+
break;
|
|
399
464
|
}
|
|
400
465
|
}
|
|
401
466
|
|
package/package.json
CHANGED
package/runtime/component.js
CHANGED
|
@@ -60,8 +60,10 @@ export const processComponent = (rootElement, onComplete, config = {}) => {
|
|
|
60
60
|
|
|
61
61
|
const src = el.getAttribute('src');
|
|
62
62
|
|
|
63
|
-
//
|
|
64
|
-
|
|
63
|
+
// Use pre-hydration slot content if available (saved by index.js before hydration ran),
|
|
64
|
+
// otherwise fall back to current innerHTML (e.g. runtime-only usage without boot).
|
|
65
|
+
const children = (el._vibeSlotContent !== undefined ? el._vibeSlotContent : el.innerHTML).trim();
|
|
66
|
+
delete el._vibeSlotContent;
|
|
65
67
|
const props = {};
|
|
66
68
|
Array.from(el.attributes).forEach((attr) => {
|
|
67
69
|
if (attr.name !== 'src') {
|
package/runtime/index.js
CHANGED
|
@@ -454,6 +454,13 @@ const main = (s, config = {}, stringSelector = '') => {
|
|
|
454
454
|
restoreMarkersFromManifest(rootElement, cloneForRestoration, hyperspeedTree);
|
|
455
455
|
}
|
|
456
456
|
|
|
457
|
+
// Save raw slot content of fetched components before hydration replaces @[...] markers.
|
|
458
|
+
// component.js captures el.innerHTML when resolving — if hydration already ran, the
|
|
459
|
+
// binding syntax is gone and the resolved component won't be reactive.
|
|
460
|
+
rootElement.querySelectorAll('component[src], div.component[src]').forEach(el => {
|
|
461
|
+
el._vibeSlotContent = el.innerHTML;
|
|
462
|
+
});
|
|
463
|
+
|
|
457
464
|
// Runtime parses DOM (which now has restored markers if hyperspeed was used)
|
|
458
465
|
let parsedTree = parse(rootElement);
|
|
459
466
|
|
|
@@ -467,8 +467,12 @@ export const restoreMarkersFromManifest = (
|
|
|
467
467
|
|
|
468
468
|
if (text.startsWith("if")) {
|
|
469
469
|
if (!startComment) {
|
|
470
|
-
|
|
471
|
-
|
|
470
|
+
const expression = childTree.meta?.expression;
|
|
471
|
+
const expectedText = expression ? 'if ' + expression : null;
|
|
472
|
+
if (expectedText && text === expectedText) {
|
|
473
|
+
startComment = comment;
|
|
474
|
+
conditionalDepth = 1;
|
|
475
|
+
}
|
|
472
476
|
} else {
|
|
473
477
|
// Track nested conditionals
|
|
474
478
|
conditionalDepth++;
|