waymark 0.4.0 โ 0.6.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.
- package/README.md +165 -63
- package/dist/index.d.ts +35 -44
- package/dist/index.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
A
|
|
6
|
+
A type-safe router for React that just works.
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
-
<
|
|
9
|
+
<div align="center">
|
|
10
10
|
<a href="https://www.npmjs.com/package/waymark">
|
|
11
11
|
<img
|
|
12
12
|
src="https://img.shields.io/npm/v/waymark?style=flat-square&color=0B0D0F&labelColor=0B0D0F"
|
|
@@ -37,28 +37,75 @@
|
|
|
37
37
|
alt="sponsors"
|
|
38
38
|
/>
|
|
39
39
|
</a>
|
|
40
|
-
</
|
|
40
|
+
</div>
|
|
41
41
|
|
|
42
42
|
<p align="center">
|
|
43
|
-
<a href="https://waymarkrouter.com"
|
|
43
|
+
๐ <a href="https://waymarkrouter.com">Documentation</a> ยท ๐ฎ <a href="https://stackblitz.com/edit/waymark-demo?file=src%2Fapp.tsx">Live playground</a>
|
|
44
44
|
</p>
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
48
48
|
Waymark is a routing library for React built around three core ideas: **type safety**, **simplicity**, and **minimal overhead**.
|
|
49
49
|
|
|
50
|
-
- **Fully type-safe** - Complete TypeScript inference for routes, path params,
|
|
51
|
-
- **Zero config** - No build plugins, no CLI, no codegen, no config files, very low boilerplate
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
54
|
-
- **Feature packed** - Search param validation, lazy loading, data preloading, SSR, error boundaries, etc.
|
|
55
|
-
- **Not vibe-coded** - Built with careful design and attention to detail by a human
|
|
56
|
-
- **Just works** -
|
|
50
|
+
- ๐ **Fully type-safe** - Complete TypeScript inference for routes, path params, search params, and more
|
|
51
|
+
- โก **Zero config** - No build plugins, no CLI, no codegen, no config files, very low boilerplate
|
|
52
|
+
- ๐ชถ **4kB gzipped** - Extremely lightweight, dependency included
|
|
53
|
+
- ๐ค **Familiar API** - If you've used React Router or TanStack Router, you'll feel at home
|
|
54
|
+
- ๐ฏ **Feature packed** - Search param validation, lazy loading, data preloading, SSR, error boundaries, etc.
|
|
55
|
+
- ๐ง **Not vibe-coded** - Built with careful design and attention to detail by a human
|
|
56
|
+
- โจ **Just works** - Simple setup, predictable behavior that never gets in your way
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
# Comparison
|
|
61
|
+
|
|
62
|
+
| Feature | Waymark | React Router | TanStack Router | Wouter |
|
|
63
|
+
| -------------------------------- | :-----: | :----------: | :-------------: | :----: |
|
|
64
|
+
| **Bundle size (gzip)**\* | ~4kB | ~26kB+ | ~19kB+ | ~2.2kB |
|
|
65
|
+
| **Zero config**\* | โ
| โ | โ ๏ธ | โ
|
|
|
66
|
+
| **Full type inference**\* | โ
| โ ๏ธ | โ
| โ |
|
|
67
|
+
| **Nested routes** | โ
| โ
| โ
| โ
|
|
|
68
|
+
| **Search param validation**\* | โ
| โ | โ
| โ |
|
|
69
|
+
| **Lazy loading** | โ
| โ
| โ
| โ |
|
|
70
|
+
| **Data preloading** | โ
| โ
| โ
| โ |
|
|
71
|
+
| **Built-in error boundaries** | โ
| โ
| โ
| โ |
|
|
72
|
+
| **Built-in suspense boundaries** | โ
| โ | โ
| โ |
|
|
73
|
+
| **Link preloading strategies** | โ
| โ
| โ
| โ |
|
|
74
|
+
| **Active link detection** | โ
| โ
| โ
| โ ๏ธ |
|
|
75
|
+
| **Browser/Hash/Memory history** | โ
| โ
| โ
| โ
|
|
|
76
|
+
| **SSR support** | โ
| โ
| โ
| โ
|
|
|
77
|
+
| **Route middlewares**\* | โ
| โ | โ | โ |
|
|
78
|
+
| **Route handles (metadata)** | โ
| โ
| โ
| โ |
|
|
79
|
+
| **Route match ranking**\* | โ
| โ
| โ
| โ |
|
|
80
|
+
| **View transitions** | โ
| โ
| โ
| โ
|
|
|
81
|
+
| **Devtools** | โ
| โ ๏ธ | โ
| โ |
|
|
82
|
+
| **Navigation blockers** | ๐จ | โ
| โ
| โ |
|
|
83
|
+
| **File-based routing** | โ | โ
| โ
| โ |
|
|
84
|
+
| **React Native** | โ | โ
| โ | โ |
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary><b>Comparison notes</b></summary>
|
|
88
|
+
|
|
89
|
+
<br />
|
|
90
|
+
|
|
91
|
+
If you believe there's a mistake in the comparison table, please [open an issue](https://github.com/strblr/waymark/issues) or [submit a PR](https://github.com/strblr/waymark/pulls) and it will be fixed.
|
|
92
|
+
|
|
93
|
+
- โ ๏ธ indicates the feature is only partially supported, supported with heavy boilerplate, or requires external libraries.
|
|
94
|
+
- ๐จ indicates the feature is not yet ready but being worked on.
|
|
95
|
+
- **Bundle sizes** are approximate gzipped values. React Router and TanStack Router sizes can vary significantly based on imports and versions; Waymark's ~4kB includes its single ~0.4kB dependency ([regexparam](https://github.com/lukeed/regexparam)), before any tree shaking. Wouter is the smallest option but lacks features.
|
|
96
|
+
- **Zero config** means no CLI tools, build plugins, code generation, or configuration files are required. React Router requires its typegen CLI or bundler plugin for full type safety. Same with TanStack Router for file-based routing. You can use code-based routing but it's more boilerplate.
|
|
97
|
+
- **Full type inference** refers to automatic TypeScript inference for routes, params, search params, and navigation without manual type annotations.
|
|
98
|
+
- **Search params validation** refers to built-in support for validating and typing URL search parameters. Wouter provides `useSearch()` but no validation layer. Same with React Router and `useSearchParams`.
|
|
99
|
+
- **Route middlewares** are reusable configuration bundles (search validation, handles, preload functions, components) that can be applied to multiple routes. This is a Waymark-specific feature.
|
|
100
|
+
- **Route match ranking** automatically picks the most specific route when multiple patterns match (e.g., `/users/new` wins over `/users/:id`). Without ranking, route definition order matters.
|
|
101
|
+
|
|
102
|
+
</details>
|
|
57
103
|
|
|
58
104
|
---
|
|
59
105
|
|
|
60
106
|
# Table of contents
|
|
61
107
|
|
|
108
|
+
- [Comparison](#comparison)
|
|
62
109
|
- [Showcase](#showcase)
|
|
63
110
|
- [Installation](#installation)
|
|
64
111
|
- [Defining routes](#defining-routes)
|
|
@@ -85,6 +132,7 @@ Waymark is a routing library for React built around three core ideas: **type saf
|
|
|
85
132
|
- [Middlewares](#middlewares)
|
|
86
133
|
- [Route matching and ranking](#route-matching-and-ranking)
|
|
87
134
|
- [History implementations](#history-implementations)
|
|
135
|
+
- [Devtools](#devtools)
|
|
88
136
|
- [Cookbook](#cookbook)
|
|
89
137
|
- [Quick start example](#quick-start-example)
|
|
90
138
|
- [Server-side rendering (SSR)](#server-side-rendering-ssr)
|
|
@@ -150,6 +198,8 @@ declare module "waymark" {
|
|
|
150
198
|
|
|
151
199
|
Everything autocompletes and type-checks automatically. No heavy setup, no magic, just a simple API that gets out of your way.
|
|
152
200
|
|
|
201
|
+
๐ [Try it live in the StackBlitz playground](https://stackblitz.com/edit/waymark-demo?file=src%2Fapp.tsx)
|
|
202
|
+
|
|
153
203
|
---
|
|
154
204
|
|
|
155
205
|
# Installation
|
|
@@ -192,31 +242,25 @@ const files = route("/files/*").component(FileBrowser);
|
|
|
192
242
|
const optional = route("/books/*?").component(FileBrowser);
|
|
193
243
|
```
|
|
194
244
|
|
|
195
|
-
Route building is immutable: every method on a route returns a new route instance
|
|
245
|
+
Route building is immutable: every method on a route returns a new route instance.
|
|
196
246
|
|
|
197
247
|
---
|
|
198
248
|
|
|
199
249
|
# Nested routes and layouts
|
|
200
250
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
Here's how it works. Start with any route:
|
|
251
|
+
Any route can have child routes. Call `.route()` on an existing route to create one:
|
|
204
252
|
|
|
205
253
|
```tsx
|
|
206
254
|
const dashboard = route("/dashboard").component(DashboardLayout);
|
|
207
|
-
```
|
|
208
255
|
|
|
209
|
-
Then create child routes by calling `.route()` on it:
|
|
210
|
-
|
|
211
|
-
```tsx
|
|
212
256
|
const overview = dashboard.route("/").component(Overview);
|
|
213
257
|
const settings = dashboard.route("/settings").component(Settings);
|
|
214
258
|
const profile = dashboard.route("/profile").component(Profile);
|
|
215
259
|
```
|
|
216
260
|
|
|
217
|
-
|
|
261
|
+
Child routes build on their parent's path. So `overview` matches `/dashboard`, `settings` matches `/dashboard/settings`, and `profile` matches `/dashboard/profile`.
|
|
218
262
|
|
|
219
|
-
|
|
263
|
+
They also nest inside the parent's component. The parent renders an `<Outlet />` to mark where child routes should appears:
|
|
220
264
|
|
|
221
265
|
```tsx
|
|
222
266
|
function DashboardLayout() {
|
|
@@ -231,7 +275,7 @@ function DashboardLayout() {
|
|
|
231
275
|
}
|
|
232
276
|
```
|
|
233
277
|
|
|
234
|
-
When the URL is `/dashboard/settings`, Waymark renders `DashboardLayout` with `Settings` inside the outlet.
|
|
278
|
+
When the URL is `/dashboard/settings`, Waymark renders `DashboardLayout` with `Settings` inside the outlet. This is how you build layouts - shared UI like navigation or sidebars that stays mounted as users navigate between child routes.
|
|
235
279
|
|
|
236
280
|
You can nest as deep as you need:
|
|
237
281
|
|
|
@@ -253,6 +297,8 @@ AppShell
|
|
|
253
297
|
|
|
254
298
|
Each level must include an `<Outlet />` to render the next level.
|
|
255
299
|
|
|
300
|
+
Beyond paths and components, child routes also inherit search param validators, handles, and preload functions from their parent chain. While you can think of nesting as building a tree, every route is self-contained: it carries everything it needs to render, including all parent components.
|
|
301
|
+
|
|
256
302
|
---
|
|
257
303
|
|
|
258
304
|
# Setting up the router
|
|
@@ -319,7 +365,9 @@ declare module "waymark" {
|
|
|
319
365
|
}
|
|
320
366
|
```
|
|
321
367
|
|
|
322
|
-
With this in place, `Link`, `navigate`, `useParams`, `useSearch`, and other APIs will know exactly which routes exist and what input they expect
|
|
368
|
+
With this in place, `Link`, `navigate`, `useParams`, `useSearch`, and other APIs will know exactly which routes exist and what input they expect.
|
|
369
|
+
|
|
370
|
+
**You're all set up!**
|
|
323
371
|
|
|
324
372
|
---
|
|
325
373
|
|
|
@@ -459,6 +507,9 @@ function SearchPage() {
|
|
|
459
507
|
const [search, setSearch] = useSearch(searchPage);
|
|
460
508
|
// search.q: string
|
|
461
509
|
// search.page: number
|
|
510
|
+
|
|
511
|
+
const [search, setSearch] = useSearch("/search");
|
|
512
|
+
// Also works
|
|
462
513
|
}
|
|
463
514
|
```
|
|
464
515
|
|
|
@@ -1015,6 +1066,16 @@ function UserPage() {
|
|
|
1015
1066
|
}
|
|
1016
1067
|
```
|
|
1017
1068
|
|
|
1069
|
+
For parametrized middlewares, define a function that returns a middleware:
|
|
1070
|
+
|
|
1071
|
+
```tsx
|
|
1072
|
+
const guard = (role: string) =>
|
|
1073
|
+
middleware().handle({ requiredRole: role }).component(RoleGuard);
|
|
1074
|
+
|
|
1075
|
+
const adminPage = route("/admin").use(guard("admin")).component(AdminPage);
|
|
1076
|
+
const editorPage = route("/editor").use(guard("editor")).component(EditorPage);
|
|
1077
|
+
```
|
|
1078
|
+
|
|
1018
1079
|
---
|
|
1019
1080
|
|
|
1020
1081
|
# Route matching and ranking
|
|
@@ -1103,6 +1164,53 @@ All history implementations conform to the `HistoryLike` interface, so you can c
|
|
|
1103
1164
|
|
|
1104
1165
|
---
|
|
1105
1166
|
|
|
1167
|
+
# Devtools
|
|
1168
|
+
|
|
1169
|
+
Waymark has a companion devtools package for inspecting routes, matches, parameters, and navigation state.
|
|
1170
|
+
|
|
1171
|
+
```bash
|
|
1172
|
+
npm install waymark-devtools
|
|
1173
|
+
```
|
|
1174
|
+
|
|
1175
|
+
Render the `Devtools` component anywhere inside your routes. It displays a toggle button that opens a draggable and resizable floating panel:
|
|
1176
|
+
|
|
1177
|
+
```tsx
|
|
1178
|
+
import { Devtools } from "waymark-devtools";
|
|
1179
|
+
|
|
1180
|
+
const layout = route("/").component(Layout);
|
|
1181
|
+
|
|
1182
|
+
function Layout() {
|
|
1183
|
+
return (
|
|
1184
|
+
<div>
|
|
1185
|
+
<Outlet />
|
|
1186
|
+
<Devtools />
|
|
1187
|
+
</div>
|
|
1188
|
+
);
|
|
1189
|
+
}
|
|
1190
|
+
```
|
|
1191
|
+
|
|
1192
|
+
If you'd rather embed the panel directly into your layout instead of using the floating window, use `DevtoolsPanel`:
|
|
1193
|
+
|
|
1194
|
+
```tsx
|
|
1195
|
+
import { DevtoolsPanel } from "waymark-devtools";
|
|
1196
|
+
|
|
1197
|
+
function DebugSidebar() {
|
|
1198
|
+
return (
|
|
1199
|
+
<aside>
|
|
1200
|
+
<DevtoolsPanel />
|
|
1201
|
+
</aside>
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
To exclude devtools from production builds (Vite example):
|
|
1207
|
+
|
|
1208
|
+
```tsx
|
|
1209
|
+
import.meta.env.DEV && <Devtools />;
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
---
|
|
1213
|
+
|
|
1106
1214
|
# Cookbook
|
|
1107
1215
|
|
|
1108
1216
|
## Quick start example
|
|
@@ -1219,27 +1327,24 @@ function AppLayout() {
|
|
|
1219
1327
|
|
|
1220
1328
|
Use `useMatch` to check if a route matches the current path from anywhere in your component tree. You can pass either a route pattern string or a route object, just like with `Link` and `navigate`. This is useful for conditional rendering, styling, access control, and more. It's also used internally by `useParams` and `Link`.
|
|
1221
1329
|
|
|
1222
|
-
|
|
1330
|
+
The hook returns a Match object (containing `route` and `params`) if there's a match, or `null` otherwise. There are two matching modes:
|
|
1331
|
+
|
|
1332
|
+
- **Loose matching** (default): Matches if the path starts with the route pattern (e.g., `/dashboard` matches `/dashboard/settings`).
|
|
1333
|
+
- **Strict matching** (`strict: true`): Matches only if the path exactly matches the route pattern.
|
|
1223
1334
|
|
|
1224
1335
|
```tsx
|
|
1225
1336
|
import { useMatch } from "waymark";
|
|
1226
1337
|
|
|
1227
1338
|
const dashboard = route("/dashboard").component(Dashboard);
|
|
1228
|
-
const settings = route("/settings").component(Settings);
|
|
1229
1339
|
|
|
1230
1340
|
function Sidebar() {
|
|
1231
|
-
//
|
|
1232
|
-
const
|
|
1341
|
+
// Matches /dashboard, /dashboard/anything, etc.
|
|
1342
|
+
const match = useMatch({ from: dashboard });
|
|
1233
1343
|
|
|
1234
|
-
//
|
|
1235
|
-
const
|
|
1344
|
+
// Matches only /dashboard
|
|
1345
|
+
const match = useMatch({ from: dashboard, strict: true });
|
|
1236
1346
|
|
|
1237
|
-
return
|
|
1238
|
-
<nav>
|
|
1239
|
-
{dashboardMatch && <DashboardMenu />}
|
|
1240
|
-
{settingsMatch && <SettingsSubmenu />}
|
|
1241
|
-
</nav>
|
|
1242
|
-
);
|
|
1347
|
+
return <nav>{match && <DashboardMenu />}</nav>;
|
|
1243
1348
|
}
|
|
1244
1349
|
```
|
|
1245
1350
|
|
|
@@ -1415,7 +1520,7 @@ const url = router.createUrl({ to: userProfile, params: { id: "42" } });
|
|
|
1415
1520
|
|
|
1416
1521
|
```tsx
|
|
1417
1522
|
const match = router.match("/users/42", { from: "/users/:id" });
|
|
1418
|
-
// Returns { route, params: { id: "42" } }
|
|
1523
|
+
// Returns { route, params: { id: "42" } }
|
|
1419
1524
|
```
|
|
1420
1525
|
|
|
1421
1526
|
**`router.matchAll(path)`** finds the best match from all registered routes.
|
|
@@ -1565,8 +1670,11 @@ const user = route("/users/:id")
|
|
|
1565
1670
|
- Returns: `Middleware` - A new middleware object
|
|
1566
1671
|
|
|
1567
1672
|
```tsx
|
|
1568
|
-
const
|
|
1569
|
-
z.object({
|
|
1673
|
+
const pagination = middleware().search(
|
|
1674
|
+
z.object({
|
|
1675
|
+
page: z.coerce.number().catch(1),
|
|
1676
|
+
limit: z.coerce.number().catch(10)
|
|
1677
|
+
})
|
|
1570
1678
|
);
|
|
1571
1679
|
const auth = middleware()
|
|
1572
1680
|
.handle({ requiresAuth: true })
|
|
@@ -1597,7 +1705,7 @@ navigate(-1);
|
|
|
1597
1705
|
|
|
1598
1706
|
**`useLocation()`** returns the current location, subscribes to changes.
|
|
1599
1707
|
|
|
1600
|
-
- Returns: `
|
|
1708
|
+
- Returns: `HistoryLocation` - The current location with path, parsed search params, and history state
|
|
1601
1709
|
|
|
1602
1710
|
```tsx
|
|
1603
1711
|
const { path, search, state } = useLocation();
|
|
@@ -1706,31 +1814,15 @@ new MemoryHistory("/initial"); // In-memory only.
|
|
|
1706
1814
|
|
|
1707
1815
|
See [History implementations](#history-implementations) for detailed usage.
|
|
1708
1816
|
|
|
1709
|
-
**`history.
|
|
1710
|
-
|
|
1711
|
-
- Returns: `string` - The current path
|
|
1712
|
-
|
|
1713
|
-
```tsx
|
|
1714
|
-
const path = history.getPath();
|
|
1715
|
-
// Returns "/users/42"
|
|
1716
|
-
```
|
|
1817
|
+
**`history.location()`** returns the current location.
|
|
1717
1818
|
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
- Returns: `Record<string, unknown>` - The parsed search params
|
|
1819
|
+
- Returns: `HistoryLocation` - The current location with path, parsed search params, and history state
|
|
1721
1820
|
|
|
1722
1821
|
```tsx
|
|
1723
|
-
const search = history.
|
|
1724
|
-
//
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
**`history.getState()`** returns the current history state.
|
|
1728
|
-
|
|
1729
|
-
- Returns: `any` - The state passed during navigation, or undefined
|
|
1730
|
-
|
|
1731
|
-
```tsx
|
|
1732
|
-
const state = history.getState();
|
|
1733
|
-
// Returns any state passed during navigation
|
|
1822
|
+
const { path, search, state } = history.location();
|
|
1823
|
+
// path: "/users/42"
|
|
1824
|
+
// search: { tab: "posts", page: 2 }
|
|
1825
|
+
// state: any state passed during navigation
|
|
1734
1826
|
```
|
|
1735
1827
|
|
|
1736
1828
|
**`history.go(delta)`** navigates forward or back in history.
|
|
@@ -1793,6 +1885,16 @@ type NavigateOptions = {
|
|
|
1793
1885
|
};
|
|
1794
1886
|
```
|
|
1795
1887
|
|
|
1888
|
+
**`HistoryLocation`** represents a history location.
|
|
1889
|
+
|
|
1890
|
+
```tsx
|
|
1891
|
+
interface HistoryLocation {
|
|
1892
|
+
path: string; // The current path
|
|
1893
|
+
search: Record<string, unknown>; // Parsed search params
|
|
1894
|
+
state: any; // History state passed during navigation
|
|
1895
|
+
}
|
|
1896
|
+
```
|
|
1897
|
+
|
|
1796
1898
|
**`HistoryPushOptions`** are options for untyped navigation.
|
|
1797
1899
|
|
|
1798
1900
|
```tsx
|
|
@@ -1859,9 +1961,9 @@ interface PreloadContext {
|
|
|
1859
1961
|
# Roadmap
|
|
1860
1962
|
|
|
1861
1963
|
- Possibility to pass an arbitrary context to the Router instance for later use in preloads?
|
|
1862
|
-
- Relative path navigation? Not sure it's
|
|
1964
|
+
- Relative path navigation? Not sure it's worth the extra bundle size given that users can export/import route objects and pass them as navigation option.
|
|
1863
1965
|
- Document usage in test environments
|
|
1864
|
-
-
|
|
1966
|
+
- Navigation blockers (`useBlocker`, etc.)
|
|
1865
1967
|
- Open to suggestions, we can discuss them [here](https://github.com/strblr/waymark/discussions).
|
|
1866
1968
|
|
|
1867
1969
|
---
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react0 from "react";
|
|
2
2
|
import { AnchorHTMLAttributes, CSSProperties, ComponentType, ReactNode, RefAttributes } from "react";
|
|
3
3
|
import { RouteParams } from "regexparam";
|
|
4
4
|
import { EmptyObject, Merge, Simplify } from "type-fest";
|
|
@@ -10,7 +10,6 @@ type NormalizePath<P extends string> = RemoveTrailingSlash<DedupSlashes<`/${P}`>
|
|
|
10
10
|
type DedupSlashes<P extends string> = P extends `${infer Prefix}//${infer Rest}` ? `${Prefix}${DedupSlashes<`/${Rest}`>}` : P;
|
|
11
11
|
type RemoveTrailingSlash<P extends string> = P extends `${infer Prefix}/` ? Prefix extends "" ? "/" : Prefix : P;
|
|
12
12
|
type MaybeKey<K extends string, T> = T extends EmptyObject ? { [P in K]?: EmptyObject } : {} extends T ? { [P in K]?: T } : { [P in K]: T };
|
|
13
|
-
type OptionalOnUndefined<T extends object> = Simplify<{ [K in keyof T as undefined extends T[K] ? never : K]: T[K] } & { [K in keyof T as undefined extends T[K] ? K : never]?: T[K] }>;
|
|
14
13
|
//#endregion
|
|
15
14
|
//#region src/types.d.ts
|
|
16
15
|
interface Register {}
|
|
@@ -21,8 +20,8 @@ type Handle = Register extends {
|
|
|
21
20
|
handle: infer Handle;
|
|
22
21
|
} ? Handle : any;
|
|
23
22
|
interface Middleware<S extends {} = any> {
|
|
24
|
-
use: <S2 extends {}>(middleware: Middleware<S2>) => Middleware<Merge<S,
|
|
25
|
-
search: <S2 extends {}>(validate: Validator<S, S2>) => Middleware<Merge<S,
|
|
23
|
+
use: <S2 extends {}>(middleware: Middleware<S2>) => Middleware<Merge<S, S2>>;
|
|
24
|
+
search: <S2 extends {}>(validate: Validator<S, S2>) => Middleware<Merge<S, S2>>;
|
|
26
25
|
handle: (handle: Handle) => Middleware<S>;
|
|
27
26
|
preload: (preload: (context: PreloadContext<{}, S>) => Promise<any>) => Middleware<S>;
|
|
28
27
|
component: (component: ComponentType) => Middleware<S>;
|
|
@@ -38,8 +37,8 @@ interface PreloadContext<Ps extends {} = any, S extends {} = any> {
|
|
|
38
37
|
search: S;
|
|
39
38
|
}
|
|
40
39
|
interface RouterOptions {
|
|
41
|
-
basePath?: string;
|
|
42
40
|
routes: RouteList;
|
|
41
|
+
basePath?: string;
|
|
43
42
|
history?: HistoryLike;
|
|
44
43
|
ssrContext?: SSRContext;
|
|
45
44
|
defaultLinkOptions?: LinkOptions;
|
|
@@ -65,7 +64,7 @@ type NavigateOptions<P extends Pattern> = {
|
|
|
65
64
|
to: P | GetRoute<P>;
|
|
66
65
|
replace?: boolean;
|
|
67
66
|
state?: any;
|
|
68
|
-
} & MaybeKey<"params", Params<P>> & MaybeKey<"search", Search<P
|
|
67
|
+
} & MaybeKey<"params", Params<P>> & MaybeKey<"search", Partial<Search<P>>>;
|
|
69
68
|
interface LinkOptions {
|
|
70
69
|
strict?: boolean;
|
|
71
70
|
preload?: "intent" | "render" | "viewport" | false;
|
|
@@ -79,19 +78,22 @@ type SSRContext = {
|
|
|
79
78
|
redirect?: string;
|
|
80
79
|
statusCode?: number;
|
|
81
80
|
};
|
|
82
|
-
interface HistoryPushOptions {
|
|
83
|
-
url: string;
|
|
84
|
-
replace?: boolean;
|
|
85
|
-
state?: any;
|
|
86
|
-
}
|
|
87
81
|
interface HistoryLike {
|
|
88
|
-
|
|
89
|
-
getSearch: () => Record<string, unknown>;
|
|
90
|
-
getState: () => any;
|
|
82
|
+
location: () => HistoryLocation;
|
|
91
83
|
go: (delta: number) => void;
|
|
92
84
|
push: (options: HistoryPushOptions) => void;
|
|
93
85
|
subscribe: (listener: () => void) => () => void;
|
|
94
86
|
}
|
|
87
|
+
interface HistoryLocation {
|
|
88
|
+
path: string;
|
|
89
|
+
search: Record<string, unknown>;
|
|
90
|
+
state: any;
|
|
91
|
+
}
|
|
92
|
+
interface HistoryPushOptions {
|
|
93
|
+
url: string;
|
|
94
|
+
replace?: boolean;
|
|
95
|
+
state?: any;
|
|
96
|
+
}
|
|
95
97
|
type Updater<T extends object> = Partial<T> | ((prev: T) => Partial<T>);
|
|
96
98
|
type ComponentLoader = () => Promise<ComponentType | {
|
|
97
99
|
default: ComponentType;
|
|
@@ -105,12 +107,13 @@ declare class Route<P extends string = string, Ps extends {} = any, S extends {}
|
|
|
105
107
|
pattern: P;
|
|
106
108
|
keys: string[];
|
|
107
109
|
regex: RegExp;
|
|
108
|
-
|
|
110
|
+
loose: RegExp;
|
|
109
111
|
weights: number[];
|
|
110
112
|
validate: (search: Record<string, unknown>) => S;
|
|
111
113
|
handles: Handle[];
|
|
112
114
|
components: ComponentType[];
|
|
113
115
|
preloads: ((context: PreloadContext) => Promise<any>)[];
|
|
116
|
+
p?: Route;
|
|
114
117
|
};
|
|
115
118
|
readonly _types: {
|
|
116
119
|
params: Ps;
|
|
@@ -118,8 +121,8 @@ declare class Route<P extends string = string, Ps extends {} = any, S extends {}
|
|
|
118
121
|
};
|
|
119
122
|
constructor(_: typeof this._);
|
|
120
123
|
route: <P2 extends string>(pattern: P2) => Route<NormalizePath<`${P}/${P2}`>, ParsePattern<NormalizePath<`${P}/${P2}`>>, S>;
|
|
121
|
-
use: <S2 extends {}>(middleware: Middleware<S2>) => Route<P, Ps, Merge<S,
|
|
122
|
-
search: <S2 extends {}>(validate: Validator<S, S2>) => Route<P, Ps, Merge<S,
|
|
124
|
+
use: <S2 extends {}>(middleware: Middleware<S2>) => Route<P, Ps, Merge<S, S2>>;
|
|
125
|
+
search: <S2 extends {}>(validate: Validator<S, S2>) => Route<P, Ps, Merge<S, S2>>;
|
|
123
126
|
handle: (handle: Handle) => Route<P, Ps, S>;
|
|
124
127
|
preload: (preload: (context: PreloadContext<Ps, S>) => Promise<any>) => Route<P, Ps, S>;
|
|
125
128
|
component: (component: ComponentType) => Route<P, Ps, S>;
|
|
@@ -133,8 +136,8 @@ declare class Route<P extends string = string, Ps extends {} = any, S extends {}
|
|
|
133
136
|
//#endregion
|
|
134
137
|
//#region src/router/router.d.ts
|
|
135
138
|
declare class Router {
|
|
136
|
-
readonly basePath: string;
|
|
137
139
|
readonly routes: RouteList;
|
|
140
|
+
readonly basePath: string;
|
|
138
141
|
readonly history: HistoryLike;
|
|
139
142
|
readonly ssrContext?: SSRContext;
|
|
140
143
|
readonly defaultLinkOptions?: LinkOptions;
|
|
@@ -150,13 +153,10 @@ declare class Router {
|
|
|
150
153
|
//#endregion
|
|
151
154
|
//#region src/router/browser-history.d.ts
|
|
152
155
|
declare class BrowserHistory implements HistoryLike {
|
|
153
|
-
private
|
|
154
|
-
|
|
156
|
+
private _?;
|
|
157
|
+
protected _loc: (path: string, search: string) => HistoryLocation;
|
|
155
158
|
constructor();
|
|
156
|
-
|
|
157
|
-
getPath: () => string;
|
|
158
|
-
getSearch: () => Record<string, unknown>;
|
|
159
|
-
getState: () => any;
|
|
159
|
+
location: () => HistoryLocation;
|
|
160
160
|
go: (delta: number) => void;
|
|
161
161
|
push: (options: HistoryPushOptions) => void;
|
|
162
162
|
subscribe: (listener: () => void) => () => void;
|
|
@@ -168,10 +168,7 @@ declare class MemoryHistory implements HistoryLike {
|
|
|
168
168
|
private index;
|
|
169
169
|
private listeners;
|
|
170
170
|
constructor(url?: string);
|
|
171
|
-
|
|
172
|
-
getPath: () => string;
|
|
173
|
-
getSearch: () => Record<string, unknown>;
|
|
174
|
-
getState: () => any;
|
|
171
|
+
location: () => HistoryLocation;
|
|
175
172
|
go: (delta: number) => void;
|
|
176
173
|
push: (options: HistoryPushOptions) => void;
|
|
177
174
|
subscribe: (listener: () => void) => () => void;
|
|
@@ -179,9 +176,7 @@ declare class MemoryHistory implements HistoryLike {
|
|
|
179
176
|
//#endregion
|
|
180
177
|
//#region src/router/hash-history.d.ts
|
|
181
178
|
declare class HashHistory extends BrowserHistory {
|
|
182
|
-
|
|
183
|
-
getPath: () => string;
|
|
184
|
-
getSearch: () => Record<string, unknown>;
|
|
179
|
+
location: () => HistoryLocation;
|
|
185
180
|
push: (options: HistoryPushOptions) => void;
|
|
186
181
|
}
|
|
187
182
|
//#endregion
|
|
@@ -200,22 +195,18 @@ declare function Link<P extends Pattern>(props: LinkProps<P>): ReactNode;
|
|
|
200
195
|
//#endregion
|
|
201
196
|
//#region src/react/hooks.d.ts
|
|
202
197
|
declare function useRouter(): Router;
|
|
198
|
+
declare function useLocation(): HistoryLocation;
|
|
199
|
+
declare function useMatch<P extends Pattern>(options: MatchOptions<P>): Match<P> | null;
|
|
200
|
+
declare function useOutlet(): ReactNode;
|
|
203
201
|
declare function useNavigate(): <P extends Pattern>(options: number | HistoryPushOptions | NavigateOptions<P>) => void;
|
|
204
|
-
declare function
|
|
205
|
-
path: string;
|
|
206
|
-
search: Record<string, unknown>;
|
|
207
|
-
state: any;
|
|
208
|
-
};
|
|
209
|
-
declare function useOutlet(): react2.ReactNode;
|
|
202
|
+
declare function useHandles(): Handle[];
|
|
210
203
|
declare function useParams<P extends Pattern>(from: P | GetRoute<P>): Params<P>;
|
|
211
204
|
declare function useSearch<P extends Pattern>(from: P | GetRoute<P>): readonly [Search<P>, (update: Updater<Search<P>>, replace?: boolean) => void];
|
|
212
|
-
declare function useMatch<P extends Pattern>(options: MatchOptions<P>): Match<P> | null;
|
|
213
|
-
declare function useHandles(): Handle[];
|
|
214
|
-
declare function useSubscribe<T>(router: Router, getSnapshot: () => T): T;
|
|
215
205
|
//#endregion
|
|
216
206
|
//#region src/react/contexts.d.ts
|
|
217
|
-
declare const RouterContext:
|
|
218
|
-
declare const
|
|
219
|
-
declare const
|
|
207
|
+
declare const RouterContext: react0.Context<Router | null>;
|
|
208
|
+
declare const LocationContext: react0.Context<HistoryLocation | null>;
|
|
209
|
+
declare const MatchContext: react0.Context<Match | null>;
|
|
210
|
+
declare const OutletContext: react0.Context<ReactNode>;
|
|
220
211
|
//#endregion
|
|
221
|
-
export { BrowserHistory, ComponentLoader, GetRoute, Handle, HashHistory, HistoryLike, HistoryPushOptions, Link, LinkOptions, LinkProps, Match, MatchContext, MatchOptions, MemoryHistory, Middleware, Navigate, NavigateOptions, NavigateProps, Outlet, OutletContext, Params, Pattern, PreloadContext, Register, Route, RouteList, Router, RouterContext, RouterOptions, RouterRoot, RouterRootProps, SSRContext, Search, Updater, Validator, middleware, route, useHandles, useLocation, useMatch, useNavigate, useOutlet, useParams, useRouter, useSearch
|
|
212
|
+
export { BrowserHistory, ComponentLoader, GetRoute, Handle, HashHistory, HistoryLike, HistoryLocation, HistoryPushOptions, Link, LinkOptions, LinkProps, LocationContext, Match, MatchContext, MatchOptions, MemoryHistory, Middleware, Navigate, NavigateOptions, NavigateProps, Outlet, OutletContext, Params, Pattern, PreloadContext, Register, Route, RouteList, Router, RouterContext, RouterOptions, RouterRoot, RouterRootProps, SSRContext, Search, Updater, Validator, middleware, route, useHandles, useLocation, useMatch, useNavigate, useOutlet, useParams, useRouter, useSearch };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Component as e,Suspense as t,cloneElement as n,createContext as r,isValidElement as i,lazy as a,memo as o,useCallback as s,useContext as c,useEffect as l,useInsertionEffect as u,useLayoutEffect as d,useMemo as f,useRef as p,useState as m,useSyncExternalStore as h}from"react";import{inject as g,parse as _}from"regexparam";import{jsx as v}from"react/jsx-runtime";function y(e){return`/${e}`.replaceAll(/\/+/g,`/`).replace(/(.+)\/$/,`$1`)}function b(e){let{keys:t,pattern:n}=_(e);return{pattern:e,keys:t,regex:n,
|
|
1
|
+
import{Component as e,Suspense as t,cloneElement as n,createContext as r,isValidElement as i,lazy as a,memo as o,useCallback as s,useContext as c,useEffect as l,useInsertionEffect as u,useLayoutEffect as d,useMemo as f,useRef as p,useState as m,useSyncExternalStore as h}from"react";import{inject as g,parse as _}from"regexparam";import{jsx as v}from"react/jsx-runtime";function y(e){return`/${e}`.replaceAll(/\/+/g,`/`).replace(/(.+)\/$/,`$1`)}function b(e){let{keys:t,pattern:n}=_(e);return{pattern:e,keys:t,regex:n,loose:_(e,!0).pattern,weights:e.split(`/`).slice(1).map(e=>e.includes(`*`)?0:e.includes(`:`)?1:2)}}function x(e){return typeof e==`function`?e:t=>{let n=e[`~standard`].validate(t);if(n instanceof Promise)throw Error(`[Waymark] Validation can't be async`);if(n.issues)throw Error(`[Waymark] Validation failed`,{cause:n.issues});return n.value}}function S(e){return Object.entries(e).filter(([e,t])=>t!==void 0).map(([e,t])=>`${e}=${encodeURIComponent(w(t))}`).join(`&`)}function C(e){let t=new URLSearchParams(e);return Object.fromEntries([...t.entries()].map(([e,t])=>(t=decodeURIComponent(t),[e,T(t)?JSON.parse(t):t])))}function w(e){return typeof e==`string`&&!T(e)?e:JSON.stringify(e)}function T(e){try{return JSON.parse(e),!0}catch{return!1}}function E(e,t){return y(`${t}/${e}`)}function D(e,t){return(e===t||e.startsWith(`${t}/`))&&(e=e.slice(t.length)||`/`),e}function O(e,t){return[e,S(t)].filter(Boolean).join(`?`)}function k(e){let{pathname:t,search:n}=new URL(e,`http://w`);return{path:t,search:C(n)}}function A({keys:e,regex:t,loose:n},r,i,a){let o=(r?t:n).exec(D(i,a));if(!o)return null;let s={};return e.forEach((e,t)=>{let n=o[t+1];n&&(s[e]=n)}),s}function j(e){return[...e].sort((e,t)=>{let n=e.route._.weights,r=t.route._.weights,i=Math.max(n.length,r.length);for(let e=0;e<i;e++){let t=n[e]??-1,i=r[e]??-1;if(t!==i)return i-t}return 0})}const M=r(null),N=r(null),P=r(null),F=r(null);function I(){let e=c(M);if(e)return e;throw Error(`[Waymark] useRouter must be within a router context`)}function L(){let e=c(N);if(e)return e;throw Error(`[Waymark] useLocation must be within a router context`)}function R(e){let t=I(),{path:n}=L();return f(()=>t.match(n,e),[t,n,e.from,e.strict,e.params])}function z(){return c(F)}function B(){return I().navigate}function V(){let e=c(P);return f(()=>e?.route._.handles??[],[e])}function H(e){let t=R({from:e});if(t)return t.params;throw Error(`[Waymark] Can't read params for non-matching route ${e}`)}function U(e){let t=I(),{search:n,path:r}=L(),i=t.getRoute(e),a=f(()=>i._.validate(n),[i,n]);return[a,Z((e,n)=>{e=typeof e==`function`?e(a):e;let i=O(r,{...a,...e});t.navigate({url:i,replace:n})})]}var W=class{_;_loc=(e,t)=>{let{state:n}=history,[r,i]=this._??[];return i?.path===e&&r===t&&i.state===n?i:(this._=[t,{path:e,search:C(t),state:n}])[1]};constructor(){if(!window[G]){for(let e of[K,q]){let t=history[e];history[e]=function(...n){t.apply(this,n);let r=new Event(e);dispatchEvent(r)}}window[G]=1}}location=()=>this._loc(location.pathname,location.search);go=e=>history.go(e);push=e=>{let{url:t,replace:n,state:r}=e;history[n?q:K](r,``,t)};subscribe=e=>(J.forEach(t=>window.addEventListener(t,e)),()=>{J.forEach(t=>window.removeEventListener(t,e))})};const G=Symbol.for(`wmp01`),K=`pushState`,q=`replaceState`,J=[`popstate`,K,q,`hashchange`];var Y=class{routes;basePath;history;ssrContext;defaultLinkOptions;_;constructor(e){let{routes:t,basePath:n=`/`,history:r,ssrContext:i,defaultLinkOptions:a}=e;this.routes=t,this.basePath=y(n),this.history=r??new W,this.ssrContext=i,this.defaultLinkOptions=a,this._={routeMap:new Map(t.map(e=>[e._.pattern,e]))}}getRoute=e=>{if(typeof e!=`string`)return e;let t=this._.routeMap.get(e);if(!t)throw Error(`[Waymark] Route not found for ${e}`);return t};match=(e,t)=>{let{from:n,strict:r,params:i}=t,a=this.getRoute(n),o=A(a._,r,e,this.basePath);return o&&(!i||Object.keys(i).every(e=>i[e]===o[e]))?{route:a,params:o}:null};matchAll=e=>j(this.routes.map(t=>this.match(e,{from:t,strict:!0})).filter(e=>!!e))[0]??null;createUrl=e=>{let{to:t,params:n={},search:r={}}=e,{pattern:i}=this.getRoute(t)._;return O(E(g(i,n),this.basePath),r)};preload=async e=>{let{to:t,params:n={},search:r={}}=e,{preloads:i}=this.getRoute(t)._;await Promise.all(i.map(e=>e({params:n,search:r})))};navigate=e=>{if(typeof e==`number`)this.history.go(e);else if(`url`in e)this.history.push(e);else{let{replace:t,state:n}=e;this.history.push({url:this.createUrl(e),replace:t,state:n})}}},X=class{stack=[];index=0;listeners=new Set;constructor(e=`/`){this.stack.push({...k(e),state:void 0})}location=()=>this.stack[this.index];go=e=>{let t=this.index+e;this.stack[t]&&(this.index=t,this.listeners.forEach(e=>e()))};push=e=>{let{url:t,replace:n,state:r}=e,i={...k(t),state:r};this.stack=this.stack.slice(0,this.index+1),n?this.stack[this.index]=i:this.index=this.stack.push(i)-1,this.listeners.forEach(e=>e())};subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e)})},ee=class extends W{location=()=>{let{pathname:e,search:t}=new URL(location.hash.slice(1),`http://w`);return this._loc(e,t)};push=e=>{let{url:t,replace:n,state:r}=e;history[n?`replaceState`:`pushState`](r,``,`#${t}`)}};function te(e){let[t]=m(()=>`router`in e?e.router:new Y(e)),{subscribe:n,location:r}=t.history,i=h(n,r,r),a=f(()=>t.matchAll(i.path),[t,i.path]);return a||console.error(`[Waymark] No matching route for path`,i.path),f(()=>v(M.Provider,{value:t,children:v(N.Provider,{value:i,children:v(P.Provider,{value:a,children:a?.route._.components.reduceRight((e,t)=>v(F.Provider,{value:e,children:v(t,{})}),null)})})}),[t,i,a])}function ne(){return z()}function re(e){let t=I();return d(()=>t.navigate(e),[]),t.ssrContext&&(t.ssrContext.redirect=t.createUrl(e)),null}function ie(e){let t=I(),{to:r,replace:a,state:o,params:c,search:u,strict:d,preload:m,preloadDelay:h=50,style:g,className:_,activeStyle:y,activeClassName:b,asChild:x,children:S,...C}={...t.defaultLinkOptions,...e},w=p(null),T=p(null),E=t.createUrl(e),D=!!R({from:r,strict:d,params:c}),O=Z(()=>t.preload(e)),k=s(()=>{clearTimeout(T.current)},[]),A=s(()=>{k(),T.current=setTimeout(O,h)},[h,k]),j=f(()=>({"data-active":D,style:{...g,...D&&y},className:[_,D&&b].filter(Boolean).join(` `)||void 0}),[D,g,_,y,b]);l(()=>{if(m===`render`)A();else if(m===`viewport`&&w.current){let e=new IntersectionObserver(e=>e.forEach(e=>{e.isIntersecting?A():k()}));return e.observe(w.current),()=>{e.disconnect(),k()}}return k},[m,A,k]);let M=e=>{C.onClick?.(e),!(e.ctrlKey||e.metaKey||e.shiftKey||e.altKey||e.button!==0||e.defaultPrevented)&&(e.preventDefault(),t.navigate({url:E,replace:a,state:o}))},N=e=>{C.onFocus?.(e),m===`intent`&&!e.defaultPrevented&&A()},P=e=>{C.onBlur?.(e),m===`intent`&&k()},F=e=>{C.onPointerEnter?.(e),m===`intent`&&!e.defaultPrevented&&A()},L=e=>{C.onPointerLeave?.(e),m===`intent`&&k()},z={...C,...j,ref:ae(w,C.ref),href:E,onClick:M,onFocus:N,onBlur:P,onPointerEnter:F,onPointerLeave:L};return x&&i(S)?n(S,z):v(`a`,{...z,children:S})}function ae(e,t){return t?n=>{e.current=n;let r=typeof t==`function`?t(n):void(t.current=n);return r&&(()=>{e.current=null,r()})}:e}function Z(e){let t=p(e);return u(()=>{t.current=e},[e]),p(((...e)=>t.current(...e))).current}function oe(e){return()=>v(t,{fallback:v(e,{}),children:z()})}function se(t){class n extends e{constructor(e){super(e),this.state={children:e.children,error:null}}static getDerivedStateFromError(e){return{error:[e]}}static getDerivedStateFromProps(e,t){return e.children===t.children?t:{children:e.children,error:null}}render(){return this.state.error?v(t,{error:this.state.error[0]}):this.props.children}}return()=>v(n,{children:z()})}function Q(e){return new $({...b(y(e)),validate:e=>e,handles:[],components:[],preloads:[]})}function ce(){return Q(``)}var $=class e{_;_types;constructor(e){this._=e}route=t=>new e({...this._,...b(y(`${this._.pattern}/${t}`)),p:this});use=t=>{let{_:n}=t;return new e({...this._,handles:[...this._.handles,...n.handles],components:[...this._.components,...n.components],preloads:[...this._.preloads,...n.preloads]}).search(n.validate)};search=t=>(t=x(t),new e({...this._,validate:e=>{let n=this._.validate(e);return{...n,...t({...e,...n})}}}));handle=t=>new e({...this._,handles:[...this._.handles,t]});preload=t=>new e({...this._,preloads:[...this._.preloads,e=>t({params:e.params,search:this._.validate(e.search)})]});component=t=>new e({...this._,components:[...this._.components,o(t)]});lazy=e=>{let t=a(async()=>{let t=await e();return`default`in t?t:{default:t}});return this.preload(e).component(t)};suspense=e=>this.component(oe(e));error=e=>this.component(se(e));toString=()=>this._.pattern};export{W as BrowserHistory,ee as HashHistory,ie as Link,N as LocationContext,P as MatchContext,X as MemoryHistory,re as Navigate,ne as Outlet,F as OutletContext,$ as Route,Y as Router,M as RouterContext,te as RouterRoot,ce as middleware,Q as route,V as useHandles,L as useLocation,R as useMatch,B as useNavigate,z as useOutlet,H as useParams,I as useRouter,U as useSearch};
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "waymark",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "strblr",
|
|
6
|
-
"description": "Type-safe router
|
|
6
|
+
"description": "Type-safe React router that just works - simple setup, full autocomplete, 4kB gzipped",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"scripts": {
|
|
40
40
|
"build": "tsc --noEmit && tsdown",
|
|
41
|
+
"dev": "tsdown --watch",
|
|
41
42
|
"prepublishOnly": "bun run build && cp ../../README.md README.md",
|
|
42
43
|
"postpublish": "rm -f README.md"
|
|
43
44
|
},
|