create-react-docs-ui 0.6.9 → 0.6.10

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-react-docs-ui",
3
- "version": "0.6.9",
3
+ "version": "0.6.10",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,7 +15,7 @@
15
15
  "gray-matter": "^4.0.3",
16
16
  "js-yaml": "^4.1.0",
17
17
  "react": "^19.0.0",
18
- "react-docs-ui": "^0.6.9",
18
+ "react-docs-ui": "^0.6.10",
19
19
  "react-dom": "^19.0.0",
20
20
  "react-router-dom": "^7.8.0"
21
21
  },
@@ -1,276 +1,305 @@
1
1
  // @ts-nocheck
2
- import path from "path"
3
- import fs from "node:fs/promises"
4
- import { spawn } from "child_process"
5
- import react from "@vitejs/plugin-react"
6
- import { defineConfig } from "vite"
7
- import yaml from "js-yaml"
8
- import { mdxComponentsPlugin } from "./vite-plugin-mdx-components"
2
+ import path from "path";
3
+ import fs from "node:fs/promises";
4
+ import { spawn } from "child_process";
5
+ import react from "@vitejs/plugin-react";
6
+ import { defineConfig } from "vite";
7
+ import yaml from "js-yaml";
8
+ import { mdxComponentsPlugin } from "./vite-plugin-mdx-components";
9
9
 
10
- const FONT_BASE_URL = "https://file.shenjianl.cn/fonts/"
10
+ const FONT_BASE_URL = "https://file.shenjianl.cn/fonts/";
11
11
 
