@zigrivers/scaffold 3.6.0 → 3.8.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 (115) hide show
  1. package/README.md +127 -12
  2. package/content/knowledge/backend/backend-api-design.md +103 -0
  3. package/content/knowledge/backend/backend-architecture.md +100 -0
  4. package/content/knowledge/backend/backend-async-patterns.md +101 -0
  5. package/content/knowledge/backend/backend-auth-patterns.md +100 -0
  6. package/content/knowledge/backend/backend-conventions.md +105 -0
  7. package/content/knowledge/backend/backend-data-modeling.md +102 -0
  8. package/content/knowledge/backend/backend-deployment.md +100 -0
  9. package/content/knowledge/backend/backend-dev-environment.md +102 -0
  10. package/content/knowledge/backend/backend-observability.md +102 -0
  11. package/content/knowledge/backend/backend-project-structure.md +100 -0
  12. package/content/knowledge/backend/backend-requirements.md +103 -0
  13. package/content/knowledge/backend/backend-security.md +104 -0
  14. package/content/knowledge/backend/backend-testing.md +101 -0
  15. package/content/knowledge/backend/backend-worker-patterns.md +100 -0
  16. package/content/knowledge/cli/cli-architecture.md +101 -0
  17. package/content/knowledge/cli/cli-conventions.md +117 -0
  18. package/content/knowledge/cli/cli-dev-environment.md +121 -0
  19. package/content/knowledge/cli/cli-distribution-patterns.md +106 -0
  20. package/content/knowledge/cli/cli-interactivity-patterns.md +116 -0
  21. package/content/knowledge/cli/cli-output-patterns.md +107 -0
  22. package/content/knowledge/cli/cli-project-structure.md +124 -0
  23. package/content/knowledge/cli/cli-requirements.md +101 -0
  24. package/content/knowledge/cli/cli-shell-integration.md +130 -0
  25. package/content/knowledge/cli/cli-testing.md +134 -0
  26. package/content/knowledge/library/library-api-design.md +306 -0
  27. package/content/knowledge/library/library-architecture.md +247 -0
  28. package/content/knowledge/library/library-bundling.md +244 -0
  29. package/content/knowledge/library/library-conventions.md +229 -0
  30. package/content/knowledge/library/library-dev-environment.md +220 -0
  31. package/content/knowledge/library/library-documentation.md +300 -0
  32. package/content/knowledge/library/library-project-structure.md +237 -0
  33. package/content/knowledge/library/library-requirements.md +173 -0
  34. package/content/knowledge/library/library-security.md +257 -0
  35. package/content/knowledge/library/library-testing.md +319 -0
  36. package/content/knowledge/library/library-type-definitions.md +284 -0
  37. package/content/knowledge/library/library-versioning.md +300 -0
  38. package/content/knowledge/mobile-app/mobile-app-architecture.md +283 -0
  39. package/content/knowledge/mobile-app/mobile-app-conventions.md +180 -0
  40. package/content/knowledge/mobile-app/mobile-app-deployment.md +298 -0
  41. package/content/knowledge/mobile-app/mobile-app-dev-environment.md +257 -0
  42. package/content/knowledge/mobile-app/mobile-app-distribution.md +264 -0
  43. package/content/knowledge/mobile-app/mobile-app-observability.md +317 -0
  44. package/content/knowledge/mobile-app/mobile-app-offline-patterns.md +311 -0
  45. package/content/knowledge/mobile-app/mobile-app-project-structure.md +245 -0
  46. package/content/knowledge/mobile-app/mobile-app-push-notifications.md +321 -0
  47. package/content/knowledge/mobile-app/mobile-app-requirements.md +147 -0
  48. package/content/knowledge/mobile-app/mobile-app-security.md +338 -0
  49. package/content/knowledge/mobile-app/mobile-app-testing.md +400 -0
  50. package/content/knowledge/web-app/web-app-api-patterns.md +224 -0
  51. package/content/knowledge/web-app/web-app-architecture.md +116 -0
  52. package/content/knowledge/web-app/web-app-auth-patterns.md +256 -0
  53. package/content/knowledge/web-app/web-app-conventions.md +121 -0
  54. package/content/knowledge/web-app/web-app-data-patterns.md +218 -0
  55. package/content/knowledge/web-app/web-app-deployment-workflow.md +143 -0
  56. package/content/knowledge/web-app/web-app-deployment.md +134 -0
  57. package/content/knowledge/web-app/web-app-design-system.md +158 -0
  58. package/content/knowledge/web-app/web-app-dev-environment.md +173 -0
  59. package/content/knowledge/web-app/web-app-observability.md +221 -0
  60. package/content/knowledge/web-app/web-app-project-structure.md +160 -0
  61. package/content/knowledge/web-app/web-app-rendering-strategies.md +133 -0
  62. package/content/knowledge/web-app/web-app-requirements.md +112 -0
  63. package/content/knowledge/web-app/web-app-security.md +193 -0
  64. package/content/knowledge/web-app/web-app-session-patterns.md +214 -0
  65. package/content/knowledge/web-app/web-app-testing.md +249 -0
  66. package/content/knowledge/web-app/web-app-ux-patterns.md +162 -0
  67. package/content/methodology/backend-overlay.yml +73 -0
  68. package/content/methodology/cli-overlay.yml +69 -0
  69. package/content/methodology/library-overlay.yml +67 -0
  70. package/content/methodology/mobile-app-overlay.yml +71 -0
  71. package/content/methodology/web-app-overlay.yml +79 -0
  72. package/dist/cli/commands/init.d.ts +21 -0
  73. package/dist/cli/commands/init.d.ts.map +1 -1
  74. package/dist/cli/commands/init.js +261 -13
  75. package/dist/cli/commands/init.js.map +1 -1
  76. package/dist/cli/commands/init.test.js +206 -0
  77. package/dist/cli/commands/init.test.js.map +1 -1
  78. package/dist/config/schema.d.ts +1392 -64
  79. package/dist/config/schema.d.ts.map +1 -1
  80. package/dist/config/schema.js +82 -5
  81. package/dist/config/schema.js.map +1 -1
  82. package/dist/config/schema.test.js +302 -1
  83. package/dist/config/schema.test.js.map +1 -1
  84. package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
  85. package/dist/core/assembly/overlay-loader.js +2 -1
  86. package/dist/core/assembly/overlay-loader.js.map +1 -1
  87. package/dist/core/assembly/overlay-loader.test.js +56 -0
  88. package/dist/core/assembly/overlay-loader.test.js.map +1 -1
  89. package/dist/e2e/game-pipeline.test.js +1 -0
  90. package/dist/e2e/game-pipeline.test.js.map +1 -1
  91. package/dist/e2e/project-type-overlays.test.d.ts +16 -0
  92. package/dist/e2e/project-type-overlays.test.d.ts.map +1 -0
  93. package/dist/e2e/project-type-overlays.test.js +834 -0
  94. package/dist/e2e/project-type-overlays.test.js.map +1 -0
  95. package/dist/types/config.d.ts +19 -2
  96. package/dist/types/config.d.ts.map +1 -1
  97. package/dist/types/index.d.ts +0 -1
  98. package/dist/types/index.d.ts.map +1 -1
  99. package/dist/types/index.js +0 -1
  100. package/dist/types/index.js.map +1 -1
  101. package/dist/wizard/questions.d.ts +27 -1
  102. package/dist/wizard/questions.d.ts.map +1 -1
  103. package/dist/wizard/questions.js +142 -3
  104. package/dist/wizard/questions.js.map +1 -1
  105. package/dist/wizard/questions.test.js +206 -8
  106. package/dist/wizard/questions.test.js.map +1 -1
  107. package/dist/wizard/wizard.d.ts +21 -0
  108. package/dist/wizard/wizard.d.ts.map +1 -1
  109. package/dist/wizard/wizard.js +27 -1
  110. package/dist/wizard/wizard.js.map +1 -1
  111. package/package.json +1 -1
  112. package/dist/types/wizard.d.ts +0 -14
  113. package/dist/types/wizard.d.ts.map +0 -1
  114. package/dist/types/wizard.js +0 -2
  115. package/dist/types/wizard.js.map +0 -1
