tywrap 0.5.1 → 0.6.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.
Files changed (178) hide show
  1. package/dist/cli.js +8 -6
  2. package/dist/cli.js.map +1 -1
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/index.js +236 -147
  5. package/dist/config/index.js.map +1 -1
  6. package/dist/core/annotation-parser.d.ts.map +1 -1
  7. package/dist/core/annotation-parser.js +112 -97
  8. package/dist/core/annotation-parser.js.map +1 -1
  9. package/dist/core/discovery.d.ts +17 -0
  10. package/dist/core/discovery.d.ts.map +1 -1
  11. package/dist/core/discovery.js +85 -49
  12. package/dist/core/discovery.js.map +1 -1
  13. package/dist/core/emit-call.d.ts +58 -0
  14. package/dist/core/emit-call.d.ts.map +1 -0
  15. package/dist/core/emit-call.js +103 -0
  16. package/dist/core/emit-call.js.map +1 -0
  17. package/dist/core/generator.d.ts +2 -0
  18. package/dist/core/generator.d.ts.map +1 -1
  19. package/dist/core/generator.js +54 -211
  20. package/dist/core/generator.js.map +1 -1
  21. package/dist/core/mapper.d.ts.map +1 -1
  22. package/dist/core/mapper.js +118 -104
  23. package/dist/core/mapper.js.map +1 -1
  24. package/dist/core/validation.d.ts +23 -0
  25. package/dist/core/validation.d.ts.map +1 -1
  26. package/dist/core/validation.js +52 -48
  27. package/dist/core/validation.js.map +1 -1
  28. package/dist/dev.d.ts.map +1 -1
  29. package/dist/dev.js +175 -133
  30. package/dist/dev.js.map +1 -1
  31. package/dist/index.d.ts +6 -25
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +9 -31
  34. package/dist/index.js.map +1 -1
  35. package/dist/runtime/bounded-context.d.ts +2 -2
  36. package/dist/runtime/bounded-context.js +2 -2
  37. package/dist/runtime/{safe-codec.d.ts → bridge-codec.d.ts} +26 -11
  38. package/dist/runtime/bridge-codec.d.ts.map +1 -0
  39. package/dist/runtime/{safe-codec.js → bridge-codec.js} +69 -72
  40. package/dist/runtime/bridge-codec.js.map +1 -0
  41. package/dist/runtime/{http-io.d.ts → http-transport.d.ts} +6 -6
  42. package/dist/runtime/http-transport.d.ts.map +1 -0
  43. package/dist/runtime/{http-io.js → http-transport.js} +3 -3
  44. package/dist/runtime/http-transport.js.map +1 -0
  45. package/dist/runtime/http.d.ts +3 -3
  46. package/dist/runtime/http.d.ts.map +1 -1
  47. package/dist/runtime/http.js +4 -4
  48. package/dist/runtime/http.js.map +1 -1
  49. package/dist/runtime/index.d.ts +9 -1
  50. package/dist/runtime/index.d.ts.map +1 -1
  51. package/dist/runtime/index.js +9 -1
  52. package/dist/runtime/index.js.map +1 -1
  53. package/dist/runtime/node.d.ts +6 -6
  54. package/dist/runtime/node.d.ts.map +1 -1
  55. package/dist/runtime/node.js +7 -7
  56. package/dist/runtime/node.js.map +1 -1
  57. package/dist/runtime/pooled-transport.d.ts +11 -11
  58. package/dist/runtime/pooled-transport.d.ts.map +1 -1
  59. package/dist/runtime/pooled-transport.js +10 -10
  60. package/dist/runtime/pooled-transport.js.map +1 -1
  61. package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -1
  62. package/dist/runtime/pyodide-bootstrap-core.generated.js +1 -1
  63. package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -1
  64. package/dist/runtime/{pyodide-io.d.ts → pyodide-transport.d.ts} +8 -8
  65. package/dist/runtime/pyodide-transport.d.ts.map +1 -0
  66. package/dist/runtime/{pyodide-io.js → pyodide-transport.js} +5 -5
  67. package/dist/runtime/pyodide-transport.js.map +1 -0
  68. package/dist/runtime/pyodide.d.ts +3 -3
  69. package/dist/runtime/pyodide.d.ts.map +1 -1
  70. package/dist/runtime/pyodide.js +5 -5
  71. package/dist/runtime/pyodide.js.map +1 -1
  72. package/dist/runtime/rpc-client.d.ts +23 -5
  73. package/dist/runtime/rpc-client.d.ts.map +1 -1
  74. package/dist/runtime/rpc-client.js +36 -31
  75. package/dist/runtime/rpc-client.js.map +1 -1
  76. package/dist/runtime/{process-io.d.ts → subprocess-transport.d.ts} +25 -9
  77. package/dist/runtime/subprocess-transport.d.ts.map +1 -0
  78. package/dist/runtime/{process-io.js → subprocess-transport.js} +60 -41
  79. package/dist/runtime/subprocess-transport.js.map +1 -0
  80. package/dist/runtime/{worker-pool.d.ts → transport-pool.d.ts} +15 -15
  81. package/dist/runtime/transport-pool.d.ts.map +1 -0
  82. package/dist/runtime/{worker-pool.js → transport-pool.js} +6 -6
  83. package/dist/runtime/transport-pool.js.map +1 -0
  84. package/dist/runtime/transport.d.ts +14 -8
  85. package/dist/runtime/transport.d.ts.map +1 -1
  86. package/dist/runtime/transport.js +10 -4
  87. package/dist/runtime/transport.js.map +1 -1
  88. package/dist/types/index.d.ts +10 -1
  89. package/dist/types/index.d.ts.map +1 -1
  90. package/dist/tywrap.d.ts +0 -5
  91. package/dist/tywrap.d.ts.map +1 -1
  92. package/dist/tywrap.js +0 -20
  93. package/dist/tywrap.js.map +1 -1
  94. package/dist/utils/cache.d.ts +13 -2
  95. package/dist/utils/cache.d.ts.map +1 -1
  96. package/dist/utils/cache.js +52 -60
  97. package/dist/utils/cache.js.map +1 -1
  98. package/dist/utils/codec.d.ts +1 -1
  99. package/dist/utils/codec.d.ts.map +1 -1
  100. package/dist/utils/codec.js +181 -182
  101. package/dist/utils/codec.js.map +1 -1
  102. package/dist/utils/python.d.ts.map +1 -1
  103. package/dist/utils/python.js +28 -17
  104. package/dist/utils/python.js.map +1 -1
  105. package/dist/utils/runtime.d.ts.map +1 -1
  106. package/dist/utils/runtime.js +22 -16
  107. package/dist/utils/runtime.js.map +1 -1
  108. package/dist/version.d.ts +12 -0
  109. package/dist/version.d.ts.map +1 -0
  110. package/dist/version.js +12 -0
  111. package/dist/version.js.map +1 -0
  112. package/package.json +3 -2
  113. package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
  114. package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
  115. package/runtime/python_bridge.py +54 -6
  116. package/runtime/safe_codec.py +8 -8
  117. package/runtime/tywrap_bridge_core.py +159 -23
  118. package/src/cli.ts +8 -12
  119. package/src/config/index.ts +271 -165
  120. package/src/core/annotation-parser.ts +135 -123
  121. package/src/core/discovery.ts +102 -54
  122. package/src/core/emit-call.ts +171 -0
  123. package/src/core/generator.ts +67 -263
  124. package/src/core/mapper.ts +125 -110
  125. package/src/core/validation.ts +84 -48
  126. package/src/dev.ts +237 -153
  127. package/src/index.ts +11 -68
  128. package/src/runtime/bounded-context.ts +2 -2
  129. package/src/runtime/{safe-codec.ts → bridge-codec.ts} +83 -95
  130. package/src/runtime/{http-io.ts → http-transport.ts} +5 -5
  131. package/src/runtime/http.ts +5 -5
  132. package/src/runtime/index.ts +23 -1
  133. package/src/runtime/node.ts +15 -15
  134. package/src/runtime/pooled-transport.ts +15 -15
  135. package/src/runtime/pyodide-bootstrap-core.generated.ts +1 -1
  136. package/src/runtime/{pyodide-io.ts → pyodide-transport.ts} +7 -7
  137. package/src/runtime/pyodide.ts +6 -6
  138. package/src/runtime/rpc-client.ts +51 -40
  139. package/src/runtime/{process-io.ts → subprocess-transport.ts} +77 -47
  140. package/src/runtime/{worker-pool.ts → transport-pool.ts} +26 -26
  141. package/src/runtime/transport.ts +15 -8
  142. package/src/types/index.ts +10 -1
  143. package/src/tywrap.ts +0 -24
  144. package/src/utils/cache.ts +61 -63
  145. package/src/utils/codec.ts +217 -196
  146. package/src/utils/python.ts +33 -20
  147. package/src/utils/runtime.ts +24 -13
  148. package/src/version.ts +12 -0
  149. package/dist/runtime/http-io.d.ts.map +0 -1
  150. package/dist/runtime/http-io.js.map +0 -1
  151. package/dist/runtime/optimized-node.d.ts +0 -23
  152. package/dist/runtime/optimized-node.d.ts.map +0 -1
  153. package/dist/runtime/optimized-node.js +0 -23
  154. package/dist/runtime/optimized-node.js.map +0 -1
  155. package/dist/runtime/process-io.d.ts.map +0 -1
  156. package/dist/runtime/process-io.js.map +0 -1
  157. package/dist/runtime/protocol.d.ts +0 -3
  158. package/dist/runtime/protocol.d.ts.map +0 -1
  159. package/dist/runtime/protocol.js +0 -3
  160. package/dist/runtime/protocol.js.map +0 -1
  161. package/dist/runtime/pyodide-io.d.ts.map +0 -1
  162. package/dist/runtime/pyodide-io.js.map +0 -1
  163. package/dist/runtime/safe-codec.d.ts.map +0 -1
  164. package/dist/runtime/safe-codec.js.map +0 -1
  165. package/dist/runtime/worker-pool.d.ts.map +0 -1
  166. package/dist/runtime/worker-pool.js.map +0 -1
  167. package/dist/utils/bundle-optimizer.d.ts +0 -182
  168. package/dist/utils/bundle-optimizer.d.ts.map +0 -1
  169. package/dist/utils/bundle-optimizer.js +0 -680
  170. package/dist/utils/bundle-optimizer.js.map +0 -1
  171. package/dist/utils/memory-profiler.d.ts +0 -123
  172. package/dist/utils/memory-profiler.d.ts.map +0 -1
  173. package/dist/utils/memory-profiler.js +0 -426
  174. package/dist/utils/memory-profiler.js.map +0 -1
  175. package/src/runtime/optimized-node.ts +0 -25
  176. package/src/runtime/protocol.ts +0 -2
  177. package/src/utils/bundle-optimizer.ts +0 -876
  178. package/src/utils/memory-profiler.ts +0 -582
