tungsten 0.3.2 → 0.3.3

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.
Files changed (278) hide show
  1. package/dist/.build-complete +1 -0
  2. package/dist/Framework.d.ts +2 -0
  3. package/dist/Framework.d.ts.map +1 -1
  4. package/dist/Framework.js +68 -57
  5. package/dist/FrameworkConfig.d.ts +2 -0
  6. package/dist/FrameworkConfig.d.ts.map +1 -1
  7. package/dist/Instance/Instance.d.ts.map +1 -1
  8. package/dist/Instance/Instance.js +18 -1
  9. package/dist/Instance/InstanceConfig.d.ts +1 -0
  10. package/dist/Instance/InstanceConfig.d.ts.map +1 -1
  11. package/dist/PopMessage.type.d.ts +6 -0
  12. package/dist/PopMessage.type.d.ts.map +1 -0
  13. package/dist/PopMessage.type.js +1 -0
  14. package/dist/Servers/DevServer.d.ts +2 -0
  15. package/dist/Servers/DevServer.d.ts.map +1 -1
  16. package/dist/Servers/DevServer.js +84 -8
  17. package/dist/Servers/MatchedRouteInfo.d.ts +1 -1
  18. package/dist/Servers/MatchedRouteInfo.d.ts.map +1 -1
  19. package/dist/Servers/ProdServer.d.ts +1 -0
  20. package/dist/Servers/ProdServer.d.ts.map +1 -1
  21. package/dist/Servers/ProdServer.js +79 -5
  22. package/dist/Servers/hono/DevServerHono.js +25 -29
  23. package/dist/Servers/hono/ProdServerHono.js +49 -142
  24. package/dist/Servers/hono/TungstenServerHono.d.ts +1 -1
  25. package/dist/Servers/hono/TungstenServerHono.d.ts.map +1 -1
  26. package/dist/Servers/hono/TungstenServerHono.js +12 -29
  27. package/dist/Servers/routing.d.ts.map +1 -1
  28. package/dist/Servers/routing.js +10 -6
  29. package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts +0 -3
  30. package/dist/Servers/tungstenCorePrivateEnvSchema.d.ts.map +1 -1
  31. package/dist/Servers/tungstenCorePrivateEnvSchema.js +3 -3
  32. package/dist/Servers/tungstenCorePublicEnvSchema.d.ts +8 -0
  33. package/dist/Servers/tungstenCorePublicEnvSchema.d.ts.map +1 -0
  34. package/dist/Servers/tungstenCorePublicEnvSchema.js +14 -0
  35. package/dist/Store/InternalStoreSchema.d.ts +2 -0
  36. package/dist/Store/InternalStoreSchema.d.ts.map +1 -1
  37. package/dist/Store/InternalStoreSchema.js +1 -0
  38. package/dist/api/Request.d.ts +1 -1
  39. package/dist/api/Request.d.ts.map +1 -1
  40. package/dist/api/Response.d.ts +1 -1
  41. package/dist/api/Response.d.ts.map +1 -1
  42. package/dist/babelPlugins/BabelTypes.d.ts +2 -2
  43. package/dist/babelPlugins/BabelTypes.d.ts.map +1 -1
  44. package/dist/babelPlugins/BabelTypes.js +0 -1
  45. package/dist/bin/commands/build.d.ts.map +1 -1
  46. package/dist/bin/commands/build.js +3 -0
  47. package/dist/bin/commands/dev.d.ts.map +1 -1
  48. package/dist/bin/commands/dev.js +15 -4
  49. package/dist/bin/commands/devmon.d.ts +2 -0
  50. package/dist/bin/commands/devmon.d.ts.map +1 -0
  51. package/dist/bin/commands/devmon.js +66 -0
  52. package/dist/bin/index.d.ts.map +1 -1
  53. package/dist/bin/index.js +6 -0
  54. package/dist/booleanAttributeValue.d.ts +21 -0
  55. package/dist/booleanAttributeValue.d.ts.map +1 -0
  56. package/dist/booleanAttributeValue.js +23 -0
  57. package/dist/build/compileJavascript.js +1 -1
  58. package/dist/build/compileUserApp.d.ts +2 -1
  59. package/dist/build/compileUserApp.d.ts.map +1 -1
  60. package/dist/build/compileUserApp.js +8 -7
  61. package/dist/build/createInstanceConfigFile.js +1 -1
  62. package/dist/compat/types.d.ts +1 -1
  63. package/dist/compat/types.d.ts.map +1 -1
  64. package/dist/components/PopMessages.d.ts +12 -0
  65. package/dist/components/PopMessages.d.ts.map +1 -0
  66. package/dist/components/PopMessages.js +45 -0
  67. package/dist/defaultConfig.d.ts +1 -0
  68. package/dist/defaultConfig.d.ts.map +1 -1
  69. package/dist/defaultConfig.js +2 -1
  70. package/dist/env/Interfaces.d.ts +2 -1
  71. package/dist/env/Interfaces.d.ts.map +1 -1
  72. package/dist/env/getEnvSchemas.d.ts +1 -1
  73. package/dist/env/getEnvSchemas.d.ts.map +1 -1
  74. package/dist/env/publicEnv.d.ts.map +1 -1
  75. package/dist/env/publicEnv.js +4 -1
  76. package/dist/frameworkConfigSchema.d.ts +4 -0
  77. package/dist/frameworkConfigSchema.d.ts.map +1 -1
  78. package/dist/frameworkConfigSchema.js +2 -1
  79. package/dist/hooks/index.d.ts +1 -0
  80. package/dist/hooks/index.d.ts.map +1 -1
  81. package/dist/hooks/index.js +1 -0
  82. package/dist/hooks/useInstance.d.ts +3 -0
  83. package/dist/hooks/useInstance.d.ts.map +1 -0
  84. package/dist/hooks/useInstance.js +4 -0
  85. package/dist/hydrate/hydrate.d.ts +6 -4
  86. package/dist/hydrate/hydrate.d.ts.map +1 -1
  87. package/dist/hydrate/hydrate.js +51 -13
  88. package/dist/hydrate/reconcileHydratedAttributes.d.ts.map +1 -1
  89. package/dist/hydrate/reconcileHydratedAttributes.js +4 -3
  90. package/dist/hydrate/renderHydrationMismatchBanner.d.ts +7 -0
  91. package/dist/hydrate/renderHydrationMismatchBanner.d.ts.map +1 -0
  92. package/dist/hydrate/renderHydrationMismatchBanner.js +69 -0
  93. package/dist/indexInner.d.ts +8 -0
  94. package/dist/indexInner.d.ts.map +1 -1
  95. package/dist/indexInner.js +7 -1
  96. package/dist/makeInstanceConfigFromFrameworkConfig.d.ts.map +1 -1
  97. package/dist/makeInstanceConfigFromFrameworkConfig.js +2 -1
  98. package/dist/pop.d.ts +10 -0
  99. package/dist/pop.d.ts.map +1 -0
  100. package/dist/pop.js +49 -0
  101. package/dist/rawHtml/filterContentNode.d.ts +20 -0
  102. package/dist/rawHtml/filterContentNode.d.ts.map +1 -0
  103. package/dist/rawHtml/filterContentNode.js +96 -0
  104. package/dist/rawHtml/parseHtml.d.ts +14 -0
  105. package/dist/rawHtml/parseHtml.d.ts.map +1 -1
  106. package/dist/rawHtml/parseHtml.js +4 -4
  107. package/dist/rawHtml/sanitizeHtml.d.ts +17 -0
  108. package/dist/rawHtml/sanitizeHtml.d.ts.map +1 -0
  109. package/dist/rawHtml/sanitizeHtml.js +19 -0
  110. package/dist/render/HeadData.d.ts +1 -0
  111. package/dist/render/HeadData.d.ts.map +1 -1
  112. package/dist/render/contentNodeToHtml.d.ts +18 -0
  113. package/dist/render/contentNodeToHtml.d.ts.map +1 -0
  114. package/dist/render/contentNodeToHtml.js +66 -0
  115. package/dist/render/contentToHtml.d.ts.map +1 -1
  116. package/dist/render/contentToHtml.js +2 -1
  117. package/dist/render/processHead.d.ts.map +1 -1
  118. package/dist/render/processHead.js +8 -2
  119. package/dist/render/renderingTreeToHtml.d.ts.map +1 -1
  120. package/dist/render/renderingTreeToHtml.js +8 -1
  121. package/dist/routing/ErrorRouteType.d.ts +6 -0
  122. package/dist/routing/ErrorRouteType.d.ts.map +1 -0
  123. package/dist/routing/ErrorRouteType.js +1 -0
  124. package/dist/routing/RouteTypes.d.ts +2 -1
  125. package/dist/routing/RouteTypes.d.ts.map +1 -1
  126. package/dist/routing/Routes.d.ts +5 -0
  127. package/dist/routing/Routes.d.ts.map +1 -1
  128. package/dist/routing/TungstenErrorInfo.d.ts +6 -0
  129. package/dist/routing/TungstenErrorInfo.d.ts.map +1 -0
  130. package/dist/routing/TungstenErrorInfo.js +6 -0
  131. package/dist/style/styleContentSxy.js +21 -0
  132. package/dist/testing/hydrateTestApp.d.ts +38 -0
  133. package/dist/testing/hydrateTestApp.d.ts.map +1 -0
  134. package/dist/testing/hydrateTestApp.js +242 -0
  135. package/dist/testing/index.d.ts +4 -3
  136. package/dist/testing/index.d.ts.map +1 -1
  137. package/dist/testing/index.js +16 -2
  138. package/dist/timeProfiler.d.ts +6 -0
  139. package/dist/timeProfiler.d.ts.map +1 -0
  140. package/dist/timeProfiler.js +31 -0
  141. package/dist/update/integrateUpdateVdom.d.ts +11 -0
  142. package/dist/update/integrateUpdateVdom.d.ts.map +1 -1
  143. package/dist/update/integrateUpdateVdom.js +9 -3
  144. package/dist/warnOnFrameworkChange.d.ts.map +1 -1
  145. package/dist/warnOnFrameworkChange.js +8 -8
  146. package/package.json +18 -16
  147. package/dist/FunctionComponentStandIn.sxyt.d.ts +0 -2
  148. package/dist/FunctionComponentStandIn.sxyt.d.ts.map +0 -1
  149. package/dist/FunctionComponentStandIn.sxyt.js +0 -52
  150. package/dist/Instance/gotoRedirect.sxyt.d.ts +0 -2
  151. package/dist/Instance/gotoRedirect.sxyt.d.ts.map +0 -1
  152. package/dist/Instance/gotoRedirect.sxyt.js +0 -148
  153. package/dist/Instance/runTrackedWork.sxyt.d.ts +0 -2
  154. package/dist/Instance/runTrackedWork.sxyt.d.ts.map +0 -1
  155. package/dist/Instance/runTrackedWork.sxyt.js +0 -35
  156. package/dist/Servers/devErrorPageAutoRetry.sxyt.d.ts +0 -2
  157. package/dist/Servers/devErrorPageAutoRetry.sxyt.d.ts.map +0 -1
  158. package/dist/Servers/devErrorPageAutoRetry.sxyt.js +0 -117
  159. package/dist/_client/cjsHelpers.sxyt.d.ts +0 -2
  160. package/dist/_client/cjsHelpers.sxyt.d.ts.map +0 -1
  161. package/dist/_client/cjsHelpers.sxyt.js +0 -45
  162. package/dist/auth/passwordHashing.sxyt.d.ts +0 -2
  163. package/dist/auth/passwordHashing.sxyt.d.ts.map +0 -1
  164. package/dist/auth/passwordHashing.sxyt.js +0 -46
  165. package/dist/auth/ref/checkPasswordResetToken.sxyt.d.ts +0 -2
  166. package/dist/auth/ref/checkPasswordResetToken.sxyt.d.ts.map +0 -1
  167. package/dist/auth/ref/checkPasswordResetToken.sxyt.js +0 -40
  168. package/dist/auth/ref/createPasswordResetToken.sxyt.d.ts +0 -2
  169. package/dist/auth/ref/createPasswordResetToken.sxyt.d.ts.map +0 -1
  170. package/dist/auth/ref/createPasswordResetToken.sxyt.js +0 -10
  171. package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.d.ts +0 -2
  172. package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.d.ts.map +0 -1
  173. package/dist/babelPlugins/BabelAddDevCacheBust.sxyt.js +0 -138
  174. package/dist/babelPlugins/fixImportsInSassErrorMessage.sxyt.d.ts +0 -2
  175. package/dist/babelPlugins/fixImportsInSassErrorMessage.sxyt.d.ts.map +0 -1
  176. package/dist/babelPlugins/fixImportsInSassErrorMessage.sxyt.js +0 -108
  177. package/dist/babelPlugins/transformPackageImports/resolveBareImport.sxyt.d.ts +0 -2
  178. package/dist/babelPlugins/transformPackageImports/resolveBareImport.sxyt.d.ts.map +0 -1
  179. package/dist/babelPlugins/transformPackageImports/resolveBareImport.sxyt.js +0 -104
  180. package/dist/bin/commands/compile.sxyt.d.ts +0 -2
  181. package/dist/bin/commands/compile.sxyt.d.ts.map +0 -1
  182. package/dist/bin/commands/compile.sxyt.js +0 -255
  183. package/dist/build/modifySassImports.sxyt.d.ts +0 -2
  184. package/dist/build/modifySassImports.sxyt.d.ts.map +0 -1
  185. package/dist/build/modifySassImports.sxyt.js +0 -55
  186. package/dist/build/sassImportParser.sxyt.d.ts +0 -2
  187. package/dist/build/sassImportParser.sxyt.d.ts.map +0 -1
  188. package/dist/build/sassImportParser.sxyt.js +0 -110
  189. package/dist/build/scssImportParserChatgpt.sxyt.d.ts +0 -2
  190. package/dist/build/scssImportParserChatgpt.sxyt.d.ts.map +0 -1
  191. package/dist/build/scssImportParserChatgpt.sxyt.js +0 -120
  192. package/dist/clone.sxyt.d.ts +0 -2
  193. package/dist/clone.sxyt.d.ts.map +0 -1
  194. package/dist/clone.sxyt.js +0 -24
  195. package/dist/componentProxyHandlers.sxyt.d.ts +0 -2
  196. package/dist/componentProxyHandlers.sxyt.d.ts.map +0 -1
  197. package/dist/componentProxyHandlers.sxyt.js +0 -130
  198. package/dist/components/RawHtml.sxyt.d.ts +0 -2
  199. package/dist/components/RawHtml.sxyt.d.ts.map +0 -1
  200. package/dist/components/RawHtml.sxyt.js +0 -104
  201. package/dist/components/bindable/BindableSelect.sxyt.d.ts +0 -2
  202. package/dist/components/bindable/BindableSelect.sxyt.d.ts.map +0 -1
  203. package/dist/components/bindable/BindableSelect.sxyt.js +0 -186
  204. package/dist/env/injectEnv.sxyt.d.ts +0 -2
  205. package/dist/env/injectEnv.sxyt.d.ts.map +0 -1
  206. package/dist/env/injectEnv.sxyt.js +0 -20
  207. package/dist/env/parseEnv.sxyt.d.ts +0 -2
  208. package/dist/env/parseEnv.sxyt.d.ts.map +0 -1
  209. package/dist/env/parseEnv.sxyt.js +0 -11
  210. package/dist/findProjectDir.sxyt.d.ts +0 -2
  211. package/dist/findProjectDir.sxyt.d.ts.map +0 -1
  212. package/dist/findProjectDir.sxyt.js +0 -82
  213. package/dist/hmr/applyHmrModule.sxyt.d.ts +0 -2
  214. package/dist/hmr/applyHmrModule.sxyt.d.ts.map +0 -1
  215. package/dist/hmr/applyHmrModule.sxyt.js +0 -182
  216. package/dist/hmr/instanceRegistry.sxyt.d.ts +0 -2
  217. package/dist/hmr/instanceRegistry.sxyt.d.ts.map +0 -1
  218. package/dist/hmr/instanceRegistry.sxyt.js +0 -24
  219. package/dist/hmr/refreshStylesheets.sxyt.d.ts +0 -2
  220. package/dist/hmr/refreshStylesheets.sxyt.d.ts.map +0 -1
  221. package/dist/hmr/refreshStylesheets.sxyt.js +0 -46
  222. package/dist/hydrate/hydrate.sxyt.d.ts +0 -2
  223. package/dist/hydrate/hydrate.sxyt.d.ts.map +0 -1
  224. package/dist/hydrate/hydrate.sxyt.js +0 -302
  225. package/dist/hydrate/reconcileHydratedAttributes.sxyt.d.ts +0 -2
  226. package/dist/hydrate/reconcileHydratedAttributes.sxyt.d.ts.map +0 -1
  227. package/dist/hydrate/reconcileHydratedAttributes.sxyt.js +0 -190
  228. package/dist/importDevModuleWithRetry.sxyt.d.ts +0 -2
  229. package/dist/importDevModuleWithRetry.sxyt.d.ts.map +0 -1
  230. package/dist/importDevModuleWithRetry.sxyt.js +0 -104
  231. package/dist/innerHtmlFromProps.sxyt.d.ts +0 -2
  232. package/dist/innerHtmlFromProps.sxyt.d.ts.map +0 -1
  233. package/dist/innerHtmlFromProps.sxyt.js +0 -99
  234. package/dist/postcssPlugins/parseSelectors.sxyt.d.ts +0 -2
  235. package/dist/postcssPlugins/parseSelectors.sxyt.d.ts.map +0 -1
  236. package/dist/postcssPlugins/parseSelectors.sxyt.js +0 -62
  237. package/dist/postcssPlugins/parseSelectorsRich.sxyt.d.ts +0 -2
  238. package/dist/postcssPlugins/parseSelectorsRich.sxyt.d.ts.map +0 -1
  239. package/dist/postcssPlugins/parseSelectorsRich.sxyt.js +0 -155
  240. package/dist/rawHtml/parseHtml.sxyt.d.ts +0 -2
  241. package/dist/rawHtml/parseHtml.sxyt.d.ts.map +0 -1
  242. package/dist/rawHtml/parseHtml.sxyt.js +0 -239
  243. package/dist/render/getFunctionBodyAsString.sxyt.d.ts +0 -2
  244. package/dist/render/getFunctionBodyAsString.sxyt.d.ts.map +0 -1
  245. package/dist/render/getFunctionBodyAsString.sxyt.js +0 -37
  246. package/dist/render/processHead.sxyt.d.ts +0 -2
  247. package/dist/render/processHead.sxyt.d.ts.map +0 -1
  248. package/dist/render/processHead.sxyt.js +0 -93
  249. package/dist/render/processProps.sxyt.d.ts +0 -2
  250. package/dist/render/processProps.sxyt.d.ts.map +0 -1
  251. package/dist/render/processProps.sxyt.js +0 -90
  252. package/dist/routing/compareRequestToRegex.sxyt.d.ts +0 -2
  253. package/dist/routing/compareRequestToRegex.sxyt.d.ts.map +0 -1
  254. package/dist/routing/compareRequestToRegex.sxyt.js +0 -65
  255. package/dist/routing/getPathPriority.sxyt.d.ts +0 -2
  256. package/dist/routing/getPathPriority.sxyt.d.ts.map +0 -1
  257. package/dist/routing/getPathPriority.sxyt.js +0 -24
  258. package/dist/routing/matchPath.sxyt.d.ts +0 -2
  259. package/dist/routing/matchPath.sxyt.d.ts.map +0 -1
  260. package/dist/routing/matchPath.sxyt.js +0 -41
  261. package/dist/routing/patternToRegex.sxyt.d.ts +0 -2
  262. package/dist/routing/patternToRegex.sxyt.d.ts.map +0 -1
  263. package/dist/routing/patternToRegex.sxyt.js +0 -69
  264. package/dist/routing/splitRequest.sxyt.d.ts +0 -2
  265. package/dist/routing/splitRequest.sxyt.d.ts.map +0 -1
  266. package/dist/routing/splitRequest.sxyt.js +0 -47
  267. package/dist/style/jssToCss.sxyt.d.ts +0 -2
  268. package/dist/style/jssToCss.sxyt.d.ts.map +0 -1
  269. package/dist/style/jssToCss.sxyt.js +0 -17
  270. package/dist/testing/createTestApp.sxyt.d.ts +0 -2
  271. package/dist/testing/createTestApp.sxyt.d.ts.map +0 -1
  272. package/dist/testing/createTestApp.sxyt.js +0 -470
  273. package/dist/testing/ensureTestEnvLoaded.sxyt.d.ts +0 -2
  274. package/dist/testing/ensureTestEnvLoaded.sxyt.d.ts.map +0 -1
  275. package/dist/testing/ensureTestEnvLoaded.sxyt.js +0 -20
  276. package/dist/update/hotSwap.sxyt.d.ts +0 -2
  277. package/dist/update/hotSwap.sxyt.d.ts.map +0 -1
  278. package/dist/update/hotSwap.sxyt.js +0 -252
