@qds.dev/tools 0.8.5 → 0.9.7

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 ADDED
@@ -0,0 +1,376 @@
1
+ # @qds.dev/tools
2
+
3
+ > Build tools and utilities for Qwik Design System
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@qds.dev/tools)](https://www.npmjs.com/package/@qds.dev/tools)
6
+
7
+ ## Overview
8
+
9
+ Building QDS packages requires specialized build-time transformations for icons, component patterns, and interactive playgrounds. Setting these up from scratch means dealing with AST parsing, virtual modules, and complex code generation. You need to transform icon JSX into inline SVG, hoist child props for polymorphic components, and extract prop metadata for documentation.
10
+
11
+ `@qds.dev/tools` provides ready-to-use Vite and Rolldown plugins that transform your code at build time. You get battle-tested transformations for icon management, asChild pattern implementation, and playground utilities without manual setup.
12
+
13
+ **What makes this useful:** These are the same build-time transformations QDS uses internally. If you're building Qwik libraries or need advanced code transformation utilities, you can reuse proven plugins rather than reinventing them.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @qds.dev/tools
19
+ ```
20
+
21
+ **Note:** This is primarily for internal/advanced use. Most users don't need to install directly. Use the [`create-qds`](https://www.npmjs.com/package/create-qds) CLI instead, which includes these tools preconfigured.
22
+
23
+ ## Quick Start
24
+
25
+ ### Simplest case: Using the icons plugin
26
+
27
+ ```typescript
28
+ // vite.config.ts
29
+ import { icons } from '@qds.dev/tools/vite';
30
+ import { defineConfig } from 'vite';
31
+
32
+ export default defineConfig({
33
+ plugins: [
34
+ icons()
35
+ ]
36
+ });
37
+ ```
38
+
39
+ ### Common pattern: Full plugin setup
40
+
41
+ ```typescript
42
+ // rolldown.config.ts
43
+ import { icons, asChild } from '@qds.dev/tools/vite';
44
+ import { defineConfig } from 'rolldown';
45
+
46
+ export default defineConfig({
47
+ plugins: [
48
+ icons(),
49
+ asChild(),
50
+ ]
51
+ });
52
+ ```
53
+
54
+ ## For Contributors
55
+
56
+ **If you're editing files in `libs/components/`, read this section first.**
57
+
58
+ When you edit a QDS component, several build plugins transform your code before it runs. Understanding these transformations prevents confusion when you see patterns like `<Lucide.Check />` or `asChild` in the codebase.
59
+
60
+ ### Quick Reference
61
+
62
+ | Pattern You See | Plugin | What Happens |
63
+ |-----------------|--------|--------------|
64
+ | `<Lucide.Check />`, `<Heroicons.Star />` | Icons | Becomes inline `<svg>` at build time |
65
+ | `<Button asChild><a href="...">` | AsChild | Child props hoisted to parent |
66
+ | `component$()` | Qwik Optimizer | Not in @qds.dev/tools (built into Qwik) |
67
+
68
+ ### When to Read More
69
+
70
+ - **Adding/changing icons?** See [Icons Plugin Transformation](#icons-plugin-jsx-to-svg-transformation)
71
+ - **Using asChild pattern?** See [AsChild Plugin Transformation](#aschild-plugin-prop-hoisting)
72
+ - **Build errors with icons?** Check that icon name matches [Iconify collection](https://icon-sets.iconify.design/)
73
+ - **Build errors with asChild?** Ensure exactly one child element exists
74
+
75
+ ### When You Edit a Component File
76
+
77
+ Here's what happens when you save a `.tsx` file in `libs/components/`:
78
+
79
+ #### 1. Vite Dev Server Detects Change
80
+
81
+ ```
82
+ File changed: libs/components/checkbox/checkbox-indicator.tsx
83
+ ```
84
+
85
+ #### 2. Icons Plugin Runs (if icons present)
86
+
87
+ **Triggered by:** Import from `@qds.dev/ui` like `Lucide`, `Heroicons`, `Tabler`
88
+
89
+ ```tsx
90
+ // Your code:
91
+ import { Lucide } from "@qds.dev/ui";
92
+ <Lucide.Check width={20} class="text-green-500" />
93
+
94
+ // After icons plugin:
95
+ import __qds_i_lucide_check from 'virtual:icons/lucide/check';
96
+ <svg width={20} class="text-green-500" height="1em" viewBox="0 0 24 24"
97
+ dangerouslySetInnerHTML={__qds_i_lucide_check} />
98
+ ```
99
+
100
+ **Why this matters:** The icon component you write doesn't exist at runtime. It's replaced with an actual `<svg>` element. If you're debugging icon issues, check the transformed output in browser devtools.
101
+
102
+ #### 3. AsChild Plugin Runs (if asChild present)
103
+
104
+ **Triggered by:** `asChild` prop on any JSX element with exactly one child
105
+
106
+ ```tsx
107
+ // Your code:
108
+ <Checkbox.Trigger asChild>
109
+ <label class="flex items-center">Toggle</label>
110
+ </Checkbox.Trigger>
111
+
112
+ // After asChild plugin:
113
+ <Checkbox.Trigger jsxType="label" movedProps={{ "class": "flex items-center" }}>
114
+ Toggle
115
+ </Checkbox.Trigger>
116
+ ```
117
+
118
+ **Why this matters:** The child element's wrapper is removed. Props move to `movedProps`, element type moves to `jsxType`. The Checkbox.Trigger component receives these and renders the correct element type.
119
+
120
+ #### 4. Qwik Optimizer Runs
121
+
122
+ After @qds.dev/tools plugins finish, Qwik's optimizer extracts `component$()` bodies into lazy-loaded chunks. This is not part of @qds.dev/tools but happens next in the build pipeline.
123
+
124
+ #### 5. Browser Receives Transformed Code
125
+
126
+ What you wrote is NOT what runs in the browser. The transformations above happen at build time. Use browser devtools to see the actual rendered output.
127
+
128
+ ### Common Debugging Scenarios
129
+
130
+ | Issue | Likely Cause | Solution |
131
+ |-------|--------------|----------|
132
+ | Icon doesn't render | Icon name not in collection | Check [Iconify](https://icon-sets.iconify.design/) for valid names |
133
+ | Icon has wrong viewBox | Icon collection uses different dimensions | Specify explicit `viewBox` prop |
134
+ | asChild throws error | More than one child element | Ensure exactly one JSX child (whitespace is ignored) |
135
+ | asChild props missing | Props not on direct child | Move props to the immediate child element |
136
+ | Build error: "asChild elements must have exactly one child" | Multiple children or JSX expression | Use single element child, not `{condition && <el>}` patterns |
137
+
138
+ ## How Plugins Transform Your Code
139
+
140
+ All transformations happen at build time. Your source code uses clean JSX patterns, and the plugins transform them into optimized runtime code.
141
+
142
+ ### Icons Plugin: JSX-to-SVG Transformation
143
+
144
+ Transforms icon JSX elements (`<Lucide.Check />`) into inline SVG at build time with virtual imports.
145
+
146
+ **Before transformation (your code):**
147
+ ```typescript
148
+ import { Lucide } from "@qds.dev/ui";
149
+
150
+ export default component$(() => {
151
+ return <Lucide.Check width={24} class="text-green-500" />;
152
+ });
153
+ ```
154
+
155
+ **After transformation (build output):**
156
+ ```typescript
157
+ import __qds_i_lucide_check from 'virtual:icons/lucide/check';
158
+
159
+ export default component$(() => {
160
+ return <svg width={24} class="text-green-500" height="1em" viewBox="0 0 24 24" dangerouslySetInnerHTML={__qds_i_lucide_check} />;
161
+ });
162
+ ```
163
+
164
+ **What this does:**
165
+ - Transforms icon JSX into inline SVG elements
166
+ - Generates virtual imports for icon data
167
+ - Adds default dimensions (1em) if not specified
168
+ - Preserves all props (class, aria attributes, etc.)
169
+ - Deduplicates icon imports (multiple uses = one import)
170
+
171
+ ### AsChild Plugin: Prop Hoisting
172
+
173
+ Hoists child element props to parent component when using asChild pattern for polymorphic rendering.
174
+
175
+ **Before transformation (your code):**
176
+ ```typescript
177
+ export const Link = component$(() => {
178
+ // Gives styles of button, but renders as the link child.
179
+ // Useful when sharing styles OR existing logic inside of button that needs to be shared.
180
+ return (
181
+ <Button asChild>
182
+ <a href="/test">Link</a>
183
+ </Button>
184
+ );
185
+ });
186
+ ```
187
+
188
+ **After transformation (build output):**
189
+ ```typescript
190
+ export const Link = component$(() => {
191
+ return (
192
+ <Button jsxType="a" movedProps={{ "href": "/test" }}>
193
+ Link
194
+ </Button>
195
+ );
196
+ });
197
+ ```
198
+
199
+ **What this does:**
200
+ - Removes child element wrapper
201
+ - Hoists child props to `movedProps` object
202
+ - Adds `jsxType` to identify element type
203
+ - Preserves grandchildren (text, nested elements)
204
+ - Supports conditional expressions (`isLink ? <a> : <button>`)
205
+
206
+ ## API
207
+
208
+ | Export | Description |
209
+ |--------|-------------|
210
+ | Main export (`@qds.dev/tools`) | Core type utilities (AsChildTypes) |
211
+ | `/vite` | Mostly re-exported from rolldown to stay modular: `icons`, `asChild`, `inlineAsset`, `minifyContentPlugin` |
212
+ | `/rolldown` | Rolldown plugins: `icons`, `asChild`, `inlineAsset`, `inlineCssPlugin`, `qwikRolldown` |
213
+ | `/playground` | Prop extraction and scenario injection plugins for component playgrounds |
214
+ | `/utils` | General utility functions for code transformation |
215
+
216
+ **Key plugins:**
217
+
218
+ ### `icons(options?)`
219
+ Transforms icon JSX elements into inline SVG with virtual imports.
220
+
221
+ **Options:**
222
+ - `packs?: Record<string, { iconifyPrefix: string }>` - Custom icon pack configuration
223
+ - `importSources?: string[]` - Additional import sources to scan (default: `["@qds.dev/ui"]`)
224
+ - `debug?: boolean` - Enable debug logging
225
+
226
+ **Default icon packs:** Lucide, Heroicons, Tabler, Hugeicons, MaterialSymbols, AkarIcons
227
+
228
+ ### `asChild(options?)`
229
+ Hoists child element props to parent component for polymorphic rendering.
230
+
231
+ **Options:**
232
+ - `debug?: boolean` - Enable debug logging
233
+
234
+ **Use case:** Implement polymorphic "as" pattern where components can render as different elements while preserving parent component logic.
235
+
236
+ ### `playground(options)`
237
+ Factory function returning two plugins for interactive component playgrounds.
238
+
239
+ **Returns:** `[propExtraction(options), scenarioInjection()]`
240
+
241
+ **Options:**
242
+ - `componentsDir: string` - Directory containing component files
243
+ - `outputDir: string` - Directory for generated metadata JSON
244
+ - `debug?: boolean` - Enable debug logging
245
+
246
+ **What it does:**
247
+ - Extracts TypeScript prop types from component files → JSON metadata
248
+ - Injects scenario imports into MDX playground files
249
+ - Enables interactive component demos with prop controls
250
+
251
+ ## Playground Utilities
252
+
253
+ The `/playground` export provides specialized plugins for building interactive component documentation.
254
+
255
+ ### Usage Example
256
+
257
+ ```typescript
258
+ // vite.config.ts
259
+ import { playground } from '@qds.dev/tools/playground';
260
+ import { defineConfig } from 'vite';
261
+
262
+ export default defineConfig({
263
+ plugins: [
264
+ playground({
265
+ componentsDir: 'libs/components/src',
266
+ outputDir: '.playground-metadata'
267
+ })
268
+ ]
269
+ });
270
+ ```
271
+
272
+ ### propExtraction Plugin
273
+
274
+ Extracts TypeScript prop types from component files and generates JSON metadata.
275
+
276
+ **Process:**
277
+ 1. Reads component `.tsx` files from `componentsDir`
278
+ 2. Parses TypeScript prop interfaces and types
279
+ 3. Extracts JSDoc comments for prop descriptions
280
+ 4. Generates JSON files in `outputDir`
281
+
282
+ **Output type:**
283
+ ```typescript
284
+ interface ComponentMetadata {
285
+ componentName: string;
286
+ pieces: ComponentPiece[];
287
+ }
288
+
289
+ interface ComponentPiece {
290
+ name: string;
291
+ props: PropType[];
292
+ jsdoc?: string;
293
+ }
294
+
295
+ interface PropType {
296
+ name: string;
297
+ type: string;
298
+ required: boolean;
299
+ description?: string;
300
+ }
301
+ ```
302
+
303
+ ### scenarioInjection Plugin
304
+
305
+ Transforms MDX files containing `<Playground />` components by auto-injecting scenario imports.
306
+
307
+ **Process:**
308
+ 1. Scans for `<Playground />` in MDX files
309
+ 2. Looks for adjacent `scenarios/` directory
310
+ 3. Auto-imports scenario components and source code
311
+ 4. Injects `scenarios` prop with component + code pairs
312
+
313
+ **Example MDX transformation:**
314
+
315
+ Before:
316
+ ```mdx
317
+ # Button Component
318
+
319
+ <Playground component={Button} />
320
+ ```
321
+
322
+ After (build time):
323
+ ```mdx
324
+ import * as Scenario1 from './scenarios/basic.tsx';
325
+ import Scenario1Code from './scenarios/basic.tsx?raw';
326
+
327
+ <Playground
328
+ component={Button}
329
+ scenarios={[
330
+ { component: Scenario1, code: Scenario1Code }
331
+ ]}
332
+ />
333
+ ```
334
+
335
+ ## Architecture
336
+
337
+ For package internals, dependency relationships, and design decisions, see [ARCHITECTURE.md](./ARCHITECTURE.md).
338
+
339
+ ## Why This Pattern?
340
+
341
+ **Separate tools package:** Build utilities shouldn't be bundled with runtime code. Separating build-time tooling keeps package sizes small and allows sharing configurations across multiple packages.
342
+
343
+ **Build-time transformation:** Instead of runtime overhead for icon resolution or asChild logic, transformations happen during build. Your production bundle contains optimized code with zero transformation cost.
344
+
345
+ **Plugin architecture:** Vite and Rolldown plugins are composable. You can mix QDS plugins with your own custom build logic. Each plugin handles one concern (icons, transformations, metadata extraction) so you only include what you need.
346
+
347
+ ## Related Packages
348
+
349
+ **Used by:**
350
+ - [`create-qds`](https://www.npmjs.com/package/create-qds) - CLI that scaffolds projects with these tools preconfigured
351
+ - All QDS packages - Internal build tooling for @qds.dev/ui, @qds.dev/motion, @qds.dev/code
352
+
353
+ **Complements:**
354
+ - [@qds.dev/ui](https://www.npmjs.com/package/@qds.dev/ui) - Uses these build tools for icon transformations
355
+ - [@qds.dev/base](https://www.npmjs.com/package/@qds.dev/base) - Uses these build tools for component patterns
356
+
357
+ **Build dependencies:**
358
+ - `vite` - Vite plugin compatibility
359
+ - `rolldown` - Rolldown plugin compatibility
360
+ - `oxc-*` - Fast JavaScript/TypeScript parsing and transformation
361
+
362
+ ## Known Limitations
363
+
364
+ - **Internal package:** API may change without major version bumps. This package prioritizes QDS internal needs over public API stability.
365
+
366
+ - **Advanced use only:** Most users should use `create-qds` CLI instead, which configures these tools automatically. Direct usage requires understanding Vite/Rolldown plugin architecture.
367
+
368
+ - **Node.js only:** These are build-time tools, not browser utilities. They run during your build process, not in the browser.
369
+
370
+ - **Qwik-specific optimizations:** While plugins work with any Vite/Rolldown project, some features (like AsChild transformations) are designed for Qwik component patterns.
371
+
372
+ ## Documentation
373
+
374
+ For usage within QDS projects, see the [QDS documentation](/docs). For general Qwik tooling questions, refer to [Qwik documentation](https://qwik.dev).
375
+
376
+ This is primarily internal tooling. For most use cases, start with `create-qds` which includes these tools preconfigured.
@@ -104,35 +104,6 @@ import { parseSync } from "oxc-parser";
104
104
  return { code: `export default '<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>';\n` };
105
105
  }
106
106
  }
107
- },
108
- async handleHotUpdate(ctx) {
109
- const fileId = ctx.file;
110
- if (fileId.endsWith(".tsx") || fileId.endsWith(".jsx") || fileId.endsWith(".mdx")) try {
111
- if (collectionLoader.getAvailableCollections().size === 0) collectionLoader.discoverCollections();
112
- const code = ctx.read?.();
113
- if (!code) return;
114
- const sourceCode = code instanceof Promise ? await code : code;
115
- if (fileId.endsWith(".mdx")) {
116
- if (importSources.some((source) => sourceCode.includes(`from "${source}"`) || sourceCode.includes(`from '${source}'`))) {
117
- debug(`Hot update detected for MDX ${fileId} - contains icon imports, forcing full reload`);
118
- ctx.server.ws.send({ type: "full-reload" });
119
- return [];
120
- }
121
- return;
122
- }
123
- const ast = parseAndValidateFile(sourceCode, fileId);
124
- if (!ast) return;
125
- const aliasToPack = resolveImportAliases(ast, importSources, collectionLoader.getAvailableCollections(), collectionNames, options.packs, debug);
126
- if (aliasToPack.size > 0) {
127
- debug(`Hot update detected for ${fileId} - contains ${aliasToPack.size} icon import(s), forcing full reload for proper transformation`);
128
- ctx.server.ws.send({ type: "full-reload" });
129
- return [];
130
- }
131
- } catch (error) {
132
- debug(`Error in handleHotUpdate for ${fileId}:`, error);
133
- ctx.server.ws.send({ type: "full-reload" });
134
- return [];
135
- }
136
107
  }
137
108
  };
138
109
  };
@@ -1,7 +1,6 @@
1
1
  import { asChild } from "../rolldown/as-child.qwik.mjs";
2
2
  import { icons } from "../rolldown/icons.qwik.mjs";
3
3
  import { inlineAsset } from "../rolldown/inline-asset.qwik.mjs";
4
- import "../rolldown/index.qwik.mjs";
5
4
  import { minifyContentPlugin } from "./minify-content.qwik.mjs";
6
5
 
7
6
  export { asChild, icons, inlineAsset, minifyContentPlugin };
@@ -1,4 +1,3 @@
1
- import type { HmrContext } from "vite";
2
1
  export type PacksMap = Record<string, {
3
2
  iconifyPrefix: string;
4
3
  sanitizeIcon?: (pascal: string) => string;
@@ -43,5 +42,4 @@ export declare const icons: (options?: IconsPluginOptions) => {
43
42
  code: string;
44
43
  } | null>;
45
44
  };
46
- handleHotUpdate(ctx: HmrContext): Promise<never[] | undefined>;
47
45
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qds.dev/tools",
3
- "version": "0.8.5",
3
+ "version": "0.9.7",
4
4
  "private": false,
5
5
  "description": "Tools and utilities for Qwik Design System",
6
6
  "type": "module",
@@ -34,6 +34,12 @@
34
34
  "types": "./lib-types/tools/playground/index.d.ts"
35
35
  }
36
36
  },
37
+ "scripts": {
38
+ "build": "pnpm run build.lib & pnpm run build.types && pnpm generate.icon.types",
39
+ "build.lib": "rolldown -c rolldown.config.ts",
40
+ "build.types": "tsc --emitDeclarationOnly --outDir ./lib-types",
41
+ "generate.icon.types": "node src/generate/icon-types.ts"
42
+ },
37
43
  "devDependencies": {
38
44
  "@iconify/types": "^2",
39
45
  "magic-string": "^0.30.17",
@@ -53,10 +59,9 @@
53
59
  "remark": "^15.0.1",
54
60
  "remark-mdx": "^3.1.1"
55
61
  },
56
- "scripts": {
57
- "build": "pnpm run build.lib & pnpm run build.types && pnpm generate.icon.types",
58
- "build.lib": "rolldown -c rolldown.config.ts",
59
- "build.types": "tsc --emitDeclarationOnly --outDir ./lib-types",
60
- "generate.icon.types": "node src/generate/icon-types.ts"
62
+ "sideEffects": false,
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "https://github.com/kunai-consulting/qwik-design-system"
61
66
  }
62
- }
67
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Kunai Consulting
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.