@zigrivers/scaffold 3.7.0 → 3.9.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.
- package/README.md +113 -8
- package/content/knowledge/browser-extension/browser-extension-architecture.md +195 -0
- package/content/knowledge/browser-extension/browser-extension-content-scripts.md +264 -0
- package/content/knowledge/browser-extension/browser-extension-conventions.md +156 -0
- package/content/knowledge/browser-extension/browser-extension-cross-browser.md +229 -0
- package/content/knowledge/browser-extension/browser-extension-dev-environment.md +247 -0
- package/content/knowledge/browser-extension/browser-extension-manifest.md +220 -0
- package/content/knowledge/browser-extension/browser-extension-project-structure.md +183 -0
- package/content/knowledge/browser-extension/browser-extension-requirements.md +107 -0
- package/content/knowledge/browser-extension/browser-extension-security.md +202 -0
- package/content/knowledge/browser-extension/browser-extension-service-workers.md +265 -0
- package/content/knowledge/browser-extension/browser-extension-store-submission.md +155 -0
- package/content/knowledge/browser-extension/browser-extension-testing.md +270 -0
- package/content/knowledge/data-pipeline/data-pipeline-architecture.md +175 -0
- package/content/knowledge/data-pipeline/data-pipeline-batch-patterns.md +263 -0
- package/content/knowledge/data-pipeline/data-pipeline-conventions.md +176 -0
- package/content/knowledge/data-pipeline/data-pipeline-dev-environment.md +350 -0
- package/content/knowledge/data-pipeline/data-pipeline-orchestration.md +291 -0
- package/content/knowledge/data-pipeline/data-pipeline-project-structure.md +257 -0
- package/content/knowledge/data-pipeline/data-pipeline-quality.md +324 -0
- package/content/knowledge/data-pipeline/data-pipeline-requirements.md +145 -0
- package/content/knowledge/data-pipeline/data-pipeline-schema-management.md +295 -0
- package/content/knowledge/data-pipeline/data-pipeline-security.md +326 -0
- package/content/knowledge/data-pipeline/data-pipeline-streaming-patterns.md +280 -0
- package/content/knowledge/data-pipeline/data-pipeline-testing.md +406 -0
- package/content/knowledge/library/library-api-design.md +306 -0
- package/content/knowledge/library/library-architecture.md +247 -0
- package/content/knowledge/library/library-bundling.md +244 -0
- package/content/knowledge/library/library-conventions.md +229 -0
- package/content/knowledge/library/library-dev-environment.md +220 -0
- package/content/knowledge/library/library-documentation.md +300 -0
- package/content/knowledge/library/library-project-structure.md +237 -0
- package/content/knowledge/library/library-requirements.md +173 -0
- package/content/knowledge/library/library-security.md +257 -0
- package/content/knowledge/library/library-testing.md +319 -0
- package/content/knowledge/library/library-type-definitions.md +284 -0
- package/content/knowledge/library/library-versioning.md +300 -0
- package/content/knowledge/ml/ml-architecture.md +172 -0
- package/content/knowledge/ml/ml-conventions.md +209 -0
- package/content/knowledge/ml/ml-dev-environment.md +299 -0
- package/content/knowledge/ml/ml-experiment-tracking.md +285 -0
- package/content/knowledge/ml/ml-model-evaluation.md +256 -0
- package/content/knowledge/ml/ml-observability.md +253 -0
- package/content/knowledge/ml/ml-project-structure.md +216 -0
- package/content/knowledge/ml/ml-requirements.md +138 -0
- package/content/knowledge/ml/ml-security.md +188 -0
- package/content/knowledge/ml/ml-serving-patterns.md +243 -0
- package/content/knowledge/ml/ml-testing.md +301 -0
- package/content/knowledge/ml/ml-training-patterns.md +269 -0
- package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
- package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
- package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
- package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
- package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
- package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
- package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
- package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
- package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
- package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
- package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
- package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
- package/content/methodology/browser-extension-overlay.yml +82 -0
- package/content/methodology/data-pipeline-overlay.yml +70 -0
- package/content/methodology/library-overlay.yml +67 -0
- package/content/methodology/ml-overlay.yml +70 -0
- package/content/methodology/mobile-app-overlay.yml +71 -0
- package/dist/cli/commands/init.d.ts +22 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +202 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +190 -0
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/config/schema.d.ts +1456 -80
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +87 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/config/schema.test.js +312 -3
- package/dist/config/schema.test.js.map +1 -1
- package/dist/core/assembly/overlay-loader.test.js +55 -0
- package/dist/core/assembly/overlay-loader.test.js.map +1 -1
- package/dist/e2e/project-type-overlays.test.d.ts +2 -1
- package/dist/e2e/project-type-overlays.test.d.ts.map +1 -1
- package/dist/e2e/project-type-overlays.test.js +780 -14
- package/dist/e2e/project-type-overlays.test.js.map +1 -1
- package/dist/types/config.d.ts +16 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/wizard/questions.d.ts +28 -1
- package/dist/wizard/questions.d.ts.map +1 -1
- package/dist/wizard/questions.js +127 -1
- package/dist/wizard/questions.js.map +1 -1
- package/dist/wizard/questions.test.js +224 -4
- package/dist/wizard/questions.test.js.map +1 -1
- package/dist/wizard/wizard.d.ts +22 -0
- package/dist/wizard/wizard.d.ts.map +1 -1
- package/dist/wizard/wizard.js +28 -1
- package/dist/wizard/wizard.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: library-bundling
|
|
3
|
+
description: ESM/CJS dual publishing, package.json exports map, bundler configuration, and tree-shaking verification for libraries
|
|
4
|
+
topics: [library, bundling, esm, cjs, dual-publishing, exports-map, tree-shaking, tsup, rollup]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Library bundling solves the problem of serving multiple module systems from one codebase. The JavaScript ecosystem is mid-transition from CommonJS to ES modules, and libraries must serve both until the transition completes. Getting bundling wrong produces libraries that fail to import in certain environments, cause dual-package hazards (two instances of the same library loaded simultaneously), or defeat tree-shaking and inflate consumer bundle sizes.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Use a bundler (tsup or rollup) rather than raw TypeScript compilation for libraries that need dual ESM/CJS output. The `package.json` exports map is the canonical module resolution contract — define it precisely with condition precedence (types before default, import before require). Set `"sideEffects": false` when true to enable aggressive tree-shaking. Test module resolution in real consumer environments, not just in your build output. ESM-only is acceptable if your minimum supported environment supports it; document this clearly.
|
|
12
|
+
|
|
13
|
+
Key bundling decisions:
|
|
14
|
+
- Output formats: ESM + CJS for maximum compatibility; ESM-only for modern toolchains
|
|
15
|
+
- File extensions: `.js`/`.cjs` to signal format explicitly
|
|
16
|
+
- Declaration files: emitted alongside each output, not separately
|
|
17
|
+
- Exports map: precise condition ordering (types, import, require, default)
|
|
18
|
+
- Tree-shaking: `sideEffects: false` + ES module output + no barrel-file anti-patterns
|
|
19
|
+
|
|
20
|
+
## Deep Guidance
|
|
21
|
+
|
|
22
|
+
### Choosing a Bundler
|
|
23
|
+
|
|
24
|
+
**tsup (recommended for most libraries):**
|
|
25
|
+
tsup is a TypeScript-first bundler built on esbuild. Fast, opinionated, handles dual ESM/CJS output with declaration files:
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// tsup.config.ts
|
|
29
|
+
import { defineConfig } from 'tsup'
|
|
30
|
+
|
|
31
|
+
export default defineConfig({
|
|
32
|
+
entry: ['src/index.ts'],
|
|
33
|
+
format: ['esm', 'cjs'],
|
|
34
|
+
dts: true, // Emit .d.ts declaration files
|
|
35
|
+
sourcemap: true,
|
|
36
|
+
clean: true, // Clean dist/ before each build
|
|
37
|
+
splitting: false, // Keep single output file per format
|
|
38
|
+
treeshake: true,
|
|
39
|
+
outExtension({ format }) {
|
|
40
|
+
return {
|
|
41
|
+
js: format === 'cjs' ? '.cjs' : '.js'
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Output:
|
|
48
|
+
```
|
|
49
|
+
dist/
|
|
50
|
+
├── index.js # ESM
|
|
51
|
+
├── index.cjs # CJS
|
|
52
|
+
├── index.d.ts # TypeScript declarations
|
|
53
|
+
└── index.d.cts # CJS declarations (tsup generates automatically)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**rollup (when you need fine-grained control):**
|
|
57
|
+
```javascript
|
|
58
|
+
// rollup.config.mjs
|
|
59
|
+
import typescript from '@rollup/plugin-typescript'
|
|
60
|
+
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
|
61
|
+
|
|
62
|
+
export default [
|
|
63
|
+
{
|
|
64
|
+
input: 'src/index.ts',
|
|
65
|
+
output: { file: 'dist/index.js', format: 'es', sourcemap: true },
|
|
66
|
+
plugins: [nodeResolve(), typescript({ declaration: false })]
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
input: 'src/index.ts',
|
|
70
|
+
output: { file: 'dist/index.cjs', format: 'cjs', sourcemap: true, exports: 'named' },
|
|
71
|
+
plugins: [nodeResolve(), typescript({ declaration: false })]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**tsc only (when bundling is unnecessary):**
|
|
77
|
+
If the library has no dependencies to bundle, raw `tsc` with separate CJS and ESM configs works. Use this for pure type libraries or libraries where consumers handle bundling:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# ESM
|
|
81
|
+
tsc -p tsconfig.json
|
|
82
|
+
|
|
83
|
+
# CJS (separate tsconfig)
|
|
84
|
+
tsc -p tsconfig.cjs.json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Exports Map Configuration
|
|
88
|
+
|
|
89
|
+
The `exports` field in `package.json` is the definitive module resolution spec for Node.js 12+ and modern bundlers. Define it precisely:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"exports": {
|
|
94
|
+
".": {
|
|
95
|
+
"import": {
|
|
96
|
+
"types": "./dist/index.d.ts",
|
|
97
|
+
"default": "./dist/index.js"
|
|
98
|
+
},
|
|
99
|
+
"require": {
|
|
100
|
+
"types": "./dist/index.d.cts",
|
|
101
|
+
"default": "./dist/index.cjs"
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"./plugins": {
|
|
105
|
+
"import": {
|
|
106
|
+
"types": "./dist/plugins/index.d.ts",
|
|
107
|
+
"default": "./dist/plugins/index.js"
|
|
108
|
+
},
|
|
109
|
+
"require": {
|
|
110
|
+
"types": "./dist/plugins/index.d.cts",
|
|
111
|
+
"default": "./dist/plugins/index.cjs"
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"./package.json": "./package.json"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Condition ordering matters:**
|
|
120
|
+
- `types` must come before `default` so TypeScript resolves declarations correctly
|
|
121
|
+
- `import` before `require` (ESM preferred when both are available)
|
|
122
|
+
- `default` as the final fallback
|
|
123
|
+
|
|
124
|
+
**Legacy fields for older tooling:**
|
|
125
|
+
Keep `"main"` and `"module"` for older bundlers and Node versions that don't support `exports`:
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"main": "./dist/index.cjs",
|
|
129
|
+
"module": "./dist/index.js",
|
|
130
|
+
"types": "./dist/index.d.ts"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Bundlers like webpack 4 and older rollup configurations use `"module"` for ESM. Modern tooling uses `exports`.
|
|
135
|
+
|
|
136
|
+
### Dual Package Hazard
|
|
137
|
+
|
|
138
|
+
The dual package hazard occurs when both ESM and CJS versions of the same library are loaded in the same process, creating two instances of what should be a singleton. Symptoms: `instanceof` checks fail, shared state doesn't sync, plugin registrations disappear.
|
|
139
|
+
|
|
140
|
+
**Prevention strategies:**
|
|
141
|
+
|
|
142
|
+
1. **State in the ESM version only:**
|
|
143
|
+
```javascript
|
|
144
|
+
// dist/index.cjs — CJS wrapper that re-exports the ESM version
|
|
145
|
+
// This ensures only one module instance regardless of import style
|
|
146
|
+
const mod = await import('./index.js')
|
|
147
|
+
module.exports = mod
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
2. **Stateless library design (best):**
|
|
151
|
+
Design the library with no module-level state. Factory functions create instances; there is no singleton. With no shared state, dual loading is harmless:
|
|
152
|
+
```typescript
|
|
153
|
+
// NO module-level state — safe for dual loading
|
|
154
|
+
export function createCache(): Cache { return new Map() }
|
|
155
|
+
export function parseConfig(input: string): Config { /* pure function */ }
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
3. **Wrapper CJS file:**
|
|
159
|
+
```javascript
|
|
160
|
+
// dist/index.cjs — thin CJS wrapper
|
|
161
|
+
'use strict'
|
|
162
|
+
const mod = require('./index.js') // This won't work if index.js is ESM
|
|
163
|
+
// Use a proper wrapper instead:
|
|
164
|
+
Object.assign(exports, require('./index-cjs-impl.cjs'))
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
For complex libraries with state, use approach 1 or design as approach 2.
|
|
168
|
+
|
|
169
|
+
### Tree-Shaking Verification
|
|
170
|
+
|
|
171
|
+
After building, verify that tree-shaking actually works:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
# Install a fresh consumer project and import one function
|
|
175
|
+
mkdir /tmp/tree-shake-test && cd /tmp/tree-shake-test
|
|
176
|
+
npm init -y
|
|
177
|
+
npm install my-library@file:/path/to/library
|
|
178
|
+
|
|
179
|
+
cat > index.js << 'EOF'
|
|
180
|
+
import { parseConfig } from 'my-library'
|
|
181
|
+
const config = parseConfig('[server]\nhost = "localhost"')
|
|
182
|
+
console.log(config)
|
|
183
|
+
EOF
|
|
184
|
+
|
|
185
|
+
# Bundle with rollup and check output size
|
|
186
|
+
npx rollup index.js --format iife --bundle > bundle.js
|
|
187
|
+
wc -c bundle.js
|
|
188
|
+
|
|
189
|
+
# If the bundle is larger than just parseConfig + its dependencies,
|
|
190
|
+
# tree-shaking is not working — investigate the sideEffects field
|
|
191
|
+
# and ES module output
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
**Common tree-shaking failures:**
|
|
195
|
+
- CommonJS output used (bundler can't statically analyze)
|
|
196
|
+
- `sideEffects: true` in package.json (prevents dead code elimination)
|
|
197
|
+
- Barrel files that import everything (forces all code into bundle)
|
|
198
|
+
- `export * from './large-module'` at root when consumers only use one export
|
|
199
|
+
|
|
200
|
+
**Subpath exports enable opt-in tree-shaking at the feature level:**
|
|
201
|
+
```typescript
|
|
202
|
+
// Consumer only needs the validator — zero parser code in bundle
|
|
203
|
+
import { validateSchema } from 'my-library/validators'
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Bundle Size Budgets
|
|
207
|
+
|
|
208
|
+
Define a bundle size budget for browser-targeted libraries:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
// package.json
|
|
212
|
+
{
|
|
213
|
+
"size-limit": [
|
|
214
|
+
{
|
|
215
|
+
"path": "./dist/index.js",
|
|
216
|
+
"limit": "10 kB"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"path": "./dist/plugins/index.js",
|
|
220
|
+
"limit": "5 kB"
|
|
221
|
+
}
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# Check with size-limit
|
|
228
|
+
npx size-limit
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Enforce in CI: if a PR increases bundle size beyond the budget, fail the check. This prevents gradual size bloat.
|
|
232
|
+
|
|
233
|
+
### Source Maps
|
|
234
|
+
|
|
235
|
+
Always emit source maps. They enable consumers to debug into the library source when troubleshooting:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
// tsup.config.ts
|
|
239
|
+
export default defineConfig({
|
|
240
|
+
sourcemap: true, // Emits .js.map alongside .js
|
|
241
|
+
})
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Source maps should be included in the published package (`dist/*.map`). They don't significantly affect install size but dramatically improve debugging.
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: library-conventions
|
|
3
|
+
description: Public API naming, deprecation patterns, changelog conventions, and export patterns for published libraries
|
|
4
|
+
topics: [library, conventions, naming, deprecation, changelog, exports, api-design]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Library conventions are the agreements that make a library predictable, navigable, and trustworthy across versions. They cover how public APIs are named, how deprecated APIs are marked and eventually removed, how changes are communicated through changelogs, and how exports are structured. Inconsistent conventions are a tax on every consumer — they create confusion about what is stable, what is safe to use, and what changed between versions.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Establish and document conventions before publishing v1. Key areas: consistent naming patterns (verbs for functions, nouns for types/classes, `is`/`has` for predicates), deprecation lifecycle with JSDoc markers and migration guidance, changelog format (Keep a Changelog or Conventional Commits), and export structure that makes tree-shaking possible. Internal exports must be clearly separated from public exports to avoid accidental API surface expansion.
|
|
12
|
+
|
|
13
|
+
Core conventions:
|
|
14
|
+
- Functions: verb-noun (`parseConfig`, `validateSchema`, `createClient`)
|
|
15
|
+
- Types/interfaces: PascalCase nouns (`ParseOptions`, `ClientConfig`, `ValidationError`)
|
|
16
|
+
- Predicates: `is` prefix (`isError`, `isValidConfig`)
|
|
17
|
+
- Constants: SCREAMING_SNAKE for module-level, camelCase for config object keys
|
|
18
|
+
- Deprecation: JSDoc `@deprecated` + replacement reference + removal version
|
|
19
|
+
- Changelog: per-version sections with Added / Changed / Deprecated / Removed / Fixed / Security
|
|
20
|
+
|
|
21
|
+
## Deep Guidance
|
|
22
|
+
|
|
23
|
+
### Public API Naming
|
|
24
|
+
|
|
25
|
+
Naming is the most visible part of the API contract. Inconsistency in naming signals immaturity and creates cognitive load for consumers.
|
|
26
|
+
|
|
27
|
+
**Functions — verb-noun pattern:**
|
|
28
|
+
```typescript
|
|
29
|
+
// Good: verb-noun, action is clear
|
|
30
|
+
parseConfig(input: string): Config
|
|
31
|
+
validateSchema(schema: Schema): ValidationResult
|
|
32
|
+
createClient(options: ClientOptions): Client
|
|
33
|
+
formatError(error: unknown): string
|
|
34
|
+
resolveModulePath(specifier: string): string
|
|
35
|
+
|
|
36
|
+
// Bad: ambiguous, no verb, or inverted
|
|
37
|
+
config(input: string): Config // what does it do?
|
|
38
|
+
schemaValidator(schema: Schema): ... // -or suffix is not a function verb
|
|
39
|
+
client(options: ClientOptions): ... // noun-only looks like a constructor
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Types and interfaces — PascalCase nouns:**
|
|
43
|
+
```typescript
|
|
44
|
+
// Good
|
|
45
|
+
interface ParseOptions { ... }
|
|
46
|
+
type ValidationResult = { valid: boolean; errors: ValidationError[] }
|
|
47
|
+
class ConfigClient { ... }
|
|
48
|
+
type ErrorCode = 'NOT_FOUND' | 'INVALID' | 'TIMEOUT'
|
|
49
|
+
|
|
50
|
+
// Bad
|
|
51
|
+
interface parseOptions { ... } // lowercase
|
|
52
|
+
type validation_result = ... // snake_case
|
|
53
|
+
type ErrCode = ... // abbreviation
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Predicates — `is` prefix:**
|
|
57
|
+
```typescript
|
|
58
|
+
function isError(value: unknown): value is Error
|
|
59
|
+
function isValidConfig(config: unknown): config is Config
|
|
60
|
+
function hasRequiredFields(obj: unknown): boolean // 'has' for possession checks
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Boolean options — avoid double negatives:**
|
|
64
|
+
```typescript
|
|
65
|
+
// Good
|
|
66
|
+
interface Options {
|
|
67
|
+
strict: boolean // enable strict mode
|
|
68
|
+
cache: boolean // enable caching
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Bad
|
|
72
|
+
interface Options {
|
|
73
|
+
noStrict: boolean // double negative when true disables
|
|
74
|
+
disableCache: boolean // confusing when combined: disableCache: false
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Error types — descriptive, namespace-prefixed:**
|
|
79
|
+
```typescript
|
|
80
|
+
// Good: namespaced, descriptive
|
|
81
|
+
class ParseError extends Error {
|
|
82
|
+
constructor(message: string, public readonly line: number, public readonly col: number) {
|
|
83
|
+
super(message)
|
|
84
|
+
this.name = 'ParseError'
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Available as named export:
|
|
89
|
+
export { ParseError, ValidationError, NetworkError, TimeoutError }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Deprecation Lifecycle
|
|
93
|
+
|
|
94
|
+
Deprecation is a promise to consumers: "this still works today, but plan to migrate." It must be communicated at multiple levels.
|
|
95
|
+
|
|
96
|
+
**Step 1: Add `@deprecated` JSDoc in a MINOR release:**
|
|
97
|
+
```typescript
|
|
98
|
+
/**
|
|
99
|
+
* Parse a configuration string.
|
|
100
|
+
* @deprecated Use `parseConfig()` instead. Will be removed in v3.0.
|
|
101
|
+
* @see parseConfig
|
|
102
|
+
*/
|
|
103
|
+
export function parse(input: string): Config {
|
|
104
|
+
return parseConfig(input)
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The `@deprecated` tag causes TypeScript to show strikethrough in IDEs and emit warnings. Always include:
|
|
109
|
+
- What to use instead
|
|
110
|
+
- When it will be removed (target major version)
|
|
111
|
+
|
|
112
|
+
**Step 2: Log a runtime warning (optional, for JS users without TypeScript):**
|
|
113
|
+
```typescript
|
|
114
|
+
export function parse(input: string): Config {
|
|
115
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
116
|
+
console.warn(
|
|
117
|
+
'[my-library] parse() is deprecated. Use parseConfig() instead. ' +
|
|
118
|
+
'Will be removed in v3.0. See migration guide: https://example.com/v3-migration'
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
return parseConfig(input)
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Only add runtime warnings if the library has significant JS (non-TypeScript) consumers. Don't pollute production logs.
|
|
126
|
+
|
|
127
|
+
**Step 3: Remove in the next major version:**
|
|
128
|
+
- Remove the export entirely
|
|
129
|
+
- Add a clear CHANGELOG entry with migration instructions
|
|
130
|
+
- Include migration guide link in the changelog entry
|
|
131
|
+
|
|
132
|
+
**Deprecation period policy:**
|
|
133
|
+
The minimum deprecation period before removal should be one full major version. If you deprecate in v2.3, the earliest removal is v3.0. Communicate the removal version at deprecation time.
|
|
134
|
+
|
|
135
|
+
### Changelog Conventions
|
|
136
|
+
|
|
137
|
+
Follow the [Keep a Changelog](https://keepachangelog.com) format. Every release must have a changelog entry before publishing.
|
|
138
|
+
|
|
139
|
+
**Format:**
|
|
140
|
+
```markdown
|
|
141
|
+
# Changelog
|
|
142
|
+
|
|
143
|
+
## [Unreleased]
|
|
144
|
+
|
|
145
|
+
## [2.1.0] - 2024-03-15
|
|
146
|
+
|
|
147
|
+
### Added
|
|
148
|
+
- `parseConfig()` function as the new primary parsing API
|
|
149
|
+
- `ParseOptions.strict` flag for strict mode validation
|
|
150
|
+
|
|
151
|
+
### Changed
|
|
152
|
+
- `createClient()` now accepts `ClientOptions.timeout` in milliseconds (previously seconds)
|
|
153
|
+
|
|
154
|
+
### Deprecated
|
|
155
|
+
- `parse()` — use `parseConfig()` instead. Will be removed in v3.0.
|
|
156
|
+
|
|
157
|
+
### Fixed
|
|
158
|
+
- `validateSchema()` no longer throws on empty input; returns `{ valid: false, errors: [] }`
|
|
159
|
+
|
|
160
|
+
## [2.0.0] - 2024-01-10
|
|
161
|
+
|
|
162
|
+
### Breaking Changes
|
|
163
|
+
- Removed `connect()` (deprecated since v1.5.0). Use `createClient()`.
|
|
164
|
+
- `Config.timeout` is now in milliseconds (was seconds in v1.x). Multiply existing values by 1000.
|
|
165
|
+
|
|
166
|
+
### Migration from v1.x
|
|
167
|
+
See: https://example.com/v2-migration
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Rules:
|
|
171
|
+
- Every entry in "Breaking Changes" must have a migration instruction or link
|
|
172
|
+
- "Added" entries must reference the new API by name
|
|
173
|
+
- "Fixed" entries must describe the incorrect behavior and the correct behavior
|
|
174
|
+
- Never put vague entries like "Various bug fixes" — enumerate them
|
|
175
|
+
|
|
176
|
+
### Export Patterns
|
|
177
|
+
|
|
178
|
+
How you structure exports determines your tree-shaking story and your public API surface.
|
|
179
|
+
|
|
180
|
+
**Root index.ts — explicit, intentional exports only:**
|
|
181
|
+
```typescript
|
|
182
|
+
// src/index.ts
|
|
183
|
+
// Public API — these are the semver-protected exports
|
|
184
|
+
|
|
185
|
+
// Core functions
|
|
186
|
+
export { parseConfig } from './parser'
|
|
187
|
+
export { validateSchema } from './validator'
|
|
188
|
+
export { createClient } from './client'
|
|
189
|
+
|
|
190
|
+
// Types
|
|
191
|
+
export type { ParseOptions, ParseResult } from './parser'
|
|
192
|
+
export type { ValidationResult, ValidationError } from './validator'
|
|
193
|
+
export type { ClientOptions, Client } from './client'
|
|
194
|
+
|
|
195
|
+
// Error types
|
|
196
|
+
export { ParseError, ValidationError as LibValidationError } from './errors'
|
|
197
|
+
|
|
198
|
+
// DO NOT export internal utilities
|
|
199
|
+
// DO NOT re-export everything with `export * from './...'`
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Avoid `export *` at the root:** It makes the API surface opaque and causes accidental exports of internal symbols.
|
|
203
|
+
|
|
204
|
+
**Subpath exports for optional features:**
|
|
205
|
+
```typescript
|
|
206
|
+
// package.json exports map (see library-bundling.md for full config)
|
|
207
|
+
{
|
|
208
|
+
"exports": {
|
|
209
|
+
".": "./dist/index.js",
|
|
210
|
+
"./plugins": "./dist/plugins/index.js",
|
|
211
|
+
"./testing": "./dist/testing/index.js"
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Consumers who don't use plugins pay zero bundle cost. Testing utilities stay separate from production code.
|
|
217
|
+
|
|
218
|
+
**Barrel files — use sparingly:**
|
|
219
|
+
Barrel files (files that re-export from many modules) can defeat tree-shaking in some bundlers. Prefer deep imports in internal code; use the root barrel only for the public API.
|
|
220
|
+
|
|
221
|
+
### Convention Documentation
|
|
222
|
+
|
|
223
|
+
Every library must have a `CONTRIBUTING.md` or `docs/conventions.md` documenting:
|
|
224
|
+
1. Naming conventions for new API additions
|
|
225
|
+
2. Deprecation lifecycle steps (checklist format)
|
|
226
|
+
3. Changelog update requirement (must update before PR merges)
|
|
227
|
+
4. Export checklist for new public APIs
|
|
228
|
+
|
|
229
|
+
Without documented conventions, contributors add APIs inconsistently, and the library accumulates naming debt that is expensive to fix without breaking changes.
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: library-dev-environment
|
|
3
|
+
description: Monorepo setup, npm link workflow, build watch mode, and local consumer testing for library development
|
|
4
|
+
topics: [library, dev-environment, monorepo, npm-link, build-watch, local-testing]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Library development environment setup is distinct from application development: you are building code that will be consumed by other projects, which means your dev workflow must include a way to test the library as a consumer would — before publishing to npm. The feedback loop between changing library source and seeing the effect in a consumer application is the central challenge. Get this wrong, and you spend hours debugging issues that only surface after publish.
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
Use build watch mode (TypeScript `--watch` or a bundler watcher) for fast feedback during development. For testing in a real consumer project, use `npm link` or workspace-relative `file:` references. In monorepos, use npm/pnpm/yarn workspaces to co-locate the library and consumer apps. Never develop library code exclusively through unit tests — always validate through a real consumer context. Set up scripts for the full dev loop: `build:watch` in one terminal, consumer app in another.
|
|
12
|
+
|
|
13
|
+
Core workflow tools:
|
|
14
|
+
- `tsc --watch` for TypeScript compilation feedback
|
|
15
|
+
- `npm link` / `pnpm link` for local cross-project testing
|
|
16
|
+
- Workspace `file:` references for monorepo consumers
|
|
17
|
+
- `npm pack` + install for pre-publish verification
|
|
18
|
+
|
|
19
|
+
## Deep Guidance
|
|
20
|
+
|
|
21
|
+
### Build Watch Mode
|
|
22
|
+
|
|
23
|
+
The development feedback loop starts with build watch mode. TypeScript's `--watch` mode recompiles on every save:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Terminal 1: watch the library build
|
|
27
|
+
npm run build:watch
|
|
28
|
+
|
|
29
|
+
# package.json script:
|
|
30
|
+
"build:watch": "tsc -p tsconfig.json --watch --preserveWatchOutput"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For more complex builds (bundling, multiple outputs), use a bundler watcher:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# With tsup (recommended for dual ESM/CJS builds)
|
|
37
|
+
"build:watch": "tsup --watch"
|
|
38
|
+
|
|
39
|
+
# tsup.config.ts
|
|
40
|
+
import { defineConfig } from 'tsup'
|
|
41
|
+
|
|
42
|
+
export default defineConfig({
|
|
43
|
+
entry: ['src/index.ts'],
|
|
44
|
+
format: ['esm', 'cjs'],
|
|
45
|
+
dts: true,
|
|
46
|
+
sourcemap: true,
|
|
47
|
+
clean: true,
|
|
48
|
+
watch: process.env.WATCH === 'true'
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
TypeScript `--watch` alone is sufficient for type-only changes. If you're bundling (minifying, inlining), use the bundler's watch mode.
|
|
53
|
+
|
|
54
|
+
### npm link Workflow
|
|
55
|
+
|
|
56
|
+
`npm link` creates a symlink from your global npm prefix to the library, then links that into the consuming project:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# In the library directory:
|
|
60
|
+
cd my-library
|
|
61
|
+
npm link
|
|
62
|
+
# Creates: ~/.nvm/versions/node/vX/lib/node_modules/my-library -> /path/to/my-library
|
|
63
|
+
|
|
64
|
+
# In the consuming project:
|
|
65
|
+
cd my-app
|
|
66
|
+
npm link my-library
|
|
67
|
+
# Creates: my-app/node_modules/my-library -> ~/.nvm/.../my-library
|
|
68
|
+
|
|
69
|
+
# The consumer now uses the live dist/ from the library
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Caveats with npm link:**
|
|
73
|
+
- The consumer uses the `dist/` directory, so the library must be built first and kept rebuilt via watch mode
|
|
74
|
+
- React and other singleton libraries can cause issues because the library and consumer may each resolve their own copy: use `npm link my-app/node_modules/react` inside the library to force shared resolution
|
|
75
|
+
- `npm install` in the consumer will break the link — you must re-run `npm link my-library`
|
|
76
|
+
|
|
77
|
+
**Preferred alternative: `file:` reference in consumer:**
|
|
78
|
+
```json
|
|
79
|
+
// my-app/package.json
|
|
80
|
+
{
|
|
81
|
+
"dependencies": {
|
|
82
|
+
"my-library": "file:../my-library"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Run `npm install` in `my-app`. This creates a symlink into `my-library/` respecting the `exports` map. Survives `npm install` (unlike `npm link`). Requires the library to have its `dist/` built.
|
|
88
|
+
|
|
89
|
+
### pnpm Workspace Setup (Recommended for Monorepos)
|
|
90
|
+
|
|
91
|
+
pnpm workspaces handle library + consumer in the same repository without symlink complexity:
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
# pnpm-workspace.yaml (at monorepo root)
|
|
95
|
+
packages:
|
|
96
|
+
- 'packages/*'
|
|
97
|
+
- 'apps/*'
|
|
98
|
+
- 'examples/*'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
monorepo/
|
|
103
|
+
├── packages/
|
|
104
|
+
│ └── my-library/
|
|
105
|
+
│ ├── src/
|
|
106
|
+
│ ├── dist/
|
|
107
|
+
│ └── package.json # name: "my-library"
|
|
108
|
+
├── apps/
|
|
109
|
+
│ └── my-app/
|
|
110
|
+
│ └── package.json # depends on "my-library": "workspace:*"
|
|
111
|
+
└── pnpm-workspace.yaml
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
// apps/my-app/package.json
|
|
116
|
+
{
|
|
117
|
+
"dependencies": {
|
|
118
|
+
"my-library": "workspace:*"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
With `workspace:*`, pnpm links to the local package automatically. The `dist/` directory is used (respecting `exports` map), so the library still needs to be built.
|
|
124
|
+
|
|
125
|
+
**Monorepo dev script:**
|
|
126
|
+
```bash
|
|
127
|
+
# Run both library watch and app dev server in parallel
|
|
128
|
+
"dev": "concurrently \"npm run build:watch -w packages/my-library\" \"npm run dev -w apps/my-app\""
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Pre-publish Verification with npm pack
|
|
132
|
+
|
|
133
|
+
Before publishing, verify the actual package contents:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Pack the library without publishing
|
|
137
|
+
npm pack --dry-run
|
|
138
|
+
|
|
139
|
+
# This shows exactly what files will be included in the published package
|
|
140
|
+
# Look for:
|
|
141
|
+
# - dist/ files present (ESM, CJS, .d.ts)
|
|
142
|
+
# - No src/ files (source not published)
|
|
143
|
+
# - No test files
|
|
144
|
+
# - README.md and CHANGELOG.md present
|
|
145
|
+
# - No .env or secrets
|
|
146
|
+
|
|
147
|
+
# Pack to a tarball and install it in a test project
|
|
148
|
+
npm pack
|
|
149
|
+
# Creates: my-library-1.0.0.tgz
|
|
150
|
+
|
|
151
|
+
# In a fresh test project:
|
|
152
|
+
npm install ../my-library/my-library-1.0.0.tgz
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Installing the tarball is the most faithful pre-publish test. It reproduces exactly what consumers get from `npm install my-library`.
|
|
156
|
+
|
|
157
|
+
### Dev Dependencies vs. Build Dependencies
|
|
158
|
+
|
|
159
|
+
Keep the dev environment fast by understanding what belongs where:
|
|
160
|
+
|
|
161
|
+
**devDependencies** (not published):
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"devDependencies": {
|
|
165
|
+
"typescript": "^5.4.0", // Build tool
|
|
166
|
+
"tsup": "^8.0.0", // Bundler
|
|
167
|
+
"vitest": "^1.4.0", // Test runner
|
|
168
|
+
"tsd": "^0.31.0", // Type testing
|
|
169
|
+
"typedoc": "^0.25.0", // Doc generation
|
|
170
|
+
"eslint": "^8.57.0", // Linter
|
|
171
|
+
"prettier": "^3.2.0", // Formatter
|
|
172
|
+
"concurrently": "^8.2.0", // Parallel scripts
|
|
173
|
+
"rimraf": "^5.0.0" // Cross-platform rm -rf
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**dependencies** (installed by consumers):
|
|
179
|
+
Only runtime dependencies that the library code imports at runtime. Keep this list minimal. Every dependency you add becomes a consumer's dependency. Prefer zero runtime dependencies for utility libraries.
|
|
180
|
+
|
|
181
|
+
**peerDependencies**:
|
|
182
|
+
Framework dependencies the consumer is expected to provide (React, Vue, etc.).
|
|
183
|
+
|
|
184
|
+
### Environment Variables for Dev
|
|
185
|
+
|
|
186
|
+
Libraries should not read environment variables at runtime (that's the consumer's responsibility). But the build process may need them:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
# .env.local (gitignored) — only for build/test scripts
|
|
190
|
+
NPM_REGISTRY=https://registry.npmjs.org
|
|
191
|
+
TYPEDOC_TOKEN=... # for doc deployment
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Document any required environment variables in `docs/dev-setup.md`. Never hardcode registry URLs or tokens.
|
|
195
|
+
|
|
196
|
+
### Recommended package.json Dev Scripts
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"scripts": {
|
|
201
|
+
"build": "rimraf dist && tsup",
|
|
202
|
+
"build:watch": "tsup --watch",
|
|
203
|
+
"dev": "npm run build:watch",
|
|
204
|
+
"test": "vitest run",
|
|
205
|
+
"test:watch": "vitest",
|
|
206
|
+
"test:types": "tsd",
|
|
207
|
+
"test:coverage": "vitest run --coverage",
|
|
208
|
+
"test:examples": "node examples/basic-usage/index.js",
|
|
209
|
+
"lint": "eslint src/ tests/",
|
|
210
|
+
"format": "prettier --write src/ tests/",
|
|
211
|
+
"typecheck": "tsc --noEmit -p tsconfig.dev.json",
|
|
212
|
+
"docs": "typedoc",
|
|
213
|
+
"pack:dry": "npm pack --dry-run",
|
|
214
|
+
"prepublishOnly": "npm run build && npm run test && npm run test:types",
|
|
215
|
+
"clean": "rimraf dist"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
The `prepublishOnly` script is a safety net — it runs automatically before `npm publish` and blocks publishing if tests fail.
|