murasaki 0.2.0 → 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/bin/murasaki.js +8 -3
- package/dist/cli/colors.d.ts +13 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +14 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/log.d.ts +14 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +25 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/components/Link.d.ts +9 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +15 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/dev.d.ts +2 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +41 -0
- package/dist/dev.js.map +1 -0
- package/dist/env.d.ts +15 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +43 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx/index.d.ts +3 -0
- package/dist/jsx/index.d.ts.map +1 -0
- package/dist/jsx/index.js +3 -0
- package/dist/jsx/index.js.map +1 -0
- package/dist/jsx/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/jsx/jsx-dev-runtime.js +7 -0
- package/dist/jsx/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx/jsx-runtime.d.ts +2 -0
- package/dist/jsx/jsx-runtime.d.ts.map +1 -0
- package/{src/jsx/jsx-runtime.ts → dist/jsx/jsx-runtime.js} +4 -3
- package/dist/jsx/jsx-runtime.js.map +1 -0
- package/dist/jsx/runtime.d.ts +24 -0
- package/dist/jsx/runtime.d.ts.map +1 -0
- package/dist/jsx/runtime.js +273 -0
- package/dist/jsx/runtime.js.map +1 -0
- package/dist/jsx/types.d.ts +30 -0
- package/dist/jsx/types.d.ts.map +1 -0
- package/dist/jsx/types.js +3 -0
- package/dist/jsx/types.js.map +1 -0
- package/dist/runtime/hmr.d.ts +2 -0
- package/dist/runtime/hmr.d.ts.map +1 -0
- package/dist/runtime/hmr.js +28 -0
- package/dist/runtime/hmr.js.map +1 -0
- package/dist/runtime/render.d.ts +3 -0
- package/dist/runtime/render.d.ts.map +1 -0
- package/dist/runtime/render.js +208 -0
- package/dist/runtime/render.js.map +1 -0
- package/dist/runtime/routes.d.ts +10 -0
- package/dist/runtime/routes.d.ts.map +1 -0
- package/dist/runtime/routes.js +65 -0
- package/dist/runtime/routes.js.map +1 -0
- package/dist/runtime/shortcuts.d.ts +8 -0
- package/dist/runtime/shortcuts.d.ts.map +1 -0
- package/dist/runtime/shortcuts.js +29 -0
- package/dist/runtime/shortcuts.js.map +1 -0
- package/dist/runtime/window.d.ts +10 -0
- package/dist/runtime/window.d.ts.map +1 -0
- package/dist/runtime/window.js +93 -0
- package/dist/runtime/window.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +12 -10
- package/src/cli/colors.ts +0 -16
- package/src/cli/log.ts +0 -42
- package/src/components/Link.tsx +0 -25
- package/src/dev.tsx +0 -60
- package/src/env.ts +0 -48
- package/src/index.ts +0 -24
- package/src/jsx/index.ts +0 -21
- package/src/jsx/jsx-dev-runtime.ts +0 -6
- package/src/jsx/runtime.ts +0 -298
- package/src/jsx/types.ts +0 -36
- package/src/runtime/hmr.ts +0 -26
- package/src/runtime/render.tsx +0 -225
- package/src/runtime/routes.ts +0 -73
- package/src/runtime/shortcuts.ts +0 -31
- package/src/runtime/window.ts +0 -94
- package/src/types.ts +0 -22
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// murasaki/jsx — SSR-only JSX runtime.
|
|
2
|
+
//
|
|
3
|
+
// Inspired by hono/jsx but trimmed for desktop-server use:
|
|
4
|
+
// - no hooks (the view is rendered once per HMR cycle, no client state)
|
|
5
|
+
// - no DOM renderer (we ship HTML to the OS WebView and that's it)
|
|
6
|
+
// - no streaming / Suspense (single render → loadHtml())
|
|
7
|
+
// - React-compatible enough to swap in for renderToStaticMarkup
|
|
8
|
+
//
|
|
9
|
+
// Two-step pipeline:
|
|
10
|
+
// 1. jsx(tag, props) → JSXNode (tree)
|
|
11
|
+
// 2. JSXNode.toString() → HTML string
|
|
12
|
+
//
|
|
13
|
+
// User code that runs is the *user's* JSX (transformed to jsx() calls
|
|
14
|
+
// by tsx/esbuild with `jsxImportSource: "murasaki"`).
|
|
15
|
+
// ── HTML escape ──────────────────────────────────────────────────────
|
|
16
|
+
const AMP = /&/g;
|
|
17
|
+
const LT = /</g;
|
|
18
|
+
const GT = />/g;
|
|
19
|
+
const QT = /"/g;
|
|
20
|
+
function escapeHtml(s) {
|
|
21
|
+
return s.replace(AMP, '&').replace(LT, '<').replace(GT, '>');
|
|
22
|
+
}
|
|
23
|
+
function escapeAttr(s) {
|
|
24
|
+
return s.replace(AMP, '&').replace(QT, '"');
|
|
25
|
+
}
|
|
26
|
+
// ── Void elements (no closing tag) ────────────────────────────────────
|
|
27
|
+
const VOID_ELEMENTS = new Set([
|
|
28
|
+
'area',
|
|
29
|
+
'base',
|
|
30
|
+
'br',
|
|
31
|
+
'col',
|
|
32
|
+
'embed',
|
|
33
|
+
'hr',
|
|
34
|
+
'img',
|
|
35
|
+
'input',
|
|
36
|
+
'link',
|
|
37
|
+
'meta',
|
|
38
|
+
'source',
|
|
39
|
+
'track',
|
|
40
|
+
'wbr',
|
|
41
|
+
]);
|
|
42
|
+
// ── Attribute name normalization (React → HTML) ───────────────────────
|
|
43
|
+
const ATTR_ALIAS = {
|
|
44
|
+
className: 'class',
|
|
45
|
+
htmlFor: 'for',
|
|
46
|
+
charSet: 'charset',
|
|
47
|
+
crossOrigin: 'crossorigin',
|
|
48
|
+
httpEquiv: 'http-equiv',
|
|
49
|
+
itemProp: 'itemprop',
|
|
50
|
+
fetchPriority: 'fetchpriority',
|
|
51
|
+
noModule: 'nomodule',
|
|
52
|
+
formAction: 'formaction',
|
|
53
|
+
acceptCharset: 'accept-charset',
|
|
54
|
+
autoComplete: 'autocomplete',
|
|
55
|
+
autoFocus: 'autofocus',
|
|
56
|
+
autoPlay: 'autoplay',
|
|
57
|
+
contentEditable: 'contenteditable',
|
|
58
|
+
defaultValue: 'value',
|
|
59
|
+
defaultChecked: 'checked',
|
|
60
|
+
encType: 'enctype',
|
|
61
|
+
formMethod: 'formmethod',
|
|
62
|
+
formNoValidate: 'formnovalidate',
|
|
63
|
+
formTarget: 'formtarget',
|
|
64
|
+
maxLength: 'maxlength',
|
|
65
|
+
minLength: 'minlength',
|
|
66
|
+
noValidate: 'novalidate',
|
|
67
|
+
readOnly: 'readonly',
|
|
68
|
+
rowSpan: 'rowspan',
|
|
69
|
+
colSpan: 'colspan',
|
|
70
|
+
spellCheck: 'spellcheck',
|
|
71
|
+
tabIndex: 'tabindex',
|
|
72
|
+
useMap: 'usemap',
|
|
73
|
+
srcDoc: 'srcdoc',
|
|
74
|
+
srcSet: 'srcset',
|
|
75
|
+
hrefLang: 'hreflang',
|
|
76
|
+
dateTime: 'datetime',
|
|
77
|
+
enterKeyHint: 'enterkeyhint',
|
|
78
|
+
inputMode: 'inputmode',
|
|
79
|
+
};
|
|
80
|
+
function normalizeAttrName(k) {
|
|
81
|
+
return ATTR_ALIAS[k] || k;
|
|
82
|
+
}
|
|
83
|
+
// ── Style object → CSS string ─────────────────────────────────────────
|
|
84
|
+
function camelToKebab(k) {
|
|
85
|
+
// Leave already-kebab keys and CSS custom props (--foo) alone.
|
|
86
|
+
if (k[0] === '-' || !/[A-Z]/.test(k))
|
|
87
|
+
return k;
|
|
88
|
+
return k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
|
|
89
|
+
}
|
|
90
|
+
// CSS properties that take unitless numbers (subset of React's list).
|
|
91
|
+
const UNITLESS = new Set([
|
|
92
|
+
'animationIterationCount',
|
|
93
|
+
'borderImageOutset',
|
|
94
|
+
'borderImageSlice',
|
|
95
|
+
'borderImageWidth',
|
|
96
|
+
'boxFlex',
|
|
97
|
+
'boxFlexGroup',
|
|
98
|
+
'boxOrdinalGroup',
|
|
99
|
+
'columnCount',
|
|
100
|
+
'columns',
|
|
101
|
+
'flex',
|
|
102
|
+
'flexGrow',
|
|
103
|
+
'flexShrink',
|
|
104
|
+
'fontWeight',
|
|
105
|
+
'gridArea',
|
|
106
|
+
'gridColumn',
|
|
107
|
+
'gridColumnEnd',
|
|
108
|
+
'gridColumnStart',
|
|
109
|
+
'gridRow',
|
|
110
|
+
'gridRowEnd',
|
|
111
|
+
'gridRowStart',
|
|
112
|
+
'lineClamp',
|
|
113
|
+
'lineHeight',
|
|
114
|
+
'opacity',
|
|
115
|
+
'order',
|
|
116
|
+
'orphans',
|
|
117
|
+
'tabSize',
|
|
118
|
+
'widows',
|
|
119
|
+
'zIndex',
|
|
120
|
+
'zoom',
|
|
121
|
+
]);
|
|
122
|
+
function styleObjToString(obj) {
|
|
123
|
+
let out = '';
|
|
124
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
125
|
+
if (v == null || v === false)
|
|
126
|
+
continue;
|
|
127
|
+
let value;
|
|
128
|
+
if (typeof v === 'number') {
|
|
129
|
+
value = UNITLESS.has(k) ? String(v) : `${v}px`;
|
|
130
|
+
}
|
|
131
|
+
else if (typeof v === 'string') {
|
|
132
|
+
value = v;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
out += `${out ? ';' : ''}${camelToKebab(k)}:${value}`;
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
// ── Attribute rendering ──────────────────────────────────────────────
|
|
142
|
+
function renderAttrs(props) {
|
|
143
|
+
let out = '';
|
|
144
|
+
for (const k in props) {
|
|
145
|
+
if (k === 'children' || k === 'key' || k === 'ref' || k === '__source' || k === '__self')
|
|
146
|
+
continue;
|
|
147
|
+
const v = props[k];
|
|
148
|
+
if (v == null || v === false)
|
|
149
|
+
continue;
|
|
150
|
+
const name = normalizeAttrName(k);
|
|
151
|
+
// Boolean attribute
|
|
152
|
+
if (v === true) {
|
|
153
|
+
out += ` ${name}`;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
// Inline style object
|
|
157
|
+
if (k === 'style' && typeof v === 'object') {
|
|
158
|
+
const css = styleObjToString(v);
|
|
159
|
+
if (css)
|
|
160
|
+
out += ` style="${escapeAttr(css)}"`;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// dangerouslySetInnerHTML is handled in JSXNode.toString(), skip here
|
|
164
|
+
if (k === 'dangerouslySetInnerHTML')
|
|
165
|
+
continue;
|
|
166
|
+
out += ` ${name}="${escapeAttr(String(v))}"`;
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
// ── Raw HTML escape hatch (pre-rendered HTML as a child) ─────────────
|
|
171
|
+
class RawHtml {
|
|
172
|
+
__isJSXNode = true;
|
|
173
|
+
tag = '__raw__';
|
|
174
|
+
props = {};
|
|
175
|
+
children = [];
|
|
176
|
+
html;
|
|
177
|
+
constructor(html) {
|
|
178
|
+
this.html = html;
|
|
179
|
+
}
|
|
180
|
+
toString() {
|
|
181
|
+
return this.html;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Wrap pre-rendered HTML so it's emitted verbatim as a JSX child. */
|
|
185
|
+
export function raw(html) {
|
|
186
|
+
return new RawHtml(html);
|
|
187
|
+
}
|
|
188
|
+
// ── Children rendering ───────────────────────────────────────────────
|
|
189
|
+
function renderChild(c) {
|
|
190
|
+
if (c == null || c === false || c === true)
|
|
191
|
+
return '';
|
|
192
|
+
if (typeof c === 'string')
|
|
193
|
+
return escapeHtml(c);
|
|
194
|
+
if (typeof c === 'number' || typeof c === 'bigint')
|
|
195
|
+
return String(c);
|
|
196
|
+
if (Array.isArray(c)) {
|
|
197
|
+
let s = '';
|
|
198
|
+
for (const item of c)
|
|
199
|
+
s += renderChild(item);
|
|
200
|
+
return s;
|
|
201
|
+
}
|
|
202
|
+
if (isJSXNode(c))
|
|
203
|
+
return c.toString();
|
|
204
|
+
return '';
|
|
205
|
+
}
|
|
206
|
+
// ── JSXNode ──────────────────────────────────────────────────────────
|
|
207
|
+
export class JSXNode {
|
|
208
|
+
__isJSXNode = true;
|
|
209
|
+
tag;
|
|
210
|
+
props;
|
|
211
|
+
children;
|
|
212
|
+
constructor(tag, props, children) {
|
|
213
|
+
this.tag = tag;
|
|
214
|
+
this.props = props;
|
|
215
|
+
this.children = children;
|
|
216
|
+
}
|
|
217
|
+
toString() {
|
|
218
|
+
const { tag, props, children } = this;
|
|
219
|
+
// Fragment / functional component
|
|
220
|
+
if (typeof tag === 'function') {
|
|
221
|
+
// Always pass children via props (React-compat)
|
|
222
|
+
const merged = { ...props, children: children.length === 1 ? children[0] : children };
|
|
223
|
+
const result = tag(merged);
|
|
224
|
+
return renderChild(result);
|
|
225
|
+
}
|
|
226
|
+
// Intrinsic element
|
|
227
|
+
const attrs = renderAttrs(props);
|
|
228
|
+
// dangerouslySetInnerHTML overrides children
|
|
229
|
+
const dsi = props.dangerouslySetInnerHTML;
|
|
230
|
+
if (dsi && typeof dsi.__html === 'string') {
|
|
231
|
+
return `<${tag}${attrs}>${dsi.__html}</${tag}>`;
|
|
232
|
+
}
|
|
233
|
+
if (VOID_ELEMENTS.has(tag)) {
|
|
234
|
+
// Self-closing for void elements
|
|
235
|
+
return `<${tag}${attrs}/>`;
|
|
236
|
+
}
|
|
237
|
+
let childHtml = '';
|
|
238
|
+
for (const c of children)
|
|
239
|
+
childHtml += renderChild(c);
|
|
240
|
+
return `<${tag}${attrs}>${childHtml}</${tag}>`;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
export function isJSXNode(v) {
|
|
244
|
+
return typeof v === 'object' && v !== null && v.__isJSXNode === true;
|
|
245
|
+
}
|
|
246
|
+
// ── Public factory (React-compat: createElement / jsx) ────────────────
|
|
247
|
+
export function jsx(tag, props, ..._restChildren) {
|
|
248
|
+
const p = props ?? {};
|
|
249
|
+
const rawChildren = p.children;
|
|
250
|
+
const children = Array.isArray(rawChildren)
|
|
251
|
+
? rawChildren
|
|
252
|
+
: rawChildren != null
|
|
253
|
+
? [rawChildren]
|
|
254
|
+
: [];
|
|
255
|
+
// Strip children from props (it's stored separately)
|
|
256
|
+
const { children: _drop, ...rest } = p;
|
|
257
|
+
return new JSXNode(tag, rest, children);
|
|
258
|
+
}
|
|
259
|
+
/** React-compatible alias. */
|
|
260
|
+
export const createElement = jsx;
|
|
261
|
+
/** Fragment — renders children without a wrapper tag. */
|
|
262
|
+
export function Fragment(props) {
|
|
263
|
+
return props.children ?? null;
|
|
264
|
+
}
|
|
265
|
+
/** Check if something is a JSX element (React.isValidElement compat). */
|
|
266
|
+
export function isValidElement(v) {
|
|
267
|
+
return isJSXNode(v);
|
|
268
|
+
}
|
|
269
|
+
/** Convert any value (JSXNode, string, array, etc.) to an HTML string. */
|
|
270
|
+
export function renderToString(value) {
|
|
271
|
+
return renderChild(value);
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/jsx/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type Props = Record<string, unknown>;
|
|
2
|
+
export type Child = string | number | bigint | boolean | null | undefined | JSXNodeLike | Child[];
|
|
3
|
+
export interface JSXNodeLike {
|
|
4
|
+
readonly __isJSXNode: true;
|
|
5
|
+
tag: string | Component;
|
|
6
|
+
props: Props;
|
|
7
|
+
children: Child[];
|
|
8
|
+
toString(): string;
|
|
9
|
+
}
|
|
10
|
+
export type Component<P = Props> = (props: P & {
|
|
11
|
+
children?: Child;
|
|
12
|
+
}) => Child;
|
|
13
|
+
/** React-compatible alias used by most user code. */
|
|
14
|
+
export type FC<P = Props> = Component<P>;
|
|
15
|
+
/** For component refs / cloning. */
|
|
16
|
+
export type Element = JSXNodeLike;
|
|
17
|
+
declare global {
|
|
18
|
+
namespace JSX {
|
|
19
|
+
type Element = JSXNodeLike;
|
|
20
|
+
interface ElementChildrenAttribute {
|
|
21
|
+
children: object;
|
|
22
|
+
}
|
|
23
|
+
interface IntrinsicElements {
|
|
24
|
+
[tagName: string]: Record<string, unknown> & {
|
|
25
|
+
children?: Child;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/jsx/types.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAE3C,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,KAAK,EAAE,CAAA;AAEjG,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAA;IAC1B,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IACvB,KAAK,EAAE,KAAK,CAAA;IACZ,QAAQ,EAAE,KAAK,EAAE,CAAA;IACjB,QAAQ,IAAI,MAAM,CAAA;CACnB;AAED,MAAM,MAAM,SAAS,CAAC,CAAC,GAAG,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,KAAK,CAAA;CAAE,KAAK,KAAK,CAAA;AAE7E,qDAAqD;AACrD,MAAM,MAAM,EAAE,CAAC,CAAC,GAAG,KAAK,IAAI,SAAS,CAAC,CAAC,CAAC,CAAA;AAExC,oCAAoC;AACpC,MAAM,MAAM,OAAO,GAAG,WAAW,CAAA;AAEjC,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,GAAG,CAAC;QACZ,KAAK,OAAO,GAAG,WAAW,CAAA;QAC1B,UAAU,wBAAwB;YAChC,QAAQ,EAAE,MAAM,CAAA;SACjB;QAID,UAAU,iBAAiB;YACzB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;gBAAE,QAAQ,CAAC,EAAE,KAAK,CAAA;aAAE,CAAA;SAClE;KACF;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/jsx/types.ts"],"names":[],"mappings":"AAAA,qCAAqC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmr.d.ts","sourceRoot":"","sources":["../../src/runtime/hmr.ts"],"names":[],"mappings":"AAMA,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAmBnE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// File watcher for src/ — debounces multi-event saves and triggers reload.
|
|
2
|
+
import { existsSync, watch } from 'node:fs';
|
|
3
|
+
import { printHint } from "../cli/log.js";
|
|
4
|
+
import { SRC_DIR } from "../env.js";
|
|
5
|
+
export function setupHmr(onChange) {
|
|
6
|
+
if (!existsSync(SRC_DIR)) {
|
|
7
|
+
printHint('src/ directory not found — nothing to watch');
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
let debounce = null;
|
|
11
|
+
let lastFile = '';
|
|
12
|
+
try {
|
|
13
|
+
watch(SRC_DIR, { recursive: true }, (_event, filename) => {
|
|
14
|
+
if (!filename)
|
|
15
|
+
return;
|
|
16
|
+
if (debounce)
|
|
17
|
+
clearTimeout(debounce);
|
|
18
|
+
lastFile = filename.toString();
|
|
19
|
+
debounce = setTimeout(() => {
|
|
20
|
+
onChange(lastFile);
|
|
21
|
+
}, 80);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
printHint(`HMR watcher failed: ${e.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=hmr.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hmr.js","sourceRoot":"","sources":["../../src/runtime/hmr.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,MAAM,UAAU,QAAQ,CAAC,QAAoC;IAC3D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,SAAS,CAAC,6CAA6C,CAAC,CAAA;QACxD,OAAM;IACR,CAAC;IACD,IAAI,QAAQ,GAA0B,IAAI,CAAA;IAC1C,IAAI,QAAQ,GAAG,EAAE,CAAA;IACjB,IAAI,CAAC;QACH,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE;YACvD,IAAI,CAAC,QAAQ;gBAAE,OAAM;YACrB,IAAI,QAAQ;gBAAE,YAAY,CAAC,QAAQ,CAAC,CAAA;YACpC,QAAQ,GAAG,QAAQ,CAAC,QAAQ,EAAE,CAAA;YAC9B,QAAQ,GAAG,UAAU,CAAC,GAAG,EAAE;gBACzB,QAAQ,CAAC,QAAQ,CAAC,CAAA;YACpB,CAAC,EAAE,EAAE,CAAC,CAAA;QACR,CAAC,CAAC,CAAA;IACJ,CAAC;IAAC,OAAO,CAAM,EAAE,CAAC;QAChB,SAAS,CAAC,uBAAuB,CAAC,CAAC,OAAO,EAAE,CAAC,CAAA;IAC/C,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/runtime/render.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAkJ/C,wBAAsB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CAUvD"}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// Multi-route renderer.
|
|
2
|
+
//
|
|
3
|
+
// Pipeline:
|
|
4
|
+
// 1. discover all src/app/<...>/page.tsx
|
|
5
|
+
// 2. render each page wrapped in its nested layouts (NOT the root layout)
|
|
6
|
+
// 3. render the root layout once with a switcher block as its children
|
|
7
|
+
// 4. inject metadata (<title>, <meta description>) + globals.css into <head>
|
|
8
|
+
// 5. inject a tiny navigation script that listens to hash changes and
|
|
9
|
+
// intercepts <Link> clicks
|
|
10
|
+
//
|
|
11
|
+
// Legacy single-page (src/app.tsx + src/layout.tsx) is still supported:
|
|
12
|
+
// if src/app/ doesn't exist, we render the legacy convention.
|
|
13
|
+
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
14
|
+
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
15
|
+
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
16
|
+
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
return path;
|
|
20
|
+
};
|
|
21
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { pathToFileURL } from 'node:url';
|
|
23
|
+
import { APP_DIR, APP_GLOBALS_CSS, LEGACY_APP_PATH, LEGACY_GLOBALS_CSS, LEGACY_LAYOUT_PATH, } from "../env.js";
|
|
24
|
+
import { jsx, raw, renderToString } from "../jsx/runtime.js";
|
|
25
|
+
import { discoverRoutes } from "./routes.js";
|
|
26
|
+
const NO_APP_HTML = '<!doctype html><html><body style="font-family:system-ui;padding:40px;">' +
|
|
27
|
+
'<h1 style="color:#A855F7">No app found</h1>' +
|
|
28
|
+
'<p>Create <code>src/app/page.tsx</code> and the window will reload.</p></body></html>';
|
|
29
|
+
function escapeHtml(s) {
|
|
30
|
+
return s
|
|
31
|
+
.replace(/&/g, '&')
|
|
32
|
+
.replace(/</g, '<')
|
|
33
|
+
.replace(/>/g, '>')
|
|
34
|
+
.replace(/"/g, '"');
|
|
35
|
+
}
|
|
36
|
+
async function dynImport(path) {
|
|
37
|
+
const url = pathToFileURL(path).href + `?v=${Date.now()}`;
|
|
38
|
+
return import(__rewriteRelativeImportExtension(url));
|
|
39
|
+
}
|
|
40
|
+
async function loadModule(path) {
|
|
41
|
+
if (!existsSync(path))
|
|
42
|
+
return null;
|
|
43
|
+
const mod = await dynImport(path);
|
|
44
|
+
if (!mod.default)
|
|
45
|
+
return null;
|
|
46
|
+
return mod;
|
|
47
|
+
}
|
|
48
|
+
// ── Page rendering ──────────────────────────────────────────────────
|
|
49
|
+
async function renderPageInner(route, rootLayoutFile) {
|
|
50
|
+
const pageMod = await loadModule(route.pageFile);
|
|
51
|
+
if (!pageMod)
|
|
52
|
+
return '';
|
|
53
|
+
let tree = jsx(pageMod.default, null);
|
|
54
|
+
// Wrap in nested layouts, innermost first (i.e. skip root layout — it wraps everything later)
|
|
55
|
+
// route.layoutFiles: outermost first → so iterate from end backwards excluding root
|
|
56
|
+
const layoutsToApply = route.layoutFiles.filter((f) => f !== rootLayoutFile);
|
|
57
|
+
// Apply from innermost (last in array) outward (first in array) so the
|
|
58
|
+
// outer layout wraps the inner: <Outer><Inner><Page/></Inner></Outer>
|
|
59
|
+
for (let i = layoutsToApply.length - 1; i >= 0; i--) {
|
|
60
|
+
const layoutMod = await loadModule(layoutsToApply[i]);
|
|
61
|
+
if (!layoutMod)
|
|
62
|
+
continue;
|
|
63
|
+
tree = jsx(layoutMod.default, { children: tree });
|
|
64
|
+
}
|
|
65
|
+
return renderToString(tree);
|
|
66
|
+
}
|
|
67
|
+
// ── Root layout + metadata ──────────────────────────────────────────
|
|
68
|
+
async function renderRootLayout(rootLayout, body) {
|
|
69
|
+
if (!rootLayout) {
|
|
70
|
+
// Fallback root if user didn't write src/app/layout.tsx
|
|
71
|
+
const fallback = jsx('html', {
|
|
72
|
+
lang: 'en',
|
|
73
|
+
children: [
|
|
74
|
+
jsx('head', { children: jsx('meta', { charSet: 'utf-8' }) }),
|
|
75
|
+
jsx('body', { children: body }),
|
|
76
|
+
],
|
|
77
|
+
});
|
|
78
|
+
return renderToString(fallback);
|
|
79
|
+
}
|
|
80
|
+
const tree = jsx(rootLayout.default, { children: body });
|
|
81
|
+
return renderToString(tree);
|
|
82
|
+
}
|
|
83
|
+
// ── Navigation script (injected once) ───────────────────────────────
|
|
84
|
+
const NAV_SCRIPT = `
|
|
85
|
+
<script>
|
|
86
|
+
(function(){
|
|
87
|
+
function showRoute(path){
|
|
88
|
+
var blocks=document.querySelectorAll('[data-murasaki-route]');
|
|
89
|
+
var matched=false;
|
|
90
|
+
for(var i=0;i<blocks.length;i++){
|
|
91
|
+
var b=blocks[i];
|
|
92
|
+
if(b.getAttribute('data-murasaki-route')===path){
|
|
93
|
+
b.removeAttribute('hidden');matched=true;
|
|
94
|
+
} else {
|
|
95
|
+
b.setAttribute('hidden','');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if(!matched){
|
|
99
|
+
// fallback to "/"
|
|
100
|
+
var root=document.querySelector('[data-murasaki-route="/"]');
|
|
101
|
+
if(root)root.removeAttribute('hidden');
|
|
102
|
+
}
|
|
103
|
+
document.dispatchEvent(new CustomEvent('murasaki:navigate',{detail:{path:path}}));
|
|
104
|
+
}
|
|
105
|
+
function currentPath(){
|
|
106
|
+
var h=location.hash||'';
|
|
107
|
+
return h.charAt(0)==='#'?h.slice(1)||'/':'/'
|
|
108
|
+
}
|
|
109
|
+
window.addEventListener('hashchange',function(){showRoute(currentPath())});
|
|
110
|
+
document.addEventListener('click',function(e){
|
|
111
|
+
var t=e.target;
|
|
112
|
+
while(t&&t.nodeType===1){
|
|
113
|
+
if(t.tagName==='A'&&t.hasAttribute('data-murasaki-link')){
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
var href=t.getAttribute('data-murasaki-link');
|
|
116
|
+
if('#'+href!==location.hash){location.hash=href;}
|
|
117
|
+
else{showRoute(href);}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
t=t.parentNode;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// Initial render
|
|
124
|
+
showRoute(currentPath());
|
|
125
|
+
})();
|
|
126
|
+
</script>
|
|
127
|
+
`.trim();
|
|
128
|
+
// ── Globals.css discovery (app/ takes precedence over src/) ─────────
|
|
129
|
+
function loadGlobalsCss() {
|
|
130
|
+
for (const p of [APP_GLOBALS_CSS, LEGACY_GLOBALS_CSS]) {
|
|
131
|
+
if (existsSync(p)) {
|
|
132
|
+
try {
|
|
133
|
+
return readFileSync(p, 'utf8');
|
|
134
|
+
}
|
|
135
|
+
catch { }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return '';
|
|
139
|
+
}
|
|
140
|
+
// ── Head injection ──────────────────────────────────────────────────
|
|
141
|
+
function injectHead(html, metadata, css) {
|
|
142
|
+
const headInjects = [];
|
|
143
|
+
if (metadata?.title && !/<title>.*?<\/title>/i.test(html)) {
|
|
144
|
+
headInjects.push(`<title>${escapeHtml(metadata.title)}</title>`);
|
|
145
|
+
}
|
|
146
|
+
if (metadata?.description && !/<meta[^>]+name=["']description["']/i.test(html)) {
|
|
147
|
+
headInjects.push(`<meta name="description" content="${escapeHtml(metadata.description)}">`);
|
|
148
|
+
}
|
|
149
|
+
if (css) {
|
|
150
|
+
headInjects.push(`<style data-murasaki="globals.css">${css}</style>`);
|
|
151
|
+
}
|
|
152
|
+
if (!headInjects.length)
|
|
153
|
+
return html;
|
|
154
|
+
const blob = headInjects.join('');
|
|
155
|
+
if (html.includes('</head>'))
|
|
156
|
+
return html.replace('</head>', blob + '</head>');
|
|
157
|
+
return html.replace('<body', blob + '<body');
|
|
158
|
+
}
|
|
159
|
+
// ── Main entry ──────────────────────────────────────────────────────
|
|
160
|
+
export async function renderApp() {
|
|
161
|
+
// 1. Try app-router convention first
|
|
162
|
+
if (existsSync(APP_DIR)) {
|
|
163
|
+
return renderAppRouter();
|
|
164
|
+
}
|
|
165
|
+
// 2. Fall back to legacy single-page
|
|
166
|
+
if (existsSync(LEGACY_APP_PATH)) {
|
|
167
|
+
return renderLegacy();
|
|
168
|
+
}
|
|
169
|
+
return { html: NO_APP_HTML };
|
|
170
|
+
}
|
|
171
|
+
async function renderAppRouter() {
|
|
172
|
+
const routes = discoverRoutes(APP_DIR);
|
|
173
|
+
if (routes.length === 0)
|
|
174
|
+
return { html: NO_APP_HTML };
|
|
175
|
+
// Identify root layout (src/app/layout.tsx) if any
|
|
176
|
+
const rootLayoutPath = routes[0]?.layoutFiles[0];
|
|
177
|
+
const rootLayoutFile = rootLayoutPath && rootLayoutPath.endsWith('/app/layout.tsx') ? rootLayoutPath : null;
|
|
178
|
+
const rootLayoutMod = rootLayoutFile ? await loadModule(rootLayoutFile) : null;
|
|
179
|
+
const metadata = rootLayoutMod?.metadata;
|
|
180
|
+
// 2. Render each page (wrapped in its nested layouts, excluding root)
|
|
181
|
+
const blocks = [];
|
|
182
|
+
for (const route of routes) {
|
|
183
|
+
const inner = await renderPageInner(route, rootLayoutFile);
|
|
184
|
+
blocks.push(`<div data-murasaki-route="${escapeHtml(route.path)}" hidden>${inner}</div>`);
|
|
185
|
+
}
|
|
186
|
+
const switcher = raw(blocks.join('') + NAV_SCRIPT);
|
|
187
|
+
// 3. Render root layout with switcher as children
|
|
188
|
+
const bodyContent = await renderRootLayout(rootLayoutMod, switcher);
|
|
189
|
+
let html = '<!doctype html>' + bodyContent;
|
|
190
|
+
// 4. Inject metadata + globals.css
|
|
191
|
+
html = injectHead(html, metadata, loadGlobalsCss());
|
|
192
|
+
return { html, metadata };
|
|
193
|
+
}
|
|
194
|
+
// ── Legacy single-page render (unchanged shape) ─────────────────────
|
|
195
|
+
async function renderLegacy() {
|
|
196
|
+
const pageMod = await loadModule(LEGACY_APP_PATH);
|
|
197
|
+
if (!pageMod)
|
|
198
|
+
return { html: NO_APP_HTML };
|
|
199
|
+
const layoutMod = await loadModule(LEGACY_LAYOUT_PATH);
|
|
200
|
+
const metadata = layoutMod?.metadata;
|
|
201
|
+
const body = layoutMod
|
|
202
|
+
? jsx(layoutMod.default, { children: jsx(pageMod.default, null) })
|
|
203
|
+
: jsx(pageMod.default, null);
|
|
204
|
+
let html = '<!doctype html>' + renderToString(body);
|
|
205
|
+
html = injectHead(html, metadata, loadGlobalsCss());
|
|
206
|
+
return { html, metadata };
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/runtime/render.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type Route = {
|
|
2
|
+
/** URL-style path: "/", "/about", "/settings/profile" */
|
|
3
|
+
path: string;
|
|
4
|
+
/** Absolute path to the page.tsx file */
|
|
5
|
+
pageFile: string;
|
|
6
|
+
/** Absolute paths to layout.tsx files, OUTERMOST first (root → innermost) */
|
|
7
|
+
layoutFiles: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function discoverRoutes(appDir: string): Route[];
|
|
10
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/runtime/routes.ts"],"names":[],"mappings":"AAeA,MAAM,MAAM,KAAK,GAAG;IAClB,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAA;IACZ,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAA;IAChB,6EAA6E;IAC7E,WAAW,EAAE,MAAM,EAAE,CAAA;CACtB,CAAA;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,CAWtD"}
|