@server/next 0.28.0 → 0.28.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@server/next",
3
- "version": "0.28.0",
3
+ "version": "0.28.2",
4
4
  "description": "A fully-fledged web server with routing, file uploads, sessions, static files, schema validation, websockets, testing, etc.",
5
5
  "homepage": "https://server-js.com/",
6
6
  "repository": "https://github.com/franciscop/server-next.git",
@@ -3,6 +3,7 @@ const entities = {
3
3
  "<": "&lt;",
4
4
  ">": "&gt;",
5
5
  '"': "&quot;",
6
+ "'": "&#39;",
6
7
  };
7
8
 
8
9
  const encode = (str = "") => {
@@ -40,8 +41,55 @@ const minifyCss = (str) =>
40
41
  .replace(/(\{) (\w)/g, "$1$2")
41
42
  .trim();
42
43
 
44
+ const REACT_ELEMENT_TYPE = Symbol.for("react.element");
45
+
46
+ const isReactElement = (val) =>
47
+ val !== null &&
48
+ typeof val === "object" &&
49
+ (val.$$typeof === REACT_ELEMENT_TYPE || // real React
50
+ ("type" in val && "props" in val)); // your test objects
51
+
52
+ const resolve = (val) => {
53
+ while (typeof val === "function") val = val();
54
+ if (isReactElement(val)) {
55
+ val = jsx(val.type, val.props || {});
56
+ }
57
+ return val ?? "";
58
+ };
59
+
60
+ const renderChild = (child) => {
61
+ while (typeof child === "function") child = child();
62
+
63
+ if (Array.isArray(child)) {
64
+ return child.map(renderChild).join("");
65
+ }
66
+
67
+ if (isReactElement(child)) {
68
+ return resolve(jsx(child.type, child.props || {}));
69
+ }
70
+
71
+ if (typeof child === "string") return encode(child);
72
+ if (typeof child === "number") return String(child);
73
+
74
+ if (!isValidChild(child)) return "";
75
+
76
+ console.warn("Unknown child:", child);
77
+ return "";
78
+ };
79
+
43
80
  const jsx = (tag, { children, ...props }) => {
44
- if (typeof tag === "function") return tag({ children, ...props });
81
+ if (typeof tag === "function") {
82
+ return () => renderChild(tag({ children, ...props }));
83
+ }
84
+
85
+ // Handle React forwardRef objects: { render: fn }
86
+ if (
87
+ typeof tag === "object" &&
88
+ tag !== null &&
89
+ typeof tag.render === "function"
90
+ ) {
91
+ return () => renderChild(tag.render({ children, ...props }, null));
92
+ }
45
93
 
46
94
  if (tag === "script" && children) {
47
95
  const src = children;
@@ -58,11 +106,10 @@ const jsx = (tag, { children, ...props }) => {
58
106
  }
59
107
 
60
108
  if (!isValidChild(children)) children = [];
61
- if (typeof children === "string") children = [children];
62
109
 
63
- children = (Array.isArray(children) ? children : [children])
64
- .flat()
65
- .map((c) => (typeof c === "function" ? c() : encode(c)))
110
+ children = (Array.isArray(children) ? children.flat() : [children])
111
+ .map(renderChild)
112
+ .map((c) => (typeof c === "string" ? c : ""))
66
113
  .join("");
67
114
 
68
115
  if (!tag) return () => children;
@@ -45,3 +45,53 @@ describe("jsx", () => {
45
45
  );
46
46
  });
47
47
  });
48
+
49
+ describe("React element interop", () => {
50
+ // Simulate a component built with React's jsx runtime, which returns plain
51
+ // objects { type, props } instead of @server/next functions
52
+ const reactEl = (type, props = {}) => ({ type, props });
53
+
54
+ it("renders a React element returned from a function component", () => {
55
+ const Greeting = () => reactEl("h1", { children: "Hello" });
56
+ expect(<Greeting />).toRender("<h1>Hello</h1>");
57
+ });
58
+
59
+ it("renders nested React elements", () => {
60
+ const Card = () =>
61
+ reactEl("div", {
62
+ children: [
63
+ reactEl("h2", { children: "Title" }),
64
+ reactEl("p", { children: "Body" }),
65
+ ],
66
+ });
67
+ expect(<Card />).toRender("<div><h2>Title</h2><p>Body</p></div>");
68
+ });
69
+
70
+ it("renders React elements as children of a native element", () => {
71
+ const child = reactEl("strong", { children: "bold" });
72
+ expect(<p>{child}</p>).toRender("<p><strong>bold</strong></p>");
73
+ });
74
+
75
+ it("renders a mix of React elements and plain strings as children", () => {
76
+ const em = reactEl("em", { children: "world" });
77
+ expect(<p>Hello {em}!</p>).toRender("<p>Hello <em>world</em>!</p>");
78
+ });
79
+
80
+ it("renders a React element with attributes", () => {
81
+ const Link = () => reactEl("a", { href: "https://example.com", children: "click" });
82
+ expect(<Link />).toRender(`<a href="https://example.com">click</a>`);
83
+ });
84
+
85
+ it("renders deeply nested React elements", () => {
86
+ const Deep = () =>
87
+ reactEl("div", {
88
+ children: reactEl("ul", {
89
+ children: [
90
+ reactEl("li", { children: "one" }),
91
+ reactEl("li", { children: "two" }),
92
+ ],
93
+ }),
94
+ });
95
+ expect(<Deep />).toRender("<div><ul><li>one</li><li>two</li></ul></div>");
96
+ });
97
+ });