murasaki 0.2.0 → 0.3.0

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 (98) hide show
  1. package/bin/murasaki.js +8 -3
  2. package/dist/cli/colors.d.ts +13 -0
  3. package/dist/cli/colors.d.ts.map +1 -0
  4. package/dist/cli/colors.js +14 -0
  5. package/dist/cli/colors.js.map +1 -0
  6. package/dist/cli/log.d.ts +14 -0
  7. package/dist/cli/log.d.ts.map +1 -0
  8. package/dist/cli/log.js +25 -0
  9. package/dist/cli/log.js.map +1 -0
  10. package/dist/components/Link.d.ts +9 -0
  11. package/dist/components/Link.d.ts.map +1 -0
  12. package/dist/components/Link.js +15 -0
  13. package/dist/components/Link.js.map +1 -0
  14. package/dist/dev.d.ts +2 -0
  15. package/dist/dev.d.ts.map +1 -0
  16. package/dist/dev.js +41 -0
  17. package/dist/dev.js.map +1 -0
  18. package/dist/env.d.ts +15 -0
  19. package/dist/env.d.ts.map +1 -0
  20. package/dist/env.js +43 -0
  21. package/dist/env.js.map +1 -0
  22. package/dist/index.d.ts +18 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +7 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/jsx/dom/index.d.ts +2 -0
  27. package/dist/jsx/dom/index.d.ts.map +1 -0
  28. package/dist/jsx/dom/index.js +3 -0
  29. package/dist/jsx/dom/index.js.map +1 -0
  30. package/dist/jsx/dom/runtime.d.ts +34 -0
  31. package/dist/jsx/dom/runtime.d.ts.map +1 -0
  32. package/dist/jsx/dom/runtime.js +303 -0
  33. package/dist/jsx/dom/runtime.js.map +1 -0
  34. package/dist/jsx/index.d.ts +3 -0
  35. package/dist/jsx/index.d.ts.map +1 -0
  36. package/dist/jsx/index.js +3 -0
  37. package/dist/jsx/index.js.map +1 -0
  38. package/dist/jsx/jsx-dev-runtime.d.ts +2 -0
  39. package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -0
  40. package/dist/jsx/jsx-dev-runtime.js +7 -0
  41. package/dist/jsx/jsx-dev-runtime.js.map +1 -0
  42. package/dist/jsx/jsx-runtime.d.ts +2 -0
  43. package/dist/jsx/jsx-runtime.d.ts.map +1 -0
  44. package/{src/jsx/jsx-runtime.ts → dist/jsx/jsx-runtime.js} +4 -3
  45. package/dist/jsx/jsx-runtime.js.map +1 -0
  46. package/dist/jsx/runtime.d.ts +24 -0
  47. package/dist/jsx/runtime.d.ts.map +1 -0
  48. package/dist/jsx/runtime.js +273 -0
  49. package/dist/jsx/runtime.js.map +1 -0
  50. package/dist/jsx/types.d.ts +30 -0
  51. package/dist/jsx/types.d.ts.map +1 -0
  52. package/dist/jsx/types.js +3 -0
  53. package/dist/jsx/types.js.map +1 -0
  54. package/dist/runtime/bundle.d.ts +8 -0
  55. package/dist/runtime/bundle.d.ts.map +1 -0
  56. package/dist/runtime/bundle.js +98 -0
  57. package/dist/runtime/bundle.js.map +1 -0
  58. package/dist/runtime/hmr.d.ts +2 -0
  59. package/dist/runtime/hmr.d.ts.map +1 -0
  60. package/dist/runtime/hmr.js +28 -0
  61. package/dist/runtime/hmr.js.map +1 -0
  62. package/dist/runtime/render.d.ts +3 -0
  63. package/dist/runtime/render.d.ts.map +1 -0
  64. package/dist/runtime/render.js +226 -0
  65. package/dist/runtime/render.js.map +1 -0
  66. package/dist/runtime/routes.d.ts +10 -0
  67. package/dist/runtime/routes.d.ts.map +1 -0
  68. package/dist/runtime/routes.js +65 -0
  69. package/dist/runtime/routes.js.map +1 -0
  70. package/dist/runtime/shortcuts.d.ts +8 -0
  71. package/dist/runtime/shortcuts.d.ts.map +1 -0
  72. package/dist/runtime/shortcuts.js +29 -0
  73. package/dist/runtime/shortcuts.js.map +1 -0
  74. package/dist/runtime/window.d.ts +10 -0
  75. package/dist/runtime/window.d.ts.map +1 -0
  76. package/dist/runtime/window.js +93 -0
  77. package/dist/runtime/window.js.map +1 -0
  78. package/dist/types.d.ts +17 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +3 -0
  81. package/dist/types.js.map +1 -0
  82. package/package.json +17 -10
  83. package/src/cli/colors.ts +0 -16
  84. package/src/cli/log.ts +0 -42
  85. package/src/components/Link.tsx +0 -25
  86. package/src/dev.tsx +0 -60
  87. package/src/env.ts +0 -48
  88. package/src/index.ts +0 -24
  89. package/src/jsx/index.ts +0 -21
  90. package/src/jsx/jsx-dev-runtime.ts +0 -6
  91. package/src/jsx/runtime.ts +0 -298
  92. package/src/jsx/types.ts +0 -36
  93. package/src/runtime/hmr.ts +0 -26
  94. package/src/runtime/render.tsx +0 -225
  95. package/src/runtime/routes.ts +0 -73
  96. package/src/runtime/shortcuts.ts +0 -31
  97. package/src/runtime/window.ts +0 -94
  98. package/src/types.ts +0 -22