@@ -0,0 +1,96 @@
1
+ import { ContentNodeIsComponent } from "../ContentNode.js";
2
+ import { $props, $type } from "../contentNodeSymbols.js";
3
+ import { StringIsHtmlEvent } from "../htmlEvents.js";
4
+ import { blockedUrl, makeAllowCheck, removalFor, urlAttributes } from "./parseHtml.js";
5
+ // The NodeProps equivalent of parseHtml's class->className, style->css
6
+ // rename: the allow list is written in terms of the HTML attribute name, but
7
+ // a content tree already carries Tungsten's prop names for these two.
8
+ const attributeCheckName = {
9
+ className: 'class',
10
+ css: 'style'
11
+ };
12
+ function filterProps(tag, props, allow) {
13
+ const filtered = {};
14
+ for (const key in props) {
15
+ if (key === 'children') continue;
16
+ const value = props[key];
17
+ // Tungsten reads these as event listeners, not attributes - same rule
18
+ // parseHtml applies to on* HTML attributes, expressed for prop names.
19
+ if (StringIsHtmlEvent(key)) continue;
20
+ // Raw markup that bypasses tree filtering entirely: parseHtml never
21
+ // produces this prop, so a tree in its shape should not carry it either.
22
+ if (key === 'dangerouslySetInnerHTML') continue;
23
+ if (key === 'ref') continue;
24
+ const checkName = (attributeCheckName[key] ?? key).toLowerCase();
25
+ if (!allow.attribute(tag, checkName)) continue;
26
+ if (checkName in urlAttributes && typeof value === 'string' && blockedUrl(checkName, value)) continue;
27
+ filtered[key] = value;
28
+ }
29
+ return filtered;
30
+ }
31
+ // Matches toContentNode() in parseHtml.ts: zero children omits the prop,
32
+ // one is the value directly (not wrapped in an array), two or more is an
33
+ // array. Without this, an element that loses every child to filtering ends
34
+ // up with an empty-array `children` instead of no `children` prop at all -
35
+ // a shape parseHtml itself would never produce.
36
+ function normalizeChildren(children) {
37
+ if (!Array.isArray(children)) return children;
38
+ if (children.length === 0) return null;
39
+ if (children.length === 1) return children[0] ?? null;
40
+ return children;
41
+ }
42
+ function filter(node, allow) {
43
+ if (node === undefined || node === null || node === true || node === false) return node;
44
+ if (typeof node === 'string' || typeof node === 'number') return node;
45
+ if (Array.isArray(node)) {
46
+ const out = [];
47
+ for (const child of node) {
48
+ const filteredChild = filter(child, allow);
49
+ // Non-rendered values, whether the child already was one or
50
+ // filtering made it one (an unwrapped or dropped element that
51
+ // left nothing behind), contribute nothing to the array - never
52
+ // a stray null placeholder in a dropped element's place.
53
+ if (filteredChild === undefined || filteredChild === null || filteredChild === true || filteredChild === false) continue;
54
+ if (Array.isArray(filteredChild)) out.push(...filteredChild);else out.push(filteredChild);
55
+ }
56
+ return out;
57
+ }
58
+ // Not authored markup - parseHtml never produces a component node, so a
59
+ // tree in its shape should not carry one either, but this is defensive
60
+ // rather than reachable: nothing that round-trips through storage (JSON)
61
+ // can carry a class or function reference.
62
+ if (ContentNodeIsComponent(node)) return node;
63
+ const tag = node[$type];
64
+ const removal = removalFor(tag, true, allow);
65
+ if (removal === 'drop') return null;
66
+ const children = normalizeChildren(filter(node[$props].children ?? null, allow));
67
+ if (removal === 'unwrap') return children ?? [];
68
+ return {
69
+ [$type]: tag,
70
+ [$props]: {
71
+ ...filterProps(tag, node[$props], allow),
72
+ ...(children === null ? {} : {
73
+ children
74
+ })
75
+ }
76
+ };
77
+ }
78
+ /**
79
+ * Applies an allow list to a content tree that already exists, rather than
80
+ * one being built - the same decisions `parseHtml(html, { allowList })` makes
81
+ * while parsing, for a tree that did not come from parsing HTML at all (one
82
+ * rebuilt from storage, for instance) and so never had the chance to.
83
+ *
84
+ * A disallowed element is unwrapped, keeping its children in its place, so
85
+ * filtering a tag never costs the reader the words inside it - the same rule
86
+ * `parseHtml` applies. An element holding program text rather than prose
87
+ * (`<script>`, and anything on the same footing as `<script>` even under a
88
+ * permissive allow list) is dropped whole instead. `on*` props, `ref`, and
89
+ * `dangerouslySetInnerHTML` are always stripped - the tree-shape equivalent
90
+ * of what `parseHtml` refuses to produce in the first place - and a `href`/
91
+ * `src`/... value using an executable scheme (`javascript:`, `vbscript:`) or,
92
+ * on a navigating attribute, `data:` is dropped the same way.
93
+ */
94
+ export function filterContentNode(node, allowList) {
95
+ return filter(node, makeAllowCheck(allowList));
96
+ }
@@ -18,6 +18,20 @@ export type ParseHtmlOptions = {
18
18
  */
19
19
  allowList?: HtmlAllowList | undefined;
20
20
  };
