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