@baybreezy/docd 0.0.1 → 0.0.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.
@@ -1,5 +1,6 @@
1
- @import "tailwindcss" source("../../../../../");
1
+ @import "tailwindcss";
2
2
  @import "tw-animate-css";
3
+ @source "../../../../";
3
4
 
4
5
  @config "../../../tailwind.config.cjs";
5
6
 
package/modules/css.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { addVitePlugin, createResolver, defineNuxtModule } from "@nuxt/kit";
2
+ import { joinURL } from "ufo";
3
+
4
+ export default defineNuxtModule({
5
+ meta: {
6
+ name: "docd:css",
7
+ },
8
+ setup(_options, nuxt) {
9
+ const resolver = createResolver(import.meta.url);
10
+
11
+ // Absolute path to the layer's tailwind entry — this is the file that owns
12
+ // `@import "tailwindcss"` and therefore controls Tailwind's scanner.
13
+ const tailwindCssPath = resolver.resolve("../app/assets/css/tailwind.css");
14
+
15
+ // The consuming app's content directory. We convert to forward-slashes so
16
+ // the injected @source glob works on Windows too.
17
+ const contentDir = joinURL(nuxt.options.rootDir, "content").replace(/\\/g, "/");
18
+
19
+ // Inject `@source <contentDir>/**/*` into tailwind.css
20
+ addVitePlugin({
21
+ name: "docd:inject-tailwind-sources",
22
+ enforce: "pre",
23
+ transform(code: string, id: string) {
24
+ if (id.split("?")[0] !== tailwindCssPath) return null;
25
+ return {
26
+ code: `${code}\n@source ${JSON.stringify(`${contentDir}/**/*`)};`,
27
+ map: null,
28
+ };
29
+ },
30
+ });
31
+
32
+ // Suppress noisy Vite warnings produced during Tailwind's build pass.
33
+ nuxt.hook("vite:extendConfig", (config) => {
34
+ const logger = config.customLogger;
35
+ if (!logger) return;
36
+ const ignore = ["@tailwindcss/vite:generate:build", "nuxt:module-preload-polyfill"];
37
+ const originalWarn = logger.warn.bind(logger);
38
+ logger.warn = (msg, options) => {
39
+ if (ignore.some((p) => msg.includes(p))) return;
40
+ originalWarn(msg, options);
41
+ };
42
+ });
43
+ },
44
+ });
package/nuxt.config.ts CHANGED
@@ -50,6 +50,7 @@ export default defineNuxtConfig({
50
50
  "@morev/vue-transitions/nuxt",
51
51
  "vue-sonner/nuxt",
52
52
  resolver.resolve("./modules/routing"),
53
+ resolver.resolve("./modules/css"),
53
54
  resolver.resolve("./modules/config"),
54
55
  resolver.resolve("./modules/custom-icons"),
55
56
  resolver.resolve("./modules/prose-component-meta"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baybreezy/docd",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "description": "A Nuxt layer for building documentation sites with UI Thing.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,22 @@
1
+ import { workerData, parentPort } from "node:worker_threads";
2
+
3
+ import { getComponentMeta } from "nuxt-component-meta/parser";
4
+
5
+ const { batch, rootDir, cacheDir } = workerData;
6
+
7
+ const results = [];
8
+
9
+ for (const { manifestPath, absolutePath } of batch) {
10
+ try {
11
+ const raw = getComponentMeta(absolutePath, {
12
+ rootDir,
13
+ cache: true,
14
+ cacheDir,
15
+ });
16
+ results.push({ manifestPath, raw, ok: true });
17
+ } catch (err) {
18
+ results.push({ manifestPath, raw: null, ok: false, error: String(err) });
19
+ }
20
+ }
21
+
22
+ parentPort.postMessage(results);
@@ -1,8 +1,9 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { mkdir, readdir, stat, writeFile } from "node:fs/promises";
3
+ import { cpus } from "node:os";
3
4
  import { dirname, join, relative, resolve } from "node:path";
4
-
5
- import { getComponentMeta } from "nuxt-component-meta/parser";
5
+ import { fileURLToPath } from "node:url";
6
+ import { Worker } from "node:worker_threads";
6
7
 
7
8
  import {
8
9
  componentNameFromPath,
@@ -170,6 +171,67 @@ function normalizeExposed(exposed: RawExposedMeta): ProseComponentMetaExposed {
170
171
  };
171
172
  }
172
173
 
174
+ interface WorkerResult {
175
+ manifestPath: string;
176
+ raw: Record<string, unknown[]> | null;
177
+ ok: boolean;
178
+ error?: string;
179
+ }
180
+
181
+ const WORKER_PATH = fileURLToPath(new URL("./component-meta-worker.mjs", import.meta.url));
182
+
183
+ async function runInWorkers(
184
+ components: ProseComponentMetaSource[],
185
+ opts: { rootDir: string; cache: boolean; cacheDir: string }
186
+ ): Promise<WorkerResult[]> {
187
+ const numWorkers = Math.min(cpus().length, components.length, 8);
188
+ const chunkSize = Math.ceil(components.length / numWorkers);
189
+
190
+ const chunks: ProseComponentMetaSource[][] = [];
191
+ for (let i = 0; i < components.length; i += chunkSize) {
192
+ chunks.push(components.slice(i, i + chunkSize));
193
+ }
194
+
195
+ const spawnWorker = (batch: ProseComponentMetaSource[]) =>
196
+ new Promise<WorkerResult[]>((resolve, reject) => {
197
+ const worker = new Worker(WORKER_PATH, {
198
+ workerData: {
199
+ batch: batch.map((c) => ({
200
+ manifestPath: c.manifestPath,
201
+ absolutePath: c.absolutePath,
202
+ })),
203
+ rootDir: opts.rootDir,
204
+ cacheDir: opts.cacheDir,
205
+ },
206
+ });
207
+ worker.on("message", resolve);
208
+ worker.on("error", reject);
209
+ worker.on("exit", (code) => {
210
+ if (code !== 0) reject(new Error(`Component meta worker exited with code ${code}`));
211
+ });
212
+ });
213
+
214
+ try {
215
+ const results = await Promise.all(chunks.map(spawnWorker));
216
+ return results.flat();
217
+ } catch {
218
+ // Fall back to sequential processing if workers fail.
219
+ const { getComponentMeta } = await import("nuxt-component-meta/parser");
220
+ return components.map(({ manifestPath, absolutePath }) => {
221
+ try {
222
+ const raw = getComponentMeta(absolutePath, {
223
+ rootDir: opts.rootDir,
224
+ cache: opts.cache,
225
+ cacheDir: opts.cacheDir,
226
+ }) as Record<string, unknown[]>;
227
+ return { manifestPath, raw, ok: true };
228
+ } catch (err) {
229
+ return { manifestPath, raw: null, ok: false, error: String(err) };
230
+ }
231
+ });
232
+ }
233
+ }
234
+
173
235
  async function listVueFiles(dir: string): Promise<string[]> {
174
236
  const files: string[] = [];
175
237
  const entries = await readdir(dir, { withFileTypes: true });
@@ -233,10 +295,12 @@ export async function generateProseComponentMeta(
233
295
  );
234
296
  let hasChanges = sources.length !== Object.keys(previousManifest.components || {}).length;
235
297
 
298
+ // First pass: resolve hashes and split into cache hits vs misses.
299
+ const cacheMisses: ProseComponentMetaSource[] = [];
300
+
236
301
  for (const component of sources) {
237
302
  const stats = await stat(component.absolutePath);
238
303
  const sourceHash = `${stats.mtimeMs}:${stats.size}`;
239
-
240
304
  sourceHashes[component.manifestPath] = sourceHash;
241
305
 
242
306
  const cachedEntry = previousManifest.components?.[component.manifestPath];
@@ -244,27 +308,33 @@ export async function generateProseComponentMeta(
244
308
 
245
309
  if (cachedEntry && cachedHash === sourceHash) {
246
310
  components[component.manifestPath] = cachedEntry;
247
- continue;
311
+ } else {
312
+ hasChanges = true;
313
+ cacheMisses.push(component);
248
314
  }
315
+ }
249
316
 
250
- hasChanges = true;
251
-
252
- const meta = getComponentMeta(component.absolutePath, {
317
+ // Second pass: process cache misses in parallel worker threads.
318
+ if (cacheMisses.length > 0) {
319
+ const rawResults = await runInWorkers(cacheMisses, {
253
320
  rootDir: options.rootDir,
254
321
  cache: options.cache ?? true,
255
- cacheDir: options.cacheDir,
322
+ cacheDir: options.cacheDir ?? "",
256
323
  });
257
324
 
258
- const name = componentNameFromPath(component.manifestPath);
259
- components[component.manifestPath] = {
260
- name,
261
- tag: componentTagFromName(name),
262
- path: component.manifestPath,
263
- props: meta.props.map(normalizeProp),
264
- slots: meta.slots.map(normalizeSlot),
265
- events: meta.events.map(normalizeEvent),
266
- exposed: meta.exposed.map(normalizeExposed),
267
- };
325
+ for (const result of rawResults) {
326
+ if (!result.ok || !result.raw) continue;
327
+ const name = componentNameFromPath(result.manifestPath);
328
+ components[result.manifestPath] = {
329
+ name,
330
+ tag: componentTagFromName(name),
331
+ path: result.manifestPath,
332
+ props: (result.raw.props ?? []).map(normalizeProp),
333
+ slots: (result.raw.slots ?? []).map(normalizeSlot),
334
+ events: (result.raw.events ?? []).map(normalizeEvent),
335
+ exposed: (result.raw.exposed ?? []).map(normalizeExposed),
336
+ };
337
+ }
268
338
  }
269
339
 
270
340
  if (!hasChanges) {