21
+ export type AllowCheck = {
22
+ tag: (name: string) => boolean;
23
+ attribute: (tag: string, name: string) => boolean;
24
+ };
25
+ export declare function makeAllowCheck(allowList: HtmlAllowList | undefined): AllowCheck;
26
+ export declare const urlAttributes: Record<string, true>;
27
+ export declare function blockedUrl(attributeName: string, value: string): boolean;
28
+ /**
29
+ * Whether an element survives, and if not, how it goes.
30
+ *
31
+ * Elements holding program text rather than prose are dropped whole — there is
32
+ * no reader's content inside them to preserve. Everything else is unwrapped.
33
+ */
34
+ export declare function removalFor(name: string, sanitize: boolean, allow: AllowCheck): 'drop' | 'unwrap' | undefined;
21
35
  /**
22
36
  * Parses raw HTML into a Tungsten content tree.
23
37
  *
@@ -1 +1 @@
1
- {"version":3,"file":"parseHtml.d.ts","sourceRoot":"","sources":["../../src/rawHtml/parseHtml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAA8B,MAAM,mBAAmB,CAAA;AAKhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,MAAM,MAAM,gBAAgB,GAAG;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;CACxC,CAAA;AAsID;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,WAAW,CAqGnF"}
1
+ {"version":3,"file":"parseHtml.d.ts","sourceRoot":"","sources":["../../src/rawHtml/parseHtml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAA8B,MAAM,mBAAmB,CAAA;AAKhF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAEvD,MAAM,MAAM,gBAAgB,GAAG;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;CACxC,CAAA;AAED,MAAM,MAAM,UAAU,GAAG;IACrB,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9B,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;CACpD,CAAA;AAID,wBAAgB,cAAc,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,GAAG,UAAU,CAe/E;AAiBD,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAE9C,CAAA;AA2BD,wBAAgB,UAAU,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAOxE;AAgCD;;;;;GAKG;AACH,wBAAgB,UAAU,CACtB,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,GACnD,MAAM,GAAG,QAAQ,GAAG,SAAS,CAK/B;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,WAAW,CAqGnF"}
@@ -6,7 +6,7 @@ const allowEverything = {
6
6
  tag: () => true,
7
7
  attribute: () => true
8
8
  };
9
- function makeAllowCheck(allowList) {
9
+ export function makeAllowCheck(allowList) {
10
10
  if (allowList === undefined) return allowEverything;
11
11
  const tags = new Set(allowList.tags);
12
12
  const attributes = new Set(allowList.attributes);
@@ -21,7 +21,7 @@ function makeAllowCheck(allowList) {
21
21
  }
22
22
  // REGEX: on followed by at least one letter, e.g. onclick, onerror
23
23
  const eventAttribute = /^on[a-z]/i;
24
- const urlAttributes = {
24
+ export const urlAttributes = {
25
25
  href: true,
26
26
  src: true,
27
27
  action: true,
@@ -56,7 +56,7 @@ function urlScheme(url) {
56
56
  }
57
57
  return '';
58
58
  }
59
- function blockedUrl(attributeName, value) {
59
+ export function blockedUrl(attributeName, value) {
60
60
  const scheme = urlScheme(value);
61
61
  if (scheme in executableSchemes) return true;
62
62
  // data: URLs can carry a whole HTML document, so they are barred from
@@ -87,7 +87,7 @@ function attributesToProps(tag, attributes, sanitize, allow) {
87
87
  * Elements holding program text rather than prose are dropped whole — there is
88
88
  * no reader's content inside them to preserve. Everything else is unwrapped.
89
89
  */
