@honeydeck/honeydeck 0.1.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.
Files changed (144) hide show
  1. package/AGENTS.md +25 -0
  2. package/DEVELOPMENT.md +522 -0
  3. package/LICENSE +21 -0
  4. package/Readme.md +49 -0
  5. package/SPEC.md +88 -0
  6. package/docs/components.md +63 -0
  7. package/docs/configuration.md +91 -0
  8. package/docs/getting-started.md +116 -0
  9. package/docs/kit-authoring.md +207 -0
  10. package/docs/kits.md +387 -0
  11. package/docs/local-development.md +95 -0
  12. package/docs/mermaid.md +198 -0
  13. package/docs/mobile.md +108 -0
  14. package/docs/navigation.md +93 -0
  15. package/docs/next-steps.md +377 -0
  16. package/docs/pdf-export.md +91 -0
  17. package/docs/presenter-mode.md +104 -0
  18. package/docs/slides.md +130 -0
  19. package/docs/slidev-migration.md +42 -0
  20. package/docs/steps-and-reveals.md +171 -0
  21. package/package.json +134 -0
  22. package/skills/SPEC.md +21 -0
  23. package/skills/honeydeck/SKILL.md +65 -0
  24. package/skills/presentation-writing/SKILL.md +75 -0
  25. package/skills/slidev-migration/SKILL.md +153 -0
  26. package/src/SPEC.md +89 -0
  27. package/src/assets.d.ts +30 -0
  28. package/src/cli/SPEC.md +230 -0
  29. package/src/cli/args.ts +3 -0
  30. package/src/cli/banner.ts +9 -0
  31. package/src/cli/bin.js +5 -0
  32. package/src/cli/build.ts +229 -0
  33. package/src/cli/deck-path.ts +32 -0
  34. package/src/cli/dev.ts +263 -0
  35. package/src/cli/index.ts +126 -0
  36. package/src/cli/init.ts +369 -0
  37. package/src/cli/pdf.ts +923 -0
  38. package/src/cli/skill.ts +75 -0
  39. package/src/cli/templates/SPEC.md +70 -0
  40. package/src/cli/templates/deck-mdx.ts +15 -0
  41. package/src/cli/templates/package-json.ts +36 -0
  42. package/src/cli/templates/sparkle-button.ts +15 -0
  43. package/src/cli/templates/starter/components/SparkleButton.tsx +84 -0
  44. package/src/cli/templates/starter/deck.mdx +153 -0
  45. package/src/cli/templates/starter/styles.css +14 -0
  46. package/src/cli/templates/styles-css.ts +14 -0
  47. package/src/defaults.ts +1 -0
  48. package/src/layouts/ColorModeImage.tsx +55 -0
  49. package/src/layouts/SPEC.md +393 -0
  50. package/src/layouts/SlideFrame.tsx +48 -0
  51. package/src/layouts/bee/Blank.tsx +12 -0
  52. package/src/layouts/bee/Cover.tsx +70 -0
  53. package/src/layouts/bee/Default.tsx +42 -0
  54. package/src/layouts/bee/Image/Image.tsx +151 -0
  55. package/src/layouts/bee/Image/placeholder-dark.webp +0 -0
  56. package/src/layouts/bee/Image/placeholder-vertical-dark.webp +0 -0
  57. package/src/layouts/bee/Image/placeholder-vertical.webp +0 -0
  58. package/src/layouts/bee/Image/placeholder.webp +0 -0
  59. package/src/layouts/bee/ImageLeft.tsx +27 -0
  60. package/src/layouts/bee/ImageRight.tsx +27 -0
  61. package/src/layouts/bee/ImageSide.tsx +107 -0
  62. package/src/layouts/bee/Section.tsx +40 -0
  63. package/src/layouts/bee/TwoCol.tsx +108 -0
  64. package/src/layouts/bee/index.ts +40 -0
  65. package/src/layouts/clean/Blank.tsx +12 -0
  66. package/src/layouts/clean/Cover.tsx +58 -0
  67. package/src/layouts/clean/Default.tsx +33 -0
  68. package/src/layouts/clean/Image/Image.tsx +103 -0
  69. package/src/layouts/clean/ImageLeft.tsx +27 -0
  70. package/src/layouts/clean/ImageRight.tsx +27 -0
  71. package/src/layouts/clean/ImageSide.tsx +113 -0
  72. package/src/layouts/clean/Section.tsx +35 -0
  73. package/src/layouts/clean/TwoCol.tsx +63 -0
  74. package/src/layouts/clean/index.ts +40 -0
  75. package/src/layouts/index.ts +60 -0
  76. package/src/layouts/placeholders.ts +9 -0
  77. package/src/layouts/utils.ts +13 -0
  78. package/src/remark/SPEC.md +49 -0
  79. package/src/remark/h1-extract.ts +124 -0
  80. package/src/remark/index.ts +4 -0
  81. package/src/remark/shiki-code-blocks.ts +325 -0
  82. package/src/remark/step-numbering.ts +412 -0
  83. package/src/runtime/Deck.tsx +533 -0
  84. package/src/runtime/SPEC.md +256 -0
  85. package/src/runtime/SlideCanvas.tsx +95 -0
  86. package/src/runtime/TimelineContext.tsx +122 -0
  87. package/src/runtime/app-shell/index.html +31 -0
  88. package/src/runtime/app-shell/main.tsx +42 -0
  89. package/src/runtime/aspectRatio.ts +34 -0
  90. package/src/runtime/colorMode.ts +23 -0
  91. package/src/runtime/components/BrowserFrame.tsx +233 -0
  92. package/src/runtime/components/Button.tsx +57 -0
  93. package/src/runtime/components/CodeBlock.tsx +210 -0
  94. package/src/runtime/components/ColorModeCycleButton.tsx +59 -0
  95. package/src/runtime/components/ErrorBoundary.tsx +125 -0
  96. package/src/runtime/components/Keyboard.tsx +87 -0
  97. package/src/runtime/components/ListStyle.tsx +203 -0
  98. package/src/runtime/components/NavBar.tsx +223 -0
  99. package/src/runtime/components/NavBarButton.tsx +47 -0
  100. package/src/runtime/components/NavBarDivider.tsx +3 -0
  101. package/src/runtime/components/Notes.tsx +171 -0
  102. package/src/runtime/components/Reveal.tsx +82 -0
  103. package/src/runtime/components/RevealGroup.tsx +193 -0
  104. package/src/runtime/components/SPEC.md +263 -0
  105. package/src/runtime/components/SlideNumberBadge.tsx +11 -0
  106. package/src/runtime/components/TimelineSteps.tsx +115 -0
  107. package/src/runtime/components/index.ts +55 -0
  108. package/src/runtime/index.ts +42 -0
  109. package/src/runtime/inputOwnership.ts +68 -0
  110. package/src/runtime/keyboardTarget.ts +7 -0
  111. package/src/runtime/lastSlideRoute.ts +56 -0
  112. package/src/runtime/navigation.ts +211 -0
  113. package/src/runtime/router.ts +157 -0
  114. package/src/runtime/slideData.ts +137 -0
  115. package/src/runtime/sync.ts +267 -0
  116. package/src/runtime/types.ts +182 -0
  117. package/src/runtime/useKeyboardNav.ts +138 -0
  118. package/src/runtime/useSwipeNav.ts +257 -0
  119. package/src/runtime/views/DocsView.tsx +74 -0
  120. package/src/runtime/views/OverviewView.tsx +386 -0
  121. package/src/runtime/views/PresenterNotesPanel.tsx +76 -0
  122. package/src/runtime/views/PresenterView.tsx +340 -0
  123. package/src/runtime/views/SPEC.md +152 -0
  124. package/src/runtime/views/docs/ComponentsTab.tsx +178 -0
  125. package/src/runtime/views/docs/DocsHeader.tsx +101 -0
  126. package/src/runtime/views/docs/Intro.tsx +20 -0
  127. package/src/runtime/views/docs/LayoutsTab.tsx +324 -0
  128. package/src/runtime/views/docs/ThemeTab.tsx +110 -0
  129. package/src/runtime/views/index.ts +7 -0
  130. package/src/runtime/views/overviewGrid.ts +106 -0
  131. package/src/runtime/views/presenterPreview.ts +27 -0
  132. package/src/runtime/virtual-modules.d.ts +98 -0
  133. package/src/theme/SPEC.md +179 -0
  134. package/src/theme/base.css +623 -0
  135. package/src/theme/bee.css +35 -0
  136. package/src/theme/clean.css +38 -0
  137. package/src/vite-plugin/SPEC.md +114 -0
  138. package/src/vite-plugin/component-doc-crawler.ts +350 -0
  139. package/src/vite-plugin/deck-loader.ts +148 -0
  140. package/src/vite-plugin/index.ts +373 -0
  141. package/src/vite-plugin/layout-demo-crawler.ts +802 -0
  142. package/src/vite-plugin/splitter.ts +353 -0
  143. package/src/vite-plugin/token-manifest.ts +163 -0
  144. package/src/vite-plugin/virtual-modules.ts +587 -0
