@deot/docs-dever 1.1.1 → 1.1.3
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 +9 -0
- package/dist/index.cjs +284 -4
- package/dist/index.js +284 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,15 @@ development 和 preview 会保持服务运行;build 在 Vite 构建结束后
|
|
|
54
54
|
- 为客户端路由提供 HTML history fallback;静态托管若只能配置自定义 404 页,请改用 Client 的 [`@deot/docs:redirect`](../client/README.md) 约定。
|
|
55
55
|
- 在 build 模式复制 Markdown、JSON(含 `.page.json`)、SFC 等静态内容资源;
|
|
56
56
|
- 当当前仓库源码存在时,将 locale、renderer、markdown、playground、theme 包映射到本地入口。
|
|
57
|
+
- 在 development 模式提供 `GET /__docs/module/<包名>`,把工作区源码编成一份浏览器 ESM。preview 与 build 对该地址返回 404。
|
|
58
|
+
|
|
59
|
+
## 本地模块
|
|
60
|
+
|
|
61
|
+
`doc dev` 扫描项目 `packages/*/package.json`。存在 `src/index.ts` 或 `index.ts`,且真实路径仍在项目内时,可以用 `/__docs/module/<包名>` 取到编译结果。例如 `@deot/docs-locale` 对应 `/__docs/module/@deot/docs-locale`。
|
|
62
|
+
|
|
63
|
+
当前包内的相对 `.vue`、`.ts`、`.tsx` 和样式会打进这一份 ESM,样式写入 iframe 的 `document.head`。`vue`、其它工作区包,以及其余裸导入保持原样,交给 Playground import map。`vue/jsx-runtime` 会打进包。
|
|
64
|
+
|
|
65
|
+
站点自己决定是否使用这些地址。本仓库的 `index.html` 在 `window.__DOCS_RUNTIME__?.mode !== 'development'` 时把 `modules` 留空,development 时改指本地地址。直接打开 HTML 没有 runtime,因此仍走内置 CDN。管理页里对同名包的覆盖会压过 `index.html`。改完源码后刷新文档页;已打开的 Playground 不会热更新这份外部模块。
|
|
57
66
|
|
|
58
67
|
### `getDeverMode(options)`
|
|
59
68
|
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ _vitejs_plugin_vue_jsx = __toESM(_vitejs_plugin_vue_jsx, 1);
|
|
|
35
35
|
let node_fs = require("node:fs");
|
|
36
36
|
node_fs = __toESM(node_fs, 1);
|
|
37
37
|
let node_crypto = require("node:crypto");
|
|
38
|
+
let node_module = require("node:module");
|
|
38
39
|
let node_http = require("node:http");
|
|
39
40
|
node_http = __toESM(node_http, 1);
|
|
40
41
|
//#region packages/dever/src/workspace.ts
|
|
@@ -99,6 +100,272 @@ var resolveDocsWorkspace = (cwd = process.cwd(), workspace) => {
|
|
|
99
100
|
node_path.join(projectRoot, "index.html")
|
|
100
101
|
].join(" "));
|
|
101
102
|
};
|
|
103
|
+
var BUNDLED_SPECIFIERS = /* @__PURE__ */ new Set(["vue/jsx-runtime", "vue/jsx-dev-runtime"]);
|
|
104
|
+
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
105
|
+
"coverage",
|
|
106
|
+
"dist",
|
|
107
|
+
"node_modules"
|
|
108
|
+
]);
|
|
109
|
+
var cache = /* @__PURE__ */ new Map();
|
|
110
|
+
var pending = /* @__PURE__ */ new Map();
|
|
111
|
+
var buildCount = 0;
|
|
112
|
+
var readPackageName = (manifestPath) => {
|
|
113
|
+
try {
|
|
114
|
+
const manifest = JSON.parse(node_fs.readFileSync(manifestPath, "utf8"));
|
|
115
|
+
return typeof manifest.name === "string" && manifest.name ? manifest.name : "";
|
|
116
|
+
} catch {
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var resolveSourceEntry = (directory) => {
|
|
121
|
+
for (const relative of ["src/index.ts", "index.ts"]) {
|
|
122
|
+
const candidate = node_path.resolve(directory, relative);
|
|
123
|
+
if (!node_fs.existsSync(candidate) || !node_fs.statSync(candidate).isFile()) continue;
|
|
124
|
+
const realEntry = node_fs.realpathSync(candidate);
|
|
125
|
+
if (!isInside(directory, realEntry)) continue;
|
|
126
|
+
return realEntry;
|
|
127
|
+
}
|
|
128
|
+
return "";
|
|
129
|
+
};
|
|
130
|
+
/**
|
|
131
|
+
* 收集项目 `packages/*` 里能编成浏览器模块的包。只接受留在项目内的真实路径。
|
|
132
|
+
* @param projectRoot 文档项目根目录。
|
|
133
|
+
* @returns 包名到源码入口的映射。同名包保留先扫描到的一项。
|
|
134
|
+
*/
|
|
135
|
+
var resolveWorkspaceModuleEntries = (projectRoot) => {
|
|
136
|
+
const realRoot = node_fs.existsSync(projectRoot) ? node_fs.realpathSync(projectRoot) : node_path.resolve(projectRoot);
|
|
137
|
+
const packagesDir = node_path.resolve(realRoot, "packages");
|
|
138
|
+
if (!node_fs.existsSync(packagesDir) || !node_fs.statSync(packagesDir).isDirectory()) return [];
|
|
139
|
+
const realPackages = node_fs.realpathSync(packagesDir);
|
|
140
|
+
if (!isInside(realRoot, realPackages)) return [];
|
|
141
|
+
const seen = /* @__PURE__ */ new Set();
|
|
142
|
+
const entries = [];
|
|
143
|
+
for (const item of node_fs.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
144
|
+
if (!item.isDirectory() && !item.isSymbolicLink()) continue;
|
|
145
|
+
const directory = node_path.resolve(packagesDir, item.name);
|
|
146
|
+
if (!node_fs.existsSync(directory) || !node_fs.statSync(directory).isDirectory()) continue;
|
|
147
|
+
const realDirectory = node_fs.realpathSync(directory);
|
|
148
|
+
if (!isInside(realPackages, realDirectory)) continue;
|
|
149
|
+
const name = readPackageName(node_path.join(realDirectory, "package.json"));
|
|
150
|
+
const entry = name ? resolveSourceEntry(realDirectory) : "";
|
|
151
|
+
if (!name || !entry || seen.has(name)) continue;
|
|
152
|
+
seen.add(name);
|
|
153
|
+
entries.push({
|
|
154
|
+
name,
|
|
155
|
+
entry,
|
|
156
|
+
directory: realDirectory
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return entries;
|
|
160
|
+
};
|
|
161
|
+
var directorySignature = (directory) => {
|
|
162
|
+
const files = [];
|
|
163
|
+
const walk = (current) => {
|
|
164
|
+
const realCurrent = node_fs.realpathSync(current);
|
|
165
|
+
if (!isInside(directory, realCurrent)) return;
|
|
166
|
+
for (const item of node_fs.readdirSync(realCurrent, { withFileTypes: true })) {
|
|
167
|
+
if (item.name === ".git" || IGNORED_DIRECTORIES.has(item.name)) continue;
|
|
168
|
+
const filename = node_path.join(realCurrent, item.name);
|
|
169
|
+
if (item.isSymbolicLink()) {
|
|
170
|
+
if (!node_fs.existsSync(filename)) continue;
|
|
171
|
+
const realName = node_fs.realpathSync(filename);
|
|
172
|
+
if (!isInside(directory, realName)) continue;
|
|
173
|
+
const stat = node_fs.statSync(realName);
|
|
174
|
+
if (stat.isDirectory()) walk(realName);
|
|
175
|
+
else if (stat.isFile()) files.push(`${realName}:${stat.mtimeMs}`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (item.isDirectory()) {
|
|
179
|
+
walk(filename);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (item.isFile()) files.push(`${filename}:${node_fs.statSync(filename).mtimeMs}`);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
walk(directory);
|
|
186
|
+
files.sort();
|
|
187
|
+
return files.join("\n");
|
|
188
|
+
};
|
|
189
|
+
var isWorkspaceModuleExternal = (id) => {
|
|
190
|
+
if (!id || id.startsWith("\0") || id.startsWith(".") || node_path.isAbsolute(id)) return false;
|
|
191
|
+
if (BUNDLED_SPECIFIERS.has(id)) return false;
|
|
192
|
+
return true;
|
|
193
|
+
};
|
|
194
|
+
var readAssetSource = (source) => typeof source === "string" ? source : Buffer.from(source).toString("utf8");
|
|
195
|
+
var toBundleResult = (result) => {
|
|
196
|
+
const output = Array.isArray(result) ? result[0] : result;
|
|
197
|
+
if (!output || !("output" in output) || !Array.isArray(output.output)) throw new Error("Module build did not produce a bundle");
|
|
198
|
+
return output;
|
|
199
|
+
};
|
|
200
|
+
var injectStyles = (name, code, css) => {
|
|
201
|
+
if (!css) return code;
|
|
202
|
+
return [
|
|
203
|
+
"if (typeof document !== \"undefined\") {",
|
|
204
|
+
" const style = document.createElement(\"style\");",
|
|
205
|
+
`\tstyle.setAttribute("data-docs-module", ${JSON.stringify(name)});`,
|
|
206
|
+
`\tstyle.textContent = ${JSON.stringify(css)};`,
|
|
207
|
+
" document.head.appendChild(style);",
|
|
208
|
+
"}",
|
|
209
|
+
code
|
|
210
|
+
].join("\n");
|
|
211
|
+
};
|
|
212
|
+
var buildWorkspaceModule = async (projectRoot, entry) => {
|
|
213
|
+
buildCount += 1;
|
|
214
|
+
const bundle = toBundleResult(await (0, vite.build)({
|
|
215
|
+
configFile: false,
|
|
216
|
+
envFile: false,
|
|
217
|
+
root: projectRoot,
|
|
218
|
+
publicDir: false,
|
|
219
|
+
logLevel: "silent",
|
|
220
|
+
mode: "development",
|
|
221
|
+
appType: "custom",
|
|
222
|
+
define: { "process.env.NODE_ENV": JSON.stringify("development") },
|
|
223
|
+
plugins: [(0, _vitejs_plugin_vue.default)(), (0, _vitejs_plugin_vue_jsx.default)()],
|
|
224
|
+
build: {
|
|
225
|
+
write: false,
|
|
226
|
+
minify: false,
|
|
227
|
+
cssMinify: false,
|
|
228
|
+
cssCodeSplit: false,
|
|
229
|
+
sourcemap: false,
|
|
230
|
+
emptyOutDir: false,
|
|
231
|
+
copyPublicDir: false,
|
|
232
|
+
modulePreload: false,
|
|
233
|
+
reportCompressedSize: false,
|
|
234
|
+
assetsInlineLimit: 1e8,
|
|
235
|
+
chunkSizeWarningLimit: 1e5,
|
|
236
|
+
rolldownOptions: {
|
|
237
|
+
input: entry.entry,
|
|
238
|
+
preserveEntrySignatures: "strict",
|
|
239
|
+
treeshake: false,
|
|
240
|
+
external: isWorkspaceModuleExternal,
|
|
241
|
+
output: {
|
|
242
|
+
format: "es",
|
|
243
|
+
codeSplitting: false,
|
|
244
|
+
entryFileNames: "module.js"
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}));
|
|
249
|
+
const chunks = bundle.output.filter((item) => item.type === "chunk");
|
|
250
|
+
if (chunks.length !== 1) throw new Error(`Expected a single module chunk, got ${chunks.length}`);
|
|
251
|
+
const css = bundle.output.filter((item) => item.type === "asset" && item.fileName.endsWith(".css")).map((item) => readAssetSource(item.source)).join("\n");
|
|
252
|
+
return injectStyles(entry.name, chunks[0].code, css);
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* 把工作区包编成一份浏览器 ESM。`vue` 和其它工作区包保持裸导入。
|
|
256
|
+
* @param projectRoot 用于解析 node_modules 的项目根。
|
|
257
|
+
* @param entry 已通过边界检查的包入口。
|
|
258
|
+
* @returns 可直接交给浏览器执行的模块源码。
|
|
259
|
+
*/
|
|
260
|
+
var compileWorkspaceModule = (projectRoot, entry) => {
|
|
261
|
+
const signature = directorySignature(entry.directory);
|
|
262
|
+
const cached = cache.get(entry.name);
|
|
263
|
+
if (cached?.signature === signature) return Promise.resolve(cached.code);
|
|
264
|
+
const inflight = pending.get(entry.name);
|
|
265
|
+
if (inflight) return inflight;
|
|
266
|
+
const task = buildWorkspaceModule(projectRoot, entry).then((code) => {
|
|
267
|
+
cache.set(entry.name, {
|
|
268
|
+
signature: directorySignature(entry.directory),
|
|
269
|
+
code
|
|
270
|
+
});
|
|
271
|
+
return code;
|
|
272
|
+
}).finally(() => {
|
|
273
|
+
if (pending.get(entry.name) === task) pending.delete(entry.name);
|
|
274
|
+
});
|
|
275
|
+
pending.set(entry.name, task);
|
|
276
|
+
return task;
|
|
277
|
+
};
|
|
278
|
+
var moduleFailure = (message) => `throw new Error(${JSON.stringify(message)});`;
|
|
279
|
+
/**
|
|
280
|
+
* 响应 `/__docs/module/<包名>`。未知包返回 404;编译失败返回会抛错的 ESM。
|
|
281
|
+
* @param req 开发服务请求。
|
|
282
|
+
* @param res 开发服务响应。
|
|
283
|
+
* @param projectRoot 文档项目根目录。
|
|
284
|
+
* @returns 是否已处理该请求。路径不匹配时为 false。
|
|
285
|
+
*/
|
|
286
|
+
var respondWorkspaceModule = async (req, res, projectRoot) => {
|
|
287
|
+
const pathname = (req.url || "/").split(/[?#]/u, 1)[0] || "/";
|
|
288
|
+
if (!pathname.startsWith("/__docs/module/")) return false;
|
|
289
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
290
|
+
res.statusCode = 405;
|
|
291
|
+
res.setHeader("Allow", "GET, HEAD");
|
|
292
|
+
res.end("Method Not Allowed");
|
|
293
|
+
return true;
|
|
294
|
+
}
|
|
295
|
+
let name = "";
|
|
296
|
+
try {
|
|
297
|
+
name = decodeURIComponent(pathname.slice(15));
|
|
298
|
+
} catch {
|
|
299
|
+
res.statusCode = 400;
|
|
300
|
+
res.end("Bad Request");
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
if (!name || name.split("/").includes("..") || name.includes("\\") || name.includes("\0")) {
|
|
304
|
+
res.statusCode = 404;
|
|
305
|
+
res.end("Not Found");
|
|
306
|
+
return true;
|
|
307
|
+
}
|
|
308
|
+
const entry = resolveWorkspaceModuleEntries(projectRoot).find((item) => item.name === name);
|
|
309
|
+
if (!entry) {
|
|
310
|
+
res.statusCode = 404;
|
|
311
|
+
res.end("Not Found");
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
let code;
|
|
315
|
+
try {
|
|
316
|
+
code = await compileWorkspaceModule(projectRoot, entry);
|
|
317
|
+
} catch (reason) {
|
|
318
|
+
code = moduleFailure(reason instanceof Error ? reason.message : "Module compile failed");
|
|
319
|
+
}
|
|
320
|
+
res.statusCode = 200;
|
|
321
|
+
res.setHeader("Content-Type", "text/javascript; charset=utf-8");
|
|
322
|
+
res.setHeader("Cache-Control", "no-store");
|
|
323
|
+
if (req.method === "HEAD") res.end();
|
|
324
|
+
else res.end(code);
|
|
325
|
+
return true;
|
|
326
|
+
};
|
|
327
|
+
//#endregion
|
|
328
|
+
//#region packages/dever/src/plugins/optional-dependencies.ts
|
|
329
|
+
var resolveOptionalDependency = (id, importer) => {
|
|
330
|
+
if (!importer || !node_path.isAbsolute(importer) || /^[./]|:/.test(id)) return;
|
|
331
|
+
const filename = importer.split("?")[0];
|
|
332
|
+
const name = id.startsWith("@") ? id.split("/").slice(0, 2).join("/") : id.split("/")[0];
|
|
333
|
+
let directory = node_path.dirname(filename);
|
|
334
|
+
while (true) {
|
|
335
|
+
const manifest = node_path.join(directory, "package.json");
|
|
336
|
+
if (node_fs.existsSync(manifest)) {
|
|
337
|
+
const pkg = JSON.parse(node_fs.readFileSync(manifest, "utf8"));
|
|
338
|
+
if (!pkg.peerDependencies?.[name] || !pkg.peerDependenciesMeta?.[name]?.optional) return;
|
|
339
|
+
try {
|
|
340
|
+
(0, node_module.createRequire)(filename).resolve(id);
|
|
341
|
+
} catch (reason) {
|
|
342
|
+
if (reason.code === "MODULE_NOT_FOUND") return {
|
|
343
|
+
id: `https://cdn.jsdelivr.net/npm/${id}/+esm`,
|
|
344
|
+
external: true
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const parent = node_path.dirname(directory);
|
|
350
|
+
if (parent === directory) return;
|
|
351
|
+
directory = parent;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
/**
|
|
355
|
+
* 本地包未安装的 optional peer 由浏览器按需从 CDN 加载。
|
|
356
|
+
* @returns 同时用于 Vite 模块转换与依赖预打包的解析插件。
|
|
357
|
+
*/
|
|
358
|
+
var createOptionalDependenciesPlugin = () => {
|
|
359
|
+
const resolver = {
|
|
360
|
+
name: "docs-optional-dependencies",
|
|
361
|
+
resolveId: resolveOptionalDependency
|
|
362
|
+
};
|
|
363
|
+
return {
|
|
364
|
+
...resolver,
|
|
365
|
+
enforce: "pre",
|
|
366
|
+
config: () => ({ optimizeDeps: { rolldownOptions: { plugins: [resolver] } } })
|
|
367
|
+
};
|
|
368
|
+
};
|
|
102
369
|
//#endregion
|
|
103
370
|
//#region packages/dever/src/plugins/index.ts
|
|
104
371
|
var innerPathRegex = /^\/(@|__)/;
|
|
@@ -298,6 +565,15 @@ var configureWorkspaceServer = (options, resolvedWorkspace) => (server) => {
|
|
|
298
565
|
let realResourceRoot = realWorkspace;
|
|
299
566
|
try {
|
|
300
567
|
const rawPathname = getRawPathname(req.url || "/");
|
|
568
|
+
if (/^\/__docs\/preview(?:-config-generate)?$/.test(rawPathname) && String(req.headers.accept || "").includes("text/html")) return next();
|
|
569
|
+
if (!options.preview && rawPathname.startsWith("/__docs/module/")) {
|
|
570
|
+
respondWorkspaceModule(req, res, resolved.projectRoot).catch((reason) => {
|
|
571
|
+
if (res.writableEnded) return;
|
|
572
|
+
res.statusCode = 500;
|
|
573
|
+
res.end(reason instanceof Error ? reason.message : "Module compile failed");
|
|
574
|
+
});
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
301
577
|
if (rawPathname.startsWith("/__docs/") && rawPathname !== "/__docs/events") {
|
|
302
578
|
res.statusCode = 404;
|
|
303
579
|
res.end("Not Found");
|
|
@@ -469,15 +745,17 @@ var createHistoryPlugin = (options, resolvedWorkspace) => ({
|
|
|
469
745
|
server.middlewares.use(async (req, res, next) => {
|
|
470
746
|
const requestUrl = req.url || "/";
|
|
471
747
|
const url = requestUrl.replace(/\/+/g, "/").replace(/[?#].*$/s, "") || "";
|
|
748
|
+
const accept = String(req.headers?.accept || "text/html");
|
|
749
|
+
const isPreviewPage = /^\/__docs\/preview(?:-config-generate)?$/.test(url) && accept.includes("text/html");
|
|
472
750
|
const candidate = node_path.resolve(root, `.${url}`);
|
|
473
751
|
const isFile = node_fs.existsSync(candidate) && node_fs.statSync(candidate).isFile();
|
|
474
|
-
if (options.preview && url.startsWith("/__docs/")) {
|
|
752
|
+
if (options.preview && url.startsWith("/__docs/") && !isPreviewPage) {
|
|
475
753
|
res.statusCode = 404;
|
|
476
754
|
res.end("Not Found");
|
|
477
755
|
return;
|
|
478
756
|
}
|
|
479
|
-
if (res.writableEnded || requestUrl.includes("html-proxy&") || innerPathRegex.test(url) || isFile) return next();
|
|
480
|
-
if (!
|
|
757
|
+
if (res.writableEnded || requestUrl.includes("html-proxy&") || innerPathRegex.test(url) && !isPreviewPage || isFile) return next();
|
|
758
|
+
if (!accept.includes("text/html")) return next();
|
|
481
759
|
const indexFile = resolvedWorkspace?.entry || resolveDocsWorkspace(options.preview ? root : process.cwd(), options.preview ? "." : options.workspace).entry;
|
|
482
760
|
if (!node_fs.existsSync(indexFile)) {
|
|
483
761
|
res.statusCode = 404;
|
|
@@ -542,6 +820,7 @@ var createStaticCopyPlugin = (options, resolvedWorkspace) => ({
|
|
|
542
820
|
}
|
|
543
821
|
});
|
|
544
822
|
var plugins_default = (options = {}, resolvedWorkspace) => (0, vite.defineConfig)({ plugins: [
|
|
823
|
+
createOptionalDependenciesPlugin(),
|
|
545
824
|
createRuntimePlugin(options, resolvedWorkspace),
|
|
546
825
|
createWorkspacePlugin(options, resolvedWorkspace),
|
|
547
826
|
createHistoryPlugin(options, resolvedWorkspace),
|
|
@@ -706,7 +985,8 @@ var createPreviewRequestHandler = (options) => {
|
|
|
706
985
|
return;
|
|
707
986
|
}
|
|
708
987
|
const pathname = decodeRequestPath(req.url || "/");
|
|
709
|
-
|
|
988
|
+
const isPreviewPage = /^\/__docs\/preview(?:-config-generate)?$/.test(pathname) && String(req.headers.accept || "").includes("text/html");
|
|
989
|
+
if (pathname.startsWith("/__docs/") && !isPreviewPage) {
|
|
710
990
|
res.statusCode = 404;
|
|
711
991
|
res.end("Not Found");
|
|
712
992
|
return;
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import vue from "@vitejs/plugin-vue";
|
|
|
7
7
|
import vueJsx from "@vitejs/plugin-vue-jsx";
|
|
8
8
|
import * as fs$1 from "node:fs";
|
|
9
9
|
import { createHash } from "node:crypto";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
10
11
|
import * as http from "node:http";
|
|
11
12
|
//#region packages/dever/src/workspace.ts
|
|
12
13
|
/**
|
|
@@ -70,6 +71,272 @@ var resolveDocsWorkspace = (cwd = process.cwd(), workspace) => {
|
|
|
70
71
|
path.join(projectRoot, "index.html")
|
|
71
72
|
].join(" "));
|
|
72
73
|
};
|
|
74
|
+
var BUNDLED_SPECIFIERS = /* @__PURE__ */ new Set(["vue/jsx-runtime", "vue/jsx-dev-runtime"]);
|
|
75
|
+
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
76
|
+
"coverage",
|
|
77
|
+
"dist",
|
|
78
|
+
"node_modules"
|
|
79
|
+
]);
|
|
80
|
+
var cache = /* @__PURE__ */ new Map();
|
|
81
|
+
var pending = /* @__PURE__ */ new Map();
|
|
82
|
+
var buildCount = 0;
|
|
83
|
+
var readPackageName = (manifestPath) => {
|
|
84
|
+
try {
|
|
85
|
+
const manifest = JSON.parse(fs$1.readFileSync(manifestPath, "utf8"));
|
|
86
|
+
return typeof manifest.name === "string" && manifest.name ? manifest.name : "";
|
|
87
|
+
} catch {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
var resolveSourceEntry = (directory) => {
|
|
92
|
+
for (const relative of ["src/index.ts", "index.ts"]) {
|
|
93
|
+
const candidate = path.resolve(directory, relative);
|
|
94
|
+
if (!fs$1.existsSync(candidate) || !fs$1.statSync(candidate).isFile()) continue;
|
|
95
|
+
const realEntry = fs$1.realpathSync(candidate);
|
|
96
|
+
if (!isInside(directory, realEntry)) continue;
|
|
97
|
+
return realEntry;
|
|
98
|
+
}
|
|
99
|
+
return "";
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* 收集项目 `packages/*` 里能编成浏览器模块的包。只接受留在项目内的真实路径。
|
|
103
|
+
* @param projectRoot 文档项目根目录。
|
|
104
|
+
* @returns 包名到源码入口的映射。同名包保留先扫描到的一项。
|
|
105
|
+
*/
|
|
106
|
+
var resolveWorkspaceModuleEntries = (projectRoot) => {
|
|
107
|
+
const realRoot = fs$1.existsSync(projectRoot) ? fs$1.realpathSync(projectRoot) : path.resolve(projectRoot);
|
|
108
|
+
const packagesDir = path.resolve(realRoot, "packages");
|
|
109
|
+
if (!fs$1.existsSync(packagesDir) || !fs$1.statSync(packagesDir).isDirectory()) return [];
|
|
110
|
+
const realPackages = fs$1.realpathSync(packagesDir);
|
|
111
|
+
if (!isInside(realRoot, realPackages)) return [];
|
|
112
|
+
const seen = /* @__PURE__ */ new Set();
|
|
113
|
+
const entries = [];
|
|
114
|
+
for (const item of fs$1.readdirSync(packagesDir, { withFileTypes: true })) {
|
|
115
|
+
if (!item.isDirectory() && !item.isSymbolicLink()) continue;
|
|
116
|
+
const directory = path.resolve(packagesDir, item.name);
|
|
117
|
+
if (!fs$1.existsSync(directory) || !fs$1.statSync(directory).isDirectory()) continue;
|
|
118
|
+
const realDirectory = fs$1.realpathSync(directory);
|
|
119
|
+
if (!isInside(realPackages, realDirectory)) continue;
|
|
120
|
+
const name = readPackageName(path.join(realDirectory, "package.json"));
|
|
121
|
+
const entry = name ? resolveSourceEntry(realDirectory) : "";
|
|
122
|
+
if (!name || !entry || seen.has(name)) continue;
|
|
123
|
+
seen.add(name);
|
|
124
|
+
entries.push({
|
|
125
|
+
name,
|
|
126
|
+
entry,
|
|
127
|
+
directory: realDirectory
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return entries;
|
|
131
|
+
};
|
|
132
|
+
var directorySignature = (directory) => {
|
|
133
|
+
const files = [];
|
|
134
|
+
const walk = (current) => {
|
|
135
|
+
const realCurrent = fs$1.realpathSync(current);
|
|
136
|
+
if (!isInside(directory, realCurrent)) return;
|
|
137
|
+
for (const item of fs$1.readdirSync(realCurrent, { withFileTypes: true })) {
|
|
138
|
+
if (item.name === ".git" || IGNORED_DIRECTORIES.has(item.name)) continue;
|
|
139
|
+
const filename = path.join(realCurrent, item.name);
|
|
140
|
+
if (item.isSymbolicLink()) {
|
|
141
|
+
if (!fs$1.existsSync(filename)) continue;
|
|
142
|
+
const realName = fs$1.realpathSync(filename);
|
|
143
|
+
if (!isInside(directory, realName)) continue;
|
|
144
|
+
const stat = fs$1.statSync(realName);
|
|
145
|
+
if (stat.isDirectory()) walk(realName);
|
|
146
|
+
else if (stat.isFile()) files.push(`${realName}:${stat.mtimeMs}`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (item.isDirectory()) {
|
|
150
|
+
walk(filename);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (item.isFile()) files.push(`${filename}:${fs$1.statSync(filename).mtimeMs}`);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
walk(directory);
|
|
157
|
+
files.sort();
|
|
158
|
+
return files.join("\n");
|
|
159
|
+
};
|
|
160
|
+
var isWorkspaceModuleExternal = (id) => {
|
|
161
|
+
if (!id || id.startsWith("\0") || id.startsWith(".") || path.isAbsolute(id)) return false;
|
|
162
|
+
if (BUNDLED_SPECIFIERS.has(id)) return false;
|
|
163
|
+
return true;
|
|
164
|
+
};
|
|
165
|
+
var readAssetSource = (source) => typeof source === "string" ? source : Buffer.from(source).toString("utf8");
|
|
166
|
+
var toBundleResult = (result) => {
|
|
167
|
+
const output = Array.isArray(result) ? result[0] : result;
|
|
168
|
+
if (!output || !("output" in output) || !Array.isArray(output.output)) throw new Error("Module build did not produce a bundle");
|
|
169
|
+
return output;
|
|
170
|
+
};
|
|
171
|
+
var injectStyles = (name, code, css) => {
|
|
172
|
+
if (!css) return code;
|
|
173
|
+
return [
|
|
174
|
+
"if (typeof document !== \"undefined\") {",
|
|
175
|
+
" const style = document.createElement(\"style\");",
|
|
176
|
+
`\tstyle.setAttribute("data-docs-module", ${JSON.stringify(name)});`,
|
|
177
|
+
`\tstyle.textContent = ${JSON.stringify(css)};`,
|
|
178
|
+
" document.head.appendChild(style);",
|
|
179
|
+
"}",
|
|
180
|
+
code
|
|
181
|
+
].join("\n");
|
|
182
|
+
};
|
|
183
|
+
var buildWorkspaceModule = async (projectRoot, entry) => {
|
|
184
|
+
buildCount += 1;
|
|
185
|
+
const bundle = toBundleResult(await build({
|
|
186
|
+
configFile: false,
|
|
187
|
+
envFile: false,
|
|
188
|
+
root: projectRoot,
|
|
189
|
+
publicDir: false,
|
|
190
|
+
logLevel: "silent",
|
|
191
|
+
mode: "development",
|
|
192
|
+
appType: "custom",
|
|
193
|
+
define: { "process.env.NODE_ENV": JSON.stringify("development") },
|
|
194
|
+
plugins: [vue(), vueJsx()],
|
|
195
|
+
build: {
|
|
196
|
+
write: false,
|
|
197
|
+
minify: false,
|
|
198
|
+
cssMinify: false,
|
|
199
|
+
cssCodeSplit: false,
|
|
200
|
+
sourcemap: false,
|
|
201
|
+
emptyOutDir: false,
|
|
202
|
+
copyPublicDir: false,
|
|
203
|
+
modulePreload: false,
|
|
204
|
+
reportCompressedSize: false,
|
|
205
|
+
assetsInlineLimit: 1e8,
|
|
206
|
+
chunkSizeWarningLimit: 1e5,
|
|
207
|
+
rolldownOptions: {
|
|
208
|
+
input: entry.entry,
|
|
209
|
+
preserveEntrySignatures: "strict",
|
|
210
|
+
treeshake: false,
|
|
211
|
+
external: isWorkspaceModuleExternal,
|
|
212
|
+
output: {
|
|
213
|
+
format: "es",
|
|
214
|
+
codeSplitting: false,
|
|
215
|
+
entryFileNames: "module.js"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}));
|
|
220
|
+
const chunks = bundle.output.filter((item) => item.type === "chunk");
|
|
221
|
+
if (chunks.length !== 1) throw new Error(`Expected a single module chunk, got ${chunks.length}`);
|
|
222
|
+
const css = bundle.output.filter((item) => item.type === "asset" && item.fileName.endsWith(".css")).map((item) => readAssetSource(item.source)).join("\n");
|
|
223
|
+
return injectStyles(entry.name, chunks[0].code, css);
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* 把工作区包编成一份浏览器 ESM。`vue` 和其它工作区包保持裸导入。
|
|
227
|
+
* @param projectRoot 用于解析 node_modules 的项目根。
|
|
228
|
+
* @param entry 已通过边界检查的包入口。
|
|
229
|
+
* @returns 可直接交给浏览器执行的模块源码。
|
|
230
|
+
*/
|
|
231
|
+
var compileWorkspaceModule = (projectRoot, entry) => {
|
|
232
|
+
const signature = directorySignature(entry.directory);
|
|
233
|
+
const cached = cache.get(entry.name);
|
|
234
|
+
if (cached?.signature === signature) return Promise.resolve(cached.code);
|
|
235
|
+
const inflight = pending.get(entry.name);
|
|
236
|
+
if (inflight) return inflight;
|
|
237
|
+
const task = buildWorkspaceModule(projectRoot, entry).then((code) => {
|
|
238
|
+
cache.set(entry.name, {
|
|
239
|
+
signature: directorySignature(entry.directory),
|
|
240
|
+
code
|
|
241
|
+
});
|
|
242
|
+
return code;
|
|
243
|
+
}).finally(() => {
|
|
244
|
+
if (pending.get(entry.name) === task) pending.delete(entry.name);
|
|
245
|
+
});
|
|
246
|
+
pending.set(entry.name, task);
|
|
247
|
+
return task;
|
|
248
|
+
};
|
|
249
|
+
var moduleFailure = (message) => `throw new Error(${JSON.stringify(message)});`;
|
|
250
|
+
/**
|
|
251
|
+
* 响应 `/__docs/module/<包名>`。未知包返回 404;编译失败返回会抛错的 ESM。
|
|
252
|
+
* @param req 开发服务请求。
|
|
253
|
+
* @param res 开发服务响应。
|
|
254
|
+
* @param projectRoot 文档项目根目录。
|
|
255
|
+
* @returns 是否已处理该请求。路径不匹配时为 false。
|
|
256
|
+
*/
|
|
257
|
+
var respondWorkspaceModule = async (req, res, projectRoot) => {
|
|
258
|
+
const pathname = (req.url || "/").split(/[?#]/u, 1)[0] || "/";
|
|
259
|
+
if (!pathname.startsWith("/__docs/module/")) return false;
|
|
260
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
261
|
+
res.statusCode = 405;
|
|
262
|
+
res.setHeader("Allow", "GET, HEAD");
|
|
263
|
+
res.end("Method Not Allowed");
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
let name = "";
|
|
267
|
+
try {
|
|
268
|
+
name = decodeURIComponent(pathname.slice(15));
|
|
269
|
+
} catch {
|
|
270
|
+
res.statusCode = 400;
|
|
271
|
+
res.end("Bad Request");
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
if (!name || name.split("/").includes("..") || name.includes("\\") || name.includes("\0")) {
|
|
275
|
+
res.statusCode = 404;
|
|
276
|
+
res.end("Not Found");
|
|
277
|
+
return true;
|
|
278
|
+
}
|
|
279
|
+
const entry = resolveWorkspaceModuleEntries(projectRoot).find((item) => item.name === name);
|
|
280
|
+
if (!entry) {
|
|
281
|
+
res.statusCode = 404;
|
|
282
|
+
res.end("Not Found");
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
let code;
|
|
286
|
+
try {
|
|
287
|
+
code = await compileWorkspaceModule(projectRoot, entry);
|
|
288
|
+
} catch (reason) {
|
|
289
|
+
code = moduleFailure(reason instanceof Error ? reason.message : "Module compile failed");
|
|
290
|
+
}
|
|
291
|
+
res.statusCode = 200;
|
|
292
|
+
res.setHeader("Content-Type", "text/javascript; charset=utf-8");
|
|
293
|
+
res.setHeader("Cache-Control", "no-store");
|
|
294
|
+
if (req.method === "HEAD") res.end();
|
|
295
|
+
else res.end(code);
|
|
296
|
+
return true;
|
|
297
|
+
};
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region packages/dever/src/plugins/optional-dependencies.ts
|
|
300
|
+
var resolveOptionalDependency = (id, importer) => {
|
|
301
|
+
if (!importer || !path.isAbsolute(importer) || /^[./]|:/.test(id)) return;
|
|
302
|
+
const filename = importer.split("?")[0];
|
|
303
|
+
const name = id.startsWith("@") ? id.split("/").slice(0, 2).join("/") : id.split("/")[0];
|
|
304
|
+
let directory = path.dirname(filename);
|
|
305
|
+
while (true) {
|
|
306
|
+
const manifest = path.join(directory, "package.json");
|
|
307
|
+
if (fs$1.existsSync(manifest)) {
|
|
308
|
+
const pkg = JSON.parse(fs$1.readFileSync(manifest, "utf8"));
|
|
309
|
+
if (!pkg.peerDependencies?.[name] || !pkg.peerDependenciesMeta?.[name]?.optional) return;
|
|
310
|
+
try {
|
|
311
|
+
createRequire(filename).resolve(id);
|
|
312
|
+
} catch (reason) {
|
|
313
|
+
if (reason.code === "MODULE_NOT_FOUND") return {
|
|
314
|
+
id: `https://cdn.jsdelivr.net/npm/${id}/+esm`,
|
|
315
|
+
external: true
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const parent = path.dirname(directory);
|
|
321
|
+
if (parent === directory) return;
|
|
322
|
+
directory = parent;
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* 本地包未安装的 optional peer 由浏览器按需从 CDN 加载。
|
|
327
|
+
* @returns 同时用于 Vite 模块转换与依赖预打包的解析插件。
|
|
328
|
+
*/
|
|
329
|
+
var createOptionalDependenciesPlugin = () => {
|
|
330
|
+
const resolver = {
|
|
331
|
+
name: "docs-optional-dependencies",
|
|
332
|
+
resolveId: resolveOptionalDependency
|
|
333
|
+
};
|
|
334
|
+
return {
|
|
335
|
+
...resolver,
|
|
336
|
+
enforce: "pre",
|
|
337
|
+
config: () => ({ optimizeDeps: { rolldownOptions: { plugins: [resolver] } } })
|
|
338
|
+
};
|
|
339
|
+
};
|
|
73
340
|
//#endregion
|
|
74
341
|
//#region packages/dever/src/plugins/index.ts
|
|
75
342
|
var innerPathRegex = /^\/(@|__)/;
|
|
@@ -269,6 +536,15 @@ var configureWorkspaceServer = (options, resolvedWorkspace) => (server) => {
|
|
|
269
536
|
let realResourceRoot = realWorkspace;
|
|
270
537
|
try {
|
|
271
538
|
const rawPathname = getRawPathname(req.url || "/");
|
|
539
|
+
if (/^\/__docs\/preview(?:-config-generate)?$/.test(rawPathname) && String(req.headers.accept || "").includes("text/html")) return next();
|
|
540
|
+
if (!options.preview && rawPathname.startsWith("/__docs/module/")) {
|
|
541
|
+
respondWorkspaceModule(req, res, resolved.projectRoot).catch((reason) => {
|
|
542
|
+
if (res.writableEnded) return;
|
|
543
|
+
res.statusCode = 500;
|
|
544
|
+
res.end(reason instanceof Error ? reason.message : "Module compile failed");
|
|
545
|
+
});
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
272
548
|
if (rawPathname.startsWith("/__docs/") && rawPathname !== "/__docs/events") {
|
|
273
549
|
res.statusCode = 404;
|
|
274
550
|
res.end("Not Found");
|
|
@@ -440,15 +716,17 @@ var createHistoryPlugin = (options, resolvedWorkspace) => ({
|
|
|
440
716
|
server.middlewares.use(async (req, res, next) => {
|
|
441
717
|
const requestUrl = req.url || "/";
|
|
442
718
|
const url = requestUrl.replace(/\/+/g, "/").replace(/[?#].*$/s, "") || "";
|
|
719
|
+
const accept = String(req.headers?.accept || "text/html");
|
|
720
|
+
const isPreviewPage = /^\/__docs\/preview(?:-config-generate)?$/.test(url) && accept.includes("text/html");
|
|
443
721
|
const candidate = path.resolve(root, `.${url}`);
|
|
444
722
|
const isFile = fs$1.existsSync(candidate) && fs$1.statSync(candidate).isFile();
|
|
445
|
-
if (options.preview && url.startsWith("/__docs/")) {
|
|
723
|
+
if (options.preview && url.startsWith("/__docs/") && !isPreviewPage) {
|
|
446
724
|
res.statusCode = 404;
|
|
447
725
|
res.end("Not Found");
|
|
448
726
|
return;
|
|
449
727
|
}
|
|
450
|
-
if (res.writableEnded || requestUrl.includes("html-proxy&") || innerPathRegex.test(url) || isFile) return next();
|
|
451
|
-
if (!
|
|
728
|
+
if (res.writableEnded || requestUrl.includes("html-proxy&") || innerPathRegex.test(url) && !isPreviewPage || isFile) return next();
|
|
729
|
+
if (!accept.includes("text/html")) return next();
|
|
452
730
|
const indexFile = resolvedWorkspace?.entry || resolveDocsWorkspace(options.preview ? root : process.cwd(), options.preview ? "." : options.workspace).entry;
|
|
453
731
|
if (!fs$1.existsSync(indexFile)) {
|
|
454
732
|
res.statusCode = 404;
|
|
@@ -513,6 +791,7 @@ var createStaticCopyPlugin = (options, resolvedWorkspace) => ({
|
|
|
513
791
|
}
|
|
514
792
|
});
|
|
515
793
|
var plugins_default = (options = {}, resolvedWorkspace) => defineConfig({ plugins: [
|
|
794
|
+
createOptionalDependenciesPlugin(),
|
|
516
795
|
createRuntimePlugin(options, resolvedWorkspace),
|
|
517
796
|
createWorkspacePlugin(options, resolvedWorkspace),
|
|
518
797
|
createHistoryPlugin(options, resolvedWorkspace),
|
|
@@ -677,7 +956,8 @@ var createPreviewRequestHandler = (options) => {
|
|
|
677
956
|
return;
|
|
678
957
|
}
|
|
679
958
|
const pathname = decodeRequestPath(req.url || "/");
|
|
680
|
-
|
|
959
|
+
const isPreviewPage = /^\/__docs\/preview(?:-config-generate)?$/.test(pathname) && String(req.headers.accept || "").includes("text/html");
|
|
960
|
+
if (pathname.startsWith("/__docs/") && !isPreviewPage) {
|
|
681
961
|
res.statusCode = 404;
|
|
682
962
|
res.end("Not Found");
|
|
683
963
|
return;
|