@eliasku/ts-transformers 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -218
- package/package.json +1 -1
- package/src/index.ts +5 -7
- package/src/types.ts +5 -14
package/README.md
CHANGED
|
@@ -1,114 +1,74 @@
|
|
|
1
1
|
# @eliasku/ts-transformers
|
|
2
2
|
|
|
3
|
-
TypeScript transformer for code
|
|
3
|
+
TypeScript transformer for aggressive code minification through type-aware property renaming and const enum inlining.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Important Requirement
|
|
6
6
|
|
|
7
7
|
**You must compile ALL your code from TypeScript files.**
|
|
8
8
|
|
|
9
|
-
- No pre-transpiled
|
|
10
|
-
- Transformer requires TypeScript type information
|
|
11
|
-
-
|
|
12
|
-
- Mix of `.ts` and `.js` sources → partial optimization, unexpected results
|
|
9
|
+
- No pre-transpiled `.js` files in source
|
|
10
|
+
- Transformer requires TypeScript type information
|
|
11
|
+
- Applicable for application builds, not libraries
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
## Core Concept
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
Two-phase optimization pipeline:
|
|
17
16
|
|
|
18
|
-
This
|
|
17
|
+
1. **This Transformer**: Analyzes TypeScript types, marks renamable properties with special prefixes
|
|
18
|
+
2. **Minifier (esbuild)**: Aggressively mangles prefixed properties while preserving public API
|
|
19
19
|
|
|
20
|
-
###
|
|
20
|
+
### Visibility Levels
|
|
21
21
|
|
|
22
|
-
Based on type analysis, properties are categorized
|
|
22
|
+
Based on type analysis, properties are categorized as:
|
|
23
23
|
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **Private**: Private class members → prefixed with `$p$` (e.g., `$p$privateMethod`)
|
|
27
|
-
|
|
28
|
-
The special prefixes make these properties easily detectable by downstream minifiers (like **esbuild** or **terser**) for aggressive mangling, while preserving your public API surface.
|
|
24
|
+
- **Public (External)**: Exported from entry points → **no prefix** (preserved)
|
|
25
|
+
- **Private**: Everything else → prefixed with `$_` (mangled by minifier)
|
|
29
26
|
|
|
30
27
|
**Example:**
|
|
31
28
|
```typescript
|
|
32
29
|
// Before
|
|
33
30
|
class MyClass {
|
|
34
|
-
/** @public
|
|
35
|
-
publicApi() {}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
private secret = 1; // Private → renamed to $p$secret
|
|
31
|
+
/** @public - keeps name */
|
|
32
|
+
publicApi() {}
|
|
33
|
+
|
|
34
|
+
method() {} // Private → $_method
|
|
35
|
+
private secret = 1; // Private → $_secret
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
// After transformer (before minifier)
|
|
43
39
|
class MyClass {
|
|
44
40
|
publicApi() {}
|
|
45
|
-
$
|
|
46
|
-
$
|
|
47
|
-
$p$secret = 1;
|
|
41
|
+
$_method() {}
|
|
42
|
+
$_secret = 1;
|
|
48
43
|
}
|
|
49
44
|
|
|
50
|
-
// After esbuild minifier
|
|
51
|
-
class A{publicApi(){},a(){},b
|
|
45
|
+
// After esbuild minifier
|
|
46
|
+
class A{publicApi(){},a(){},b=1}
|
|
52
47
|
```
|
|
53
48
|
|
|
54
|
-
###
|
|
55
|
-
|
|
56
|
-
Const enums are compile-time constants that should never exist at runtime. This transformer:
|
|
49
|
+
### Const Enum Inlining
|
|
57
50
|
|
|
58
|
-
|
|
59
|
-
- Removes const enum declarations from output
|
|
60
|
-
- Strips `const` modifier from declarations in `.d.ts` files
|
|
61
|
-
- Removes unused const enum imports
|
|
51
|
+
Replaces const enum accesses with literal values and removes declarations.
|
|
62
52
|
|
|
63
|
-
**Example:**
|
|
64
53
|
```typescript
|
|
65
54
|
// Before
|
|
66
|
-
const enum Status {
|
|
67
|
-
|
|
68
|
-
Inactive = 0
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const status = Status.Active; // Access
|
|
55
|
+
const enum Status { Active = 1, Inactive = 0 }
|
|
56
|
+
const status = Status.Active;
|
|
72
57
|
|
|
73
|
-
// After transformer
|
|
58
|
+
// After transformer + minifier
|
|
74
59
|
const status = 1;
|
|
75
|
-
// Status declaration removed, import removed
|
|
76
60
|
```
|
|
77
61
|
|
|
78
|
-
## Workflow
|
|
79
|
-
|
|
80
|
-
This transformer is **Phase 1** of a two-phase optimization pipeline:
|
|
81
|
-
|
|
82
|
-
### Phase 1: Type-Aware Preparation (This Transformer)
|
|
83
|
-
- **Input**: TypeScript source files
|
|
84
|
-
- **Process**: Analyze types, detect visibility, apply prefixes, inline const enums
|
|
85
|
-
- **Output**: ES modules with detectable prefixes
|
|
86
|
-
- **Tools**: `@eliasku/ts-transformers` + Rollup + TypeScript compiler
|
|
87
|
-
|
|
88
|
-
### Phase 2: Aggressive Minification (esbuild / terser)
|
|
89
|
-
- **Input**: ES modules from Phase 1
|
|
90
|
-
- **Process**: Detect prefixes, mangle aggressively, apply all minification techniques
|
|
91
|
-
- **Output**: Minified bundle with preserved public API
|
|
92
|
-
- **Tools**: esbuild with property mangling
|
|
93
|
-
|
|
94
|
-
**Why Two Phases?**
|
|
95
|
-
- TypeScript types are only available during compilation (Phase 1)
|
|
96
|
-
- Production minifiers (Phase 2) are faster and more sophisticated
|
|
97
|
-
- Prefixes bridge the gap: Phase 1 marks what's safe to mangle, Phase 2 performs the mangling
|
|
98
|
-
|
|
99
62
|
## Usage
|
|
100
63
|
|
|
101
|
-
[Example Code](./example/build.ts)
|
|
102
|
-
|
|
103
64
|
```typescript
|
|
104
65
|
import { optimizer } from "@eliasku/ts-transformers";
|
|
105
66
|
import typescript from "@rollup/plugin-typescript";
|
|
106
67
|
import { rollup } from "rollup";
|
|
107
68
|
import { build } from "esbuild";
|
|
108
69
|
|
|
109
|
-
// Phase 1: Type-aware optimization
|
|
70
|
+
// Phase 1: Type-aware optimization
|
|
110
71
|
const bundle = await rollup({
|
|
111
|
-
/// ...
|
|
112
72
|
input: "./src/index.ts",
|
|
113
73
|
plugins: [
|
|
114
74
|
typescript({
|
|
@@ -125,193 +85,79 @@ const bundle = await rollup({
|
|
|
125
85
|
});
|
|
126
86
|
|
|
127
87
|
await bundle.write({
|
|
128
|
-
/// ...
|
|
129
88
|
file: "./dist/bundle.js",
|
|
130
89
|
format: "es",
|
|
131
90
|
});
|
|
132
91
|
|
|
133
|
-
// Phase 2: Aggressive minification
|
|
92
|
+
// Phase 2: Aggressive minification
|
|
134
93
|
await build({
|
|
135
94
|
entryPoints: ["./dist/bundle.js"],
|
|
136
95
|
outfile: "./dist/bundle.min.js",
|
|
137
96
|
minify: true,
|
|
138
|
-
mangleProps: /^\$
|
|
97
|
+
mangleProps: /^\$_/, // Match your privatePrefix
|
|
139
98
|
mangleQuoted: false,
|
|
140
99
|
keepNames: false,
|
|
141
|
-
/// ...
|
|
142
|
-
});
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
### Customizing esbuild to Match Your Prefixes
|
|
146
|
-
|
|
147
|
-
If you customize the prefix options, update esbuild config to match:
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
optimizer(program, {
|
|
151
|
-
entrySourceFiles: ["./src/index.ts"],
|
|
152
|
-
internalPrefix: "_int_", // Custom internal prefix
|
|
153
|
-
privatePrefix: "_priv_", // Custom private prefix
|
|
154
100
|
});
|
|
155
|
-
|
|
156
|
-
// Then in esbuild:
|
|
157
|
-
mangleProps: /^(_int_|_priv_)/, // Match custom prefixes
|
|
158
101
|
```
|
|
159
102
|
|
|
160
103
|
## Options
|
|
161
104
|
|
|
162
105
|
### entrySourceFiles (required)
|
|
163
106
|
|
|
164
|
-
|
|
107
|
+
Entry points defining your public API surface.
|
|
165
108
|
|
|
166
109
|
```typescript
|
|
167
110
|
entrySourceFiles: ["./src/index.ts"]
|
|
168
111
|
```
|
|
169
112
|
|
|
170
|
-
###
|
|
171
|
-
|
|
172
|
-
Prefix for internal properties (not exported, but used across your codebase). These will be aggressively mangled by esbuild.
|
|
173
|
-
|
|
174
|
-
```typescript
|
|
175
|
-
internalPrefix: "$i$" // default: myFunction → $i$myFunction
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### privatePrefix (optional, default: "$p$")
|
|
113
|
+
### privatePrefix (optional, default: "$_")
|
|
179
114
|
|
|
180
|
-
Prefix for private
|
|
115
|
+
Prefix for private properties that will be mangled by esbuild.
|
|
181
116
|
|
|
182
117
|
```typescript
|
|
183
|
-
privatePrefix: "$
|
|
118
|
+
privatePrefix: "$_" // myFunction → $_myFunction
|
|
184
119
|
```
|
|
185
120
|
|
|
186
121
|
### publicJSDocTag (optional, default: "public")
|
|
187
122
|
|
|
188
|
-
JSDoc tag
|
|
123
|
+
JSDoc tag marking types/properties as public. Set to empty string to disable.
|
|
189
124
|
|
|
190
125
|
```typescript
|
|
191
|
-
publicJSDocTag: "public"
|
|
126
|
+
publicJSDocTag: "public"
|
|
192
127
|
|
|
193
128
|
class MyClass {
|
|
194
129
|
/** @public */
|
|
195
|
-
apiMethod() {} //
|
|
130
|
+
apiMethod() {} // Public, no prefix
|
|
196
131
|
|
|
197
|
-
internalHelper() {} //
|
|
132
|
+
internalHelper() {} // Private, gets $_ prefix
|
|
198
133
|
}
|
|
199
134
|
```
|
|
200
135
|
|
|
201
136
|
### ignoreDecorated (optional, default: false)
|
|
202
137
|
|
|
203
|
-
|
|
138
|
+
Skip renaming decorated fields.
|
|
204
139
|
|
|
205
140
|
```typescript
|
|
206
|
-
ignoreDecorated: true
|
|
141
|
+
ignoreDecorated: true
|
|
207
142
|
|
|
208
|
-
@Component({
|
|
209
|
-
selector: "app-root"
|
|
210
|
-
})
|
|
143
|
+
@Component({ selector: "app-root" })
|
|
211
144
|
class AppComponent {
|
|
212
|
-
@Input() data: any; //
|
|
213
|
-
private internal = 1; //
|
|
145
|
+
@Input() data: any; // Not renamed
|
|
146
|
+
private internal = 1; // Renamed to $_internal
|
|
214
147
|
}
|
|
215
148
|
```
|
|
216
149
|
|
|
217
150
|
### inlineConstEnums (optional, default: true)
|
|
218
151
|
|
|
219
|
-
|
|
152
|
+
Inline const enum values and remove declarations.
|
|
220
153
|
|
|
221
|
-
##
|
|
222
|
-
|
|
223
|
-
### Example 1: Simple Property Renaming
|
|
224
|
-
|
|
225
|
-
```typescript
|
|
226
|
-
// src/index.ts (before)
|
|
227
|
-
class Calculator {
|
|
228
|
-
add(a: number, b: number): number {
|
|
229
|
-
return a + b;
|
|
230
|
-
}
|
|
231
|
-
logResult(value: number): void {
|
|
232
|
-
console.log(value);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
export const calc = new Calculator();
|
|
237
|
-
export { Calculator };
|
|
238
|
-
|
|
239
|
-
// After transformer (before minifier)
|
|
240
|
-
class Calculator {
|
|
241
|
-
add(a, b) { return a + b; } // Exported method → no prefix
|
|
242
|
-
$i$logResult(value) { // Internal method → $i$ prefix
|
|
243
|
-
console.log(value);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
### Example 2: JSDoc Public Annotation
|
|
154
|
+
## Complete Example
|
|
249
155
|
|
|
250
156
|
```typescript
|
|
251
157
|
// src/index.ts (before)
|
|
252
|
-
class API {
|
|
253
|
-
/** @public */
|
|
254
|
-
fetchData(url: string): Promise<Data> {
|
|
255
|
-
return fetch(url);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
private cache = new Map();
|
|
259
|
-
internalTransform(data: Data): Processed {
|
|
260
|
-
// transformation logic
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export const api = new API();
|
|
265
|
-
|
|
266
|
-
// After transformer (before minifier)
|
|
267
|
-
class API {
|
|
268
|
-
fetchData(url) { return fetch(url); } // @public → no prefix
|
|
269
|
-
$p$cache = new Map(); // Private → $p$ prefix
|
|
270
|
-
$i$internalTransform(data) { // Internal → $i$ prefix
|
|
271
|
-
// transformation logic
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
```
|
|
275
|
-
|
|
276
|
-
### Example 3: Const Enum Inlining
|
|
277
|
-
|
|
278
|
-
```typescript
|
|
279
|
-
// src/index.ts (before)
|
|
280
|
-
const enum LogLevel {
|
|
281
|
-
Debug = 0,
|
|
282
|
-
Info = 1,
|
|
283
|
-
Error = 2
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function log(level: LogLevel, message: string): void {
|
|
287
|
-
console.log(`[${LogLevel[level]}] ${message}`);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export { log, LogLevel };
|
|
291
|
-
|
|
292
|
-
// After transformer (before minifier)
|
|
293
|
-
function log(level, message) {
|
|
294
|
-
console.log(`[${level}] ${message}`);
|
|
295
|
-
}
|
|
296
|
-
export { log };
|
|
297
|
-
|
|
298
|
-
// After esbuild minifier
|
|
299
|
-
function log(n,e){console.log(`[${n}]${e}`)}export{log};
|
|
300
|
-
// LogLevel values inlined: LogLevel.Debug → 0, LogLevel.Info → 1, etc.
|
|
301
|
-
// LogLevel enum declaration removed entirely
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
### Example 4: Complete Transformation + Minification
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
// src/index.ts (before)
|
|
308
|
-
const enum HttpStatus {
|
|
309
|
-
OK = 200,
|
|
310
|
-
NotFound = 404
|
|
311
|
-
}
|
|
312
|
-
|
|
313
158
|
class API {
|
|
314
159
|
private baseUrl = "https://api.example.com";
|
|
160
|
+
|
|
315
161
|
/** @public */
|
|
316
162
|
async get(path: string): Promise<Response> {
|
|
317
163
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -322,36 +168,24 @@ class API {
|
|
|
322
168
|
private async handleResponse(response: Response): Promise<Response> {
|
|
323
169
|
return response;
|
|
324
170
|
}
|
|
325
|
-
|
|
326
|
-
logStatus(status: HttpStatus): void {
|
|
327
|
-
console.log(status);
|
|
328
|
-
}
|
|
329
171
|
}
|
|
330
172
|
|
|
331
173
|
export const api = new API();
|
|
332
174
|
|
|
333
|
-
// After transformer
|
|
175
|
+
// After transformer
|
|
334
176
|
class API {
|
|
335
|
-
$
|
|
177
|
+
$_baseUrl = "https://api.example.com";
|
|
336
178
|
async get(path) {
|
|
337
|
-
const url = `${this.$
|
|
179
|
+
const url = `${this.$_baseUrl}${path}`;
|
|
338
180
|
const response = await fetch(url);
|
|
339
|
-
return this.$
|
|
181
|
+
return this.$_handleResponse(response);
|
|
340
182
|
}
|
|
341
183
|
|
|
342
|
-
$
|
|
184
|
+
$_handleResponse(response) {
|
|
343
185
|
return response;
|
|
344
186
|
}
|
|
345
|
-
|
|
346
|
-
$i$logStatus(status) {
|
|
347
|
-
console.log(status);
|
|
348
|
-
}
|
|
349
187
|
}
|
|
350
188
|
|
|
351
189
|
// After esbuild minifier
|
|
352
|
-
class
|
|
190
|
+
class A{a="https://api.example.com";async get(t){const n=`${this.a}${t}`;return await fetch(n)}b(t){return t}}const s=new A;export{s};
|
|
353
191
|
```
|
|
354
|
-
|
|
355
|
-
## License
|
|
356
|
-
|
|
357
|
-
MIT
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -160,7 +160,7 @@ function createTransformerFactory(
|
|
|
160
160
|
return node;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
return createNewNode(propertyName, VisibilityType.
|
|
163
|
+
return createNewNode(propertyName, VisibilityType.Private, context.factory.createStringLiteral);
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
// obj.node
|
|
@@ -270,14 +270,12 @@ function createTransformerFactory(
|
|
|
270
270
|
type: VisibilityType,
|
|
271
271
|
createNode: (newName: string) => T,
|
|
272
272
|
): T {
|
|
273
|
-
const newPropertyName = getNewName(oldPropertyName
|
|
273
|
+
const newPropertyName = getNewName(oldPropertyName);
|
|
274
274
|
return createNode(newPropertyName);
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
-
function getNewName(originalName: string
|
|
278
|
-
return `${
|
|
279
|
-
type === VisibilityType.Private ? fullOptions.privatePrefix : fullOptions.internalPrefix
|
|
280
|
-
}${originalName}`;
|
|
277
|
+
function getNewName(originalName: string): string {
|
|
278
|
+
return `${fullOptions.privatePrefix}${originalName}`;
|
|
281
279
|
}
|
|
282
280
|
|
|
283
281
|
function getActualSymbol(symbol: ts.Symbol): ts.Symbol {
|
|
@@ -603,7 +601,7 @@ function createTransformerFactory(
|
|
|
603
601
|
}
|
|
604
602
|
}
|
|
605
603
|
|
|
606
|
-
return putToCache(nodeSymbol, VisibilityType.
|
|
604
|
+
return putToCache(nodeSymbol, VisibilityType.Private);
|
|
607
605
|
}
|
|
608
606
|
|
|
609
607
|
function getShorthandObjectBindingElementSymbol(element: ts.BindingElement): ts.Symbol | null {
|
package/src/types.ts
CHANGED
|
@@ -8,18 +8,11 @@ export interface OptimizerOptions {
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Prefix of generated names for private fields
|
|
11
|
-
* @example '_private_'
|
|
12
|
-
* @example '$
|
|
11
|
+
* @example '_private_'
|
|
12
|
+
* @example '$_' // default
|
|
13
13
|
*/
|
|
14
14
|
privatePrefix: string;
|
|
15
15
|
|
|
16
|
-
/**
|
|
17
|
-
* Prefix of generated names for internal fields
|
|
18
|
-
* @example '_internal_' // default
|
|
19
|
-
* @example '$i$'
|
|
20
|
-
*/
|
|
21
|
-
internalPrefix: string;
|
|
22
|
-
|
|
23
16
|
/**
|
|
24
17
|
* Comment which will treat a class/interface/type/property/etc and all its children as "public".
|
|
25
18
|
* Set it to empty string to disable using JSDoc comment to detecting "visibility level".
|
|
@@ -42,15 +35,13 @@ export interface OptimizerOptions {
|
|
|
42
35
|
}
|
|
43
36
|
|
|
44
37
|
export const enum VisibilityType {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
External = 2,
|
|
38
|
+
Private = 0,
|
|
39
|
+
External = 1,
|
|
48
40
|
}
|
|
49
41
|
|
|
50
42
|
export const defaultOptions: OptimizerOptions = {
|
|
51
43
|
entrySourceFiles: [],
|
|
52
|
-
privatePrefix: "$
|
|
53
|
-
internalPrefix: "$i$",
|
|
44
|
+
privatePrefix: "$_",
|
|
54
45
|
publicJSDocTag: "public",
|
|
55
46
|
ignoreDecorated: false,
|
|
56
47
|
inlineConstEnums: true,
|