12
12
  function fontDownloadPlugin() {
13
- let checkedInServe = false
14
-
15
- const log = (message) => {
16
- console.log(`[font-download] ${message}`)
17
- }
18
-
19
- const formatBytes = (bytes) => {
20
- if (!Number.isFinite(bytes) || bytes <= 0) return "0 B"
21
-
22
- const units = ["B", "KB", "MB", "GB"]
23
- let value = bytes
24
- let unitIndex = 0
25
-
26
- while (value >= 1024 && unitIndex < units.length - 1) {
27
- value /= 1024
28
- unitIndex += 1
29
- }
30
-
31
- return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`
32
- }
33
-
34
- const downloadFont = async (task) => {
35
- const response = await fetch(task.source)
36
- if (!response.ok) {
37
- throw new Error(`HTTP ${response.status}`)
38
- }
39
-
40
- if (!response.body) {
41
- throw new Error("response body is empty")
42
- }
43
-
44
- const totalBytes = Number(response.headers.get("content-length") || 0)
45
- const reader = response.body.getReader()
46
- const chunks = []
47
- const startedAt = Date.now()
48
- let downloadedBytes = 0
49
- let lastLogAt = 0
50
-
51
- log(`start: ${task.filename} <- ${task.source}`)
52
-
53
- while (true) {
54
- const { done, value } = await reader.read()
55
- if (done) break
56
-
57
- const chunk = Buffer.from(value)
58
- chunks.push(chunk)
59
- downloadedBytes += chunk.byteLength
60
-
61
- const now = Date.now()
62
- if (lastLogAt === 0 || now - lastLogAt >= 500 || (totalBytes > 0 && downloadedBytes >= totalBytes)) {
63
- const elapsedSeconds = Math.max((now - startedAt) / 1000, 0.001)
64
- const speed = downloadedBytes / elapsedSeconds
65
- const progress = totalBytes > 0 ? `${((downloadedBytes / totalBytes) * 100).toFixed(1)}%` : "unknown"
66
- const totalText = totalBytes > 0 ? formatBytes(totalBytes) : "unknown"
67
-
68
- log(
69
- `progress: ${task.filename} ${formatBytes(downloadedBytes)}/${totalText} (${progress}) @ ${formatBytes(speed)}/s`
70
- )
71
- lastLogAt = now
72
- }
73
- }
74
-
75
- return Buffer.concat(chunks)
76
- }
77
-
78
- const ensureFonts = async () => {
79
- const root = path.resolve(__dirname)
80
- const configDir = path.resolve(root, "public", "config")
81
- const fontsDir = path.resolve(root, "public", "fonts")
82
-
83
- await fs.mkdir(fontsDir, { recursive: true })
84
-
85
- const tasks = new Map()
86
- for (const fileName of ["site.yaml", "site.en.yaml"]) {
87
- const filePath = path.resolve(configDir, fileName)
88
- try {
89
- const content = await fs.readFile(filePath, "utf8")
90
- const parsed = yaml.load(content) || {}
91
- const entries = parsed?.fonts?.downloadFonts || []
92
-
93
- for (const entry of entries) {
94
- const normalized = String(entry || "").trim()
95
- if (!normalized) continue
96
-
97
- const url = /^https?:\/\//i.test(normalized)
98
- ? new URL(normalized)
99
- : new URL(normalized.replace(/^\/+/, ""), FONT_BASE_URL)
100
-
101
- const filename = path.posix.basename(url.pathname)
102
- if (filename) {
103
- tasks.set(filename, { filename, source: url.toString() })
104
- }
13
+ let checkedInServe = false;
14
+
15
+ const log = (message) => {
16
+ console.log(`[font-download] ${message}`);
17
+ };
18
+
19
+ const formatBytes = (bytes) => {
20
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
21
+
22
+ const units = ["B", "KB", "MB", "GB"];
23
+ let value = bytes;
24
+ let unitIndex = 0;
25
+
26
+ while (value >= 1024 && unitIndex < units.length - 1) {
27
+ value /= 1024;
28
+ unitIndex += 1;
105
29
  }
106
- } catch (error) {
107
- if (error?.code !== "ENOENT") {
108
- log(`failed to read ${fileName}: ${error.message}`)
30
+
31
+ return `${value.toFixed(value >= 100 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
32
+ };
33
+
34
+ const downloadFont = async (task) => {
35
+ const response = await fetch(task.source);
36
+ if (!response.ok) {
37
+ throw new Error(`HTTP ${response.status}`);
109
38
  }
110
- }
111
- }
112
-
113
- if (tasks.size === 0) {
114
- log("no fonts.downloadFonts entries found in public/config/site*.yaml")
115
- return
116
- }
117
-
118
- for (const task of tasks.values()) {
119
- const targetPath = path.resolve(fontsDir, task.filename)
120
-
121
- try {
122
- await fs.access(targetPath)
123
- log(`exists: public/fonts/${task.filename}`)
124
- continue
125
- } catch {
126
- // Continue to download.
127
- }
128
-
129
- try {
130
- const fileBuffer = await downloadFont(task)
131
- await fs.writeFile(targetPath, fileBuffer)
132
- log(`downloaded: ${task.source} -> public/fonts/${task.filename}`)
133
- } catch (error) {
134
- log(`failed: ${task.source} -> public/fonts/${task.filename} (${error.message})`)
135
- }
136
- }
137
- }
138
-
139
- return {
140
- name: "font-download-plugin",
141
- async configureServer() {
142
- if (checkedInServe) return
143
- checkedInServe = true
144
- await ensureFonts()
145
- },
146
- async buildStart() {
147
- await ensureFonts()
148
- },
149
- }
150
- }
151
39
 