@@ -1,25 +0,0 @@
1
- // <Link href="/about">About</Link>
2
- //
3
- // Emits a plain <a> tagged with data-murasaki-link. The dev runner injects
4
- // a tiny script that intercepts clicks on these and switches the visible
5
- // route block in place — no full reload, no flash.
6
-
7
- import { jsx } from '../jsx/runtime.ts'
8
- import type { Child } from '../jsx/types.ts'
9
-
10
- export type LinkProps = {
11
- href: string
12
- children?: Child
13
- className?: string
14
- // Pass-through anchor props
15
- [key: string]: unknown
16
- }
17
-
18
- export function Link({ href, children, ...rest }: LinkProps) {
19
- return jsx('a', {
20
- href: `#${href}`,
21
- 'data-murasaki-link': href,
22
- ...rest,
23
- children,
24
- })
25
- }
package/src/dev.tsx DELETED
@@ -1,60 +0,0 @@
1
- // src/dev.tsx
2
- // Murasaki dev runner — Next.js-like file-based routing without Next.js.
3
- //
4
- // Reads the consumer's src/ directory:
5
- // src/layout.tsx (optional, can export `metadata`)
6
- // src/app.tsx (required)
7
- // src/globals.css (optional, auto-injected)
8
- //
9
- // Renders <Layout><App /></Layout> with React, ships HTML to the WebView,
10
- // and reloads in place on file change.
11
-
12
- import {
13
- printBanner,
14
- printBye,
15
- printOpened,
16
- printReady,
17
- printShortcuts,
18
- printStarting,
19
- } from './cli/log.ts'
20
- import { setupHmr } from './runtime/hmr.ts'
21
- import { setupShortcuts, teardownStdin } from './runtime/shortcuts.ts'
22
- import {
23
- closeWindow,
24
- exitApp,
25
- getConfig,
26
- openWindow,
27
- reloadWindow,
28
- runApp,
29
- } from './runtime/window.ts'
30
-
31
- const startAt = Date.now()
32
-
33
- // Boot: render once (applies metadata) → banner → ready
34
- await openWindow()
35
- printBanner(getConfig().title, getConfig())
36
- printShortcuts()
37
- printStarting()
38
- printReady(Date.now() - startAt)
39
-
40
- setupShortcuts({
41
- onOpen: () => {
42
- void openWindow().then(printOpened)
43
- },
44
- onRestart: () => {
45
- closeWindow()
46
- void openWindow().then(printOpened)
47
- },
48
- onQuit: () => {
49
- printBye()
50
- teardownStdin()
51
- exitApp()
52
- process.exit(0)
53
- },
54
- })
55
-
56
- setupHmr((file) => {
57
- void reloadWindow(file)
58
- })
59
-
60
- runApp()
package/src/env.ts DELETED
@@ -1,48 +0,0 @@
1
- // Project paths + version + platform — resolved once at boot.
2
-
3
- import { readFileSync } from 'node:fs'
4
- import { dirname, join } from 'node:path'
5
- import { fileURLToPath } from 'node:url'
6
-
7
- export const projectRoot = process.cwd()
8
- export const SRC_DIR = join(projectRoot, 'src')
9
-
10
- // App-router convention (new, preferred):
11
- // src/app/page.tsx → "/"
12
- // src/app/layout.tsx → root layout (html/head/body)
13
- // src/app/<sub>/page.tsx → "/<sub>"
14
- // src/app/globals.css → auto-injected
15
- export const APP_DIR = join(SRC_DIR, 'app')
16
- export const APP_GLOBALS_CSS = join(APP_DIR, 'globals.css')
17
-
18
- // Legacy single-page convention (still supported):
19
- // src/app.tsx + src/layout.tsx + src/globals.css
20
- export const LEGACY_APP_PATH = join(SRC_DIR, 'app.tsx')
21
- export const LEGACY_LAYOUT_PATH = join(SRC_DIR, 'layout.tsx')
22
- export const LEGACY_GLOBALS_CSS = join(SRC_DIR, 'globals.css')
23
-
24
- const __dirname = dirname(fileURLToPath(import.meta.url))
25
- export const VERSION: string = (() => {
26
- try {
27
- const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'))
28
- return pkg.version || '0.0.0'
29
- } catch {
30
- return '0.0.0'
31
- }
32
- })()
33
-
34
- export const WEBVIEW_ENGINE: string = (() => {
35
- switch (process.platform) {
36
- case 'darwin':
37
- return 'WKWebView (macOS)'
38
- case 'win32':
39
- return 'WebView2 (Windows)'
40
- case 'linux':
41
- return 'WebKitGTK (Linux)'
42
- default:
43
- return `OS native (${process.platform})`
44
- }
45
- })()
46
-
47
- export const DEFAULT_WIN_TITLE = 'Murasaki App'
48
- export const DEFAULT_WIN_SIZE = { width: 1280, height: 800 }
package/src/index.ts DELETED
@@ -1,24 +0,0 @@
1
- // murasaki — public API
2
- //
3
- // Import like:
4
- // import type { Metadata } from 'murasaki'
5
- // import { Link } from 'murasaki'
6
-
7
- export type { LinkProps } from './components/Link.tsx'
8
- export { Link } from './components/Link.tsx'
9
-
10
- export type Metadata = {
11
- /** Default <title> for the app (overridden by <title> tag inside <head>) */
12
- title?: string
13
- /** <meta name="description"> */
14
- description?: string
15
- /** Initial window options (applied at first open; user can resize after) */
16
- window?: {
17
- /** Window title bar text (defaults to metadata.title) */
18
- title?: string
19
- /** Initial width in logical pixels */
20
- width?: number
21
- /** Initial height in logical pixels */
22
- height?: number
23
- }
24
- }
package/src/jsx/index.ts DELETED
@@ -1,21 +0,0 @@
1
- // Public surface for `import { ... } from 'murasaki/jsx'`.
2
-
3
- export {
4
- createElement,
5
- Fragment,
6
- isJSXNode,
7
- isValidElement,
8
- JSXNode,
9
- jsx,
10
- raw,
11
- renderToString,
12
- } from './runtime.ts'
13
-
14
- export type {
15
- Child,
16
- Component,
17
- Element,
18
- FC,
19
- JSXNodeLike,
20
- Props,
21
- } from './types.ts'
@@ -1,6 +0,0 @@
1
- // JSX dev runtime entry — used when tsconfig has `jsx: "react-jsxdev"`.
2
- // We don't (yet) track source locations or component stacks, so jsxDEV
3
- // behaves identically to jsx.
4
-
5
- export { Fragment, jsx as jsxDEV } from './runtime.ts'
6
- export type { JSX } from './types.ts'
@@ -1,298 +0,0 @@
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
-
16
- import type { Child, Component, JSXNodeLike, Props } from './types.ts'
17
-
18
- // ── HTML escape ──────────────────────────────────────────────────────
19
- const AMP = /&/g
20
- const LT = /</g
21
- const GT = />/g
22
- const QT = /"/g
23
-
24
- function escapeHtml(s: string): string {
25
- return s.replace(AMP, '&amp;').replace(LT, '&lt;').replace(GT, '&gt;')
26
- }
27
-
28
- function escapeAttr(s: string): string {
29
- return s.replace(AMP, '&amp;').replace(QT, '&quot;')
30
- }
31
-
32
- // ── Void elements (no closing tag) ────────────────────────────────────
33
- const VOID_ELEMENTS = new Set([
34
- 'area',
35
- 'base',
36
- 'br',
37
- 'col',
38
- 'embed',
39
- 'hr',
40
- 'img',
41
- 'input',
42
- 'link',
43
- 'meta',
44
- 'source',
45
- 'track',
46
- 'wbr',
47
- ])
48
-
49
- // ── Attribute name normalization (React → HTML) ───────────────────────
50
- const ATTR_ALIAS: Record<string, string> = {
51
- className: 'class',
52
- htmlFor: 'for',
53
- charSet: 'charset',
54
- crossOrigin: 'crossorigin',
55
- httpEquiv: 'http-equiv',
56
- itemProp: 'itemprop',
57
- fetchPriority: 'fetchpriority',
58
- noModule: 'nomodule',
59
- formAction: 'formaction',
60
- acceptCharset: 'accept-charset',
61
- autoComplete: 'autocomplete',
62
- autoFocus: 'autofocus',
63
- autoPlay: 'autoplay',
64
- contentEditable: 'contenteditable',
65
- defaultValue: 'value',
66
- defaultChecked: 'checked',
67
- encType: 'enctype',
68
- formMethod: 'formmethod',
69
- formNoValidate: 'formnovalidate',
70
- formTarget: 'formtarget',
71
- maxLength: 'maxlength',
72
- minLength: 'minlength',
73
- noValidate: 'novalidate',
74
- readOnly: 'readonly',
75
- rowSpan: 'rowspan',
76
- colSpan: 'colspan',
77
- spellCheck: 'spellcheck',
78
- tabIndex: 'tabindex',
79
- useMap: 'usemap',
80
- srcDoc: 'srcdoc',
81
- srcSet: 'srcset',
82
- hrefLang: 'hreflang',
83
- dateTime: 'datetime',
84
- enterKeyHint: 'enterkeyhint',
85
- inputMode: 'inputmode',
86
- }
87
-
88
- function normalizeAttrName(k: string): string {
89
- return ATTR_ALIAS[k] || k
90
- }
91
-
92
- // ── Style object → CSS string ─────────────────────────────────────────
93
- function camelToKebab(k: string): string {
94
- // Leave already-kebab keys and CSS custom props (--foo) alone.
95
- if (k[0] === '-' || !/[A-Z]/.test(k)) return k
96
- return k.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)
97
- }
98
-
99
- // CSS properties that take unitless numbers (subset of React's list).
100
- const UNITLESS = new Set([
101
- 'animationIterationCount',
102
- 'borderImageOutset',
103
- 'borderImageSlice',
104
- 'borderImageWidth',
105
- 'boxFlex',
106
- 'boxFlexGroup',
107
- 'boxOrdinalGroup',
108
- 'columnCount',
109
- 'columns',
110
- 'flex',
111
- 'flexGrow',
112
- 'flexShrink',
113
- 'fontWeight',
114
- 'gridArea',
115
- 'gridColumn',
116
- 'gridColumnEnd',
117
- 'gridColumnStart',
118
- 'gridRow',
119
- 'gridRowEnd',
120
- 'gridRowStart',
121
- 'lineClamp',
122
- 'lineHeight',
123
- 'opacity',
124
- 'order',
125
- 'orphans',
126
- 'tabSize',
127
- 'widows',
128
- 'zIndex',
129
- 'zoom',
130
- ])
131
-
132
- function styleObjToString(obj: Record<string, unknown>): string {
133
- let out = ''
134
- for (const [k, v] of Object.entries(obj)) {
135
- if (v == null || v === false) continue
136
- let value: string
137
- if (typeof v === 'number') {
138
- value = UNITLESS.has(k) ? String(v) : `${v}px`
139
- } else if (typeof v === 'string') {
140
- value = v
141
- } else {
142
- continue
143
- }
144
- out += `${out ? ';' : ''}${camelToKebab(k)}:${value}`
145
- }
146
- return out
147
- }
148
-
149
- // ── Attribute rendering ──────────────────────────────────────────────
150
- function renderAttrs(props: Props): string {
151
- let out = ''
152
- for (const k in props) {
153
- if (k === 'children' || k === 'key' || k === 'ref' || k === '__source' || k === '__self')
154
- continue
155
- const v = props[k]
156
- if (v == null || v === false) continue
157
-
158
- const name = normalizeAttrName(k)
159
-
160
- // Boolean attribute
161
- if (v === true) {
162
- out += ` ${name}`
163
- continue
164
- }
165
-
166
- // Inline style object
167
- if (k === 'style' && typeof v === 'object') {
168
- const css = styleObjToString(v as Record<string, unknown>)
169
- if (css) out += ` style="${escapeAttr(css)}"`
170
- continue
171
- }
172
-
173
- // dangerouslySetInnerHTML is handled in JSXNode.toString(), skip here
174
- if (k === 'dangerouslySetInnerHTML') continue
175
-
176
- out += ` ${name}="${escapeAttr(String(v))}"`
177
- }
178
- return out
179
- }
180
-
181
- // ── Raw HTML escape hatch (pre-rendered HTML as a child) ─────────────
182
- class RawHtml {
183
- readonly __isJSXNode = true as const
184
- tag = '__raw__'
185
- props: Props = {}
186
- children: Child[] = []
187
- html: string
188
- constructor(html: string) {
189
- this.html = html
190
- }
191
- toString(): string {
192
- return this.html
193
- }
194
- }
195
-
196
- /** Wrap pre-rendered HTML so it's emitted verbatim as a JSX child. */
197
- export function raw(html: string): JSXNodeLike {
198
- return new RawHtml(html)
199
- }
200
-
201
- // ── Children rendering ───────────────────────────────────────────────
202
- function renderChild(c: Child): string {
203
- if (c == null || c === false || c === true) return ''
204
- if (typeof c === 'string') return escapeHtml(c)
205
- if (typeof c === 'number' || typeof c === 'bigint') return String(c)
206
- if (Array.isArray(c)) {
207
- let s = ''
208
- for (const item of c) s += renderChild(item)
209
- return s
210
- }
211
- if (isJSXNode(c)) return c.toString()
212
- return ''
213
- }
214
-
215
- // ── JSXNode ──────────────────────────────────────────────────────────
216
- export class JSXNode implements JSXNodeLike {
217
- readonly __isJSXNode = true as const
218
- tag: string | Component
219
- props: Props
220
- children: Child[]
221
-
222
- constructor(tag: string | Component, props: Props, children: Child[]) {
223
- this.tag = tag
224
- this.props = props
225
- this.children = children
226
- }
227
-
228
- toString(): string {
229
- const { tag, props, children } = this
230
-
231
- // Fragment / functional component
232
- if (typeof tag === 'function') {
233
- // Always pass children via props (React-compat)
234
- const merged = { ...props, children: children.length === 1 ? children[0] : children }
235
- const result = tag(merged)
236
- return renderChild(result as Child)
237
- }
238
-
239
- // Intrinsic element
240
- const attrs = renderAttrs(props)
241
-
242
- // dangerouslySetInnerHTML overrides children
243
- const dsi = props.dangerouslySetInnerHTML as { __html?: string } | undefined
244
- if (dsi && typeof dsi.__html === 'string') {
245
- return `<${tag}${attrs}>${dsi.__html}</${tag}>`
246
- }
247
-
248
- if (VOID_ELEMENTS.has(tag)) {
249
- // Self-closing for void elements
250
- return `<${tag}${attrs}/>`
251
- }
252
-
253
- let childHtml = ''
254
- for (const c of children) childHtml += renderChild(c)
255
-
256
- return `<${tag}${attrs}>${childHtml}</${tag}>`
257
- }
258
- }
259
-
260
- export function isJSXNode(v: unknown): v is JSXNode {
261
- return typeof v === 'object' && v !== null && (v as JSXNodeLike).__isJSXNode === true
262
- }
263
-
264
- // ── Public factory (React-compat: createElement / jsx) ────────────────
265
- export function jsx(
266
- tag: string | Component,
267
- props: Props | null,
268
- ..._restChildren: unknown[]
269
- ): JSXNode {
270
- const p = props ?? {}
271
- const rawChildren = (p as { children?: Child }).children
272
- const children: Child[] = Array.isArray(rawChildren)
273
- ? rawChildren
274
- : rawChildren != null
275
- ? [rawChildren]
276
- : []
277
- // Strip children from props (it's stored separately)
278
- const { children: _drop, ...rest } = p as Props & { children?: Child }
279
- return new JSXNode(tag, rest, children)
280
- }
281
-
282
- /** React-compatible alias. */
283
- export const createElement = jsx
284
-
285
- /** Fragment — renders children without a wrapper tag. */
286
- export function Fragment(props: { children?: Child }): Child {
287
- return props.children ?? null
288
- }
289
-
290
- /** Check if something is a JSX element (React.isValidElement compat). */
291
- export function isValidElement(v: unknown): v is JSXNode {
292
- return isJSXNode(v)
293
- }
294
-
295
- /** Convert any value (JSXNode, string, array, etc.) to an HTML string. */
296
- export function renderToString(value: Child): string {
297
- return renderChild(value)
298
- }
package/src/jsx/types.ts DELETED
@@ -1,36 +0,0 @@
1
- // Public JSX types for murasaki/jsx.
2
-
3
- export type Props = Record<string, unknown>
4
-
5
- export type Child = string | number | bigint | boolean | null | undefined | JSXNodeLike | Child[]
6
-
7
- export interface JSXNodeLike {
8
- readonly __isJSXNode: true
9
- tag: string | Component
10
- props: Props
11
- children: Child[]
12
- toString(): string
13
- }
14
-
15
- export type Component<P = Props> = (props: P & { children?: Child }) => Child
16
-
17
- /** React-compatible alias used by most user code. */
18
- export type FC<P = Props> = Component<P>
19
-
20
- /** For component refs / cloning. */
21
- export type Element = JSXNodeLike
22
-
23
- declare global {
24
- namespace JSX {
25
- type Element = JSXNodeLike
26
- interface ElementChildrenAttribute {
27
- children: object
28
- }
29
- // Loose intrinsic catalog — every HTML/SVG tag accepts any prop.
30
- // (Tightening this to a real catalog is a follow-up; keeps the
31
- // runtime usable without bloating the type surface today.)
32
- interface IntrinsicElements {
33
- [tagName: string]: Record<string, unknown> & { children?: Child }
34
- }
35
- }
36
- }
@@ -1,26 +0,0 @@
1
- // File watcher for src/ — debounces multi-event saves and triggers reload.
2
-
3
- import { existsSync, watch } from 'node:fs'
4
- import { printHint } from '../cli/log.ts'
5
- import { SRC_DIR } from '../env.ts'
6
-
7
- export function setupHmr(onChange: (filename: string) => void): void {
8
- if (!existsSync(SRC_DIR)) {
9
- printHint('src/ directory not found — nothing to watch')
10
- return
11
- }
12
- let debounce: NodeJS.Timeout | null = null
13
- let lastFile = ''
14
- try {
15
- watch(SRC_DIR, { recursive: true }, (_event, filename) => {
16
- if (!filename) return
17
- if (debounce) clearTimeout(debounce)
18
- lastFile = filename.toString()
19
- debounce = setTimeout(() => {
20
- onChange(lastFile)
21
- }, 80)
22
- })
23
- } catch (e: any) {
24
- printHint(`HMR watcher failed: ${e.message}`)
25
- }
26
- }