weifuwu 0.31.1 → 0.32.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 +238 -120
- package/dist/ai/agent.d.ts +127 -0
- package/dist/ai/index.d.ts +26 -0
- package/dist/ai/types.d.ts +59 -0
- package/dist/core/router.d.ts +11 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +1186 -315
- package/dist/middleware/auth.d.ts +68 -0
- package/dist/middleware/esbuild-dev.d.ts +69 -0
- package/dist/middleware/esbuild-dev.js +335 -0
- package/dist/middleware/sandbox.d.ts +52 -0
- package/dist/middleware/tailwind-dev.d.ts +43 -0
- package/dist/middleware/tailwind-dev.js +199 -0
- package/dist/react/client.d.ts +64 -27
- package/dist/react/client.js +130 -248
- package/dist/react/compile.d.ts +17 -0
- package/dist/react/error-boundary.d.ts +18 -19
- package/dist/react/index.d.ts +80 -16
- package/dist/react/index.js +835 -267
- package/dist/react/types.d.ts +134 -34
- package/dist/types.d.ts +7 -0
- package/package.json +17 -6
- package/dist/react/navigation.d.ts +0 -63
- package/dist/react/navigation.js +0 -154
- package/dist/react/render.d.ts +0 -8
- package/dist/react/route-utils.d.ts +0 -31
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
// src/middleware/tailwind-dev.ts
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { stat, readFile, glob } from "node:fs/promises";
|
|
4
|
+
function escapeHtml(s) {
|
|
5
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
6
|
+
}
|
|
7
|
+
function defaultErrorTemplate(errors) {
|
|
8
|
+
return `<!DOCTYPE html>
|
|
9
|
+
<html>
|
|
10
|
+
<head>
|
|
11
|
+
<meta charset="utf-8">
|
|
12
|
+
<title>Tailwind Build Error</title>
|
|
13
|
+
<style>
|
|
14
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
15
|
+
body { font-family: system-ui; background: #1a1a2e; color: #e0e0e0; padding: 2rem; }
|
|
16
|
+
.overlay { position: fixed; inset: 0; display: flex; align-items: center; justify-content: center; }
|
|
17
|
+
.card { background: #16213e; border: 1px solid #06b6d4; border-radius: 12px; padding: 2rem; max-width: 800px; width: 100%; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
|
|
18
|
+
h1 { color: #06b6d4; font-size: 1.5rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
|
|
19
|
+
pre { background: #0f0f23; padding: 1.5rem; border-radius: 8px; overflow: auto; font-size: 0.85rem; line-height: 1.6; white-space: pre-wrap; word-break: break-word; }
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div class="overlay">
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h1>\u{1F3A8} Tailwind CSS Build Error</h1>
|
|
26
|
+
<pre>${escapeHtml(errors)}</pre>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</body>
|
|
30
|
+
</html>`;
|
|
31
|
+
}
|
|
32
|
+
var TW_CANDIDATE_RE = /[a-z][\w-]*(?::[a-z][\w-]*)*(?:-\[[^\]]*\])*(?:\/[0-9]+)?/gi;
|
|
33
|
+
var CLASS_CONTEXT_RE = /(?:class(?:Name)?\s*[=:]\s*["'`]([^"'`]+)["'`]|["'`]([^"'`\n]{2,})["'`])/g;
|
|
34
|
+
async function extractCandidates(patterns, root) {
|
|
35
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36
|
+
for (const pattern of patterns) {
|
|
37
|
+
const resolved = resolve(root, pattern);
|
|
38
|
+
try {
|
|
39
|
+
for await (const file of glob(resolved)) {
|
|
40
|
+
try {
|
|
41
|
+
const content = await readFile(file, "utf-8");
|
|
42
|
+
for (const m of content.matchAll(CLASS_CONTEXT_RE)) {
|
|
43
|
+
const str = m[1] ?? m[2];
|
|
44
|
+
if (!str) continue;
|
|
45
|
+
for (const c of str.matchAll(TW_CANDIDATE_RE)) {
|
|
46
|
+
seen.add(c[0]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return [...seen];
|
|
56
|
+
}
|
|
57
|
+
async function collectFileMtimes(files) {
|
|
58
|
+
const map = /* @__PURE__ */ new Map();
|
|
59
|
+
await Promise.all(
|
|
60
|
+
files.map(async (f) => {
|
|
61
|
+
try {
|
|
62
|
+
const s = await stat(f);
|
|
63
|
+
map.set(f, s.mtimeMs);
|
|
64
|
+
} catch {
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
return map;
|
|
69
|
+
}
|
|
70
|
+
function isCacheValid(cached) {
|
|
71
|
+
return Promise.all(
|
|
72
|
+
[...cached.files].map(async ([file, mtime]) => {
|
|
73
|
+
try {
|
|
74
|
+
const s = await stat(file);
|
|
75
|
+
return s.mtimeMs === mtime;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
).then((results) => results.every(Boolean));
|
|
81
|
+
}
|
|
82
|
+
function tailwindDev(opts) {
|
|
83
|
+
const cacheMode = opts.cache ?? "memory";
|
|
84
|
+
const errorTemplate = opts.errorTemplate ?? defaultErrorTemplate;
|
|
85
|
+
const entries = Object.entries(opts.entries).map(([p, e]) => {
|
|
86
|
+
const path = p.startsWith("/") ? p : "/" + p;
|
|
87
|
+
const config = typeof e === "string" ? { entry: e } : e;
|
|
88
|
+
return { path, config };
|
|
89
|
+
});
|
|
90
|
+
const cacheStore = /* @__PURE__ */ new Map();
|
|
91
|
+
let compileFn = null;
|
|
92
|
+
let loadError = null;
|
|
93
|
+
async function getCompile() {
|
|
94
|
+
if (compileFn) return compileFn;
|
|
95
|
+
try {
|
|
96
|
+
const mod = await import("@tailwindcss/node");
|
|
97
|
+
compileFn = mod.compile;
|
|
98
|
+
return compileFn;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
loadError = `@tailwindcss/node is not installed. Run: npm install -D tailwindcss @tailwindcss/node
|
|
101
|
+
|
|
102
|
+
${String(err)}`;
|
|
103
|
+
throw new Error(loadError, { cause: err });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function compileCss(entry) {
|
|
107
|
+
const compile = await getCompile();
|
|
108
|
+
const entryAbs = resolve(entry.entry);
|
|
109
|
+
const cssSource = await readFile(entryAbs, "utf-8");
|
|
110
|
+
const baseDir = resolve(entry.entry, "..");
|
|
111
|
+
const deps = [entryAbs];
|
|
112
|
+
const result = await compile(cssSource, {
|
|
113
|
+
base: baseDir,
|
|
114
|
+
onDependency: (p) => {
|
|
115
|
+
if (!deps.includes(p)) deps.push(p);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const contentPatterns = entry.content ?? [];
|
|
119
|
+
for (const src of result.sources) {
|
|
120
|
+
if (!src.negated) {
|
|
121
|
+
contentPatterns.push(resolve(src.base, src.pattern));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let candidates = [];
|
|
125
|
+
if (contentPatterns.length > 0) {
|
|
126
|
+
candidates = await extractCandidates(contentPatterns, baseDir);
|
|
127
|
+
}
|
|
128
|
+
const css = result.build(candidates);
|
|
129
|
+
for (const pattern of contentPatterns) {
|
|
130
|
+
try {
|
|
131
|
+
for await (const file of glob(resolve(baseDir, pattern))) {
|
|
132
|
+
if (!deps.includes(file)) deps.push(file);
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const etag = `"tw-${hashCode(css)}"`;
|
|
138
|
+
return { css, etag, files: deps };
|
|
139
|
+
}
|
|
140
|
+
return async (req, ctx, next) => {
|
|
141
|
+
const url = new URL(req.url);
|
|
142
|
+
const pathname = url.pathname;
|
|
143
|
+
const matched = entries.find((e) => e.path === pathname);
|
|
144
|
+
if (!matched) return next(req, ctx);
|
|
145
|
+
const { config } = matched;
|
|
146
|
+
try {
|
|
147
|
+
if (cacheMode === "memory") {
|
|
148
|
+
const cached = cacheStore.get(pathname);
|
|
149
|
+
if (cached) {
|
|
150
|
+
const valid = await isCacheValid(cached);
|
|
151
|
+
if (valid) {
|
|
152
|
+
if (req.headers.get("if-none-match") === cached.etag) {
|
|
153
|
+
return new Response(null, { status: 304 });
|
|
154
|
+
}
|
|
155
|
+
return new Response(cached.css, {
|
|
156
|
+
headers: {
|
|
157
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
158
|
+
ETag: cached.etag,
|
|
159
|
+
"Cache-Control": "no-cache"
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
cacheStore.delete(pathname);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const { css, etag, files } = await compileCss(config);
|
|
167
|
+
if (cacheMode === "memory") {
|
|
168
|
+
const mtimes = await collectFileMtimes(files);
|
|
169
|
+
cacheStore.set(pathname, { css, etag, files: mtimes });
|
|
170
|
+
}
|
|
171
|
+
return new Response(css, {
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "text/css; charset=utf-8",
|
|
174
|
+
ETag: etag,
|
|
175
|
+
"Cache-Control": "no-cache"
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
} catch (err) {
|
|
179
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
180
|
+
const html = errorTemplate(message);
|
|
181
|
+
return new Response(html, {
|
|
182
|
+
status: 500,
|
|
183
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function hashCode(s) {
|
|
189
|
+
let hash = 0;
|
|
190
|
+
for (let i = 0; i < s.length; i++) {
|
|
191
|
+
const ch = s.charCodeAt(i);
|
|
192
|
+
hash = (hash << 5) - hash + ch;
|
|
193
|
+
hash |= 0;
|
|
194
|
+
}
|
|
195
|
+
return Math.abs(hash).toString(36);
|
|
196
|
+
}
|
|
197
|
+
export {
|
|
198
|
+
tailwindDev
|
|
199
|
+
};
|
package/dist/react/client.d.ts
CHANGED
|
@@ -1,39 +1,76 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Client-side entry for React hydration
|
|
2
|
+
* Client-side entry for React hydration and SPA routing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Hydration:
|
|
5
5
|
* ```ts
|
|
6
|
-
* import { hydrate
|
|
7
|
-
*
|
|
6
|
+
* import { hydrate } from 'weifuwu/react/client'
|
|
7
|
+
* hydrate(HomePage, { layout: PageShell })
|
|
8
|
+
* ```
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* Client-side router:
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { createBrowserRouter } from 'weifuwu/react/client'
|
|
13
|
+
* createBrowserRouter({
|
|
14
|
+
* layout: PageShell,
|
|
15
|
+
* routes: {
|
|
16
|
+
* '/': () => import('./pages/HomePage.tsx'),
|
|
17
|
+
* '/users/:id': () => import('./pages/UserPage.tsx'),
|
|
18
|
+
* },
|
|
19
|
+
* })
|
|
14
20
|
* ```
|
|
15
21
|
*/
|
|
16
|
-
import { type ComponentType, type
|
|
17
|
-
import { type LinkProps } from './navigation.ts';
|
|
22
|
+
import { type ComponentType, type ReactNode } from 'react';
|
|
18
23
|
export interface HydrateOptions {
|
|
24
|
+
/** Hydration target element. Default: document.getElementById('root'). */
|
|
19
25
|
container?: HTMLElement;
|
|
26
|
+
/**
|
|
27
|
+
* Layout component matching the server-side `react({ layout })` config.
|
|
28
|
+
* Must accept `{ children: ReactNode }`.
|
|
29
|
+
* When provided, wraps the page component before hydrating —
|
|
30
|
+
* producing the same tree the server rendered.
|
|
31
|
+
*/
|
|
32
|
+
layout?: ComponentType<{
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}>;
|
|
20
35
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Hydrate a page component. When `layout` is provided, the hydration tree
|
|
38
|
+
* matches the server-rendered structure: `<div id="root"><Layout><App /></Layout></div>`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function hydrate(App: ComponentType, opts?: HydrateOptions): void;
|
|
41
|
+
export interface ClientRouterOptions {
|
|
42
|
+
/** Layout component shared across all routes. */
|
|
43
|
+
layout?: ComponentType<{
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* Route map: URL pattern → lazy component loader.
|
|
48
|
+
* Patterns support `:param` segments (e.g. `/users/:id`).
|
|
49
|
+
*/
|
|
50
|
+
routes: Record<string, () => Promise<Record<string, unknown>>>;
|
|
51
|
+
/**
|
|
52
|
+
* Fallback component for unmatched routes.
|
|
53
|
+
* When omitted, unmatched routes do a full page navigation.
|
|
54
|
+
*/
|
|
55
|
+
fallback?: () => Promise<Record<string, unknown>>;
|
|
56
|
+
/** Target element. Default: document.getElementById('root'). */
|
|
57
|
+
container?: HTMLElement;
|
|
25
58
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
useParams: () => Record<string, string>;
|
|
30
|
-
navigate: (url: string) => Promise<void>;
|
|
59
|
+
interface RouterHandle {
|
|
60
|
+
navigate(url: string): Promise<void>;
|
|
61
|
+
renderPath(pathname: string, isInitial: boolean, data?: Record<string, unknown>): Promise<void>;
|
|
31
62
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Initialise the client-side router.
|
|
65
|
+
*
|
|
66
|
+
* On the initial page load, it hydrates the matching route from the
|
|
67
|
+
* server-rendered HTML. Subsequent in-app navigations fetch fresh
|
|
68
|
+
* server data and render the new page client-side.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createBrowserRouter(opts: ClientRouterOptions): RouterHandle;
|
|
71
|
+
/**
|
|
72
|
+
* Programmatic navigation (call from anywhere).
|
|
73
|
+
* Falls back to `window.location.href = url` if the router hasn't been initialised.
|
|
74
|
+
*/
|
|
75
|
+
export declare function navigate(url: string): void;
|
|
39
76
|
export { useServerData } from './hooks.ts';
|