@limlabs/rex 0.0.1-test.1 → 0.2.1
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/dist/document.d.ts +14 -0
- package/dist/document.d.ts.map +1 -0
- package/dist/document.js +17 -0
- package/dist/document.js.map +1 -0
- package/dist/image.d.ts +13 -0
- package/dist/image.d.ts.map +1 -0
- package/dist/image.js +22 -0
- package/dist/image.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +11 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +27 -0
- package/dist/link.js.map +1 -0
- package/dist/router.d.ts +29 -0
- package/dist/router.d.ts.map +1 -0
- package/dist/router.js +65 -0
- package/dist/router.js.map +1 -0
- package/dist/server.d.ts +111 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +49 -0
- package/dist/server.js.map +1 -0
- package/package.json +45 -8
- package/src/document.tsx +39 -0
- package/src/image.tsx +33 -0
- package/src/{index.js → index.ts} +1 -0
- package/src/{link.js → link.tsx} +9 -12
- package/src/middleware.d.ts +84 -0
- package/src/router.ts +109 -0
- package/src/server.ts +163 -0
- package/tsconfig.json +19 -0
- package/src/document.js +0 -40
- package/src/router.js +0 -114
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface HtmlProps extends React.HTMLAttributes<HTMLHtmlElement> {
|
|
3
|
+
children?: React.ReactNode;
|
|
4
|
+
}
|
|
5
|
+
interface HeadProps {
|
|
6
|
+
children?: React.ReactNode;
|
|
7
|
+
}
|
|
8
|
+
export declare function Html({ children, ...props }: HtmlProps): React.ReactElement;
|
|
9
|
+
export declare function Head({ children }: HeadProps): React.ReactElement;
|
|
10
|
+
export declare function Main(): React.ReactElement;
|
|
11
|
+
export declare function NextScript(): null;
|
|
12
|
+
export default function Document(): React.ReactElement;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../src/document.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,SAAU,SAAQ,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC;IAC/D,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,UAAU,SAAS;IACjB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,CAE1E;AAED,wBAAgB,IAAI,CAAC,EAAE,QAAQ,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,CAEhE;AAED,wBAAgB,IAAI,IAAI,KAAK,CAAC,YAAY,CAEzC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,IAAI,KAAK,CAAC,YAAY,CAYrD"}
|
package/dist/document.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export function Html({ children, ...props }) {
|
|
3
|
+
return React.createElement('html', props, children);
|
|
4
|
+
}
|
|
5
|
+
export function Head({ children }) {
|
|
6
|
+
return React.createElement('head', null, children);
|
|
7
|
+
}
|
|
8
|
+
export function Main() {
|
|
9
|
+
return React.createElement('div', { id: '__rex' });
|
|
10
|
+
}
|
|
11
|
+
export function NextScript() {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
export default function Document() {
|
|
15
|
+
return React.createElement(Html, null, React.createElement(Head, null), React.createElement('body', null, React.createElement(Main, null), React.createElement(NextScript, null)));
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"document.js","sourceRoot":"","sources":["../src/document.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAU1B,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAa;IACpD,OAAO,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,UAAU,IAAI,CAAC,EAAE,QAAQ,EAAa;IAC1C,OAAO,KAAK,CAAC,aAAa,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,IAAI;IAClB,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,UAAU;IACxB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ;IAC9B,OAAO,KAAK,CAAC,aAAa,CACxB,IAAI,EACJ,IAAI,EACJ,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,EAC/B,KAAK,CAAC,aAAa,CACjB,MAAM,EACN,IAAI,EACJ,KAAK,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,EAC/B,KAAK,CAAC,aAAa,CAAC,UAAU,EAAE,IAAI,CAAC,CACtC,CACF,CAAC;AACJ,CAAC"}
|
package/dist/image.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface ImageProps {
|
|
3
|
+
src: string;
|
|
4
|
+
width?: number;
|
|
5
|
+
height?: number;
|
|
6
|
+
alt?: string;
|
|
7
|
+
quality?: number;
|
|
8
|
+
priority?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
export default function Image(props: ImageProps): React.ReactElement;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,UAAU,UAAU;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAAC,YAAY,CAgBnE"}
|
package/dist/image.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
function buildSrc(src, w, q) {
|
|
3
|
+
return '/_rex/image?url=' + encodeURIComponent(src) + '&w=' + w + '&q=' + q;
|
|
4
|
+
}
|
|
5
|
+
export default function Image(props) {
|
|
6
|
+
const { src, width, height, alt, quality = 75, priority = false, className } = props;
|
|
7
|
+
const imgProps = {
|
|
8
|
+
alt: alt ?? '',
|
|
9
|
+
src: buildSrc(src, width ?? 1920, quality),
|
|
10
|
+
loading: priority ? 'eager' : 'lazy',
|
|
11
|
+
decoding: 'async',
|
|
12
|
+
style: { display: 'block', maxWidth: '100%', height: 'auto' },
|
|
13
|
+
};
|
|
14
|
+
if (width !== undefined)
|
|
15
|
+
imgProps.width = width;
|
|
16
|
+
if (height !== undefined)
|
|
17
|
+
imgProps.height = height;
|
|
18
|
+
if (className)
|
|
19
|
+
imgProps.className = className;
|
|
20
|
+
return React.createElement('img', imgProps);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../src/image.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAY1B,SAAS,QAAQ,CAAC,GAAW,EAAE,CAAS,EAAE,CAAS;IACjD,OAAO,kBAAkB,GAAG,kBAAkB,CAAC,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;AAC9E,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,KAAK,CAAC,KAAiB;IAC7C,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,GAAG,EAAE,EAAE,QAAQ,GAAG,KAAK,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAErF,MAAM,QAAQ,GAA8C;QAC1D,GAAG,EAAE,GAAG,IAAI,EAAE;QACd,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,KAAK,IAAI,IAAI,EAAE,OAAO,CAAC;QAC1C,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM;QACpC,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE;KAC9D,CAAC;IAEF,IAAI,KAAK,KAAK,SAAS;QAAE,QAAQ,CAAC,KAAK,GAAG,KAAK,CAAC;IAChD,IAAI,MAAM,KAAK,SAAS;QAAE,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC;IACnD,IAAI,SAAS;QAAE,QAAQ,CAAC,SAAS,GAAG,SAAS,CAAC;IAE9C,OAAO,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Rex framework exports
|
|
2
|
+
export { Html, Head, Main, NextScript } from './document.js';
|
|
3
|
+
export { default as Link } from './link.js';
|
|
4
|
+
export { useRouter, navigateTo } from './router.js';
|
|
5
|
+
export { default as Image } from './image.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/link.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
3
|
+
href: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* rex/link - Client-side navigation link.
|
|
7
|
+
* Renders an <a> tag that intercepts clicks for SPA navigation.
|
|
8
|
+
*/
|
|
9
|
+
export default function Link({ href, children, target, ...rest }: LinkProps): React.ReactElement;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=link.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAG1B,UAAU,SAAU,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IACvE,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,GAAG,KAAK,CAAC,YAAY,CA2B/F"}
|
package/dist/link.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { navigateTo } from './router.js';
|
|
3
|
+
/**
|
|
4
|
+
* rex/link - Client-side navigation link.
|
|
5
|
+
* Renders an <a> tag that intercepts clicks for SPA navigation.
|
|
6
|
+
*/
|
|
7
|
+
export default function Link({ href, children, target, ...rest }) {
|
|
8
|
+
function handleClick(e) {
|
|
9
|
+
if (e.metaKey ||
|
|
10
|
+
e.ctrlKey ||
|
|
11
|
+
e.shiftKey ||
|
|
12
|
+
e.altKey ||
|
|
13
|
+
target === '_blank' ||
|
|
14
|
+
(href && (href.startsWith('http://') || href.startsWith('https://')))) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
navigateTo(href);
|
|
19
|
+
}
|
|
20
|
+
return React.createElement('a', {
|
|
21
|
+
href,
|
|
22
|
+
onClick: handleClick,
|
|
23
|
+
target,
|
|
24
|
+
...rest,
|
|
25
|
+
}, children);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=link.js.map
|
package/dist/link.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link.js","sourceRoot":"","sources":["../src/link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzC;;;GAGG;AACH,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI,EAAa;IACzE,SAAS,WAAW,CAAC,CAAsC;QACzD,IACE,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,OAAO;YACT,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,MAAM;YACR,MAAM,KAAK,QAAQ;YACnB,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,EACrE,CAAC;YACD,OAAO;QACT,CAAC;QAED,CAAC,CAAC,cAAc,EAAE,CAAC;QACnB,UAAU,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IAED,OAAO,KAAK,CAAC,aAAa,CACxB,GAAG,EACH;QACE,IAAI;QACJ,OAAO,EAAE,WAAW;QACpB,MAAM;QACN,GAAG,IAAI;KACR,EACD,QAAQ,CACT,CAAC;AACJ,CAAC"}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface RouterEvents {
|
|
2
|
+
on(event: string, handler: (...args: unknown[]) => void): void;
|
|
3
|
+
off(event: string, handler: (...args: unknown[]) => void): void;
|
|
4
|
+
emit(event: string, ...args: unknown[]): void;
|
|
5
|
+
}
|
|
6
|
+
export interface RexRouter {
|
|
7
|
+
pathname: string;
|
|
8
|
+
asPath: string;
|
|
9
|
+
query: Record<string, string>;
|
|
10
|
+
route: string;
|
|
11
|
+
push(url: string): void;
|
|
12
|
+
replace(url: string): void;
|
|
13
|
+
back(): void;
|
|
14
|
+
forward(): void;
|
|
15
|
+
reload(): void;
|
|
16
|
+
prefetch(url: string): void;
|
|
17
|
+
events: RouterEvents;
|
|
18
|
+
isReady: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Navigate to a new path via client-side routing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function navigateTo(path: string): void;
|
|
24
|
+
/**
|
|
25
|
+
* React hook that returns the current router instance.
|
|
26
|
+
*/
|
|
27
|
+
export declare function useRouter(): RexRouter;
|
|
28
|
+
export default useRouter;
|
|
29
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAChE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;CAC/C;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,IAAI,IAAI,CAAC;IACb,OAAO,IAAI,IAAI,CAAC;IAChB,MAAM,IAAI,IAAI,CAAC;IACf,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;CAClB;AA0BD;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAO7C;AAaD;;GAEG;AACH,wBAAgB,SAAS,IAAI,SAAS,CAmCrC;AAED,eAAe,SAAS,CAAC"}
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function getRouter() {
|
|
2
|
+
return window.__REX_ROUTER ?? null;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Navigate to a new path via client-side routing.
|
|
6
|
+
*/
|
|
7
|
+
export function navigateTo(path) {
|
|
8
|
+
const r = getRouter();
|
|
9
|
+
if (r) {
|
|
10
|
+
r.push(path);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
window.location.href = path;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function parseQuery(search) {
|
|
17
|
+
const query = {};
|
|
18
|
+
if (!search || search.length <= 1)
|
|
19
|
+
return query;
|
|
20
|
+
const pairs = search.substring(1).split('&');
|
|
21
|
+
for (const pair of pairs) {
|
|
22
|
+
const [key, value] = pair.split('=');
|
|
23
|
+
query[decodeURIComponent(key)] = decodeURIComponent(value ?? '');
|
|
24
|
+
}
|
|
25
|
+
return query;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* React hook that returns the current router instance.
|
|
29
|
+
*/
|
|
30
|
+
export function useRouter() {
|
|
31
|
+
const r = getRouter();
|
|
32
|
+
const noop = () => { };
|
|
33
|
+
if (r?.state) {
|
|
34
|
+
return {
|
|
35
|
+
pathname: r.state.pathname,
|
|
36
|
+
asPath: r.state.asPath,
|
|
37
|
+
query: r.state.query,
|
|
38
|
+
route: r.state.route,
|
|
39
|
+
push: r.push,
|
|
40
|
+
replace: r.replace,
|
|
41
|
+
back: r.back,
|
|
42
|
+
forward: r.forward,
|
|
43
|
+
reload: r.reload,
|
|
44
|
+
prefetch: r.prefetch,
|
|
45
|
+
events: r.events,
|
|
46
|
+
isReady: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
pathname: window.location.pathname,
|
|
51
|
+
asPath: window.location.pathname + window.location.search,
|
|
52
|
+
query: parseQuery(window.location.search),
|
|
53
|
+
route: window.location.pathname,
|
|
54
|
+
push: (url) => { window.location.href = url; },
|
|
55
|
+
replace: (url) => { window.location.replace(url); },
|
|
56
|
+
back: () => { history.back(); },
|
|
57
|
+
forward: () => { history.forward(); },
|
|
58
|
+
reload: () => { window.location.reload(); },
|
|
59
|
+
prefetch: noop,
|
|
60
|
+
events: { on: noop, off: noop, emit: noop },
|
|
61
|
+
isReady: false,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export default useRouter;
|
|
65
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
export interface RexApiRequest {
|
|
2
|
+
/** HTTP method (e.g. "GET", "POST"). */
|
|
3
|
+
method: string;
|
|
4
|
+
/** The request URL path. */
|
|
5
|
+
url: string;
|
|
6
|
+
/** Request headers as a plain object. */
|
|
7
|
+
headers: Record<string, string>;
|
|
8
|
+
/** Parsed query parameters. */
|
|
9
|
+
query: Record<string, string>;
|
|
10
|
+
/** Request body (parsed JSON or raw string). */
|
|
11
|
+
body: unknown;
|
|
12
|
+
/** Request cookies as a plain object. */
|
|
13
|
+
cookies: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
export interface RexApiResponse<T = unknown> {
|
|
16
|
+
/** Set the HTTP status code. */
|
|
17
|
+
status(code: number): RexApiResponse<T>;
|
|
18
|
+
/** Set a response header. */
|
|
19
|
+
setHeader(name: string, value: string): RexApiResponse<T>;
|
|
20
|
+
/** Send a JSON response. */
|
|
21
|
+
json(data: T): RexApiResponse<T>;
|
|
22
|
+
/** Send a string or object response. */
|
|
23
|
+
send(body: string | T): RexApiResponse<T>;
|
|
24
|
+
/** End the response. */
|
|
25
|
+
end(body?: string): RexApiResponse<T>;
|
|
26
|
+
/** Redirect to a URL, optionally with a status code. */
|
|
27
|
+
redirect(url: string): RexApiResponse<T>;
|
|
28
|
+
redirect(status: number, url: string): RexApiResponse<T>;
|
|
29
|
+
}
|
|
30
|
+
/** @deprecated Use `RexApiRequest` instead. */
|
|
31
|
+
export type NextApiRequest = RexApiRequest;
|
|
32
|
+
/** @deprecated Use `RexApiResponse` instead. */
|
|
33
|
+
export type NextApiResponse<T = unknown> = RexApiResponse<T>;
|
|
34
|
+
export interface RexOptions {
|
|
35
|
+
/** Path to the project root directory (containing pages/). */
|
|
36
|
+
root: string;
|
|
37
|
+
/** Whether to run in dev mode (enables HMR, error overlays). */
|
|
38
|
+
dev?: boolean;
|
|
39
|
+
}
|
|
40
|
+
export interface RouteMatch {
|
|
41
|
+
/** The route pattern, e.g. "/blog/:slug" */
|
|
42
|
+
pattern: string;
|
|
43
|
+
/** The module name, e.g. "blog/[slug]" */
|
|
44
|
+
moduleName: string;
|
|
45
|
+
/** Matched params, e.g. { slug: "hello" } */
|
|
46
|
+
params: Record<string, string>;
|
|
47
|
+
}
|
|
48
|
+
export interface PageResult {
|
|
49
|
+
/** Full HTML document. */
|
|
50
|
+
html: string;
|
|
51
|
+
/** HTTP status code. */
|
|
52
|
+
status: number;
|
|
53
|
+
/** Response headers. */
|
|
54
|
+
headers: Array<{
|
|
55
|
+
key: string;
|
|
56
|
+
value: string;
|
|
57
|
+
}>;
|
|
58
|
+
}
|
|
59
|
+
export interface RexInstance {
|
|
60
|
+
/** Whether this instance is running in dev mode. */
|
|
61
|
+
readonly isDev: boolean;
|
|
62
|
+
/** The current build ID. */
|
|
63
|
+
readonly buildId: string;
|
|
64
|
+
/** The path to the static files directory (client JS/CSS bundles). */
|
|
65
|
+
readonly staticDir: string;
|
|
66
|
+
/**
|
|
67
|
+
* Match a URL path against the route trie.
|
|
68
|
+
* Returns the matched route info with params, or null if no match.
|
|
69
|
+
*/
|
|
70
|
+
matchRoute(path: string): RouteMatch | null;
|
|
71
|
+
/**
|
|
72
|
+
* Run getServerSideProps for a given path and return the result.
|
|
73
|
+
*/
|
|
74
|
+
getServerSideProps(path: string): Promise<Record<string, unknown>>;
|
|
75
|
+
/**
|
|
76
|
+
* Render a page to an HTML string with the given props.
|
|
77
|
+
*/
|
|
78
|
+
renderToString(path: string, props: Record<string, unknown>): Promise<string>;
|
|
79
|
+
/**
|
|
80
|
+
* Render a full page (GSSP + SSR + document assembly).
|
|
81
|
+
* Returns HTML, status code, and headers.
|
|
82
|
+
*/
|
|
83
|
+
renderPage(path: string): Promise<PageResult>;
|
|
84
|
+
/**
|
|
85
|
+
* Get a request handler function compatible with the Web Fetch API.
|
|
86
|
+
* Returns `(req: Request) => Promise<Response>`.
|
|
87
|
+
*/
|
|
88
|
+
getRequestHandler(): (req: Request) => Promise<Response>;
|
|
89
|
+
/**
|
|
90
|
+
* Shut down the Rex instance, releasing V8 isolates and other resources.
|
|
91
|
+
*/
|
|
92
|
+
close(): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Create a new Rex application instance.
|
|
96
|
+
*
|
|
97
|
+
* Scans the pages directory, builds bundles, initializes the V8 isolate pool,
|
|
98
|
+
* and returns a ready-to-use RexInstance.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* import { createRex } from '@limlabs/rex/server'
|
|
103
|
+
*
|
|
104
|
+
* const rex = await createRex({ root: './my-app' })
|
|
105
|
+
* const handle = rex.getRequestHandler()
|
|
106
|
+
*
|
|
107
|
+
* Bun.serve({ fetch: handle })
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
export declare const createRex: (options: RexOptions) => Promise<RexInstance>;
|
|
111
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { arch, platform } from 'node:process';
|
|
3
|
+
function loadNativeBinding() {
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const platformPackage = `@limlabs/rex-${platform}-${arch}`;
|
|
6
|
+
// Try platform-specific package first (for published npm packages)
|
|
7
|
+
try {
|
|
8
|
+
return require(platformPackage);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
// Fall through to local .node file (development)
|
|
12
|
+
}
|
|
13
|
+
// Try local .node file (built with napi build)
|
|
14
|
+
try {
|
|
15
|
+
return require('../rex-napi.node');
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Fall through
|
|
19
|
+
}
|
|
20
|
+
// Try the build output from cargo
|
|
21
|
+
try {
|
|
22
|
+
return require('../../crates/rex_napi/rex-napi.node');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Fall through
|
|
26
|
+
}
|
|
27
|
+
throw new Error(`Failed to load Rex native binding. ` +
|
|
28
|
+
`Tried: ${platformPackage}, ../rex-napi.node, ../../crates/rex_napi/rex-napi.node. ` +
|
|
29
|
+
`Make sure the native module is built (cd crates/rex_napi && npm run build-debug).`);
|
|
30
|
+
}
|
|
31
|
+
const binding = loadNativeBinding();
|
|
32
|
+
/**
|
|
33
|
+
* Create a new Rex application instance.
|
|
34
|
+
*
|
|
35
|
+
* Scans the pages directory, builds bundles, initializes the V8 isolate pool,
|
|
36
|
+
* and returns a ready-to-use RexInstance.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* import { createRex } from '@limlabs/rex/server'
|
|
41
|
+
*
|
|
42
|
+
* const rex = await createRex({ root: './my-app' })
|
|
43
|
+
* const handle = rex.getRequestHandler()
|
|
44
|
+
*
|
|
45
|
+
* Bun.serve({ fetch: handle })
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export const createRex = binding.createRex;
|
|
49
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AA+G9C,SAAS,iBAAiB;IACxB,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,eAAe,GAAG,gBAAgB,QAAQ,IAAI,IAAI,EAAE,CAAC;IAE3D,mEAAmE;IACnE,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,eAAe,CAAkB,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IAED,+CAA+C;IAC/C,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,kBAAkB,CAAkB,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IAED,kCAAkC;IAClC,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,qCAAqC,CAAkB,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,eAAe;IACjB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,qCAAqC;QACrC,UAAU,eAAe,2DAA2D;QACpF,mFAAmF,CACpF,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,GAAG,iBAAiB,EAAE,CAAC;AAEpC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,16 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@limlabs/rex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Rex - Next.js Pages Router reimplemented in Rust",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./server": {
|
|
13
|
+
"types": "./dist/server.d.ts",
|
|
14
|
+
"import": "./dist/server.js"
|
|
15
|
+
},
|
|
16
|
+
"./document": {
|
|
17
|
+
"types": "./dist/document.d.ts",
|
|
18
|
+
"import": "./dist/document.js"
|
|
19
|
+
},
|
|
20
|
+
"./link": {
|
|
21
|
+
"types": "./dist/link.d.ts",
|
|
22
|
+
"import": "./dist/link.js"
|
|
23
|
+
},
|
|
24
|
+
"./router": {
|
|
25
|
+
"types": "./dist/router.d.ts",
|
|
26
|
+
"import": "./dist/router.js"
|
|
27
|
+
},
|
|
28
|
+
"./image": {
|
|
29
|
+
"types": "./dist/image.d.ts",
|
|
30
|
+
"import": "./dist/image.js"
|
|
31
|
+
},
|
|
32
|
+
"./middleware": {
|
|
33
|
+
"types": "./src/middleware.d.ts",
|
|
34
|
+
"import": "../../runtime/server/middleware.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"prepublishOnly": "npm run build"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"@types/react": "^19.0.0",
|
|
44
|
+
"typescript": "^5.7.0"
|
|
45
|
+
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"oxlint": "^1.0.0"
|
|
11
48
|
},
|
|
12
49
|
"peerDependencies": {
|
|
13
|
-
"react": "^18.0.0",
|
|
14
|
-
"react-dom": "^18.0.0"
|
|
50
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
51
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
15
52
|
}
|
|
16
53
|
}
|
package/src/document.tsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface HtmlProps extends React.HTMLAttributes<HTMLHtmlElement> {
|
|
4
|
+
children?: React.ReactNode;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface HeadProps {
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Html({ children, ...props }: HtmlProps): React.ReactElement {
|
|
12
|
+
return React.createElement('html', props, children);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function Head({ children }: HeadProps): React.ReactElement {
|
|
16
|
+
return React.createElement('head', null, children);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function Main(): React.ReactElement {
|
|
20
|
+
return React.createElement('div', { id: '__rex' });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function NextScript(): null {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function Document(): React.ReactElement {
|
|
28
|
+
return React.createElement(
|
|
29
|
+
Html,
|
|
30
|
+
null,
|
|
31
|
+
React.createElement(Head, null),
|
|
32
|
+
React.createElement(
|
|
33
|
+
'body',
|
|
34
|
+
null,
|
|
35
|
+
React.createElement(Main, null),
|
|
36
|
+
React.createElement(NextScript, null)
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
}
|
package/src/image.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
interface ImageProps {
|
|
4
|
+
src: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
height?: number;
|
|
7
|
+
alt?: string;
|
|
8
|
+
quality?: number;
|
|
9
|
+
priority?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function buildSrc(src: string, w: number, q: number): string {
|
|
14
|
+
return '/_rex/image?url=' + encodeURIComponent(src) + '&w=' + w + '&q=' + q;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function Image(props: ImageProps): React.ReactElement {
|
|
18
|
+
const { src, width, height, alt, quality = 75, priority = false, className } = props;
|
|
19
|
+
|
|
20
|
+
const imgProps: React.ImgHTMLAttributes<HTMLImageElement> = {
|
|
21
|
+
alt: alt ?? '',
|
|
22
|
+
src: buildSrc(src, width ?? 1920, quality),
|
|
23
|
+
loading: priority ? 'eager' : 'lazy',
|
|
24
|
+
decoding: 'async',
|
|
25
|
+
style: { display: 'block', maxWidth: '100%', height: 'auto' },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (width !== undefined) imgProps.width = width;
|
|
29
|
+
if (height !== undefined) imgProps.height = height;
|
|
30
|
+
if (className) imgProps.className = className;
|
|
31
|
+
|
|
32
|
+
return React.createElement('img', imgProps);
|
|
33
|
+
}
|
package/src/{link.js → link.tsx}
RENAMED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { navigateTo } from './router.js';
|
|
3
3
|
|
|
4
|
+
interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
5
|
+
href: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
4
8
|
/**
|
|
5
|
-
* rex/link - Client-side navigation link
|
|
9
|
+
* rex/link - Client-side navigation link.
|
|
6
10
|
* Renders an <a> tag that intercepts clicks for SPA navigation.
|
|
7
11
|
*/
|
|
8
|
-
export default function Link({ href, children, target,
|
|
9
|
-
function handleClick(e) {
|
|
10
|
-
// Don't intercept if:
|
|
11
|
-
// - modifier key is held (user wants new tab)
|
|
12
|
-
// - target is set (external link behavior)
|
|
13
|
-
// - href is external
|
|
12
|
+
export default function Link({ href, children, target, ...rest }: LinkProps): React.ReactElement {
|
|
13
|
+
function handleClick(e: React.MouseEvent<HTMLAnchorElement>): void {
|
|
14
14
|
if (
|
|
15
15
|
e.metaKey ||
|
|
16
16
|
e.ctrlKey ||
|
|
@@ -29,12 +29,9 @@ export default function Link({ href, children, target, rel, className, style, ..
|
|
|
29
29
|
return React.createElement(
|
|
30
30
|
'a',
|
|
31
31
|
{
|
|
32
|
-
href
|
|
32
|
+
href,
|
|
33
33
|
onClick: handleClick,
|
|
34
|
-
target
|
|
35
|
-
rel: rel,
|
|
36
|
-
className: className,
|
|
37
|
-
style: style,
|
|
34
|
+
target,
|
|
38
35
|
...rest,
|
|
39
36
|
},
|
|
40
37
|
children
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface NextURL {
|
|
2
|
+
/** The full URL string. */
|
|
3
|
+
href: string;
|
|
4
|
+
/** The pathname portion of the URL (e.g. "/blog/hello"). */
|
|
5
|
+
pathname: string;
|
|
6
|
+
/** The search/query string (e.g. "?foo=bar"). */
|
|
7
|
+
search: string;
|
|
8
|
+
/** String representation of the URL. */
|
|
9
|
+
toString(): string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface NextRequest {
|
|
13
|
+
/** HTTP method (e.g. "GET", "POST"). */
|
|
14
|
+
method: string;
|
|
15
|
+
/** The full request URL string. */
|
|
16
|
+
url: string;
|
|
17
|
+
/** Parsed URL object with pathname, search, etc. */
|
|
18
|
+
nextUrl: NextURL;
|
|
19
|
+
/** Request headers as a plain object. */
|
|
20
|
+
headers: Record<string, string>;
|
|
21
|
+
/** Request cookies as a plain object. */
|
|
22
|
+
cookies: Record<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface NextResponseInit {
|
|
26
|
+
/** Headers to add to the response. */
|
|
27
|
+
headers?: Record<string, string>;
|
|
28
|
+
/** Headers/options to modify the downstream request. */
|
|
29
|
+
request?: {
|
|
30
|
+
headers?: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare class NextResponse {
|
|
35
|
+
/**
|
|
36
|
+
* Continue to the next handler, optionally modifying response or request headers.
|
|
37
|
+
*/
|
|
38
|
+
static next(init?: NextResponseInit): NextResponse;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Redirect the request to a different URL.
|
|
42
|
+
* @param url - The target URL (string or URL object).
|
|
43
|
+
* @param status - HTTP status code (default: 307).
|
|
44
|
+
*/
|
|
45
|
+
static redirect(url: string | URL, status?: number): NextResponse;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Rewrite the request to a different URL without changing the browser URL.
|
|
49
|
+
* @param url - The target URL (string or URL object).
|
|
50
|
+
*/
|
|
51
|
+
static rewrite(url: string | URL): NextResponse;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface MiddlewareConfig {
|
|
55
|
+
/**
|
|
56
|
+
* Route patterns to match. Supports:
|
|
57
|
+
* - Exact paths: "/about"
|
|
58
|
+
* - Dynamic segments: "/blog/:slug"
|
|
59
|
+
* - Catch-all: "/api/:path*"
|
|
60
|
+
*
|
|
61
|
+
* If omitted, middleware runs on all routes.
|
|
62
|
+
*/
|
|
63
|
+
matcher?: string[];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Middleware function signature.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { NextResponse } from '@limlabs/rex/middleware'
|
|
72
|
+
* import type { MiddlewareFunction } from '@limlabs/rex/middleware'
|
|
73
|
+
*
|
|
74
|
+
* export const middleware: MiddlewareFunction = (request) => {
|
|
75
|
+
* if (request.nextUrl.pathname === '/old') {
|
|
76
|
+
* return NextResponse.redirect(new URL('/new', request.url))
|
|
77
|
+
* }
|
|
78
|
+
* return NextResponse.next()
|
|
79
|
+
* }
|
|
80
|
+
*
|
|
81
|
+
* export const config = { matcher: ['/old', '/dashboard/:path*'] }
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
export type MiddlewareFunction = (request: NextRequest) => NextResponse;
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export interface RouterEvents {
|
|
2
|
+
on(event: string, handler: (...args: unknown[]) => void): void;
|
|
3
|
+
off(event: string, handler: (...args: unknown[]) => void): void;
|
|
4
|
+
emit(event: string, ...args: unknown[]): void;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface RexRouter {
|
|
8
|
+
pathname: string;
|
|
9
|
+
asPath: string;
|
|
10
|
+
query: Record<string, string>;
|
|
11
|
+
route: string;
|
|
12
|
+
push(url: string): void;
|
|
13
|
+
replace(url: string): void;
|
|
14
|
+
back(): void;
|
|
15
|
+
forward(): void;
|
|
16
|
+
reload(): void;
|
|
17
|
+
prefetch(url: string): void;
|
|
18
|
+
events: RouterEvents;
|
|
19
|
+
isReady: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface InternalRouter {
|
|
23
|
+
push(path: string): void;
|
|
24
|
+
replace(path: string): void;
|
|
25
|
+
back(): void;
|
|
26
|
+
forward(): void;
|
|
27
|
+
reload(): void;
|
|
28
|
+
prefetch(url: string): void;
|
|
29
|
+
events: RouterEvents;
|
|
30
|
+
state: {
|
|
31
|
+
pathname: string;
|
|
32
|
+
asPath: string;
|
|
33
|
+
query: Record<string, string>;
|
|
34
|
+
route: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
declare const window: Window & {
|
|
39
|
+
__REX_ROUTER?: InternalRouter;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
function getRouter(): InternalRouter | null {
|
|
43
|
+
return window.__REX_ROUTER ?? null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Navigate to a new path via client-side routing.
|
|
48
|
+
*/
|
|
49
|
+
export function navigateTo(path: string): void {
|
|
50
|
+
const r = getRouter();
|
|
51
|
+
if (r) {
|
|
52
|
+
r.push(path);
|
|
53
|
+
} else {
|
|
54
|
+
window.location.href = path;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function parseQuery(search: string): Record<string, string> {
|
|
59
|
+
const query: Record<string, string> = {};
|
|
60
|
+
if (!search || search.length <= 1) return query;
|
|
61
|
+
const pairs = search.substring(1).split('&');
|
|
62
|
+
for (const pair of pairs) {
|
|
63
|
+
const [key, value] = pair.split('=');
|
|
64
|
+
query[decodeURIComponent(key)] = decodeURIComponent(value ?? '');
|
|
65
|
+
}
|
|
66
|
+
return query;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* React hook that returns the current router instance.
|
|
71
|
+
*/
|
|
72
|
+
export function useRouter(): RexRouter {
|
|
73
|
+
const r = getRouter();
|
|
74
|
+
const noop = (): void => {};
|
|
75
|
+
|
|
76
|
+
if (r?.state) {
|
|
77
|
+
return {
|
|
78
|
+
pathname: r.state.pathname,
|
|
79
|
+
asPath: r.state.asPath,
|
|
80
|
+
query: r.state.query,
|
|
81
|
+
route: r.state.route,
|
|
82
|
+
push: r.push,
|
|
83
|
+
replace: r.replace,
|
|
84
|
+
back: r.back,
|
|
85
|
+
forward: r.forward,
|
|
86
|
+
reload: r.reload,
|
|
87
|
+
prefetch: r.prefetch,
|
|
88
|
+
events: r.events,
|
|
89
|
+
isReady: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
pathname: window.location.pathname,
|
|
95
|
+
asPath: window.location.pathname + window.location.search,
|
|
96
|
+
query: parseQuery(window.location.search),
|
|
97
|
+
route: window.location.pathname,
|
|
98
|
+
push: (url: string) => { window.location.href = url; },
|
|
99
|
+
replace: (url: string) => { window.location.replace(url); },
|
|
100
|
+
back: () => { history.back(); },
|
|
101
|
+
forward: () => { history.forward(); },
|
|
102
|
+
reload: () => { window.location.reload(); },
|
|
103
|
+
prefetch: noop,
|
|
104
|
+
events: { on: noop, off: noop, emit: noop },
|
|
105
|
+
isReady: false,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default useRouter;
|
package/src/server.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import { arch, platform } from 'node:process';
|
|
3
|
+
|
|
4
|
+
// --- Public types ---
|
|
5
|
+
|
|
6
|
+
export interface RexApiRequest {
|
|
7
|
+
/** HTTP method (e.g. "GET", "POST"). */
|
|
8
|
+
method: string;
|
|
9
|
+
/** The request URL path. */
|
|
10
|
+
url: string;
|
|
11
|
+
/** Request headers as a plain object. */
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
/** Parsed query parameters. */
|
|
14
|
+
query: Record<string, string>;
|
|
15
|
+
/** Request body (parsed JSON or raw string). */
|
|
16
|
+
body: unknown;
|
|
17
|
+
/** Request cookies as a plain object. */
|
|
18
|
+
cookies: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface RexApiResponse<T = unknown> {
|
|
22
|
+
/** Set the HTTP status code. */
|
|
23
|
+
status(code: number): RexApiResponse<T>;
|
|
24
|
+
/** Set a response header. */
|
|
25
|
+
setHeader(name: string, value: string): RexApiResponse<T>;
|
|
26
|
+
/** Send a JSON response. */
|
|
27
|
+
json(data: T): RexApiResponse<T>;
|
|
28
|
+
/** Send a string or object response. */
|
|
29
|
+
send(body: string | T): RexApiResponse<T>;
|
|
30
|
+
/** End the response. */
|
|
31
|
+
end(body?: string): RexApiResponse<T>;
|
|
32
|
+
/** Redirect to a URL, optionally with a status code. */
|
|
33
|
+
redirect(url: string): RexApiResponse<T>;
|
|
34
|
+
redirect(status: number, url: string): RexApiResponse<T>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** @deprecated Use `RexApiRequest` instead. */
|
|
38
|
+
export type NextApiRequest = RexApiRequest;
|
|
39
|
+
/** @deprecated Use `RexApiResponse` instead. */
|
|
40
|
+
export type NextApiResponse<T = unknown> = RexApiResponse<T>;
|
|
41
|
+
|
|
42
|
+
export interface RexOptions {
|
|
43
|
+
/** Path to the project root directory (containing pages/). */
|
|
44
|
+
root: string;
|
|
45
|
+
/** Whether to run in dev mode (enables HMR, error overlays). */
|
|
46
|
+
dev?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface RouteMatch {
|
|
50
|
+
/** The route pattern, e.g. "/blog/:slug" */
|
|
51
|
+
pattern: string;
|
|
52
|
+
/** The module name, e.g. "blog/[slug]" */
|
|
53
|
+
moduleName: string;
|
|
54
|
+
/** Matched params, e.g. { slug: "hello" } */
|
|
55
|
+
params: Record<string, string>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface PageResult {
|
|
59
|
+
/** Full HTML document. */
|
|
60
|
+
html: string;
|
|
61
|
+
/** HTTP status code. */
|
|
62
|
+
status: number;
|
|
63
|
+
/** Response headers. */
|
|
64
|
+
headers: Array<{ key: string; value: string }>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RexInstance {
|
|
68
|
+
/** Whether this instance is running in dev mode. */
|
|
69
|
+
readonly isDev: boolean;
|
|
70
|
+
/** The current build ID. */
|
|
71
|
+
readonly buildId: string;
|
|
72
|
+
/** The path to the static files directory (client JS/CSS bundles). */
|
|
73
|
+
readonly staticDir: string;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Match a URL path against the route trie.
|
|
77
|
+
* Returns the matched route info with params, or null if no match.
|
|
78
|
+
*/
|
|
79
|
+
matchRoute(path: string): RouteMatch | null;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Run getServerSideProps for a given path and return the result.
|
|
83
|
+
*/
|
|
84
|
+
getServerSideProps(path: string): Promise<Record<string, unknown>>;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Render a page to an HTML string with the given props.
|
|
88
|
+
*/
|
|
89
|
+
renderToString(path: string, props: Record<string, unknown>): Promise<string>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Render a full page (GSSP + SSR + document assembly).
|
|
93
|
+
* Returns HTML, status code, and headers.
|
|
94
|
+
*/
|
|
95
|
+
renderPage(path: string): Promise<PageResult>;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get a request handler function compatible with the Web Fetch API.
|
|
99
|
+
* Returns `(req: Request) => Promise<Response>`.
|
|
100
|
+
*/
|
|
101
|
+
getRequestHandler(): (req: Request) => Promise<Response>;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Shut down the Rex instance, releasing V8 isolates and other resources.
|
|
105
|
+
*/
|
|
106
|
+
close(): Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
interface NativeBinding {
|
|
110
|
+
createRex(options: RexOptions): Promise<RexInstance>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function loadNativeBinding(): NativeBinding {
|
|
114
|
+
const require = createRequire(import.meta.url);
|
|
115
|
+
const platformPackage = `@limlabs/rex-${platform}-${arch}`;
|
|
116
|
+
|
|
117
|
+
// Try platform-specific package first (for published npm packages)
|
|
118
|
+
try {
|
|
119
|
+
return require(platformPackage) as NativeBinding;
|
|
120
|
+
} catch {
|
|
121
|
+
// Fall through to local .node file (development)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Try local .node file (built with napi build)
|
|
125
|
+
try {
|
|
126
|
+
return require('../rex-napi.node') as NativeBinding;
|
|
127
|
+
} catch {
|
|
128
|
+
// Fall through
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Try the build output from cargo
|
|
132
|
+
try {
|
|
133
|
+
return require('../../crates/rex_napi/rex-napi.node') as NativeBinding;
|
|
134
|
+
} catch {
|
|
135
|
+
// Fall through
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Failed to load Rex native binding. ` +
|
|
140
|
+
`Tried: ${platformPackage}, ../rex-napi.node, ../../crates/rex_napi/rex-napi.node. ` +
|
|
141
|
+
`Make sure the native module is built (cd crates/rex_napi && npm run build-debug).`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const binding = loadNativeBinding();
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Create a new Rex application instance.
|
|
149
|
+
*
|
|
150
|
+
* Scans the pages directory, builds bundles, initializes the V8 isolate pool,
|
|
151
|
+
* and returns a ready-to-use RexInstance.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* import { createRex } from '@limlabs/rex/server'
|
|
156
|
+
*
|
|
157
|
+
* const rex = await createRex({ root: './my-app' })
|
|
158
|
+
* const handle = rex.getRequestHandler()
|
|
159
|
+
*
|
|
160
|
+
* Bun.serve({ fetch: handle })
|
|
161
|
+
* ```
|
|
162
|
+
*/
|
|
163
|
+
export const createRex = binding.createRex;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"declaration": true,
|
|
12
|
+
"declarationMap": true,
|
|
13
|
+
"sourceMap": true,
|
|
14
|
+
"outDir": "dist",
|
|
15
|
+
"rootDir": "src"
|
|
16
|
+
},
|
|
17
|
+
"include": ["src"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|
package/src/document.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* rex/document exports
|
|
5
|
-
* These components are meaningful during SSR and provide structure for the HTML document.
|
|
6
|
-
* On the client side, they are essentially pass-throughs.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
export function Html({ children, ...props }) {
|
|
10
|
-
return React.createElement('html', props, children);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function Head({ children }) {
|
|
14
|
-
return React.createElement('head', null, children);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function Main() {
|
|
18
|
-
// During SSR, this is replaced with the actual page content
|
|
19
|
-
return React.createElement('div', { id: '__rex' });
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function NextScript() {
|
|
23
|
-
// During SSR, this is replaced with the actual script tags
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Default document component
|
|
28
|
-
export default function Document() {
|
|
29
|
-
return React.createElement(
|
|
30
|
-
Html,
|
|
31
|
-
null,
|
|
32
|
-
React.createElement(Head, null),
|
|
33
|
-
React.createElement(
|
|
34
|
-
'body',
|
|
35
|
-
null,
|
|
36
|
-
React.createElement(Main, null),
|
|
37
|
-
React.createElement(NextScript, null)
|
|
38
|
-
)
|
|
39
|
-
);
|
|
40
|
-
}
|
package/src/router.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* rex/router - Client-side router
|
|
3
|
-
* Handles SPA navigation by fetching page data and re-rendering.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
let _initialized = false;
|
|
7
|
-
let _currentPath = null;
|
|
8
|
-
let _buildId = null;
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Initialize the client-side router.
|
|
12
|
-
* Called automatically on page load.
|
|
13
|
-
*/
|
|
14
|
-
export function initializeRouter() {
|
|
15
|
-
if (_initialized) return;
|
|
16
|
-
_initialized = true;
|
|
17
|
-
|
|
18
|
-
_currentPath = window.location.pathname;
|
|
19
|
-
_buildId = window.__REX_BUILD_ID__ || '';
|
|
20
|
-
|
|
21
|
-
// Listen for browser back/forward
|
|
22
|
-
window.addEventListener('popstate', function(event) {
|
|
23
|
-
var path = window.location.pathname;
|
|
24
|
-
if (path !== _currentPath) {
|
|
25
|
-
_currentPath = path;
|
|
26
|
-
loadPage(path, false);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Navigate to a new path via client-side routing.
|
|
33
|
-
*/
|
|
34
|
-
export function navigateTo(path) {
|
|
35
|
-
if (path === _currentPath) return;
|
|
36
|
-
|
|
37
|
-
_currentPath = path;
|
|
38
|
-
window.history.pushState(null, '', path);
|
|
39
|
-
loadPage(path, true);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get the current route information (React hook style, but simplified).
|
|
44
|
-
*/
|
|
45
|
-
export function useRouter() {
|
|
46
|
-
return {
|
|
47
|
-
pathname: window.location.pathname,
|
|
48
|
-
query: parseQuery(window.location.search),
|
|
49
|
-
push: navigateTo,
|
|
50
|
-
back: function() { window.history.back(); },
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
async function loadPage(path, isNavigation) {
|
|
55
|
-
try {
|
|
56
|
-
// Fetch page data
|
|
57
|
-
var dataPath = path === '/' ? '/index' : path;
|
|
58
|
-
var dataUrl = '/_rex/data/' + _buildId + dataPath + '.json';
|
|
59
|
-
|
|
60
|
-
var response = await fetch(dataUrl);
|
|
61
|
-
|
|
62
|
-
if (response.status === 404) {
|
|
63
|
-
// Build ID mismatch or route not found - full reload
|
|
64
|
-
window.location.href = path;
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
if (!response.ok) {
|
|
69
|
-
throw new Error('Failed to fetch page data: ' + response.status);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
var data = await response.json();
|
|
73
|
-
|
|
74
|
-
if (data.redirect) {
|
|
75
|
-
navigateTo(data.redirect.destination);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (data.notFound) {
|
|
80
|
-
// Show 404
|
|
81
|
-
window.location.href = path;
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// For the prototype, we do a full page reload
|
|
86
|
-
// A full implementation would dynamically import the page module
|
|
87
|
-
// and re-render using window.__REX_ROOT__.render()
|
|
88
|
-
window.location.href = path;
|
|
89
|
-
|
|
90
|
-
} catch (err) {
|
|
91
|
-
console.error('[Rex Router] Navigation error:', err);
|
|
92
|
-
window.location.href = path;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function parseQuery(search) {
|
|
97
|
-
var query = {};
|
|
98
|
-
if (!search || search.length <= 1) return query;
|
|
99
|
-
var pairs = search.substring(1).split('&');
|
|
100
|
-
for (var i = 0; i < pairs.length; i++) {
|
|
101
|
-
var pair = pairs[i].split('=');
|
|
102
|
-
query[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || '');
|
|
103
|
-
}
|
|
104
|
-
return query;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Auto-initialize when loaded in browser
|
|
108
|
-
if (typeof window !== 'undefined') {
|
|
109
|
-
if (document.readyState === 'loading') {
|
|
110
|
-
document.addEventListener('DOMContentLoaded', initializeRouter);
|
|
111
|
-
} else {
|
|
112
|
-
initializeRouter();
|
|
113
|
-
}
|
|
114
|
-
}
|