90
- function removalFor(name, sanitize, allow) {
90
+ export function removalFor(name, sanitize, allow) {
91
91
  if (sanitize && name === 'script') return 'drop';
92
92
  if (allow.tag(name)) return undefined;
93
93
  return name in htmlRawTextTags ? 'drop' : 'unwrap';
@@ -0,0 +1,17 @@
1
+ import type { HtmlAllowList } from './HtmlAllowList.js';
2
+ export type SanitizeHtmlOptions = {
3
+ /** Defaults to `safeHtmlAllowList`. Pass your own list to allow more or less. */
4
+ allowList?: HtmlAllowList;
5
+ };
6
+ /**
7
+ * Cleans untrusted HTML and returns it as a string, synchronously.
8
+ *
9
+ * `parseHtml()` already does the cleaning - this only adds the return trip,
10
+ * for a caller that needs the result as a string rather than a content tree:
11
+ * a paste handler cleaning a clipboard payload before it is inserted, or an
12
+ * editor cleaning its content before it is stored. Both happen at a rate
13
+ * `renderingNodeToHtml` (the async, component-aware server renderer) is the
14
+ * wrong shape for.
15
+ */
16
+ export declare function sanitizeHtml(html: string, options?: SanitizeHtmlOptions): string;
17
+ //# sourceMappingURL=sanitizeHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeHtml.d.ts","sourceRoot":"","sources":["../../src/rawHtml/sanitizeHtml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAIvD,MAAM,MAAM,mBAAmB,GAAG;IAC9B,iFAAiF;IACjF,SAAS,CAAC,EAAE,aAAa,CAAA;CAC5B,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB,GAAG,MAAM,CAGpF"}
@@ -0,0 +1,19 @@
1
+ import { contentNodeToHtml } from "../render/contentNodeToHtml.js";
2
+ import { parseHtml } from "./parseHtml.js";
3
+ import { safeHtmlAllowList } from "./safeHtmlAllowList.js";
4
+ /**
5
+ * Cleans untrusted HTML and returns it as a string, synchronously.
6
+ *
7
+ * `parseHtml()` already does the cleaning - this only adds the return trip,
8
+ * for a caller that needs the result as a string rather than a content tree:
9
+ * a paste handler cleaning a clipboard payload before it is inserted, or an
10
+ * editor cleaning its content before it is stored. Both happen at a rate
11
+ * `renderingNodeToHtml` (the async, component-aware server renderer) is the
12
+ * wrong shape for.
13
+ */
14
+ export function sanitizeHtml(html, options = {}) {
15
+ const allowList = options.allowList ?? safeHtmlAllowList;
16
+ return contentNodeToHtml(parseHtml(html, {
17
+ allowList
18
+ }));
19
+ }
@@ -12,5 +12,6 @@ export type HeadData = {
12
12
  tungstenPublicEnv: TungstenEnvOrUndefined;
13
13
  siteBasePath: SiteBasePath;
14
14
  hmr: boolean | 'reload';
15
+ csr: boolean;
15
16
  };
16
17
  //# sourceMappingURL=HeadData.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"HeadData.d.ts","sourceRoot":"","sources":["../../src/render/HeadData.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAE9D,MAAM,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,MAAM,EAAE,CAAA;AAE5C,MAAM,MAAM,QAAQ,GAAG;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,mBAAmB,EAAE,aAAa,CAAC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,YAAY,EAAE,YAAY,CAAC;IAC3B,GAAG,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC1B,CAAA"}
1
+ {"version":3,"file":"HeadData.d.ts","sourceRoot":"","sources":["../../src/render/HeadData.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAE9D,MAAM,MAAM,YAAY,GAAG,EAAE,GAAG,IAAI,MAAM,EAAE,CAAA;AAE5C,MAAM,MAAM,QAAQ,GAAG;IACnB,KAAK,EAAE,OAAO,CAAA;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,mBAAmB,EAAE,aAAa,CAAC;IACnC,aAAa,EAAE,aAAa,CAAC;IAC7B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,OAAO,CAAC;IACb,iBAAiB,EAAE,sBAAsB,CAAC;IAC1C,YAAY,EAAE,YAAY,CAAC;IAC3B,GAAG,EAAE,OAAO,GAAG,QAAQ,CAAA;IACvB,GAAG,EAAE,OAAO,CAAA;CACf,CAAA"}
@@ -0,0 +1,18 @@
1
+ import type { ContentNode } from '../ContentNode.js';
2
+ /**
3
+ * Serialises a content tree to HTML, synchronously.
4
+ *
5
+ * `renderingNodeToHtml` (the server's real renderer) covers the general case -
6
+ * components, async data, `<head>` - and is async and internal because of it.
7
+ * This covers the subset that never needs any of that: plain HTML elements,
8
+ * text and arrays, the exact shape `parseHtml()` produces. Given a component
9
+ * node it throws rather than silently rendering nothing, since a caller
10
+ * reaching for the synchronous path has nothing that could resolve one.
11
+ *
12
+ * Attribute values are escaped, unlike `renderingNodeToHtml`'s - that renderer
13
+ * trusts values from application-authored JSX; this one exists specifically to
14
+ * round-trip untrusted markup back out as a string, so a value carrying `"`
15
+ * must not be able to break out of its attribute.
16
+ */
17
+ export declare function contentNodeToHtml(node: ContentNode): string;
18
+ //# sourceMappingURL=contentNodeToHtml.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contentNodeToHtml.d.ts","sourceRoot":"","sources":["../../src/render/contentNodeToHtml.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAapD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CA+C3D"}
@@ -0,0 +1,66 @@
1
+ import { ContentNodeIsComponent } from "../ContentNode.js";
2
+ import { booleanAttributeValue } from "../booleanAttributeValue.js";
3
+ import { $props, $type } from "../contentNodeSymbols.js";
4
+ import { htmlEntities } from "../htmlEntities.js";
5
+ import { innerHtmlFromProps } from "../innerHtmlFromProps.js";
6
+ import { throwError } from "../throwError.js";
7
+ import { validChildren } from "../validChildren.js";
8
+ import { htmlMonoTags } from "./htmlMonoTags.js";
9
+ import { htmlSelfClosableTags } from "./htmlSelfClosableTags.js";
10
+ import { htmlVoidTags } from "./htmlVoidTags.js";
11
+ import { processProps } from "./processProps.js";
12
+ /**
13
+ * Serialises a content tree to HTML, synchronously.
14
+ *
15
+ * `renderingNodeToHtml` (the server's real renderer) covers the general case -
16
+ * components, async data, `<head>` - and is async and internal because of it.
17
+ * This covers the subset that never needs any of that: plain HTML elements,
18
+ * text and arrays, the exact shape `parseHtml()` produces. Given a component
19
+ * node it throws rather than silently rendering nothing, since a caller
20
+ * reaching for the synchronous path has nothing that could resolve one.
21
+ *
22
+ * Attribute values are escaped, unlike `renderingNodeToHtml`'s - that renderer
23
+ * trusts values from application-authored JSX; this one exists specifically to
24
+ * round-trip untrusted markup back out as a string, so a value carrying `"`
25
+ * must not be able to break out of its attribute.
26
+ */
27
+ export function contentNodeToHtml(node) {
28
+ if (node === undefined || node === null || node === false || node === true) return '';
29
+ if (Array.isArray(node)) return node.map(contentNodeToHtml).join('');
30
+ if (typeof node === 'string' || typeof node === 'number') return htmlEntities(node.toString());
31
+ if (ContentNodeIsComponent(node)) {
32
+ throwError('contentNodeToHtml', `component nodes are not supported - encountered one of type ` + `${String(typeof node[$type] === 'string' ? node[$type] : node[$type].name)}.`);
33
+ }
34
+ const type = node[$type];
35
+ const {
36
+ children,
37
+ ...restProps
38
+ } = node[$props];
39
+ const attribProps = processProps(restProps);
40
+ let attributes = '';
41
+ for (const key in attribProps) {
42
+ const value = attribProps[key];
43
+ if (typeof value === 'boolean') {
44
+ const attrValue = booleanAttributeValue(key, value);
45
+ if (attrValue !== null) attributes += attrValue === '' ? ` ${key}` : ` ${key}="${attrValue}"`;
46
+ } else if (value !== undefined && value !== null) {
47
+ attributes += ` ${key}="${htmlEntities(String(value))}"`;
48
+ }
49
+ }
50
+ const innerHtml = innerHtmlFromProps(type, node[$props], validChildren(children));
51
+ if (innerHtml !== null) {
52
+ if (type in htmlVoidTags) {
53
+ throwError('contentNodeToHtml', `<${type}> is an HTML void type (cannot have children), but the node has dangerouslySetInnerHTML.`);
54
+ }
55
+ return `<${type}${attributes}>${innerHtml}</${type}>`;
56
+ }
57
+ if (validChildren(children)) {
58
+ if (type in htmlVoidTags) {
59
+ throwError('contentNodeToHtml', `<${type}> is an HTML void type (cannot have children), but the node has children.`);
60
+ }
61
+ return `<${type}${attributes}>${contentNodeToHtml(children)}</${type}>`;
62
+ }
63
+ if (type in htmlMonoTags) return `<${type}${attributes}>`;
64
+ if (type in htmlSelfClosableTags) return `<${type}${attributes} />`;
65
+ return `<${type}${attributes}></${type}>`;
66
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"contentToHtml.d.ts","sourceRoot":"","sources":["../../src/render/contentToHtml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAMvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAI9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AAGrE,wBAAsB,aAAa,CAC/B,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EACpE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EACxC,MAAM,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,YAAY,EACnE,iBAAiB,GAAE,sBAAkC,EAAE,GAAG,UAAQ,GACnE,OAAO,CAAC,MAAM,CAAC,CAqDjB"}
1
+ {"version":3,"file":"contentToHtml.d.ts","sourceRoot":"","sources":["../../src/render/contentToHtml.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAC9D,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,eAAe,CAAA;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAEpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AAMvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAI9D,OAAO,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AAGrE,wBAAsB,aAAa,CAC/B,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EACpE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EACxC,MAAM,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,YAAY,EACnE,iBAAiB,GAAE,sBAAkC,EAAE,GAAG,UAAQ,GACnE,OAAO,CAAC,MAAM,CAAC,CAsDjB"}
@@ -23,7 +23,8 @@ export async function contentToHtml(compilationInfo, clientImportAliases, conten
23
23
  dev,
24
24
  tungstenPublicEnv,
25
25
  siteBasePath: siteBasePathOverride,
26
- hmr: config.hmr
26
+ hmr: config.hmr,
27
+ csr: config.csr
27
28
  };
28
29
  //console.log('rendering tree', inspect(tree, { depth: 99 } ))
29
30
  let html = await renderingNodeToHtml(tree, false, headData, null);
@@ -1 +1 @@
1
- {"version":3,"file":"processHead.d.ts","sourceRoot":"","sources":["../../src/render/processHead.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAK7C,OAAO,EAAE,oBAAoB,EAAqB,MAAM,qBAAqB,CAAA;AAS7E,wBAAgB,WAAW,CACvB,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,QAAQ,QAwLrB"}
1
+ {"version":3,"file":"processHead.d.ts","sourceRoot":"","sources":["../../src/render/processHead.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAK7C,OAAO,EAAE,oBAAoB,EAAqB,MAAM,qBAAqB,CAAA;AAS7E,wBAAgB,WAAW,CACvB,QAAQ,EAAE,oBAAoB,EAC9B,QAAQ,EAAE,QAAQ,QA6MrB"}
@@ -86,10 +86,16 @@ export function processHead(headNode, headData) {
86
86
  // Tier 1 (hmr: 'reload', or nothing left for Tier 2 to do): always full-reload. A
87
87
  // simple, independent branch from Tier 2's logic - restored as an explicit config value
88
88
  // so it's available as a one-line escape hatch if Tier 2 itself is misbehaving.
89
- const tier1OnlyScript = `(function(){` + `var es=new EventSource('${siteBasePath}/__tungsten_live_reload__');` + `es.addEventListener('css',function(){location.reload()});` + `es.addEventListener('js',function(){location.reload()});` + `})();`;
89
+ // The server sends its bootId as the first event on every connection (see
90
+ // handleLiveReloadStream). If sessionStorage already holds a different one, this
91
+ // connection is a reconnect to a process that restarted since the page loaded (e.g. via
92
+ // tungsten devmon on a framework rebuild) - EventSource retries silently on its own, so
93
+ // this is the only reliable signal that the page is now stale and needs reloading.
94
+ const bootIdReloadSnippet = `es.addEventListener('boot',function(ev){` + `var bootId=JSON.parse(ev.data).bootId;` + `var key='__tungsten_boot_id__';` + `var prev=sessionStorage.getItem(key);` + `sessionStorage.setItem(key,bootId);` + `if(prev!==null&&prev!==bootId){location.reload()}` + `});`;
95
+ const tier1OnlyScript = `(function(){` + `var es=new EventSource('${siteBasePath}/__tungsten_live_reload__');` + bootIdReloadSnippet + `es.addEventListener('css',function(){location.reload()});` + `es.addEventListener('js',function(){location.reload()});` + `})();`;
90
96
  // Tier 2: attempt an in-place component/CSS hot-swap before falling back to a full
91
97
  // reload. See tungsten/docs/hmr.md.
92
- const tier2Script = `(function(){` + `var es=new EventSource('${siteBasePath}/__tungsten_live_reload__');` + `function fullReload(reason){console.info('[tungsten-hmr] full reload:',reason);location.reload()}` + `function loadHmr(){return import('${frameworkDestLocation}/hmr/applyHmrModule.js')}`
98
+ const tier2Script = `(function(){` + `var es=new EventSource('${siteBasePath}/__tungsten_live_reload__');` + `function fullReload(reason){console.info('[tungsten-hmr] full reload:',reason);location.reload()}` + `es.addEventListener('boot',function(ev){` + `var bootId=JSON.parse(ev.data).bootId;` + `var key='__tungsten_boot_id__';` + `var prev=sessionStorage.getItem(key);` + `sessionStorage.setItem(key,bootId);` + `if(prev!==null&&prev!==bootId){fullReload('dev server restarted')}` + `});` + `function loadHmr(){return import('${frameworkDestLocation}/hmr/applyHmrModule.js')}`
93
99
  // CSS has no state to preserve, so re-applying it is always safe: refreshStylesheets()
94
100
  // inserts the new link and only removes the old one once it's loaded, so there's no
95
101
  // gap where nothing is applied (a plain href swap can produce a visible flash).
@@ -1 +1 @@
1
- {"version":3,"file":"renderingTreeToHtml.d.ts","sourceRoot":"","sources":["../../src/render/renderingTreeToHtml.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAY7C,OAAO,EACH,KAAK,aAAa,EAAE,KAAK,iBAAiB,EAG7C,MAAM,qBAAqB,CAAA;AAU5B,wBAAsB,mBAAmB,CACrC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAC1D,UAAU,EAAE,iBAAiB,GAAG,IAAI,GACrC,OAAO,CAAC,MAAM,CAAC,CAwJjB"}
1
+ {"version":3,"file":"renderingTreeToHtml.d.ts","sourceRoot":"","sources":["../../src/render/renderingTreeToHtml.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAY7C,OAAO,EACH,KAAK,aAAa,EAAE,KAAK,iBAAiB,EAG7C,MAAM,qBAAqB,CAAA;AAU5B,wBAAsB,mBAAmB,CACrC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAC1D,UAAU,EAAE,iBAAiB,GAAG,IAAI,GACrC,OAAO,CAAC,MAAM,CAAC,CA8JjB"}
@@ -1,4 +1,5 @@
1
1
  import { reduce as objectReduce } from 'sxy-lib/objects.js';
2
+ import { booleanAttributeValue } from "../booleanAttributeValue.js";
2
3
  import { processProps } from "./processProps.js";
3
4
  import { htmlVoidTags } from "./htmlVoidTags.js";
4
5
  import { validRenderingChildren } from "../validChildren.js";
@@ -77,7 +78,9 @@ export async function renderingNodeToHtml(node, noEscape, headData, lastSelect)
77
78
  const attribs = objectReduce(processProps(attribProps), (string, key, value) => {
78
79
  //debug('reducing attr', key, value)
79
80
  if (typeof value === 'boolean') {
80
- if (value) return string + ` ${key}`;else return string;
81
+ const attrValue = booleanAttributeValue(key, value);
82
+ if (attrValue === null) return string;
83
+ return attrValue === '' ? string + ` ${key}` : string + ` ${key}="${attrValue}"`;
81
84
  } else if (value === undefined || value === null) {
82
85
  return string;
83
86
  } else {
@@ -86,6 +89,10 @@ export async function renderingNodeToHtml(node, noEscape, headData, lastSelect)
86
89
  }, '');
87
90
  // : ''
88
91
  debug('attribs', attribs);
92
+ // CSR: body's own attributes render, its children don't - see FrameworkConfig.ts.
93
+ if (type === 'body' && headData.csr) {
94
+ return html + `<${type}${attribs}></${type}>`;
95
+ }
89
96
  if (typeof node[$props].__zoneHtml === 'string') {
90
97
  return html + `<${type}${attribs}>${node[$props].__zoneHtml}</${type}>`;
91
98
  }
@@ -0,0 +1,6 @@
1
+ import type { RouteContentFunction } from './RouteContentFunction.js';
2
+ import type { TungstenErrorInfo } from './TungstenErrorInfo.js';
3
+ export type ErrorRouteType = {
4
+ content: RouteContentFunction<object, TungstenErrorInfo>;
5
+ };
6
+ //# sourceMappingURL=ErrorRouteType.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ErrorRouteType.d.ts","sourceRoot":"","sources":["../../src/routing/ErrorRouteType.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAM/D,MAAM,MAAM,cAAc,GAAG;IACzB,OAAO,EAAE,oBAAoB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;CAC3D,CAAA"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,7 @@
1
1
  import type { Routes } from './Routes.js';
2
2
  export type Path = `/${string}`;
3
- export type RouteKey = Path | 'default' | '404' | 'error';
3
+ export type SpecialRoute = 'default' | '404' | 'error';
4
+ export type RouteKey = Path | SpecialRoute;
4
5
  export declare function StringIsValidRouteKey(string: string, routes: Routes): string is RouteKey;
5
6
  export declare function StringIsPath(string: string): string is Path;
6
7
  export declare function RouteKeyIsPath(path: RouteKey): path is Path;
@@ -1 +1 @@
1
- {"version":3,"file":"RouteTypes.d.ts","sourceRoot":"","sources":["../../src/routing/RouteTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,IAAI,GAAG,IAAI,MAAM,EAAE,CAAA;AAC/B,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAA;AACzD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,QAAQ,CAExF;AACD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,IAAI,CAE3D;AACD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,IAAI,IAAI,CAE3D"}
1
+ {"version":3,"file":"RouteTypes.d.ts","sourceRoot":"","sources":["../../src/routing/RouteTypes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,IAAI,GAAG,IAAI,MAAM,EAAE,CAAA;AAC/B,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,KAAK,GAAG,OAAO,CAAA;AACtD,MAAM,MAAM,QAAQ,GAAG,IAAI,GAAG,YAAY,CAAA;AAE1C,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,QAAQ,CAExF;AACD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,IAAI,CAE3D;AACD,wBAAgB,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,IAAI,IAAI,CAE3D"}
@@ -1,5 +1,6 @@
1
1
  import type { Path } from './RouteTypes.js';
2
2
  import type { RouteType } from './RouteType.js';
3
+ import type { ErrorRouteType } from './ErrorRouteType.js';
3
4
  export type MinimumRoutes = {
4
5
  default: RouteType<any, any>;
5
6
  } | {
@@ -7,6 +8,8 @@ export type MinimumRoutes = {
7
8
  };
8
9
  export type Routes = {
9
10
  [key in Path]: RouteType<any, any>;
11
+ } & {
12
+ 'error'?: ErrorRouteType;
10
13
  } & ({
11
14
  'default': RouteType<any, any>;
12
15
  } | {
@@ -14,6 +17,8 @@ export type Routes = {
14
17
  });
15
18
  export type CustomRoutes<Route extends RouteType<any, any>> = {
16
19
  [key in Path]: Route;
20
+ } & {
21
+ error: ErrorRouteType;
17
22
  } & ({
18
23
  'default': Route;
19
24
  } | {
@@ -1 +1 @@
1
- {"version":3,"file":"Routes.d.ts","sourceRoot":"","sources":["../../src/routing/Routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAY,MAAM,iBAAiB,CAAA;AACrD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C,MAAM,MAAM,aAAa,GACnB;IAAE,OAAO,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,GAChC;IAAE,KAAK,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,CAAA;AAEpC,MAAM,MAAM,MAAM,GAAG;KAAG,GAAG,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;CAAE,GACzD,CACE;IAAE,SAAS,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,GAChC;IAAE,KAAK,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,CACnC,CAAA;AAED,MAAM,MAAM,YAAY,CAAC,KAAK,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI;KAAG,GAAG,IAAI,IAAI,GAAG,KAAK;CAAE,GACpF,CACE;IAAE,SAAS,EAAE,KAAK,CAAA;CAAE,GAClB;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CACrB,CAAA"}
1
+ {"version":3,"file":"Routes.d.ts","sourceRoot":"","sources":["../../src/routing/Routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD,MAAM,MAAM,aAAa,GACnB;IAAE,OAAO,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,GAChC;IAAE,KAAK,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,CAAA;AAEpC,MAAM,MAAM,MAAM,GAAG;KAAG,GAAG,IAAI,IAAI,GAAG,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC;CAAE,GACzD;IAAE,OAAO,CAAC,EAAE,cAAc,CAAA;CAAE,GAC5B,CACE;IAAE,SAAS,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,GAChC;IAAE,KAAK,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;CAAE,CACnC,CAAA;AAED,MAAM,MAAM,YAAY,CAAC,KAAK,SAAS,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI;KAAG,GAAG,IAAI,IAAI,GAAG,KAAK;CAAE,GACpF;IAAE,KAAK,EAAE,cAAc,CAAA;CAAE,GACzB,CACE;IAAE,SAAS,EAAE,KAAK,CAAA;CAAE,GAClB;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CACrB,CAAA"}
@@ -0,0 +1,6 @@
1
+ export type TungstenErrorInfo = {
2
+ error: unknown;
3
+ message: string;
4
+ };
5
+ export declare function makeTungstenErrorInfo(error: unknown): TungstenErrorInfo;
6
+ //# sourceMappingURL=TungstenErrorInfo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TungstenErrorInfo.d.ts","sourceRoot":"","sources":["../../src/routing/TungstenErrorInfo.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,iBAAiB,GAAG;IAC5B,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAEvE"}
@@ -0,0 +1,6 @@
1
+ export function makeTungstenErrorInfo(error) {
2
+ return {
3
+ error,
4
+ message: error instanceof Error ? error.message : String(error)
5
+ };
6
+ }
@@ -121,6 +121,27 @@ function processItem(element, styles, usage) {
121
121
  }
122
122
  newClassName = newClassName.trim();
123
123
  if (newClassName !== '') element[$props].className = newClassName;
124
+ // Any other *ClassName prop (e.g. messageClassName, handed to a child component to apply
125
+ // to one of its own elements) gets the same local-stylesheet lookup applied in place, so a
126
+ // classname threaded down through props can still be scoped against this component's own
127
+ // stylesheet, even though the child's own render tree is outside this walk - see
128
+ // styleContentSxy() above.
129
+ for (const key in element[$props]) {
130
+ if (key === 'className' || !key.endsWith('ClassName')) continue;
131
+ const value = element[$props][key];
132
+ if (value === undefined || value === null) continue;
133
+ if (typeof value !== 'string') {
134
+ throw new TungstenError(`styleContentSxy() error: ${ContentNodeElementIsHtml(element) ? element[$type] : getComponentName(element)}` + ` ${key} prop should be a string. Received ${typeof value}`);
135
+ }
136
+ let newValue = value;
137
+ for (const cn of value.split(' ')) {
138
+ if (cn in styles.classes) {
139
+ newValue += ` ${styles.classes[cn]}`;
140
+ usage?.usedClasses.add(cn);
141
+ }
142
+ }
143
+ if (newValue !== value) element[$props][key] = newValue;
144
+ }
124
145
  debug('element after mods', tagName, element);
125
146
  // apply this to both fragment and elements
126
147
  if (element[$props].children !== undefined) {
@@ -0,0 +1,38 @@
1
+ import type { ConstructPackage, TungstenBaseComponent, TungstenComponentPropsBase } from '../Components.jsx';
2
+ import type { TaggedAttributeMismatch } from '../hydrate/renderHydrationMismatchBanner.js';
3
+ import type { TestAppOptions } from './index.js';
4
+ export type HydrateTestAppOptions<Props extends object> = TestAppOptions<Props> & {
5
+ serverProps?: Props;
6
+ csr?: boolean;
7
+ };
8
+ type HydrateTestAppArgs<Props extends object> = object extends Props ? [options?: HydrateTestAppOptions<Props>] : keyof Props extends never ? [options?: HydrateTestAppOptions<Props>] : [options: HydrateTestAppOptions<Props> & {
9
+ props: Props;
10
+ }];
11
+ export type HydrateTestApp<Component extends TungstenBaseComponent> = {
12
+ readonly document: Document;
13
+ readonly container: HTMLElement;
14
+ readonly component: Component;
15
+ readonly html: string;
16
+ readonly mismatches: readonly TaggedAttributeMismatch[];
17
+ resolved(): Promise<void>;
18
+ setProps(props: Component['props']): Promise<void>;
19
+ unmount(): Promise<void>;
20
+ };
21
+ /**
22
+ * The hydration round trip createTestApp() deliberately doesn't cover: render a component to
23
+ * HTML the way a real server would, parse it with a real HTML parser, hydrate a fresh client
24
+ * instance against the result, and hand back the live, hydrated component - still fully usable
25
+ * via setProps()/resolved(), exactly like createTestApp()'s result.
26
+ *
27
+ * By default `props` renders on both sides, so a passing test proves an ordinary page hydrates
28
+ * clean (`mismatches` empty). Pass `serverProps` to diverge the two passes on purpose and assert
29
+ * on the resulting `mismatches` - the case a one-off "does the reconciler still agree with real
30
+ * served pages" script never could, because nothing fed it markup that didn't already match.
31
+ *
32
+ * A structural mismatch (a different tag, a different number of children) is not something
33
+ * reconcileHydratedAttributes() can fix - hydrateRootComponent() throws, exactly as the real
34
+ * client hydrator does, so a caller reproducing that class of bug should assert on the rejection.
35
+ */
36
+ export declare function hydrateTestApp<Props extends TungstenComponentPropsBase, Component extends TungstenBaseComponent<any, any>, RouteData = undefined>(RootComponent: new (pkg: ConstructPackage<NoInfer<Props>, RouteData>) => Component, ...args: HydrateTestAppArgs<Props>): Promise<HydrateTestApp<Component>>;
37
+ export {};
38
+ //# sourceMappingURL=hydrateTestApp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hydrateTestApp.d.ts","sourceRoot":"","sources":["../../src/testing/hydrateTestApp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACR,gBAAgB,EAChB,qBAAqB,EACrB,0BAA0B,EAC7B,MAAM,mBAAmB,CAAA;AAO1B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,6CAA6C,CAAA;AAa1F,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAEnE,MAAM,MAAM,qBAAqB,CAAC,KAAK,SAAS,MAAM,IAAI,cAAc,CAAC,KAAK,CAAC,GAAG;IAM9E,WAAW,CAAC,EAAE,KAAK,CAAA;IAEnB,GAAG,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,KAAK,kBAAkB,CAAC,KAAK,SAAS,MAAM,IAAI,MAAM,SAAS,KAAK,GAC9D,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC,GACxC,MAAM,KAAK,SAAS,KAAK,GACrB,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC,GACxC,CAAC,OAAO,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG;IAAE,KAAK,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAEpE,MAAM,MAAM,cAAc,CAAC,SAAS,SAAS,qBAAqB,IAAI;IAClE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAC3B,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAA;IAC/B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAG7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAIrB,QAAQ,CAAC,UAAU,EAAE,SAAS,uBAAuB,EAAE,CAAA;IAEvD,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClD,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3B,CAAA;AAkGD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAChC,KAAK,SAAS,0BAA0B,EACxC,SAAS,SAAS,qBAAqB,CAAC,GAAG,EAAE,GAAG,CAAC,EACjD,SAAS,GAAG,SAAS,EAErB,aAAa,EAAE,KAAK,GAAG,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,KAAK,SAAS,EAClF,GAAG,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,GACnC,OAAO,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CA6HpC"}