@@ -0,0 +1,247 @@
1
+ ---
2
+ name: library-architecture
3
+ description: Module design, dependency minimization, tree-shaking enablement, and plugin patterns for published libraries
4
+ topics: [library, architecture, modules, tree-shaking, plugins, dependencies, design]
5
+ ---
6
+
7
+ Library architecture is constrained by two forces that do not apply to applications: the consumer's bundle size budget and the consumer's dependency graph. Every architectural decision must account for what the library adds to consumers who import it — not just the feature complexity it solves. A well-architected library is modular by default, has minimal or zero runtime dependencies, enables tree-shaking so consumers pay only for what they use, and provides extension points that don't require forking the library.
8
+
9
+ ## Summary
10
+
11
+ Design libraries as collections of composable, independently tree-shakeable units. The root module is a barrel export; each feature module is independently importable. Minimize runtime dependencies to zero where possible — every dependency you add becomes a transitive dependency for every consumer. Enable tree-shaking by using ES modules, avoiding side effects, and ensuring the `"sideEffects": false` field is accurate. Plugin patterns allow extension without coupling; prefer dependency injection and factory functions over class hierarchies.
12
+
13
+ Core architectural decisions:
14
+ - Module boundary: each exported function/class is its own module file, barrel at root
15
+ - Dependency strategy: zero runtime deps preferred; peer deps for framework integration
16
+ - Side effects: none at module load time; `"sideEffects": false` in package.json
17
+ - Extension pattern: plugin factory or dependency injection, not subclassing
18
+ - Error strategy: typed error classes, not string codes; never swallow errors
19
+
20
+ ## Deep Guidance
21
+
22
+ ### Module Boundary Design
23
+
24
+ Each public export should have a clear, single responsibility. The module structure mirrors the API surface:
25
+
26
+ ```
27
+ src/
28
+ ├── index.ts # Barrel: re-exports all public APIs
29
+ ├── parser.ts # parseConfig, ParseOptions, ParseError
30
+ ├── validator.ts # validateSchema, ValidationResult, ValidationError
31
+ ├── client.ts # createClient, ClientOptions, Client interface
32
+ ├── types.ts # Shared types used by multiple modules
33
+ ├── errors.ts # Base error classes
34
+ └── internal/
35
+ ├── cache.ts # Internal LRU cache (not exported)
36
+ ├── http.ts # Internal HTTP utilities
37
+ └── utils.ts # Internal pure utilities
38
+ ```
39
+
40
+ **The key constraint:** modules in `internal/` must never be imported by consumers. Enforce this with an ESLint rule:
41
+
42
+ ```json
43
+ // .eslintrc.json
44
+ {
45
+ "rules": {
46
+ "no-restricted-imports": ["error", {
47
+ "patterns": ["*/internal/*"]
48
+ }]
49
+ }
50
+ }
51
+ ```
52
+
53
+ This rule is for consumer code, not for library internals. The library's own modules can freely import from `internal/`.
54
+
55
+ ### Dependency Minimization
56
+
57
+ Every runtime dependency you add has costs:
58
+ 1. Adds to install size for all consumers
59
+ 2. Creates a version conflict risk (consumer uses different version of same dep)
60
+ 3. Introduces a supply chain attack surface
61
+ 4. Creates license compliance requirements for consumers
62
+
63
+ **Target: zero runtime dependencies for utility libraries.** If the library's value is transforming data, parsing strings, or providing utilities, it should have no runtime dependencies.
64
+
65
+ **When dependencies are justified:**
66
+ - The dependency solves a genuinely hard problem with no reasonable alternative (cryptography, date parsing)
67
+ - The dependency is a peer dependency the consumer already has (React, Vue, Node.js built-ins)
68
+ - The functionality would require thousands of lines of well-tested code to replicate
69
+
70
+ **Inlining vs. depending:**
71
+ For small, stable utilities (10-50 lines), consider inlining instead of depending:
72
+ ```typescript
73
+ // Instead of: import { clamp } from 'lodash-es'
74
+ // Inline it:
75
+ function clamp(value: number, min: number, max: number): number {
76
+ return Math.min(Math.max(value, min), max)
77
+ }
78
+ ```
79
+
80
+ For large, complex utilities (crypto, parsing), depend on the established library.
81
+
82
+ ### Tree-Shaking Enablement
83
+
84
+ Tree-shaking requires the bundler to statically analyze which exports are used. Three requirements:
85
+
86
+ **1. ES module syntax throughout:**
87
+ ```typescript
88
+ // Good — static, analyzable
89
+ export function parseConfig(input: string): Config { ... }
90
+ export { validateSchema } from './validator'
91
+
92
+ // Bad — dynamic, blocks tree-shaking
93
+ module.exports = { parseConfig } // CommonJS
94
+ exports[functionName] = fn // Dynamic export key
95
+ ```
96
+
97
+ **2. No side effects at module load time:**
98
+ ```typescript
99
+ // BAD: side effect on import — breaks tree-shaking
100
+ const cache = new Map()
101
+ globalThis.__myLibCache = cache // Pollutes global on import
102
+ console.log('my-library loaded') // Log on import
103
+
104
+ // GOOD: factory function — no side effect until called
105
+ export function createCache(): Map<string, unknown> {
106
+ return new Map()
107
+ }
108
+ ```
109
+
110
+ **3. Accurate `sideEffects` field:**
111
+ ```json
112
+ // package.json — only if genuinely no side effects
113
+ { "sideEffects": false }
114
+
115
+ // If CSS imports or polyfills have side effects:
116
+ { "sideEffects": ["*.css", "src/polyfills.js"] }
117
+ ```
118
+
119
+ **Verify tree-shaking works:**
120
+ ```bash
121
+ # Use bundle-buddy, rollup-plugin-visualizer, or bundlephobia to verify
122
+ npx bundlephobia my-library@1.0.0
123
+
124
+ # Or build a minimal consumer and check the output bundle size
125
+ # A consumer that only imports parseConfig should not include validateSchema code
126
+ ```
127
+
128
+ ### Plugin Architecture Patterns
129
+
130
+ Plugins allow consumers to extend the library without modifying it. Three patterns in increasing flexibility order:
131
+
132
+ **Pattern 1: Factory function with options (simplest):**
133
+ ```typescript
134
+ export interface ClientOptions {
135
+ transport?: Transport // Consumer provides custom transport
136
+ serializer?: Serializer
137
+ logger?: Logger
138
+ }
139
+
140
+ export function createClient(options: ClientOptions = {}): Client {
141
+ const transport = options.transport ?? defaultHttpTransport()
142
+ const serializer = options.serializer ?? jsonSerializer()
143
+ const logger = options.logger ?? noopLogger()
144
+ return new ClientImpl(transport, serializer, logger)
145
+ }
146
+
147
+ // Consumer can inject their own transport:
148
+ const client = createClient({
149
+ transport: myCustomTransport,
150
+ logger: pinoLogger
151
+ })
152
+ ```
153
+
154
+ This is dependency injection — the simplest, most testable plugin pattern.
155
+
156
+ **Pattern 2: Plugin registry (for named, composable extensions):**
157
+ ```typescript
158
+ export interface Plugin {
159
+ name: string
160
+ setup(context: PluginContext): void | Promise<void>
161
+ }
162
+
163
+ export interface PluginContext {
164
+ registerTransform(name: string, fn: TransformFn): void
165
+ registerValidator(name: string, fn: ValidatorFn): void
166
+ onParse(hook: ParseHook): void
167
+ }
168
+
169
+ export function createClient(options: { plugins?: Plugin[] } = {}): Client {
170
+ const context = createPluginContext()
171
+ for (const plugin of (options.plugins ?? [])) {
172
+ plugin.setup(context)
173
+ }
174
+ return new ClientImpl(context)
175
+ }
176
+
177
+ // Consumer uses plugins:
178
+ import { myPlugin } from 'my-library-plugin-example'
179
+ const client = createClient({ plugins: [myPlugin()] })
180
+ ```
181
+
182
+ **Pattern 3: Middleware chain (for transform pipelines):**
183
+ ```typescript
184
+ export type Middleware<T> = (value: T, next: (value: T) => T) => T
185
+
186
+ export function createPipeline<T>(middlewares: Middleware<T>[]): (value: T) => T {
187
+ return (initial: T) => {
188
+ const chain = middlewares.reduceRight<(value: T) => T>(
189
+ (next, middleware) => (value) => middleware(value, next),
190
+ (value) => value
191
+ )
192
+ return chain(initial)
193
+ }
194
+ }
195
+ ```
196
+
197
+ **What to avoid:**
198
+ - Class inheritance as extension mechanism (consumers must subclass to customize — creates tight coupling)
199
+ - Singleton registries (one global registry makes testing and isolation difficult)
200
+ - Monkey-patching as extension (fragile, hidden coupling)
201
+
202
+ ### Error Architecture
203
+
204
+ Typed errors are part of the public API contract:
205
+
206
+ ```typescript
207
+ // errors.ts — exported as part of public API
208
+ export class LibraryError extends Error {
209
+ constructor(message: string, public readonly code: ErrorCode) {
210
+ super(message)
211
+ this.name = 'LibraryError'
212
+ // Maintain proper prototype chain in transpiled environments
213
+ Object.setPrototypeOf(this, new.target.prototype)
214
+ }
215
+ }
216
+
217
+ export class ParseError extends LibraryError {
218
+ constructor(
219
+ message: string,
220
+ public readonly line: number,
221
+ public readonly column: number,
222
+ public readonly source?: string
223
+ ) {
224
+ super(message, 'PARSE_ERROR')
225
+ this.name = 'ParseError'
226
+ Object.setPrototypeOf(this, new.target.prototype)
227
+ }
228
+ }
229
+
230
+ export type ErrorCode = 'PARSE_ERROR' | 'VALIDATION_ERROR' | 'NETWORK_ERROR' | 'TIMEOUT'
231
+ ```
232
+
233
+ Never throw raw `Error` objects from library code — consumers cannot distinguish library errors from their own errors. Always throw typed errors with enough context to diagnose the problem.
234
+
235
+ ### Avoiding Circular Dependencies
236
+
237
+ Circular dependencies in libraries cause subtle initialization order bugs. Enforce absence with ESLint:
238
+
239
+ ```json
240
+ {
241
+ "rules": {
242
+ "import/no-cycle": ["error", { "maxDepth": 10 }]
243
+ }
244
+ }
245
+ ```
246
+
247
+ When you detect a circular dependency, the usual fix is extracting shared types to `types.ts` (which has no imports) rather than rearranging imports.
@@ -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.