152
- function searchIndexPlugin() {
153
- return {
154
- name: 'search-index-plugin',
155
- configureServer(server) {
156
- const publicDir = path.resolve(__dirname, 'public')
157
- const docsDir = path.resolve(publicDir, 'docs')
158
-
159
- server.watcher.add(docsDir)
160
-
161
- let generating = false
162
- let pending = false
163
-
164
- const generateIndex = () => {
165
- if (generating) {
166
- pending = true
167
- return
40
+ if (!response.body) {
41
+ throw new Error("response body is empty");
168
42
  }
169
- generating = true
170
-
171
- const child = spawn('node', ['scripts/generate-search-index.js'], {
172
- cwd: path.resolve(__dirname),
173
- stdio: 'inherit'
174
- })
175
-
176
- child.on('close', () => {
177
- generating = false
178
- if (pending) {
179
- pending = false
180
- generateIndex()
181
- }
182
- })
183
- }
184
-
185
- generateIndex()
186
-
187
- const isDocFile = (file) => {
188
- return file.includes(path.sep + 'docs' + path.sep) &&
189
- (file.endsWith('.md') || file.endsWith('.mdx'))
190
- }
191
-
192
- const debounce = (fn, delay) => {
193
- let timer = null
194
- return (...args) => {
195
- if (timer) clearTimeout(timer)
196
- timer = setTimeout(() => fn(...args), delay)
43
+
44
+ const totalBytes = Number(response.headers.get("content-length") || 0);
45
+ const reader = response.body.getReader();
46
+ const chunks = [];
47
+ const startedAt = Date.now();
48
+ let downloadedBytes = 0;
49
+ let lastLogAt = 0;
50
+
51
+ log(`start: ${task.filename} <- ${task.source}`);
52
+
53
+ while (true) {
54
+ const { done, value } = await reader.read();
55
+ if (done) break;
56
+
57
+ const chunk = Buffer.from(value);
58
+ chunks.push(chunk);
59
+ downloadedBytes += chunk.byteLength;
60
+
61
+ const now = Date.now();
62
+ if (
63
+ lastLogAt === 0 ||
64
+ now - lastLogAt >= 500 ||
65
+ (totalBytes > 0 && downloadedBytes >= totalBytes)
66
+ ) {
67
+ const elapsedSeconds = Math.max(
68
+ (now - startedAt) / 1000,
69
+ 0.001,
70
+ );
71
+ const speed = downloadedBytes / elapsedSeconds;
72
+ const progress =
73
+ totalBytes > 0
74
+ ? `${((downloadedBytes / totalBytes) * 100).toFixed(1)}%`
75
+ : "unknown";
76
+ const totalText =
77
+ totalBytes > 0 ? formatBytes(totalBytes) : "unknown";
78
+
79
+ log(
80
+ `progress: ${task.filename} ${formatBytes(downloadedBytes)}/${totalText} (${progress}) @ ${formatBytes(speed)}/s`,
81
+ );
82
+ lastLogAt = now;
83
+ }
197
84
  }
198
- }
199
-
200
- const debouncedGenerate = debounce(generateIndex, 500)
201
-
202
- server.watcher.on('change', (file) => {
203
- if (isDocFile(file)) debouncedGenerate()
204
- })
205
-
206
- server.watcher.on('add', (file) => {
207
- if (isDocFile(file)) debouncedGenerate()
208
- })
209
-
210
- server.watcher.on('unlink', (file) => {
211
- if (isDocFile(file)) debouncedGenerate()
212
- })
213
- }
214
- }
215
- }
216
85
 
217
- function publicHmrPlugin() {
218
- return {
219
- name: 'public-hmr',
220
- configureServer(server) {
221
- const publicDir = path.resolve(__dirname, 'public')
222
- const configDir = path.resolve(publicDir, 'config')
223
- const docsDir = path.resolve(publicDir, 'docs')
224
-
225
- server.watcher.add([configDir, docsDir])
226
-
227
- const isTargetFile = (file) => {
228
- const relativePath = path.relative(publicDir, file)
229
- return (
230
- relativePath.startsWith('config' + path.sep) && file.endsWith('.yaml')
231
- ) || (
232
- relativePath.startsWith('docs' + path.sep) &&
233
- (file.endsWith('.md') || file.endsWith('.mdx'))
234
- )
235
- }
236
-
237
- const triggerReload = (file) => {
238
- if (isTargetFile(file)) {
239
- server.ws.send({ type: 'full-reload', path: '*' })
86
+ return Buffer.concat(chunks);
87
+ };
88
+
89
+ const ensureFonts = async () => {
90
+ const root = path.resolve(__dirname);
91
+ const configDir = path.resolve(root, "public", "config");
92
+ const fontsDir = path.resolve(root, "public", "fonts");
93
+
94
+ await fs.mkdir(fontsDir, { recursive: true });
95
+
96
+ const tasks = new Map();
97
+ for (const fileName of ["site.yaml", "site.en.yaml"]) {
98
+ const filePath = path.resolve(configDir, fileName);
99
+ try {
100
+ const content = await fs.readFile(filePath, "utf8");
101
+ const parsed = yaml.load(content) || {};
102
+ const entries = parsed?.fonts?.downloadFonts || [];
103
+
104
+ for (const entry of entries) {
105
+ const normalized = String(entry || "").trim();
106
+ if (!normalized) continue;
107
+
108
+ const url = /^https?:\/\//i.test(normalized)
109
+ ? new URL(normalized)
110
+ : new URL(
111
+ normalized.replace(/^\/+/, ""),
112
+ FONT_BASE_URL,
113
+ );
114
+
115
+ const filename = path.posix.basename(url.pathname);
116
+ if (filename) {
117
+ tasks.set(filename, {
118
+ filename,
119
+ source: url.toString(),
120
+ });
121
+ }
122
+ }
123
+ } catch (error) {
124
+ if (error?.code !== "ENOENT") {
125
+ log(`failed to read ${fileName}: ${error.message}`);
126
+ }
127
+ }
240
128
  }
241
- }
242
129
 
243
- server.watcher.on('change', triggerReload)
244
- server.watcher.on('add', triggerReload)
245
- }
246
- }
130
+ if (tasks.size === 0) {
131
+ log(
132
+ "no fonts.downloadFonts entries found in public/config/site*.yaml",
133
+ );
134
+ return;
135
+ }
136
+
137
+ for (const task of tasks.values()) {
138
+ const targetPath = path.resolve(fontsDir, task.filename);
139
+
140
+ try {
141
+ await fs.access(targetPath);
142
+ log(`exists: public/fonts/${task.filename}`);
143
+ continue;
144
+ } catch {
145
+ // Continue to download.
146
+ }
147
+
148
+ try {
149
+ const fileBuffer = await downloadFont(task);
150
+ await fs.writeFile(targetPath, fileBuffer);
151
+ log(
152
+ `downloaded: ${task.source} -> public/fonts/${task.filename}`,
153
+ );
154
+ } catch (error) {
155
+ log(
156
+ `failed: ${task.source} -> public/fonts/${task.filename} (${error.message})`,
157
+ );
158
+ }
159
+ }
160
+ };
161
+
162
+ return {
163
+ name: "font-download-plugin",
164
+ async configureServer() {
165
+ if (checkedInServe) return;
166
+ checkedInServe = true;
167
+ await ensureFonts();
168
+ },
169
+ async buildStart() {
170
+ await ensureFonts();
171
+ },
172
+ };
173
+ }
174
+
175
+ function searchIndexPlugin() {
176
+ return {
177
+ name: "search-index-plugin",
178
+ configureServer(server) {
179
+ const publicDir = path.resolve(__dirname, "public");
180
+ const docsDir = path.resolve(publicDir, "docs");
181
+
182
+ server.watcher.add(docsDir);
183
+
184
+ let generating = false;
185
+ let pending = false;
186
+
187
+ const generateIndex = () => {
188
+ if (generating) {
189
+ pending = true;
190
+ return;
191
+ }
192
+ generating = true;
193
+
194
+ const child = spawn(
195
+ "node",
196
+ ["scripts/generate-search-index.js"],
197
+ {
198
+ cwd: path.resolve(__dirname),
199
+ stdio: "inherit",
200
+ },
201
+ );
202
+
203
+ child.on("close", () => {
204
+ generating = false;
205
+ if (pending) {
206
+ pending = false;
207
+ generateIndex();
208
+ }
209
+ });
210
+ };
211
+
212
+ generateIndex();
213
+
214
+ const isDocFile = (file) => {
215
+ return (
216
+ file.includes(path.sep + "docs" + path.sep) &&
217
+ (file.endsWith(".md") || file.endsWith(".mdx"))
218
+ );
219
+ };
220
+
221
+ const debounce = (fn, delay) => {
222
+ let timer = null;
223
+ return (...args) => {
224
+ if (timer) clearTimeout(timer);
225
+ timer = setTimeout(() => fn(...args), delay);
226
+ };
227
+ };
228
+
229
+ const debouncedGenerate = debounce(generateIndex, 500);
230
+
231
+ server.watcher.on("change", (file) => {
232
+ if (isDocFile(file)) debouncedGenerate();
233
+ });
234
+
235
+ server.watcher.on("add", (file) => {
236
+ if (isDocFile(file)) debouncedGenerate();
237
+ });
238
+
239
+ server.watcher.on("unlink", (file) => {
240
+ if (isDocFile(file)) debouncedGenerate();
241
+ });
242
+ },
243
+ };
244
+ }
245
+
246
+ function publicHmrPlugin() {
247
+ return {
248
+ name: "public-hmr",
249
+ configureServer(server) {
250
+ const publicDir = path.resolve(__dirname, "public");
251
+ const configDir = path.resolve(publicDir, "config");
252
+ const docsDir = path.resolve(publicDir, "docs");
253
+
254
+ server.watcher.add([configDir, docsDir]);
255
+
256
+ const isTargetFile = (file) => {
257
+ const relativePath = path.relative(publicDir, file);
258
+ return (
259
+ (relativePath.startsWith("config" + path.sep) &&
260
+ file.endsWith(".yaml")) ||
261
+ (relativePath.startsWith("docs" + path.sep) &&
262
+ (file.endsWith(".md") || file.endsWith(".mdx")))
263
+ );
264
+ };
265
+
266
+ const triggerReload = (file) => {
267
+ if (isTargetFile(file)) {
268
+ server.ws.send({ type: "full-reload", path: "*" });
269
+ }
270
+ };
271
+
272
+ server.watcher.on("change", triggerReload);
273
+ server.watcher.on("add", triggerReload);
274
+ },
275
+ };
247
276
  }
248
277
 
249
278
  export default defineConfig({
250
- plugins: [
251
- react(),
252
- mdxComponentsPlugin({
253
- componentsPath: './src/components',
254
- outputPath: './src/generated/mdx-components.ts'
255
- }),
256
- fontDownloadPlugin(),
257
- searchIndexPlugin(),
258
- publicHmrPlugin()
259
- ],
260
- resolve: {
261
- alias: {
262
- "@": "src",
263
- buffer: "buffer",
279
+ plugins: [
280
+ react(),
281
+ mdxComponentsPlugin({
282
+ componentsPath: "./src/components",
283
+ outputPath: "./src/generated/mdx-components.ts",
284
+ }),
285
+ fontDownloadPlugin(),
286
+ searchIndexPlugin(),
287
+ publicHmrPlugin(),
288
+ ],
289
+ resolve: {
290
+ alias: {
291
+ "@": "src",
292
+ buffer: "buffer",
293
+ },
294
+ },
295
+ define: {
296
+ global: "globalThis",
297
+ },
298
+ server: {
299
+ host: "0.0.0.0",
300
+ port: 5173,
301
+ },
302
+ build: {
303
+ chunkSizeWarningLimit: 2000,
264
304
  },
265
- },
266
- define: {
267
- global: 'globalThis',
268
- },
269
- server: {
270
- host: "0.0.0.0",
271
- port: 5173,
272
- },
273
- build: {
274
- chunkSizeWarningLimit: 2000
275
- }
276
- })
305
+ });