@canonical/code-standards 0.1.0 → 0.1.1
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/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- package/.github/workflows/ci.yml +40 -0
- package/biome.json +6 -0
- package/bun.lock +200 -0
- package/data/code.ttl +208 -167
- package/data/css.ttl +110 -91
- package/data/icons.ttl +186 -150
- package/data/packaging.ttl +428 -170
- package/data/react.ttl +306 -244
- package/data/rust.ttl +563 -467
- package/data/storybook.ttl +108 -90
- package/data/styling.ttl +40 -40
- package/data/tsdoc.ttl +111 -86
- package/data/turtle.ttl +89 -68
- package/definitions/CodeStandard.ttl +28 -20
- package/docs/code.md +37 -327
- package/docs/css.md +20 -19
- package/docs/icons.md +37 -41
- package/docs/index.md +2 -1
- package/docs/packaging.md +643 -0
- package/docs/react.md +54 -58
- package/docs/rust.md +92 -158
- package/docs/storybook.md +18 -20
- package/docs/styling.md +8 -8
- package/docs/tsdoc.md +16 -16
- package/docs/turtle.md +15 -15
- package/package.json +16 -2
- package/skills/add-standard/SKILL.md +83 -47
- package/src/scripts/generate-docs.ts +95 -13
- package/src/scripts/index.ts +4 -2
- package/tsconfig.json +8 -0
package/docs/code.md
CHANGED
|
@@ -8,7 +8,7 @@ Experimental APIs must be marked with the @experimental JSDoc tag in type defini
|
|
|
8
8
|
|
|
9
9
|
### Do
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Mark an experimental interface with a clear @experimental JSDoc tag and description.
|
|
12
12
|
```typescript
|
|
13
13
|
/**
|
|
14
14
|
* Configuration for the data processing pipeline.
|
|
@@ -20,7 +20,7 @@ interface PipelineConfig {
|
|
|
20
20
|
}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
Add an @experimental tag to a specific property with context about its experimental status.
|
|
24
24
|
```typescript
|
|
25
25
|
interface PipelineConfig {
|
|
26
26
|
/**
|
|
@@ -31,7 +31,7 @@ interface PipelineConfig {
|
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
Describe the experimental status and any future plans for the API.
|
|
35
35
|
```typescript
|
|
36
36
|
interface CacheConfig {
|
|
37
37
|
/**
|
|
@@ -44,7 +44,7 @@ interface CacheConfig {
|
|
|
44
44
|
|
|
45
45
|
### Don't
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
Use the @experimental tag without an explanation.
|
|
48
48
|
```typescript
|
|
49
49
|
interface ProcessorConfig {
|
|
50
50
|
/** @experimental */ // Bad: No context provided.
|
|
@@ -52,7 +52,7 @@ interface ProcessorConfig {
|
|
|
52
52
|
}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Use the @experimental tag without describing what is experimental.
|
|
56
56
|
```typescript
|
|
57
57
|
/**
|
|
58
58
|
* @experimental // Bad: No description of what is experimental.
|
|
@@ -70,7 +70,7 @@ Domain constants must be collected in a `constants.ts` file using named exports
|
|
|
70
70
|
|
|
71
71
|
### Do
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
Create a `constants.ts` file with named exports at the domain level
|
|
74
74
|
```typescript
|
|
75
75
|
// features/pricing/constants.ts
|
|
76
76
|
export const DEFAULT_CURRENCY = "USD";
|
|
@@ -78,7 +78,7 @@ export const TAX_RATE = 0.21;
|
|
|
78
78
|
export const MAX_DISCOUNT_PERCENT = 50;
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
Colocate `constants.ts` inside the domain folder it belongs to
|
|
82
82
|
```
|
|
83
83
|
features/
|
|
84
84
|
├── pricing/
|
|
@@ -91,13 +91,13 @@ features/
|
|
|
91
91
|
│ └── index.ts
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
Import constants directly from the owning domain's `constants.ts`
|
|
95
95
|
```typescript
|
|
96
96
|
// features/pricing/calculatePrice.ts
|
|
97
97
|
import { TAX_RATE, DEFAULT_CURRENCY } from "./constants.js";
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
Keep all exports in `constants.ts` as named exports with the same shape (plain values)
|
|
101
101
|
```typescript
|
|
102
102
|
// constants.ts
|
|
103
103
|
export const RECONNECT_INTERVAL_MS = 5000;
|
|
@@ -105,7 +105,7 @@ export const MAX_RETRIES = 3;
|
|
|
105
105
|
export const DEFAULT_TIMEOUT_MS = 30_000;
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
Keep single-use constants inline in the file that uses them
|
|
109
109
|
```typescript
|
|
110
110
|
// features/pricing/applyDiscount.ts
|
|
111
111
|
const MAX_DISCOUNT_PERCENT = 50;
|
|
@@ -118,7 +118,7 @@ export default function applyDiscount(price: number, rate: number): number {
|
|
|
118
118
|
|
|
119
119
|
### Don't
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
Use a default export in a constants file
|
|
122
122
|
```typescript
|
|
123
123
|
// Bad: default export forces consumers to name the import
|
|
124
124
|
export default {
|
|
@@ -127,7 +127,7 @@ export default {
|
|
|
127
127
|
};
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
|
|
130
|
+
Scatter constants across unrelated files instead of collecting them in `constants.ts`
|
|
131
131
|
```typescript
|
|
132
132
|
// Bad: constants buried inside implementation files
|
|
133
133
|
// calculatePrice.ts
|
|
@@ -136,7 +136,7 @@ const calculatePrice = (base: number) => base * (1 + TAX_RATE);
|
|
|
136
136
|
export default calculatePrice;
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
|
|
139
|
+
Place constants far from the domain that owns them
|
|
140
140
|
```typescript
|
|
141
141
|
// Bad: pricing constants in a top-level shared file
|
|
142
142
|
// src/constants.ts
|
|
@@ -145,7 +145,7 @@ export const MAX_RETRIES = 3; // Belongs in network/
|
|
|
145
145
|
export const SESSION_TTL_MS = 3600; // Belongs in auth/
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
|
|
148
|
+
Mix constants with functions, types, or other non-constant exports
|
|
149
149
|
```typescript
|
|
150
150
|
// Bad: constants.ts should only contain constant values
|
|
151
151
|
export const MAX_RETRIES = 3;
|
|
@@ -155,149 +155,20 @@ export type RetryConfig = { max: number };
|
|
|
155
155
|
|
|
156
156
|
---
|
|
157
157
|
|
|
158
|
-
## code/export/barrel-public-api
|
|
159
|
-
|
|
160
|
-
Barrel files must exist only to define a public API surface. Internal implementation code must import concrete modules directly rather than routing through local barrels. Public barrels may exist at package entry points or at explicitly public subdomain boundaries.
|
|
161
|
-
|
|
162
|
-
### Do
|
|
163
|
-
|
|
164
|
-
(Do) Use a barrel for a package entry point or explicit public API:
|
|
165
|
-
```typescript
|
|
166
|
-
// src/index.ts
|
|
167
|
-
export { Button } from "./components/Button.js";
|
|
168
|
-
export type { ButtonProps } from "./components/Button.types.js";
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
(Do) Import internal implementation code from the owning file:
|
|
172
|
-
```typescript
|
|
173
|
-
// src/build/buildTheme.ts
|
|
174
|
-
import { computeDeltas } from "./computeDeltas.js";
|
|
175
|
-
import { recoverPrimitiveRef } from "./recoverPrimitiveRef.js";
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
(Do) Keep compatibility barrels thin and documented as public facades:
|
|
179
|
-
```typescript
|
|
180
|
-
/** Public compatibility surface. */
|
|
181
|
-
export { computeDeltas } from "./computeDeltas.js";
|
|
182
|
-
export { formatDelta } from "./formatDelta.js";
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### Don't
|
|
186
|
-
|
|
187
|
-
(Don't) Route internal code through a sibling barrel just because it exists:
|
|
188
|
-
```typescript
|
|
189
|
-
// Bad: internal implementation depends on a local barrel
|
|
190
|
-
import { computeDeltas, recoverPrimitiveRef } from "./index.js";
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
(Don't) Hide domain ownership behind convenience barrels:
|
|
194
|
-
```typescript
|
|
195
|
-
// Bad: types appear to belong to context.ts instead of their owning domains
|
|
196
|
-
import type { Artifact, CSSNode, OverlayToken } from "./context.js";
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
(Don't) Create folder barrels for implementation-only directories with no public API contract:
|
|
200
|
-
```typescript
|
|
201
|
-
// build/helpers/index.ts
|
|
202
|
-
export * from "./parse.js";
|
|
203
|
-
export * from "./format.js";
|
|
204
|
-
export * from "./walk.js";
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## code/export/shape
|
|
210
|
-
|
|
211
|
-
Files must use either a single default export or multiple named exports. When using multiple named exports, all exports must have the same type or shape.
|
|
212
|
-
|
|
213
|
-
### Do
|
|
214
|
-
|
|
215
|
-
(Do) Use a single default export for files implementing a single component or function:
|
|
216
|
-
```typescript
|
|
217
|
-
// ComponentName.tsx
|
|
218
|
-
export default ComponentName;
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
(Do) Name atomic function files after the function they export:
|
|
222
|
-
```typescript
|
|
223
|
-
// assignElement.ts
|
|
224
|
-
export default function assignElement(target: Element, source: Partial<Element>) {
|
|
225
|
-
// ...
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// formatCurrency.ts
|
|
229
|
-
export default function formatCurrency(value: number) {
|
|
230
|
-
// ...
|
|
231
|
-
}
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
(Do) Use multiple named exports for files providing a public API or a collection of related items, and ensure all exports have the same type:
|
|
235
|
-
```typescript
|
|
236
|
-
// index.ts
|
|
237
|
-
export { ComponentA, ComponentB };
|
|
238
|
-
|
|
239
|
-
// types.ts
|
|
240
|
-
export type TypeA = { ... };
|
|
241
|
-
export type TypeB = { ... };
|
|
242
|
-
|
|
243
|
-
// Consistent export shape:
|
|
244
|
-
export const myFuncA = (value: string) => {};
|
|
245
|
-
export const myFuncB = (value: string) => {};
|
|
246
|
-
export const myFuncC = (value: string) => {};
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
### Don't
|
|
250
|
-
|
|
251
|
-
(Don't) Mix default and unrelated named exports in a way that confuses the file's purpose:
|
|
252
|
-
```typescript
|
|
253
|
-
export default ComponentName;
|
|
254
|
-
export const helper = () => {};
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
(Don't) Name an atomic function file with a name that doesn't match its exported function:
|
|
258
|
-
```typescript
|
|
259
|
-
// helpers.ts — wrong: generic name instead of function name
|
|
260
|
-
export default function assignElement(target: Element, source: Partial<Element>) {
|
|
261
|
-
// ...
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// utils.ts — wrong: generic name instead of function name
|
|
265
|
-
export default function formatCurrency(value: number) {
|
|
266
|
-
// ...
|
|
267
|
-
}
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
(Don't) Provide multiple unrelated exports from a file meant for a single domain:
|
|
271
|
-
```typescript
|
|
272
|
-
export default debounce;
|
|
273
|
-
export const throttle = () => {};
|
|
274
|
-
export const logger = () => {};
|
|
275
|
-
```
|
|
276
|
-
|
|
277
|
-
(Don't) Export objects of different types or shapes from the same file:
|
|
278
|
-
```typescript
|
|
279
|
-
export const transformer = (value: string) => {};
|
|
280
|
-
export const reducer = (map: string[]) => {};
|
|
281
|
-
class ABC {}
|
|
282
|
-
export { ABC };
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
---
|
|
286
|
-
|
|
287
158
|
## code/function/composition
|
|
288
159
|
|
|
289
160
|
Each function must handle exactly one specific task. Complex operations must be broken down into smaller, single-purpose functions.
|
|
290
161
|
|
|
291
162
|
### Do
|
|
292
163
|
|
|
293
|
-
|
|
164
|
+
Define functions with a single, clear responsibility.
|
|
294
165
|
```typescript
|
|
295
166
|
const validateEmail = (email: string): boolean => {
|
|
296
167
|
return /^[^@]+@[^@]+\\.[^@]+$/.test(email);
|
|
297
168
|
};
|
|
298
169
|
```
|
|
299
170
|
|
|
300
|
-
|
|
171
|
+
Compose single-purpose functions to build complex logic.
|
|
301
172
|
```typescript
|
|
302
173
|
const validatePassword = (password: string): boolean => {
|
|
303
174
|
return password.length >= 8;
|
|
@@ -310,7 +181,7 @@ const validateForm = (email: string, password:string): boolean => {
|
|
|
310
181
|
|
|
311
182
|
### Don't
|
|
312
183
|
|
|
313
|
-
|
|
184
|
+
Mix multiple responsibilities in a single function.
|
|
314
185
|
```typescript
|
|
315
186
|
const processUserData = (user: any) => {
|
|
316
187
|
// Validates user data
|
|
@@ -328,7 +199,7 @@ Functions must be defined as close to their broadest point of use as possible. S
|
|
|
328
199
|
|
|
329
200
|
### Do
|
|
330
201
|
|
|
331
|
-
|
|
202
|
+
Place functions in the file or module where they are most broadly used. If a function is shared across multiple components or modules, place it in a common utility location.
|
|
332
203
|
```typescript
|
|
333
204
|
// utils/math.ts
|
|
334
205
|
export function calculateSum(a: number, b: number): number {
|
|
@@ -339,21 +210,11 @@ export function calculateSum(a: number, b: number): number {
|
|
|
339
210
|
import { calculateSum } from './utils/math.js';
|
|
340
211
|
|
|
341
212
|
// For single-use functions, keep them close to their usage:
|
|
342
|
-
```
|
|
343
|
-
// services/UserService.ts
|
|
344
|
-
const formatUserName = (user: User) => `${user.firstName} ${user.lastName}`;
|
|
345
|
-
|
|
346
|
-
const getProfileData = (user: User) => {
|
|
347
|
-
const formattedName = formatUserName(user);
|
|
348
|
-
return {
|
|
349
|
-
...user,
|
|
350
|
-
formattedName,
|
|
351
|
-
};
|
|
352
|
-
};
|
|
213
|
+
```
|
|
353
214
|
|
|
354
215
|
### Don't
|
|
355
216
|
|
|
356
|
-
|
|
217
|
+
Place functions far from where they are used, or in a generic location if only used in one place.
|
|
357
218
|
```typescript
|
|
358
219
|
// utils/validators.ts
|
|
359
220
|
export const validateUser = (user: User) => { ... }; // Only used in one component
|
|
@@ -362,9 +223,7 @@ export const validateUser = (user: User) => { ... }; // Only used in one compone
|
|
|
362
223
|
import { validateUser } from '../utils/validators.js'; // Less ideal if only used here
|
|
363
224
|
|
|
364
225
|
// Don't mix unrelated functions in a single file:
|
|
365
|
-
```
|
|
366
|
-
export const mathFunc = () => {};
|
|
367
|
-
export const stringFunc = () => {};
|
|
226
|
+
```
|
|
368
227
|
|
|
369
228
|
---
|
|
370
229
|
|
|
@@ -374,14 +233,14 @@ Functions should be pure where possible. A pure function returns the same output
|
|
|
374
233
|
|
|
375
234
|
### Do
|
|
376
235
|
|
|
377
|
-
|
|
236
|
+
Create pure functions that rely only on their inputs to compute the output.
|
|
378
237
|
```typescript
|
|
379
238
|
const calculatePrice = (basePrice: number, taxRate: number): number => {
|
|
380
239
|
return basePrice * (1 + taxRate);
|
|
381
240
|
};
|
|
382
241
|
```
|
|
383
242
|
|
|
384
|
-
|
|
243
|
+
Annotate and document impure functions that perform I/O or modify external state, explaining why impurity is necessary using @note.
|
|
385
244
|
```typescript
|
|
386
245
|
/**
|
|
387
246
|
* Writes data to the file system.
|
|
@@ -394,7 +253,7 @@ function writeOutputToFile(data: string, path: string) {
|
|
|
394
253
|
|
|
395
254
|
### Don't
|
|
396
255
|
|
|
397
|
-
|
|
256
|
+
Create impure functions without annotation or documentation.
|
|
398
257
|
```typescript
|
|
399
258
|
let total = 0;
|
|
400
259
|
const addToTotal = (value: number) => {
|
|
@@ -402,7 +261,7 @@ const addToTotal = (value: number) => {
|
|
|
402
261
|
};
|
|
403
262
|
```
|
|
404
263
|
|
|
405
|
-
|
|
264
|
+
Hide side effects in functions that appear pure.
|
|
406
265
|
```typescript
|
|
407
266
|
function getConfig() {
|
|
408
267
|
// Reads from disk without annotation
|
|
@@ -418,7 +277,7 @@ All function and method names must start with a verb that describes the action t
|
|
|
418
277
|
|
|
419
278
|
### Do
|
|
420
279
|
|
|
421
|
-
|
|
280
|
+
Start function names with an action verb
|
|
422
281
|
```typescript
|
|
423
282
|
const findPathToModule = (name: string): string => { /* ... */ };
|
|
424
283
|
const fetchDataForUser = (userId: string): Promise<User> => { /* ... */ };
|
|
@@ -428,7 +287,7 @@ const isValidEmail = (email: string): boolean => { /* ... */ };
|
|
|
428
287
|
const hasPermission = (user: User, action: string): boolean => { /* ... */ };
|
|
429
288
|
```
|
|
430
289
|
|
|
431
|
-
|
|
290
|
+
Use descriptive verb prefixes that convey the operation
|
|
432
291
|
```typescript
|
|
433
292
|
// Retrieval: get, fetch, find, resolve, load, read
|
|
434
293
|
const getUser = (id: string): User => { /* ... */ };
|
|
@@ -455,7 +314,7 @@ const buildQuery = (params: Params): string => { /* ... */ };
|
|
|
455
314
|
|
|
456
315
|
### Don't
|
|
457
316
|
|
|
458
|
-
|
|
317
|
+
Name functions as nouns or noun phrases — they read like values, not actions
|
|
459
318
|
```typescript
|
|
460
319
|
// Bad: reads like a variable, not a function
|
|
461
320
|
const pathToModule = (name: string): string => { /* ... */ };
|
|
@@ -468,7 +327,7 @@ const userPermissions = (user: User): Permission[] => { /* ... */ };
|
|
|
468
327
|
// Good: getPermissions or listPermissions
|
|
469
328
|
```
|
|
470
329
|
|
|
471
|
-
|
|
330
|
+
Use vague or non-descriptive verbs that don't convey meaning
|
|
472
331
|
```typescript
|
|
473
332
|
// Bad: "do" and "process" are too vague on their own
|
|
474
333
|
const doEmail = (email: string) => { /* ... */ };
|
|
@@ -480,162 +339,13 @@ const processUser = (user: User) => { /* ... */ };
|
|
|
480
339
|
|
|
481
340
|
---
|
|
482
341
|
|
|
483
|
-
## code/naming/single-export-file
|
|
484
|
-
|
|
485
|
-
Files that contain a single export must be named after that export. This applies to functions, classes, types, constants, and any other single-export module. The file name must match the exported identifier exactly, preserving its casing (camelCase for functions/variables, PascalCase for classes/types/components).
|
|
486
|
-
|
|
487
|
-
### Do
|
|
488
|
-
|
|
489
|
-
(Do) Name files after the single function they export:
|
|
490
|
-
```typescript
|
|
491
|
-
// isAccepted.ts
|
|
492
|
-
export const isAccepted = (status: string): boolean => {
|
|
493
|
-
return status === "accepted";
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
// formatCurrency.ts
|
|
497
|
-
export default function formatCurrency(value: number): string {
|
|
498
|
-
return `$${value.toFixed(2)}`;
|
|
499
|
-
}
|
|
500
|
-
```
|
|
501
|
-
|
|
502
|
-
(Do) Name files after the single type or interface they export:
|
|
503
|
-
```typescript
|
|
504
|
-
// ConnectionConfig.ts
|
|
505
|
-
export interface ConnectionConfig {
|
|
506
|
-
host: string;
|
|
507
|
-
port: number;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
// UserRole.ts
|
|
511
|
-
export type UserRole = "admin" | "editor" | "viewer";
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
(Do) Name files after the single class they export:
|
|
515
|
-
```typescript
|
|
516
|
-
// EventEmitter.ts
|
|
517
|
-
export class EventEmitter {
|
|
518
|
-
// ...
|
|
519
|
-
}
|
|
520
|
-
```
|
|
521
|
-
|
|
522
|
-
(Do) Name files after the single constant they export:
|
|
523
|
-
```typescript
|
|
524
|
-
// DEFAULT_TIMEOUT.ts
|
|
525
|
-
export const DEFAULT_TIMEOUT = 5000;
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
### Don't
|
|
529
|
-
|
|
530
|
-
(Don't) Use generic names like `helpers.ts`, `utils.ts`, or `types.ts` for files with a single export:
|
|
531
|
-
```typescript
|
|
532
|
-
// helpers.ts — wrong: should be isAccepted.ts
|
|
533
|
-
export const isAccepted = (status: string): boolean => {
|
|
534
|
-
return status === "accepted";
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
// utils.ts — wrong: should be formatCurrency.ts
|
|
538
|
-
export default function formatCurrency(value: number): string {
|
|
539
|
-
return `$${value.toFixed(2)}`;
|
|
540
|
-
}
|
|
541
|
-
```
|
|
542
|
-
|
|
543
|
-
(Don't) Use names that describe the domain instead of the export:
|
|
544
|
-
```typescript
|
|
545
|
-
// validation.ts — wrong: should be isAccepted.ts
|
|
546
|
-
export const isAccepted = (status: string): boolean => {
|
|
547
|
-
return status === "accepted";
|
|
548
|
-
};
|
|
549
|
-
|
|
550
|
-
// network.ts — wrong: should be ConnectionConfig.ts
|
|
551
|
-
export interface ConnectionConfig {
|
|
552
|
-
host: string;
|
|
553
|
-
port: number;
|
|
554
|
-
}
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
---
|
|
558
|
-
|
|
559
|
-
## code/package/lib-folder
|
|
560
|
-
|
|
561
|
-
Packages that export reusable logic (components, utilities, hooks, types) must organize their exportable code in a `lib` folder within the `src` directory. This convention ensures consistency across the monorepo and clearly identifies code that is part of the package's public API.
|
|
562
|
-
|
|
563
|
-
### Do
|
|
564
|
-
|
|
565
|
-
(Do) Place all reusable/exportable code in `src/lib/`:
|
|
566
|
-
```
|
|
567
|
-
packages/my-package/
|
|
568
|
-
├── src/
|
|
569
|
-
│ ├── lib/
|
|
570
|
-
│ │ ├── Button/
|
|
571
|
-
│ │ │ ├── Button.tsx
|
|
572
|
-
│ │ │ ├── Button.tests.tsx
|
|
573
|
-
│ │ │ ├── types.ts
|
|
574
|
-
│ │ │ └── index.ts
|
|
575
|
-
│ │ ├── hooks/
|
|
576
|
-
│ │ │ └── useToggle.ts
|
|
577
|
-
│ │ ├── types/
|
|
578
|
-
│ │ │ └── index.ts
|
|
579
|
-
│ │ └── index.ts
|
|
580
|
-
│ ├── storybook/ # Storybook-specific files (not exported)
|
|
581
|
-
│ └── index.ts # Re-exports from lib
|
|
582
|
-
└── package.json
|
|
583
|
-
```
|
|
584
|
-
|
|
585
|
-
(Do) Re-export from the lib folder in the package entry point:
|
|
586
|
-
```typescript
|
|
587
|
-
// src/index.ts
|
|
588
|
-
export * from "./lib/index.js";
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
(Do) Use the lib folder for components, hooks, utilities, types, and any other code meant to be consumed by package users:
|
|
592
|
-
```typescript
|
|
593
|
-
// src/lib/index.ts
|
|
594
|
-
export * from "./Button/index.js";
|
|
595
|
-
export * from "./hooks/index.js";
|
|
596
|
-
export type * from "./types/index.js";
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
### Don't
|
|
600
|
-
|
|
601
|
-
(Don't) Use alternative folder names like `ui`, `components`, or `utils` for exportable code at the package level:
|
|
602
|
-
```
|
|
603
|
-
// Bad: Using 'ui' instead of 'lib'
|
|
604
|
-
packages/my-package/
|
|
605
|
-
├── src/
|
|
606
|
-
│ ├── ui/ # Wrong: should be 'lib'
|
|
607
|
-
│ │ └── Button/
|
|
608
|
-
│ └── index.ts
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
(Don't) Mix exportable and non-exportable code at the same level:
|
|
612
|
-
```
|
|
613
|
-
// Bad: Mixing concerns
|
|
614
|
-
packages/my-package/
|
|
615
|
-
├── src/
|
|
616
|
-
│ ├── Button/ # Component mixed with...
|
|
617
|
-
│ ├── storybook/ # ...non-exportable storybook config
|
|
618
|
-
│ └── test-utils/ # ...and test utilities
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
(Don't) Create deeply nested lib-like structures; keep lib at the top level of src:
|
|
622
|
-
```
|
|
623
|
-
// Bad: Nested lib folders
|
|
624
|
-
packages/my-package/
|
|
625
|
-
├── src/
|
|
626
|
-
│ ├── features/
|
|
627
|
-
│ │ └── lib/ # Wrong: lib should be at src level
|
|
628
|
-
```
|
|
629
|
-
|
|
630
|
-
---
|
|
631
|
-
|
|
632
342
|
## code/types/file
|
|
633
343
|
|
|
634
344
|
Reusable domain types must be collected in a `types.ts` file using named exports (no default export), colocated at the level of the domain that owns them. Types that are only used in a single file may be declared inline in that file instead of being extracted to `types.ts`.
|
|
635
345
|
|
|
636
346
|
### Do
|
|
637
347
|
|
|
638
|
-
|
|
348
|
+
Create a `types.ts` file with named exports for types shared across the domain
|
|
639
349
|
```typescript
|
|
640
350
|
// features/pricing/types.ts
|
|
641
351
|
export type Currency = "USD" | "EUR" | "GBP";
|
|
@@ -647,7 +357,7 @@ export interface PriceBreakdown {
|
|
|
647
357
|
export type DiscountStrategy = "percentage" | "fixed";
|
|
648
358
|
```
|
|
649
359
|
|
|
650
|
-
|
|
360
|
+
Colocate `types.ts` inside the domain folder it belongs to
|
|
651
361
|
```
|
|
652
362
|
features/
|
|
653
363
|
├── pricing/
|
|
@@ -661,7 +371,7 @@ features/
|
|
|
661
371
|
│ └── index.ts
|
|
662
372
|
```
|
|
663
373
|
|
|
664
|
-
|
|
374
|
+
Keep single-use types inline in the file that uses them
|
|
665
375
|
```typescript
|
|
666
376
|
// features/pricing/applyDiscount.ts
|
|
667
377
|
type DiscountResult = { discounted: number; savings: number };
|
|
@@ -672,7 +382,7 @@ export default function applyDiscount(price: number, rate: number): DiscountResu
|
|
|
672
382
|
}
|
|
673
383
|
```
|
|
674
384
|
|
|
675
|
-
|
|
385
|
+
Import shared types from the owning domain's `types.ts`
|
|
676
386
|
```typescript
|
|
677
387
|
// features/pricing/calculatePrice.ts
|
|
678
388
|
import type { Currency, PriceBreakdown } from "./types.js";
|
|
@@ -680,7 +390,7 @@ import type { Currency, PriceBreakdown } from "./types.js";
|
|
|
680
390
|
|
|
681
391
|
### Don't
|
|
682
392
|
|
|
683
|
-
|
|
393
|
+
Use a default export in a types file
|
|
684
394
|
```typescript
|
|
685
395
|
// Bad: default export for a collection of types
|
|
686
396
|
export default interface PriceBreakdown {
|
|
@@ -690,7 +400,7 @@ export default interface PriceBreakdown {
|
|
|
690
400
|
}
|
|
691
401
|
```
|
|
692
402
|
|
|
693
|
-
|
|
403
|
+
Extract single-use types to `types.ts` when they are only used in one file
|
|
694
404
|
```typescript
|
|
695
405
|
// Bad: types.ts contains a type only used in applyDiscount.ts
|
|
696
406
|
// types.ts
|
|
@@ -700,7 +410,7 @@ export type DiscountResult = { discounted: number; savings: number };
|
|
|
700
410
|
import type { DiscountResult } from "./types.js"; // Unnecessary indirection
|
|
701
411
|
```
|
|
702
412
|
|
|
703
|
-
|
|
413
|
+
Place types far from the domain that owns them
|
|
704
414
|
```typescript
|
|
705
415
|
// Bad: all types dumped in a top-level shared file
|
|
706
416
|
// src/types.ts
|
|
@@ -709,7 +419,7 @@ export type AuthToken = { jwt: string }; // Belongs in auth/
|
|
|
709
419
|
export type RetryPolicy = { max: number }; // Belongs in network/
|
|
710
420
|
```
|
|
711
421
|
|
|
712
|
-
|
|
422
|
+
Mix types with functions, constants, or other non-type exports
|
|
713
423
|
```typescript
|
|
714
424
|
// Bad: types.ts should only contain type declarations
|
|
715
425
|
export type Currency = "USD" | "EUR" | "GBP";
|