@@ -0,0 +1,373 @@
1
+ /**
2
+ * Main Honeydeck Vite plugin.
3
+ *
4
+ * Composes three plugin layers into a single `Plugin[]` array:
5
+ *
6
+ * 1. `honeydeck:virtual-modules` — splits `deck.mdx`, serves virtual MDX modules,
7
+ * handles per-slide HMR invalidation.
8
+ * 2. `@mdx-js/rollup` — compiles `.mdx` files (real and virtual) to
9
+ * React JSX via the `transform` hook.
10
+ * 3. `@tailwindcss/vite` — Tailwind CSS v4 (CSS-first, no config file).
11
+ *
12
+ * ### Plugin ordering rationale
13
+ * Vite applies `resolveId`/`load` hooks in plugin order until the first
14
+ * non-null result. Because our virtual modules plugin handles `resolveId` and
15
+ * `load` for virtual IDs, it must come first. The `transform` hook runs for
16
+ * ALL plugins in order, so `@mdx-js/rollup` naturally sees the raw MDX source
17
+ * returned by our `load` hook and compiles it to JSX.
18
+ *
19
+ * ### Virtual `.mdx` compilation
20
+ * `@mdx-js/rollup` filters by file extension. The resolved virtual ID
21
+ * `\0virtual:honeydeck/slide/N.mdx` ends in `.mdx`, so `path.extname()` returns
22
+ * `.mdx` and the filter passes — no special configuration needed.
23
+ */
24
+
25
+ import { dirname, resolve } from "node:path";
26
+ import { fileURLToPath } from "node:url";
27
+ import mdx from "@mdx-js/rollup";
28
+ import tailwindcss from "@tailwindcss/vite";
29
+ import remarkFrontmatter from "remark-frontmatter";
30
+ import remarkGfm from "remark-gfm";
31
+ import type { Plugin, PluginOption } from "vite";
32
+ import { DEFAULT_DECK_ENTRY } from "../defaults.ts";
33
+ import { remarkH1Extract } from "../remark/h1-extract.ts";
34
+ import { remarkShikiCodeBlocks } from "../remark/shiki-code-blocks.ts";
35
+ import { remarkStepNumbering } from "../remark/step-numbering.ts";
36
+ import { tokenManifestPlugin } from "./token-manifest.ts";
37
+ import { virtualModulesPlugin } from "./virtual-modules.ts";
38
+
39
+ // ESM-safe equivalent of __dirname
40
+ const __dirname = dirname(fileURLToPath(import.meta.url));
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Types
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export type HoneydeckPluginOptions = {
47
+ /**
48
+ * Project root directory. All relative paths (entry, assets, components)
49
+ * are resolved against this.
50
+ *
51
+ * @default process.cwd()
52
+ */
53
+ root?: string;
54
+
55
+ /**
56
+ * Entry MDX file path, relative to `root`.
57
+ *
58
+ * @default 'deck.mdx'
59
+ */
60
+ entry?: string;
61
+ };
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Tailwind source injection
65
+ // ---------------------------------------------------------------------------
66
+
67
+ const TAILWIND_IMPORT_RE =
68
+ /@import\s+(?:url\()?['"]tailwindcss(?:\/[^'"]*)?['"]\)?\s*;/;
69
+ const HONEYDECK_TAILWIND_SOURCE_MARKER = "/* honeydeck:tailwind-user-source */";
70
+
71
+ function cssString(value: string): string {
72
+ return JSON.stringify(value.replace(/\\/g, "/"));
73
+ }
74
+
75
+ export function injectTailwindUserSource(
76
+ css: string,
77
+ userRoot: string,
78
+ ): string {
79
+ if (!TAILWIND_IMPORT_RE.test(css)) return css;
80
+ if (css.includes(HONEYDECK_TAILWIND_SOURCE_MARKER)) return css;
81
+
82
+ return `${HONEYDECK_TAILWIND_SOURCE_MARKER}\n@source ${cssString(userRoot)};\n${css}`;
83
+ }
84
+
85
+ function tailwindUserSourcePlugin(userRoot: string): Plugin {
86
+ return {
87
+ name: "honeydeck:tailwind-user-source",
88
+ enforce: "pre",
89
+ transform(code, id) {
90
+ const path = id.split("?", 1)[0] ?? id;
91
+ if (!path.endsWith(".css")) return null;
92
+
93
+ const nextCode = injectTailwindUserSource(code, userRoot);
94
+ if (nextCode === code) return null;
95
+ return { code: nextCode, map: null };
96
+ },
97
+ };
98
+ }
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Factory
102
+ // ---------------------------------------------------------------------------
103
+
104
+ /**
105
+ * Returns the ordered Vite plugin array for Honeydeck.
106
+ *
107
+ * Pass this to Vite's `plugins` array in your programmatic config:
108
+ *
109
+ * ```ts
110
+ * import { createServer } from 'vite';
111
+ * import { honeydeckPlugin } from '#vite-plugin/index.ts';
112
+ *
113
+ * const server = await createServer({
114
+ * plugins: honeydeckPlugin({ root: 'deck-project' }),
115
+ * });
116
+ * ```
117
+ */
118
+ export function honeydeckPlugin(
119
+ options: HoneydeckPluginOptions = {},
120
+ ): PluginOption[] {
121
+ const root = resolve(options.root ?? process.cwd());
122
+ const entry = options.entry ?? DEFAULT_DECK_ENTRY;
123
+ const entryPath = resolve(root, entry);
124
+
125
+ return [
126
+ // ── Layer 0: package aliases ───────────────────────────────────────
127
+ //
128
+ // Maps the bare `honeydeck` specifier (used in user deck.mdx) to the
129
+ // local runtime components, so Vite can resolve it without a published
130
+ // package on npm.
131
+ {
132
+ name: "honeydeck:aliases",
133
+ config() {
134
+ return {
135
+ resolve: {
136
+ // Use an array so more-specific subpath entries are matched before
137
+ // the bare 'honeydeck' entry (Vite processes array aliases in order).
138
+ alias: [
139
+ {
140
+ find: "@honeydeck/honeydeck/components/code-block",
141
+ replacement: resolve(
142
+ __dirname,
143
+ "../runtime/components/CodeBlock.tsx",
144
+ ),
145
+ },
146
+ {
147
+ find: "@honeydeck/honeydeck/layouts/ColorModeImage",
148
+ replacement: resolve(
149
+ __dirname,
150
+ "../layouts/ColorModeImage.tsx",
151
+ ),
152
+ },
153
+ {
154
+ find: "@honeydeck/honeydeck/layouts/bee/Blank",
155
+ replacement: resolve(__dirname, "../layouts/bee/Blank.tsx"),
156
+ },
157
+ {
158
+ find: "@honeydeck/honeydeck/layouts/bee/Default",
159
+ replacement: resolve(__dirname, "../layouts/bee/Default.tsx"),
160
+ },
161
+ {
162
+ find: "@honeydeck/honeydeck/layouts/bee/Cover",
163
+ replacement: resolve(__dirname, "../layouts/bee/Cover.tsx"),
164
+ },
165
+ {
166
+ find: "@honeydeck/honeydeck/layouts/bee/Section",
167
+ replacement: resolve(__dirname, "../layouts/bee/Section.tsx"),
168
+ },
169
+ {
170
+ find: "@honeydeck/honeydeck/layouts/bee/TwoCol",
171
+ replacement: resolve(__dirname, "../layouts/bee/TwoCol.tsx"),
172
+ },
173
+ {
174
+ find: "@honeydeck/honeydeck/layouts/bee/ImageLeft",
175
+ replacement: resolve(__dirname, "../layouts/bee/ImageLeft.tsx"),
176
+ },
177
+ {
178
+ find: "@honeydeck/honeydeck/layouts/bee/ImageRight",
179
+ replacement: resolve(
180
+ __dirname,
181
+ "../layouts/bee/ImageRight.tsx",
182
+ ),
183
+ },
184
+ {
185
+ find: "@honeydeck/honeydeck/layouts/bee/Image",
186
+ replacement: resolve(
187
+ __dirname,
188
+ "../layouts/bee/Image/Image.tsx",
189
+ ),
190
+ },
191
+ {
192
+ find: "@honeydeck/honeydeck/layouts/bee",
193
+ replacement: resolve(__dirname, "../layouts/bee/index.ts"),
194
+ },
195
+ {
196
+ find: "@honeydeck/honeydeck/layouts/clean/Blank",
197
+ replacement: resolve(__dirname, "../layouts/clean/Blank.tsx"),
198
+ },
199
+ {
200
+ find: "@honeydeck/honeydeck/layouts/clean/Default",
201
+ replacement: resolve(__dirname, "../layouts/clean/Default.tsx"),
202
+ },
203
+ {
204
+ find: "@honeydeck/honeydeck/layouts/clean/Cover",
205
+ replacement: resolve(__dirname, "../layouts/clean/Cover.tsx"),
206
+ },
207
+ {
208
+ find: "@honeydeck/honeydeck/layouts/clean/Section",
209
+ replacement: resolve(__dirname, "../layouts/clean/Section.tsx"),
210
+ },
211
+ {
212
+ find: "@honeydeck/honeydeck/layouts/clean/TwoCol",
213
+ replacement: resolve(__dirname, "../layouts/clean/TwoCol.tsx"),
214
+ },
215
+ {
216
+ find: "@honeydeck/honeydeck/layouts/clean/ImageLeft",
217
+ replacement: resolve(
218
+ __dirname,
219
+ "../layouts/clean/ImageLeft.tsx",
220
+ ),
221
+ },
222
+ {
223
+ find: "@honeydeck/honeydeck/layouts/clean/ImageRight",
224
+ replacement: resolve(
225
+ __dirname,
226
+ "../layouts/clean/ImageRight.tsx",
227
+ ),
228
+ },
229
+ {
230
+ find: "@honeydeck/honeydeck/layouts/clean/Image",
231
+ replacement: resolve(
232
+ __dirname,
233
+ "../layouts/clean/Image/Image.tsx",
234
+ ),
235
+ },
236
+ {
237
+ find: "@honeydeck/honeydeck/layouts/clean",
238
+ replacement: resolve(__dirname, "../layouts/clean/index.ts"),
239
+ },
240
+ {
241
+ find: "@honeydeck/honeydeck/layouts/Blank",
242
+ replacement: resolve(__dirname, "../layouts/clean/Blank.tsx"),
243
+ },
244
+ {
245
+ find: "@honeydeck/honeydeck/layouts/Default",
246
+ replacement: resolve(__dirname, "../layouts/clean/Default.tsx"),
247
+ },
248
+ {
249
+ find: "@honeydeck/honeydeck/layouts/placeholders",
250
+ replacement: resolve(__dirname, "../layouts/placeholders.ts"),
251
+ },
252
+ {
253
+ find: "@honeydeck/honeydeck/layouts/Cover",
254
+ replacement: resolve(__dirname, "../layouts/clean/Cover.tsx"),
255
+ },
256
+ {
257
+ find: "@honeydeck/honeydeck/layouts/Section",
258
+ replacement: resolve(__dirname, "../layouts/clean/Section.tsx"),
259
+ },
260
+ {
261
+ find: "@honeydeck/honeydeck/layouts/TwoCol",
262
+ replacement: resolve(__dirname, "../layouts/clean/TwoCol.tsx"),
263
+ },
264
+ {
265
+ find: "@honeydeck/honeydeck/layouts/ImageLeft",
266
+ replacement: resolve(
267
+ __dirname,
268
+ "../layouts/clean/ImageLeft.tsx",
269
+ ),
270
+ },
271
+ {
272
+ find: "@honeydeck/honeydeck/layouts/ImageRight",
273
+ replacement: resolve(
274
+ __dirname,
275
+ "../layouts/clean/ImageRight.tsx",
276
+ ),
277
+ },
278
+ {
279
+ find: "@honeydeck/honeydeck/layouts/Image",
280
+ replacement: resolve(
281
+ __dirname,
282
+ "../layouts/clean/Image/Image.tsx",
283
+ ),
284
+ },
285
+ {
286
+ find: "@honeydeck/honeydeck/layouts",
287
+ replacement: resolve(__dirname, "../layouts/index.ts"),
288
+ },
289
+ {
290
+ find: "@honeydeck/honeydeck/types",
291
+ replacement: resolve(__dirname, "../runtime/types.ts"),
292
+ },
293
+ {
294
+ find: "@honeydeck/honeydeck/theme.css",
295
+ replacement: resolve(__dirname, "../theme/base.css"),
296
+ },
297
+ {
298
+ find: "@honeydeck/honeydeck/themes/base.css",
299
+ replacement: resolve(__dirname, "../theme/base.css"),
300
+ },
301
+ {
302
+ find: "@honeydeck/honeydeck/themes/clean.css",
303
+ replacement: resolve(__dirname, "../theme/clean.css"),
304
+ },
305
+ {
306
+ find: "@honeydeck/honeydeck/themes/bee.css",
307
+ replacement: resolve(__dirname, "../theme/bee.css"),
308
+ },
309
+ {
310
+ find: "@honeydeck/honeydeck/components",
311
+ replacement: resolve(
312
+ __dirname,
313
+ "../runtime/components/index.ts",
314
+ ),
315
+ },
316
+ {
317
+ find: "@honeydeck/honeydeck",
318
+ replacement: resolve(__dirname, "../runtime/index.ts"),
319
+ },
320
+ ],
321
+ },
322
+ };
323
+ },
324
+ } satisfies Plugin,
325
+
326
+ // ── Layer 1a: Tailwind source injection ────────────────────────────────
327
+ //
328
+ // `honeydeck build` / `honeydeck pdf` use the package app-shell as Vite root, so
329
+ // Tailwind's default scanner would otherwise miss project-local MDX/TSX
330
+ // files. Add an explicit @source to every user Tailwind entry stylesheet.
331
+ tailwindUserSourcePlugin(root),
332
+
333
+ // ── Layer 1b: token manifest ────────────────────────────────────────────
334
+ //
335
+ // Parses src/theme/base.css → virtual:honeydeck/token-manifest (used by DocsView).
336
+ tokenManifestPlugin(),
337
+
338
+ // ── Layer 1c: virtual slide modules ─────────────────────────────────────
339
+ //
340
+ // Handles resolveId + load for all `virtual:honeydeck/*` IDs.
341
+ // Must come before @mdx-js/rollup so our load hook runs first and
342
+ // returns raw MDX source for virtual slides.
343
+ virtualModulesPlugin({ entryPath }),
344
+
345
+ // ── Layer 2: MDX compilation ─────────────────────────────────────────
346
+ //
347
+ // Transforms `.mdx` source (real files and virtual modules) to React JSX.
348
+ // `remarkFrontmatter` strips YAML frontmatter blocks so they don't
349
+ // appear as raw text in the rendered output.
350
+ mdx({
351
+ // remarkFrontmatter strips YAML front-matter blocks so they don't
352
+ // appear as raw text in the rendered output.
353
+ // remarkStepNumbering assigns at={n} props to <Reveal>/<RevealGroup>
354
+ // and records vfile.data.stepCount for each slide.
355
+ remarkPlugins: [
356
+ remarkFrontmatter,
357
+ remarkGfm,
358
+ remarkH1Extract,
359
+ remarkStepNumbering,
360
+ remarkShikiCodeBlocks,
361
+ ],
362
+ // Be explicit about the JSX runtime so the output is consistent
363
+ // regardless of tsconfig.json `jsxImportSource` settings.
364
+ jsxImportSource: "react",
365
+ }) as Plugin,
366
+
367
+ // ── Layer 3: Tailwind CSS v4 ─────────────────────────────────────────
368
+ //
369
+ // @tailwindcss/vite returns an array of plugins (scan, generate:serve,
370
+ // generate:build). Spread them in to preserve their relative ordering.
371
+ ...(tailwindcss() as Plugin[]),
372
+ ];
373
+ }