weifuwu 0.33.1 → 0.33.2
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 +160 -3
- package/dist/client/app.d.ts +21 -0
- package/dist/client/index.d.ts +21 -0
- package/dist/client/index.js +320 -0
- package/dist/client/jsx-runtime.d.ts +60 -0
- package/dist/client/jsx-runtime.js +320 -0
- package/dist/client/router.d.ts +51 -0
- package/dist/client/signal.d.ts +19 -0
- package/dist/client/types.d.ts +45 -0
- package/dist/index.d.ts +0 -4
- package/dist/index.js +11 -475
- package/package.json +11 -24
- package/dist/middleware/esbuild-dev.d.ts +0 -55
- package/dist/middleware/esbuild-dev.js +0 -266
- package/dist/middleware/tailwind-dev.d.ts +0 -43
- package/dist/middleware/tailwind-dev.js +0 -199
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
// src/middleware/esbuild-dev.ts
|
|
2
|
-
import { resolve, relative, dirname } from "node:path";
|
|
3
|
-
import { stat, readFile, realpath } 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>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 #e74c3c; border-radius: 12px; padding: 2rem; max-width: 800px; width: 100%; box-shadow: 0 8px 32px rgba(0,0,0,0.4); }
|
|
18
|
-
h1 { color: #e74c3c; 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>\u26A0 esbuild Build Error</h1>
|
|
26
|
-
<pre>${escapeHtml(errors)}</pre>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
</body>
|
|
30
|
-
</html>`;
|
|
31
|
-
}
|
|
32
|
-
async function collectDeps(entryPath) {
|
|
33
|
-
const files = /* @__PURE__ */ new Map();
|
|
34
|
-
const visited = /* @__PURE__ */ new Set();
|
|
35
|
-
const queue = [entryPath];
|
|
36
|
-
while (queue.length > 0) {
|
|
37
|
-
const p = queue.shift();
|
|
38
|
-
try {
|
|
39
|
-
const real = await realpath(p);
|
|
40
|
-
if (visited.has(real)) continue;
|
|
41
|
-
visited.add(real);
|
|
42
|
-
const s = await stat(real);
|
|
43
|
-
files.set(real, s.mtimeMs);
|
|
44
|
-
} catch {
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
const content = await readFile(p, "utf-8");
|
|
49
|
-
const importRe = /from\s+['"]([^'"]+)['"]|import\s+['"]([^'"]+)['"]/g;
|
|
50
|
-
let m;
|
|
51
|
-
while ((m = importRe.exec(content)) !== null) {
|
|
52
|
-
const spec = m[1] ?? m[2];
|
|
53
|
-
if (spec.startsWith(".") || spec.startsWith("/")) {
|
|
54
|
-
const dir = dirname(p);
|
|
55
|
-
const resolved = resolve(dir, spec);
|
|
56
|
-
for (const ext of ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", "/index.ts", "/index.tsx", "/index.js"]) {
|
|
57
|
-
const candidate = resolved + ext;
|
|
58
|
-
if (!visited.has(candidate)) {
|
|
59
|
-
queue.push(candidate);
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
queue.push(resolved);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
} catch {
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return files;
|
|
71
|
-
}
|
|
72
|
-
function esbuildDev(opts) {
|
|
73
|
-
const cache = opts.cache ?? "memory";
|
|
74
|
-
const importmap = opts.importmap ?? false;
|
|
75
|
-
const importmapOverrides = opts.importmapOverrides ?? {};
|
|
76
|
-
const errorTemplate = opts.errorTemplate ?? defaultErrorTemplate;
|
|
77
|
-
const entries = Object.entries(opts.entries).map(([p, e]) => {
|
|
78
|
-
const path = p.startsWith("/") ? p : "/" + p;
|
|
79
|
-
const config = typeof e === "string" ? { entry: e } : e;
|
|
80
|
-
return { path, config };
|
|
81
|
-
});
|
|
82
|
-
const cacheStore = /* @__PURE__ */ new Map();
|
|
83
|
-
const chunkMap = /* @__PURE__ */ new Map();
|
|
84
|
-
let esbuild_ = null;
|
|
85
|
-
let esbuildLoadError = null;
|
|
86
|
-
async function getEsbuild() {
|
|
87
|
-
if (esbuild_) return esbuild_;
|
|
88
|
-
try {
|
|
89
|
-
esbuild_ = await import("esbuild");
|
|
90
|
-
return esbuild_;
|
|
91
|
-
} catch (err) {
|
|
92
|
-
esbuildLoadError = `esbuild is not installed. Run: npm install -D esbuild
|
|
93
|
-
|
|
94
|
-
${String(err)}`;
|
|
95
|
-
throw new Error(esbuildLoadError, { cause: err });
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
let importmapScript = null;
|
|
99
|
-
function buildImportmap() {
|
|
100
|
-
const mappings = {};
|
|
101
|
-
Object.assign(mappings, importmapOverrides);
|
|
102
|
-
for (const { path, config } of entries) {
|
|
103
|
-
const entryName = path.split("/").pop() ?? "";
|
|
104
|
-
if (entryName.includes("vendor") || config.external && config.external.length === 0 || !config.external && config.bundle !== false) {
|
|
105
|
-
if (!mappings["react"]) mappings["react"] = path;
|
|
106
|
-
if (!mappings["react/jsx-runtime"]) mappings["react/jsx-runtime"] = path;
|
|
107
|
-
if (!mappings["react-dom/client"]) mappings["react-dom/client"] = path;
|
|
108
|
-
if (!mappings["react-dom"]) mappings["react-dom"] = path;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const json = JSON.stringify({ imports: mappings });
|
|
112
|
-
return `<script type="importmap">${json}</script>`;
|
|
113
|
-
}
|
|
114
|
-
async function compile(entry) {
|
|
115
|
-
const esbuild = await getEsbuild();
|
|
116
|
-
const entryAbs = resolve(entry.entry);
|
|
117
|
-
const buildOpts = {
|
|
118
|
-
entryPoints: [entryAbs],
|
|
119
|
-
bundle: entry.bundle ?? true,
|
|
120
|
-
external: entry.external ?? [],
|
|
121
|
-
minify: entry.minify ?? true,
|
|
122
|
-
platform: entry.platform ?? "browser",
|
|
123
|
-
format: entry.format ?? "esm",
|
|
124
|
-
sourcemap: entry.sourcemap ?? false,
|
|
125
|
-
splitting: entry.splitting ?? false,
|
|
126
|
-
...entry.splitting ? { outdir: dirname(entryAbs) } : {},
|
|
127
|
-
define: entry.define,
|
|
128
|
-
loader: entry.loader,
|
|
129
|
-
write: false,
|
|
130
|
-
logLevel: "silent"
|
|
131
|
-
};
|
|
132
|
-
const result = await esbuild.build(buildOpts);
|
|
133
|
-
const msgs = [];
|
|
134
|
-
for (const w of result.warnings) {
|
|
135
|
-
const loc = w.location ? ` at ${relative(".", w.location.file ?? "")}:${w.location.line}:${w.location.column}` : "";
|
|
136
|
-
msgs.push(`[warn] ${w.text}${loc}`);
|
|
137
|
-
}
|
|
138
|
-
for (const e of result.errors) {
|
|
139
|
-
const loc = e.location ? ` at ${relative(".", e.location.file ?? "")}:${e.location.line}:${e.location.column}` : "";
|
|
140
|
-
msgs.push(`[error] ${e.text}${loc}`);
|
|
141
|
-
}
|
|
142
|
-
if (result.errors.length > 0) {
|
|
143
|
-
throw new Error(msgs.join("\n"));
|
|
144
|
-
}
|
|
145
|
-
const outputFiles = result.outputFiles;
|
|
146
|
-
const entryFile = outputFiles.find((f) => f.path === entryAbs) ?? outputFiles[0];
|
|
147
|
-
const code = entryFile?.text ?? "";
|
|
148
|
-
let chunks;
|
|
149
|
-
if (outputFiles.length > 1) {
|
|
150
|
-
chunks = /* @__PURE__ */ new Map();
|
|
151
|
-
for (const f of outputFiles) {
|
|
152
|
-
if (f === entryFile) continue;
|
|
153
|
-
const chunkName = f.path.split("/").pop() ?? f.path;
|
|
154
|
-
chunks.set(chunkName, { code: f.text, etag: `"esbuild-${hashCode(f.text)}"` });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
const etag = `"esbuild-${hashCode(code)}"`;
|
|
158
|
-
if (msgs.length > 0) {
|
|
159
|
-
console.error("[esbuildDev]", msgs.join("\n"));
|
|
160
|
-
}
|
|
161
|
-
return { code, etag, chunks };
|
|
162
|
-
}
|
|
163
|
-
function isCacheValid(cached) {
|
|
164
|
-
return Promise.all(
|
|
165
|
-
[...cached.files].map(async ([file, mtime]) => {
|
|
166
|
-
try {
|
|
167
|
-
const s = await stat(file);
|
|
168
|
-
return s.mtimeMs === mtime;
|
|
169
|
-
} catch {
|
|
170
|
-
return false;
|
|
171
|
-
}
|
|
172
|
-
})
|
|
173
|
-
).then((results) => results.every(Boolean));
|
|
174
|
-
}
|
|
175
|
-
return async (req, ctx, next) => {
|
|
176
|
-
const url = new URL(req.url);
|
|
177
|
-
const pathname = url.pathname;
|
|
178
|
-
if (importmap && pathname === "/assets/importmap") {
|
|
179
|
-
if (!importmapScript) importmapScript = buildImportmap();
|
|
180
|
-
return new Response(importmapScript, {
|
|
181
|
-
headers: {
|
|
182
|
-
"Content-Type": "application/javascript; charset=utf-8",
|
|
183
|
-
"Cache-Control": "no-cache"
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
const matched = entries.find((e) => e.path === pathname);
|
|
188
|
-
const chunkName = pathname.split("/").pop() ?? "";
|
|
189
|
-
const chunkOwner = chunkMap.get(chunkName);
|
|
190
|
-
if (!matched && !chunkOwner) return next(req, ctx);
|
|
191
|
-
if (!matched && chunkOwner) {
|
|
192
|
-
const ownerCache = cacheStore.get(chunkOwner);
|
|
193
|
-
const chunk = ownerCache?.chunks?.get(chunkName);
|
|
194
|
-
if (chunk) {
|
|
195
|
-
if (req.headers.get("if-none-match") === chunk.etag) {
|
|
196
|
-
return new Response(null, { status: 304 });
|
|
197
|
-
}
|
|
198
|
-
return new Response(chunk.code, {
|
|
199
|
-
headers: {
|
|
200
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
201
|
-
ETag: chunk.etag,
|
|
202
|
-
"Cache-Control": "no-cache"
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
return next(req, ctx);
|
|
207
|
-
}
|
|
208
|
-
const { config } = matched;
|
|
209
|
-
try {
|
|
210
|
-
if (cache === "memory") {
|
|
211
|
-
const cached = cacheStore.get(pathname);
|
|
212
|
-
if (cached) {
|
|
213
|
-
const valid = await isCacheValid(cached);
|
|
214
|
-
if (valid) {
|
|
215
|
-
if (req.headers.get("if-none-match") === cached.etag) {
|
|
216
|
-
return new Response(null, { status: 304 });
|
|
217
|
-
}
|
|
218
|
-
return new Response(cached.code, {
|
|
219
|
-
headers: {
|
|
220
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
221
|
-
ETag: cached.etag,
|
|
222
|
-
"Cache-Control": "no-cache"
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
for (const [cn] of cached.chunks ?? []) chunkMap.delete(cn);
|
|
227
|
-
cacheStore.delete(pathname);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
const { code, etag, chunks } = await compile(config);
|
|
231
|
-
const deps = await collectDeps(resolve(config.entry));
|
|
232
|
-
if (cache === "memory") {
|
|
233
|
-
cacheStore.set(pathname, { code, etag, files: deps, chunks });
|
|
234
|
-
if (chunks) {
|
|
235
|
-
for (const [cn] of chunks) chunkMap.set(cn, pathname);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return new Response(code, {
|
|
239
|
-
headers: {
|
|
240
|
-
"Content-Type": "text/javascript; charset=utf-8",
|
|
241
|
-
ETag: etag,
|
|
242
|
-
"Cache-Control": "no-cache"
|
|
243
|
-
}
|
|
244
|
-
});
|
|
245
|
-
} catch (err) {
|
|
246
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
247
|
-
const html = errorTemplate(message);
|
|
248
|
-
return new Response(html, {
|
|
249
|
-
status: 500,
|
|
250
|
-
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
function hashCode(s) {
|
|
256
|
-
let hash = 0;
|
|
257
|
-
for (let i = 0; i < s.length; i++) {
|
|
258
|
-
const ch = s.charCodeAt(i);
|
|
259
|
-
hash = (hash << 5) - hash + ch;
|
|
260
|
-
hash |= 0;
|
|
261
|
-
}
|
|
262
|
-
return Math.abs(hash).toString(36);
|
|
263
|
-
}
|
|
264
|
-
export {
|
|
265
|
-
esbuildDev
|
|
266
|
-
};
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tailwindDev — on-the-fly Tailwind CSS v4 compilation for development.
|
|
3
|
-
*
|
|
4
|
-
* Compiles a CSS entry file (with @import "tailwindcss") and scans content
|
|
5
|
-
* files for class names, generating optimized CSS in memory on first request.
|
|
6
|
-
*
|
|
7
|
-
* Caches results based on source file mtime and serves with ETag support.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { tailwindDev } from 'weifuwu'
|
|
12
|
-
*
|
|
13
|
-
* app.use(tailwindDev({
|
|
14
|
-
* '/assets/tailwind.css': {
|
|
15
|
-
* entry: './styles/input.css',
|
|
16
|
-
* content: ['./components/**\/*.ts', './pages/**\/*.ts'],
|
|
17
|
-
* },
|
|
18
|
-
* }))
|
|
19
|
-
* ```
|
|
20
|
-
*
|
|
21
|
-
* CSS entry example (styles/input.css):
|
|
22
|
-
* ```css
|
|
23
|
-
* @import "tailwindcss";
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
import type { Middleware, Context } from '../types.ts';
|
|
27
|
-
export interface TailwindDevEntry {
|
|
28
|
-
/** Path to CSS entry file (with @import "tailwindcss"). */
|
|
29
|
-
entry: string;
|
|
30
|
-
/** Glob patterns for content files to scan for class names. */
|
|
31
|
-
content?: string[];
|
|
32
|
-
/** Minify output (default: false in dev). */
|
|
33
|
-
minify?: boolean;
|
|
34
|
-
}
|
|
35
|
-
export interface TailwindDevOptions {
|
|
36
|
-
/** Map of URL path → entry config (string shorthand for { entry }). */
|
|
37
|
-
entries: Record<string, TailwindDevEntry | string>;
|
|
38
|
-
/** Cache strategy: 'memory' keeps compiled results, 'none' recompiles every request. */
|
|
39
|
-
cache?: 'memory' | 'none';
|
|
40
|
-
/** Custom HTML template for build errors (receives escaped error text). */
|
|
41
|
-
errorTemplate?: (errors: string) => string;
|
|
42
|
-
}
|
|
43
|
-
export declare function tailwindDev(opts: TailwindDevOptions): Middleware<Context, Context>;
|
|
@@ -1,199 +0,0 @@
|
|
|
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
|
-
};
|