@@ -1,876 +0,0 @@
1
- /**
2
- * Bundle Size Optimization for tywrap
3
- * Implements tree-shaking, code splitting, and runtime optimization strategies
4
- */
5
-
6
- import { writeFile } from 'node:fs/promises';
7
- import ts from 'typescript';
8
-
9
- import type { GeneratedCode } from '../types/index.js';
10
-
11
- import { globalCache } from './cache.js';
12
-
13
- export interface BundleAnalysis {
14
- totalSize: number;
15
- compressedSize: number;
16
- compressionRatio: number;
17
- modules: ModuleAnalysis[];
18
- runtime: RuntimeAnalysis;
19
- suggestions: OptimizationSuggestion[];
20
- }
21
-
22
- export interface BundleManifest {
23
- version: string;
24
- target: BundleOptions['target'];
25
- modules: string[];
26
- runtime: BundleOptions['runtimeMode'] | 'minimal';
27
- optimizations: {
28
- treeShaking: boolean;
29
- minified: boolean;
30
- compressed: boolean;
31
- codeSplitting: boolean;
32
- };
33
- stats: BundleAnalysis;
34
- }
35
-
36
- export interface ModuleAnalysis {
37
- name: string;
38
- size: number;
39
- exports: string[];
40
- imports: string[];
41
- unusedExports: string[];
42
- dependencies: string[];
43
- treeshakeable: boolean;
44
- complexity: number;
45
- }
46
-
47
- export interface RuntimeAnalysis {
48
- coreSize: number;
49
- bridgeSize: number;
50
- codecSize: number;
51
- utilsSize: number;
52
- totalSize: number;
53
- minimizable: boolean;
54
- }
55
-
56
- export interface OptimizationSuggestion {
57
- type: 'tree-shaking' | 'code-splitting' | 'runtime-minimal' | 'compression' | 'lazy-loading';
58
- severity: 'low' | 'medium' | 'high';
59
- description: string;
60
- estimatedSaving: number; // bytes
61
- implementation: string;
62
- }
63
-
64
- export interface BundleOptions {
65
- target: 'node' | 'browser' | 'deno' | 'bun' | 'universal';
66
- minify: boolean;
67
- compress: boolean;
68
- treeShaking: boolean;
69
- codeSplitting: boolean;
70
- runtimeMode: 'full' | 'minimal' | 'lazy';
71
- outputFormat: 'esm' | 'cjs' | 'umd';
72
- }
73
-
74
- export class BundleOptimizer {
75
- private options: BundleOptions;
76
- private generatedModules = new Map<string, GeneratedCode>();
77
- private analysisCache = new Map<string, ModuleAnalysis>();
78
-
79
- constructor(options: Partial<BundleOptions> = {}) {
80
- this.options = {
81
- target: 'universal',
82
- minify: true,
83
- compress: true,
84
- treeShaking: true,
85
- codeSplitting: false,
86
- runtimeMode: 'minimal',
87
- outputFormat: 'esm',
88
- ...options,
89
- };
90
- }
91
-
92
- /**
93
- * Add generated module for optimization
94
- */
95
- addModule(name: string, code: GeneratedCode): void {
96
- this.generatedModules.set(name, code);
97
- }
98
-
99
- /**
100
- * Analyze bundle composition and identify optimization opportunities
101
- */
102
- async analyzeBundles(): Promise<BundleAnalysis> {
103
- const modules: ModuleAnalysis[] = [];
104
- let totalSize = 0;
105
-
106
- // Analyze each generated module
107
- for (const [name, code] of this.generatedModules) {
108
- const analysis = await this.analyzeModule(name, code);
109
- modules.push(analysis);
110
- totalSize += analysis.size;
111
- }
112
-
113
- // Analyze runtime overhead
114
- const runtime = this.analyzeRuntime();
115
- totalSize += runtime.totalSize;
116
-
117
- // Estimate compressed size
118
- const compressedSize = this.estimateCompressedSize(totalSize);
119
- const compressionRatio = totalSize > 0 ? compressedSize / totalSize : 0;
120
-
121
- // Generate optimization suggestions
122
- const suggestions = this.generateOptimizationSuggestions(modules, runtime);
123
-
124
- return {
125
- totalSize,
126
- compressedSize,
127
- compressionRatio,
128
- modules,
129
- runtime,
130
- suggestions,
131
- };
132
- }
133
-
134
- /**
135
- * Analyze individual module
136
- */
137
- private async analyzeModule(name: string, code: GeneratedCode): Promise<ModuleAnalysis> {
138
- const cacheKey = globalCache.generateKey('module_analysis', name, code.typescript);
139
- const cached = await globalCache.get<ModuleAnalysis>(cacheKey);
140
- if (cached) {
141
- return cached;
142
- }
143
-
144
- const typescript = code.typescript;
145
- const size = Buffer.byteLength(typescript, 'utf8');
146
-
147
- // Extract exports
148
- const exports = this.extractExports(typescript);
149
-
150
- // Extract imports
151
- const imports = this.extractImports(typescript);
152
-
153
- // Find dependencies
154
- const dependencies = this.extractDependencies(typescript);
155
-
156
- // Calculate complexity
157
- const complexity = this.calculateComplexity(typescript);
158
-
159
- // Check if treeshakeable
160
- const treeshakeable = this.isTreeshakeable(typescript);
161
-
162
- const analysis: ModuleAnalysis = {
163
- name,
164
- size,
165
- exports,
166
- imports,
167
- unusedExports: [], // Would need usage analysis
168
- dependencies,
169
- treeshakeable,
170
- complexity,
171
- };
172
-
173
- // Cache the analysis
174
- await globalCache.set(cacheKey, analysis, { computeTime: 0 });
175
- this.analysisCache.set(name, analysis);
176
-
177
- return analysis;
178
- }
179
-
180
- /**
181
- * Extract export statements from TypeScript code
182
- */
183
- private extractExports(code: string): string[] {
184
- const source = this.parseSource(code);
185
- const exports = new Set<string>();
186
-
187
- const visit = (node: ts.Node): void => {
188
- if (
189
- ts.isFunctionDeclaration(node) ||
190
- ts.isClassDeclaration(node) ||
191
- ts.isInterfaceDeclaration(node) ||
192
- ts.isTypeAliasDeclaration(node) ||
193
- ts.isEnumDeclaration(node)
194
- ) {
195
- if (node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
196
- const name = (node as ts.NamedDeclaration).name?.getText();
197
- if (name) {
198
- exports.add(name);
199
- }
200
- }
201
- } else if (ts.isVariableStatement(node)) {
202
- if (node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
203
- node.declarationList.declarations.forEach(d => {
204
- if (ts.isIdentifier(d.name)) {
205
- exports.add(d.name.text);
206
- }
207
- });
208
- }
209
- } else if (ts.isExportAssignment(node)) {
210
- if (ts.isIdentifier(node.expression)) {
211
- exports.add(node.expression.text);
212
- } else {
213
- exports.add('default');
214
- }
215
- } else if (ts.isExportDeclaration(node)) {
216
- if (node.exportClause && ts.isNamedExports(node.exportClause)) {
217
- node.exportClause.elements.forEach(el => {
218
- exports.add((el.propertyName ?? el.name).text);
219
- });
220
- } else {
221
- exports.add('*');
222
- }
223
- }
224
- ts.forEachChild(node, visit);
225
- };
226
-
227
- visit(source);
228
- return Array.from(exports);
229
- }
230
-
231
- /**
232
- * Extract import statements from TypeScript code
233
- */
234
- private extractImports(code: string): string[] {
235
- const source = this.parseSource(code);
236
- const imports = new Set<string>();
237
-
238
- const visit = (node: ts.Node): void => {
239
- if (ts.isImportDeclaration(node)) {
240
- const moduleName = (node.moduleSpecifier as ts.StringLiteral).text;
241
- imports.add(moduleName);
242
- const clause = node.importClause;
243
- if (clause) {
244
- if (clause.name) {
245
- imports.add(clause.name.text);
246
- }
247
- if (clause.namedBindings) {
248
- if (ts.isNamedImports(clause.namedBindings)) {
249
- clause.namedBindings.elements.forEach(el => {
250
- imports.add((el.propertyName ?? el.name).text);
251
- });
252
- } else if (ts.isNamespaceImport(clause.namedBindings)) {
253
- imports.add(clause.namedBindings.name.text);
254
- }
255
- }
256
- }
257
- } else if (
258
- ts.isCallExpression(node) &&
259
- node.expression.kind === ts.SyntaxKind.ImportKeyword
260
- ) {
261
- const arg = node.arguments[0];
262
- if (arg && ts.isStringLiteral(arg)) {
263
- imports.add(arg.text);
264
- }
265
- }
266
- ts.forEachChild(node, visit);
267
- };
268
-
269
- visit(source);
270
- return Array.from(imports);
271
- }
272
-
273
- /**
274
- * Extract module dependencies
275
- */
276
- private extractDependencies(code: string): string[] {
277
- const source = this.parseSource(code);
278
- const dependencies = new Set<string>();
279
-
280
- const visit = (node: ts.Node): void => {
281
- if (ts.isPropertyAccessExpression(node)) {
282
- const expr = node.expression;
283
- if (
284
- ts.isCallExpression(expr) &&
285
- ts.isIdentifier(expr.expression) &&
286
- expr.expression.text === 'getRuntimeBridge'
287
- ) {
288
- dependencies.add('runtime-bridge');
289
- }
290
- }
291
-
292
- if (ts.isIdentifier(node)) {
293
- if (node.text === 'decodeValue' || node.text === 'encodeValue') {
294
- dependencies.add('codec');
295
- }
296
- }
297
-
298
- if (ts.isImportDeclaration(node)) {
299
- const moduleName = (node.moduleSpecifier as ts.StringLiteral).text;
300
- if (node.importClause?.isTypeOnly) {
301
- dependencies.add(moduleName);
302
- } else {
303
- const comments = ts.getTrailingCommentRanges(code, node.end) ?? [];
304
- for (const range of comments) {
305
- const comment = code.slice(range.pos, range.end);
306
- if (/types?/i.test(comment)) {
307
- dependencies.add(moduleName);
308
- break;
309
- }
310
- }
311
- }
312
- }
313
-
314
- ts.forEachChild(node, visit);
315
- };
316
-
317
- visit(source);
318
- return Array.from(dependencies);
319
- }
320
-
321
- private parseSource(code: string): ts.SourceFile {
322
- return ts.createSourceFile('module.ts', code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
323
- }
324
-
325
- /**
326
- * Calculate code complexity score
327
- */
328
- private calculateComplexity(code: string): number {
329
- let complexity = 0;
330
-
331
- // Function complexity
332
- const functions = (code.match(/\bfunction\b/g) ?? []).length;
333
- const asyncFunctions = (code.match(/\basync\s+function\b/g) ?? []).length;
334
- complexity += functions + asyncFunctions * 1.5;
335
-
336
- // Class complexity
337
- const classes = (code.match(/\bclass\b/g) ?? []).length;
338
- complexity += classes * 2;
339
-
340
- // Interface complexity
341
- const interfaces = (code.match(/\binterface\b/g) ?? []).length;
342
- complexity += interfaces * 0.5;
343
-
344
- // Type alias complexity
345
- const types = (code.match(/\btype\b/g) ?? []).length;
346
- complexity += types * 0.3;
347
-
348
- // Generic complexity
349
- const generics = (code.match(/<[^>]*>/g) ?? []).length;
350
- complexity += generics * 0.2;
351
-
352
- return Math.round(complexity);
353
- }
354
-
355
- /**
356
- * Check if code is tree-shakeable
357
- */
358
- private isTreeshakeable(code: string): boolean {
359
- // Code is tree-shakeable if it uses ES modules and has no side effects
360
- const hasESModules = code.includes('export') && !code.includes('module.exports');
361
- const hasSideEffects =
362
- code.includes('console.') || code.includes('window.') || code.includes('global.');
363
-
364
- return hasESModules && !hasSideEffects;
365
- }
366
-
367
- /**
368
- * Analyze runtime overhead
369
- */
370
- private analyzeRuntime(): RuntimeAnalysis {
371
- const estimates = {
372
- coreSize: 5 * 1024, // 5KB - Core runtime logic
373
- bridgeSize: this.estimateBridgeSize(),
374
- codecSize: 8 * 1024, // 8KB - Codec utilities
375
- utilsSize: 3 * 1024, // 3KB - Utility functions
376
- };
377
-
378
- const totalSize = Object.values(estimates).reduce((sum, size) => sum + size, 0);
379
-
380
- return {
381
- ...estimates,
382
- totalSize,
383
- minimizable: this.options.runtimeMode !== 'full',
384
- };
385
- }
386
-
387
- /**
388
- * Estimate bridge size based on target
389
- */
390
- private estimateBridgeSize(): number {
391
- switch (this.options.target) {
392
- case 'node':
393
- return 12 * 1024; // 12KB - Node.js specific bridge
394
- case 'browser':
395
- return 15 * 1024; // 15KB - Browser/Pyodide bridge
396
- case 'deno':
397
- return 10 * 1024; // 10KB - Deno bridge
398
- case 'bun':
399
- return 8 * 1024; // 8KB - Bun bridge
400
- case 'universal':
401
- return 25 * 1024; // 25KB - Universal bridge with all runtimes
402
- default:
403
- return 15 * 1024;
404
- }
405
- }
406
-
407
- /**
408
- * Estimate compressed size using typical compression ratios
409
- */
410
- private estimateCompressedSize(originalSize: number): number {
411
- // Typical gzip compression ratios for JavaScript/TypeScript:
412
- // - Minified: 3:1 to 4:1
413
- // - Non-minified: 4:1 to 6:1
414
- const compressionRatio = this.options.minify ? 3.5 : 5;
415
- return Math.round(originalSize / compressionRatio);
416
- }
417
-
418
- /**
419
- * Generate optimization suggestions
420
- */
421
- private generateOptimizationSuggestions(
422
- modules: ModuleAnalysis[],
423
- runtime: RuntimeAnalysis
424
- ): OptimizationSuggestion[] {
425
- const suggestions: OptimizationSuggestion[] = [];
426
-
427
- // Tree-shaking suggestions
428
- if (this.options.treeShaking) {
429
- const treeshakeableModules = modules.filter(m => m.treeshakeable);
430
- if (treeshakeableModules.length > 0) {
431
- const estimatedSaving = treeshakeableModules.reduce((sum, m) => sum + m.size * 0.3, 0);
432
- suggestions.push({
433
- type: 'tree-shaking',
434
- severity: estimatedSaving > 10 * 1024 ? 'high' : 'medium',
435
- description: `Enable tree-shaking for ${treeshakeableModules.length} modules`,
436
- estimatedSaving,
437
- implementation: 'Configure bundler with ES modules and sideEffects: false',
438
- });
439
- }
440
- }
441
-
442
- // Code splitting suggestions
443
- const largeModules = modules.filter(m => m.size > 50 * 1024);
444
- if (largeModules.length > 1 && !this.options.codeSplitting) {
445
- const estimatedSaving = largeModules.reduce((sum, m) => sum + m.size * 0.2, 0);
446
- suggestions.push({
447
- type: 'code-splitting',
448
- severity: 'medium',
449
- description: `Split ${largeModules.length} large modules for lazy loading`,
450
- estimatedSaving,
451
- implementation: 'Implement dynamic imports and route-based code splitting',
452
- });
453
- }
454
-
455
- // Runtime optimization suggestions
456
- if (runtime.minimizable && this.options.runtimeMode === 'full') {
457
- const estimatedSaving = runtime.totalSize * 0.4;
458
- suggestions.push({
459
- type: 'runtime-minimal',
460
- severity: 'medium',
461
- description: 'Use minimal runtime for production builds',
462
- estimatedSaving,
463
- implementation: 'Set runtimeMode to "minimal" and include only used features',
464
- });
465
- }
466
-
467
- // Compression suggestions
468
- if (!this.options.compress) {
469
- const totalSize = modules.reduce((sum, m) => sum + m.size, 0) + runtime.totalSize;
470
- const estimatedSaving = totalSize - this.estimateCompressedSize(totalSize);
471
- suggestions.push({
472
- type: 'compression',
473
- severity: estimatedSaving > 20 * 1024 ? 'high' : 'medium',
474
- description: 'Enable gzip/brotli compression',
475
- estimatedSaving,
476
- implementation: 'Configure web server or CDN with compression enabled',
477
- });
478
- }
479
-
480
- // Lazy loading suggestions
481
- const complexModules = modules.filter(m => m.complexity > 10);
482
- if (complexModules.length > 0) {
483
- const estimatedSaving = complexModules.reduce((sum, m) => sum + m.size * 0.5, 0);
484
- suggestions.push({
485
- type: 'lazy-loading',
486
- severity: 'low',
487
- description: `Implement lazy loading for ${complexModules.length} complex modules`,
488
- estimatedSaving,
489
- implementation: 'Use dynamic imports and load modules on demand',
490
- });
491
- }
492
-
493
- // Sort suggestions by estimated saving
494
- return suggestions.sort((a, b) => b.estimatedSaving - a.estimatedSaving);
495
- }
496
-
497
- /**
498
- * Generate optimized bundle with tree-shaking
499
- */
500
- async generateOptimizedBundle(): Promise<{
501
- modules: Map<string, string>;
502
- runtime: string;
503
- manifest: BundleManifest;
504
- }> {
505
- const optimizedModules = new Map<string, string>();
506
-
507
- // Apply optimizations to each module
508
- for (const [name, code] of this.generatedModules) {
509
- let optimized = code.typescript;
510
-
511
- if (this.options.treeShaking) {
512
- optimized = this.applyTreeShaking(optimized);
513
- }
514
-
515
- if (this.options.minify) {
516
- optimized = this.applyMinification(optimized);
517
- }
518
-
519
- optimizedModules.set(name, optimized);
520
- }
521
-
522
- // Generate minimal runtime
523
- const runtime = this.generateMinimalRuntime();
524
-
525
- // Create bundle manifest
526
- const manifest: BundleManifest = {
527
- version: '1.0.0',
528
- target: this.options.target,
529
- modules: Array.from(optimizedModules.keys()),
530
- runtime: 'minimal',
531
- optimizations: {
532
- treeShaking: this.options.treeShaking,
533
- minified: this.options.minify,
534
- compressed: this.options.compress,
535
- codeSplitting: this.options.codeSplitting,
536
- },
537
- stats: await this.analyzeBundles(),
538
- };
539
-
540
- return { modules: optimizedModules, runtime, manifest };
541
- }
542
-
543
- /**
544
- * Apply tree-shaking to remove unused exports
545
- */
546
- private applyTreeShaking(code: string): string {
547
- // Basic tree-shaking implementation
548
- // In a real implementation, this would use AST analysis
549
-
550
- // Remove unused function declarations
551
- const usedFunctions = this.findUsedFunctions(code);
552
- const lines = code.split('\n');
553
- const filteredLines = lines.filter(line => {
554
- const functionName = this.getExportedFunctionName(line);
555
- if (functionName) {
556
- return usedFunctions.has(functionName);
557
- }
558
- return true;
559
- });
560
-
561
- return filteredLines.join('\n');
562
- }
563
-
564
- /**
565
- * Find functions that are actually used
566
- */
567
- private findUsedFunctions(code: string): Set<string> {
568
- const used = new Set<string>();
569
-
570
- // Find all function calls
571
- const callRegex = /(\w+)\s*\(/g;
572
- let match;
573
- while ((match = callRegex.exec(code)) !== null) {
574
- if (match[1]) {
575
- used.add(match[1]);
576
- }
577
- }
578
-
579
- // Find all exported functions (assume all exports are used externally)
580
- for (const line of code.split('\n')) {
581
- const exportName = this.getExportedFunctionName(line);
582
- if (exportName) {
583
- used.add(exportName);
584
- }
585
- }
586
-
587
- return used;
588
- }
589
-
590
- private getExportedFunctionName(line: string): string | null {
591
- const trimmed = line.trimStart();
592
- if (!trimmed.startsWith('export ')) {
593
- return null;
594
- }
595
- const tokens = this.splitWhitespace(trimmed);
596
- const functionIndex = tokens.indexOf('function');
597
- if (functionIndex === -1) {
598
- return null;
599
- }
600
- const nameToken = tokens[functionIndex + 1];
601
- if (!nameToken) {
602
- return null;
603
- }
604
- const name = nameToken.split('(')[0];
605
- if (!name) {
606
- return null;
607
- }
608
- return name;
609
- }
610
-
611
- private splitWhitespace(value: string): string[] {
612
- const tokens: string[] = [];
613
- let current = '';
614
- for (const char of value) {
615
- if (char === ' ' || char === '\t' || char === '\n' || char === '\r') {
616
- if (current) {
617
- tokens.push(current);
618
- current = '';
619
- }
620
- } else {
621
- current += char;
622
- }
623
- }
624
- if (current) {
625
- tokens.push(current);
626
- }
627
- return tokens;
628
- }
629
-
630
- /**
631
- * Apply basic minification
632
- */
633
- private applyMinification(code: string): string {
634
- let minified = code;
635
-
636
- // Remove comments
637
- minified = minified.replace(/\/\*[\s\S]*?\*\//g, '');
638
- minified = minified.replace(/\/\/.*$/gm, '');
639
-
640
- // Remove extra whitespace
641
- minified = minified.replace(/\s+/g, ' ');
642
- minified = minified.replace(/\s*([{}();,:])\s*/g, '$1');
643
-
644
- // Remove trailing semicolons before }
645
- minified = minified.replace(/;\s*}/g, '}');
646
-
647
- return minified.trim();
648
- }
649
-
650
- /**
651
- * Generate minimal runtime based on target
652
- */
653
- private generateMinimalRuntime(): string {
654
- const baseRuntime = `
655
- // tywrap minimal runtime
656
- let __runtimeBridge;
657
-
658
- const getRuntimeBridge = () => {
659
- if (!__runtimeBridge) {
660
- throw new Error('No runtime bridge configured. Call setRuntimeBridge(...) before using generated modules.');
661
- }
662
- return __runtimeBridge;
663
- };
664
-
665
- const setRuntimeBridge = (bridge) => {
666
- __runtimeBridge = bridge;
667
- };
668
-
669
- const runtimeBridge = {
670
- async call(module, functionName, args, kwargs) {
671
- // Target-specific bridge implementation
672
- ${this.generateBridgeImplementation('call')}
673
- },
674
- async instantiate(module, className, args, kwargs) {
675
- // Target-specific bridge implementation
676
- ${this.generateBridgeImplementation('instantiate')}
677
- },
678
- async callMethod(handle, methodName, args, kwargs) {
679
- // Target-specific bridge implementation
680
- ${this.generateBridgeImplementation('call_method')}
681
- },
682
- async disposeInstance(handle) {
683
- // Target-specific bridge implementation
684
- ${this.generateBridgeImplementation('dispose_instance')}
685
- }
686
- };
687
-
688
- setRuntimeBridge(runtimeBridge);
689
-
690
- // Minimal codec
691
- const decodeValue = (value) => {
692
- if (value?.__tywrap__) {
693
- // Handle special types
694
- return value.data ?? value;
695
- }
696
- return value;
697
- };
698
-
699
- export { getRuntimeBridge, setRuntimeBridge, decodeValue };
700
- `;
701
-
702
- return this.options.minify ? this.applyMinification(baseRuntime) : baseRuntime;
703
- }
704
-
705
- /**
706
- * Generate bridge implementation based on target
707
- */
708
- private generateBridgeImplementation(
709
- method: 'call' | 'instantiate' | 'call_method' | 'dispose_instance'
710
- ): string {
711
- switch (this.options.target) {
712
- case 'node':
713
- return `
714
- // Node.js subprocess bridge
715
- if (!this._process) {
716
- const { spawn } = require('child_process');
717
- this._process = spawn('python3', ['python_bridge.py']);
718
- }
719
- // Implementation details for ${method}...
720
- return result;
721
- `;
722
-
723
- case 'browser':
724
- return `
725
- // Pyodide browser bridge
726
- if (!globalThis.pyodide) {
727
- await import('pyodide');
728
- }
729
- // Implementation details for ${method}...
730
- return result;
731
- `;
732
-
733
- default:
734
- return `
735
- // Universal bridge - detect environment
736
- if (typeof process !== 'undefined') {
737
- // Node.js environment
738
- } else if (typeof window !== 'undefined') {
739
- // Browser environment
740
- }
741
- // Implementation details for ${method}...
742
- return result;
743
- `;
744
- }
745
- }
746
-
747
- /**
748
- * Save bundle analysis report
749
- */
750
- async saveAnalysisReport(filePath: string): Promise<BundleAnalysis> {
751
- const analysis = await this.analyzeBundles();
752
-
753
- const report = {
754
- timestamp: new Date().toISOString(),
755
- options: this.options,
756
- analysis,
757
- modules: Object.fromEntries(this.generatedModules.entries()),
758
- };
759
-
760
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- report path is caller-provided
761
- await writeFile(filePath, JSON.stringify(report, null, 2));
762
- process.stdout.write(`Bundle analysis report saved to ${filePath}\n`);
763
-
764
- return analysis;
765
- }
766
-
767
- /**
768
- * Clear all modules and cache
769
- */
770
- clear(): void {
771
- this.generatedModules.clear();
772
- this.analysisCache.clear();
773
- }
774
- }
775
-
776
- interface RollupBundleChunk {
777
- code?: string;
778
- }
779
-
780
- type RollupBundle = Record<string, RollupBundleChunk>;
781
-
782
- interface RollupPlugin {
783
- name: string;
784
- generateBundle: (options: unknown, bundle: RollupBundle) => void;
785
- writeBundle: () => Promise<void>;
786
- }
787
-
788
- interface WebpackAsset {
789
- source: () => string | Buffer;
790
- }
791
-
792
- interface WebpackCompilation {
793
- assets: Record<string, WebpackAsset>;
794
- }
795
-
796
- interface WebpackCompiler {
797
- hooks: {
798
- emit: {
799
- tap: (name: string, handler: (compilation: WebpackCompilation) => void) => void;
800
- };
801
- };
802
- }
803
-
804
- interface WebpackPlugin {
805
- apply: (compiler: WebpackCompiler) => void;
806
- }
807
-
808
- // Export utilities for rollup/webpack integration
809
- export function createRollupPlugin(options: Partial<BundleOptions> = {}): RollupPlugin {
810
- const optimizer = new BundleOptimizer(options);
811
-
812
- return {
813
- name: 'tywrap-optimizer',
814
- generateBundle(_options: unknown, bundle: RollupBundle): void {
815
- // Integrate with Rollup bundle generation
816
- for (const [fileName, chunk] of Object.entries(bundle)) {
817
- if (
818
- (fileName.endsWith('.ts') || fileName.endsWith('.js')) &&
819
- typeof chunk.code === 'string'
820
- ) {
821
- // Add to optimizer for analysis
822
- const code = {
823
- typescript: chunk.code,
824
- declaration: '',
825
- metadata: {
826
- generatedAt: new Date(),
827
- sourceFiles: [fileName],
828
- runtime: 'node' as const,
829
- optimizations: ['tree-shaking'],
830
- },
831
- };
832
- optimizer.addModule(fileName, code);
833
- }
834
- }
835
- },
836
- async writeBundle(): Promise<void> {
837
- // Generate analysis report
838
- await optimizer.saveAnalysisReport('bundle-analysis.json');
839
- },
840
- };
841
- }
842
-
843
- export function createWebpackPlugin(options: Partial<BundleOptions> = {}): WebpackPlugin {
844
- const optimizer = new BundleOptimizer(options);
845
-
846
- return {
847
- apply(compiler: WebpackCompiler): void {
848
- compiler.hooks.emit.tap('TywrapOptimizerPlugin', (compilation: WebpackCompilation) => {
849
- // Process webpack assets
850
- for (const [filename, asset] of Object.entries(compilation.assets)) {
851
- if (filename.endsWith('.js') || filename.endsWith('.ts')) {
852
- const assetSource = asset.source();
853
- const sourceText =
854
- typeof assetSource === 'string' ? assetSource : assetSource.toString();
855
- const code = {
856
- typescript: sourceText,
857
- declaration: '',
858
- metadata: {
859
- generatedAt: new Date(),
860
- sourceFiles: [filename],
861
- runtime: 'node' as const,
862
- optimizations: ['minification'],
863
- },
864
- };
865
- optimizer.addModule(filename, code);
866
- }
867
- }
868
-
869
- // Generate analysis
870
- optimizer.saveAnalysisReport('webpack-bundle-analysis.json').catch(error => {
871
- process.stderr.write(`Failed to write webpack analysis report: ${String(error)}\n`);
872
- });
873
- });
874
- },
875
- };
876
- }