@eliasku/ts-transformers 0.0.3 → 0.0.5
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 +68 -216
- package/package.json +1 -1
- package/src/index.ts +6 -8
- package/src/types.ts +5 -14
package/README.md
CHANGED
|
@@ -1,114 +1,81 @@
|
|
|
1
|
+
[](https://www.npmjs.com/package/@eliasku/ts-transformers)
|
|
2
|
+

|
|
3
|
+
|
|
1
4
|
# @eliasku/ts-transformers
|
|
2
5
|
|
|
3
|
-
TypeScript transformer for code
|
|
6
|
+
TypeScript transformer for aggressive code minification through type-aware property renaming and const enum inlining.
|
|
4
7
|
|
|
5
|
-
##
|
|
8
|
+
## Important Requirement
|
|
6
9
|
|
|
7
10
|
**You must compile ALL your code from TypeScript files.**
|
|
8
11
|
|
|
9
|
-
- No pre-transpiled
|
|
10
|
-
- Transformer requires TypeScript type information
|
|
11
|
-
-
|
|
12
|
-
- Mix of `.ts` and `.js` sources → partial optimization, unexpected results
|
|
13
|
-
|
|
14
|
-
Applicable for only for application build, not libraries. For libraries it's better to use buildless approach, and provide `*.ts` files.
|
|
12
|
+
- No pre-transpiled `.js` files in source
|
|
13
|
+
- Transformer requires TypeScript type information
|
|
14
|
+
- Applicable for application builds, not libraries
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## Core Concept
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Two-phase optimization pipeline:
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
1. **This Transformer**: Analyzes TypeScript types, marks renamable properties with special prefixes
|
|
21
|
+
2. **Minifier (esbuild)**: Aggressively mangles prefixed properties while preserving public API
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
### Visibility Levels
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
- **Internal**: Used internally but not exported → prefixed with `$i$` (e.g., `$i$internalProperty`)
|
|
26
|
-
- **Private**: Private class members → prefixed with `$p$` (e.g., `$p$privateMethod`)
|
|
25
|
+
Based on type analysis, properties are categorized as:
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
- **Public (External)**: Exported from entry points → **no prefix** (preserved)
|
|
28
|
+
- **Private**: Everything else → prefixed with `$_` (mangled by minifier)
|
|
29
29
|
|
|
30
30
|
**Example:**
|
|
31
|
+
|
|
31
32
|
```typescript
|
|
32
33
|
// Before
|
|
33
34
|
class MyClass {
|
|
34
|
-
/** @public
|
|
35
|
-
publicApi() {}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
private secret = 1; // Private → renamed to $p$secret
|
|
35
|
+
/** @public - keeps name */
|
|
36
|
+
publicApi() {}
|
|
37
|
+
|
|
38
|
+
method() {} // Private → $_method
|
|
39
|
+
private secret = 1; // Private → $_secret
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// After transformer (before minifier)
|
|
43
43
|
class MyClass {
|
|
44
44
|
publicApi() {}
|
|
45
|
-
$
|
|
46
|
-
$
|
|
47
|
-
$p$secret = 1;
|
|
45
|
+
$_method() {}
|
|
46
|
+
$_secret = 1;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
// After esbuild minifier
|
|
51
|
-
class A{publicApi(){},a(){},b
|
|
49
|
+
// After esbuild minifier
|
|
50
|
+
class A{publicApi(){},a(){},b=1}
|
|
52
51
|
```
|
|
53
52
|
|
|
54
|
-
###
|
|
53
|
+
### Const Enum Inlining
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
Replaces const enum accesses with literal values and removes declarations.
|
|
57
56
|
|
|
58
|
-
- Replaces all const enum member accesses with their literal values
|
|
59
|
-
- Removes const enum declarations from output
|
|
60
|
-
- Strips `const` modifier from declarations in `.d.ts` files
|
|
61
|
-
- Removes unused const enum imports
|
|
62
|
-
|
|
63
|
-
**Example:**
|
|
64
57
|
```typescript
|
|
65
58
|
// Before
|
|
66
59
|
const enum Status {
|
|
67
60
|
Active = 1,
|
|
68
|
-
Inactive = 0
|
|
61
|
+
Inactive = 0,
|
|
69
62
|
}
|
|
63
|
+
const status = Status.Active;
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// After transformer (and minifier)
|
|
65
|
+
// After transformer + minifier
|
|
74
66
|
const status = 1;
|
|
75
|
-
// Status declaration removed, import removed
|
|
76
67
|
```
|
|
77
68
|
|
|
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
69
|
## Usage
|
|
100
70
|
|
|
101
|
-
[Example Code](./example/build.ts)
|
|
102
|
-
|
|
103
71
|
```typescript
|
|
104
72
|
import { optimizer } from "@eliasku/ts-transformers";
|
|
105
73
|
import typescript from "@rollup/plugin-typescript";
|
|
106
74
|
import { rollup } from "rollup";
|
|
107
75
|
import { build } from "esbuild";
|
|
108
76
|
|
|
109
|
-
// Phase 1: Type-aware optimization
|
|
77
|
+
// Phase 1: Type-aware optimization
|
|
110
78
|
const bundle = await rollup({
|
|
111
|
-
/// ...
|
|
112
79
|
input: "./src/index.ts",
|
|
113
80
|
plugins: [
|
|
114
81
|
typescript({
|
|
@@ -125,193 +92,79 @@ const bundle = await rollup({
|
|
|
125
92
|
});
|
|
126
93
|
|
|
127
94
|
await bundle.write({
|
|
128
|
-
/// ...
|
|
129
95
|
file: "./dist/bundle.js",
|
|
130
96
|
format: "es",
|
|
131
97
|
});
|
|
132
98
|
|
|
133
|
-
// Phase 2: Aggressive minification
|
|
99
|
+
// Phase 2: Aggressive minification
|
|
134
100
|
await build({
|
|
135
101
|
entryPoints: ["./dist/bundle.js"],
|
|
136
102
|
outfile: "./dist/bundle.min.js",
|
|
137
103
|
minify: true,
|
|
138
|
-
mangleProps: /^\$
|
|
104
|
+
mangleProps: /^\$_/, // Match your privatePrefix
|
|
139
105
|
mangleQuoted: false,
|
|
140
106
|
keepNames: false,
|
|
141
|
-
/// ...
|
|
142
107
|
});
|
|
143
108
|
```
|
|
144
109
|
|
|
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
|
-
});
|
|
155
|
-
|
|
156
|
-
// Then in esbuild:
|
|
157
|
-
mangleProps: /^(_int_|_priv_)/, // Match custom prefixes
|
|
158
|
-
```
|
|
159
|
-
|
|
160
110
|
## Options
|
|
161
111
|
|
|
162
112
|
### entrySourceFiles (required)
|
|
163
113
|
|
|
164
|
-
|
|
114
|
+
Entry points defining your public API surface.
|
|
165
115
|
|
|
166
116
|
```typescript
|
|
167
|
-
entrySourceFiles: ["./src/index.ts"]
|
|
117
|
+
entrySourceFiles: ["./src/index.ts"];
|
|
168
118
|
```
|
|
169
119
|
|
|
170
|
-
###
|
|
120
|
+
### privatePrefix (optional, default: "$\_")
|
|
171
121
|
|
|
172
|
-
Prefix for
|
|
122
|
+
Prefix for private properties that will be mangled by esbuild.
|
|
173
123
|
|
|
174
124
|
```typescript
|
|
175
|
-
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### privatePrefix (optional, default: "$p$")
|
|
179
|
-
|
|
180
|
-
Prefix for private class members. These will be aggressively mangled by esbuild.
|
|
181
|
-
|
|
182
|
-
```typescript
|
|
183
|
-
privatePrefix: "$p$" // default: this.private → this.$p$private
|
|
125
|
+
privatePrefix: "$_"; // myFunction → $_myFunction
|
|
184
126
|
```
|
|
185
127
|
|
|
186
128
|
### publicJSDocTag (optional, default: "public")
|
|
187
129
|
|
|
188
|
-
JSDoc tag
|
|
130
|
+
JSDoc tag marking types/properties as public. Set to empty string to disable.
|
|
189
131
|
|
|
190
132
|
```typescript
|
|
191
|
-
publicJSDocTag: "public"
|
|
133
|
+
publicJSDocTag: "public";
|
|
192
134
|
|
|
193
135
|
class MyClass {
|
|
194
136
|
/** @public */
|
|
195
|
-
apiMethod() {}
|
|
137
|
+
apiMethod() {} // Public, no prefix
|
|
196
138
|
|
|
197
|
-
internalHelper() {}
|
|
139
|
+
internalHelper() {} // Private, gets $_ prefix
|
|
198
140
|
}
|
|
199
141
|
```
|
|
200
142
|
|
|
201
143
|
### ignoreDecorated (optional, default: false)
|
|
202
144
|
|
|
203
|
-
|
|
145
|
+
Skip renaming decorated fields.
|
|
204
146
|
|
|
205
147
|
```typescript
|
|
206
|
-
ignoreDecorated: true
|
|
148
|
+
ignoreDecorated: true;
|
|
207
149
|
|
|
208
|
-
@Component({
|
|
209
|
-
selector: "app-root"
|
|
210
|
-
})
|
|
150
|
+
@Component({ selector: "app-root" })
|
|
211
151
|
class AppComponent {
|
|
212
|
-
@Input() data: any;
|
|
213
|
-
private internal = 1;
|
|
152
|
+
@Input() data: any; // Not renamed
|
|
153
|
+
private internal = 1; // Renamed to $_internal
|
|
214
154
|
}
|
|
215
155
|
```
|
|
216
156
|
|
|
217
157
|
### inlineConstEnums (optional, default: true)
|
|
218
158
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
## Examples
|
|
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
|
|
249
|
-
|
|
250
|
-
```typescript
|
|
251
|
-
// 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
|
-
```
|
|
159
|
+
Inline const enum values and remove declarations.
|
|
275
160
|
|
|
276
|
-
|
|
161
|
+
## Complete Example
|
|
277
162
|
|
|
278
163
|
```typescript
|
|
279
164
|
// 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
165
|
class API {
|
|
314
166
|
private baseUrl = "https://api.example.com";
|
|
167
|
+
|
|
315
168
|
/** @public */
|
|
316
169
|
async get(path: string): Promise<Response> {
|
|
317
170
|
const url = `${this.baseUrl}${path}`;
|
|
@@ -322,36 +175,35 @@ class API {
|
|
|
322
175
|
private async handleResponse(response: Response): Promise<Response> {
|
|
323
176
|
return response;
|
|
324
177
|
}
|
|
325
|
-
|
|
326
|
-
logStatus(status: HttpStatus): void {
|
|
327
|
-
console.log(status);
|
|
328
|
-
}
|
|
329
178
|
}
|
|
330
179
|
|
|
331
180
|
export const api = new API();
|
|
332
181
|
|
|
333
|
-
// After transformer
|
|
182
|
+
// After transformer
|
|
334
183
|
class API {
|
|
335
|
-
$
|
|
184
|
+
$_baseUrl = "https://api.example.com";
|
|
336
185
|
async get(path) {
|
|
337
|
-
const url = `${this.$
|
|
186
|
+
const url = `${this.$_baseUrl}${path}`;
|
|
338
187
|
const response = await fetch(url);
|
|
339
|
-
return this.$
|
|
188
|
+
return this.$_handleResponse(response);
|
|
340
189
|
}
|
|
341
190
|
|
|
342
|
-
$
|
|
191
|
+
$_handleResponse(response) {
|
|
343
192
|
return response;
|
|
344
193
|
}
|
|
345
|
-
|
|
346
|
-
$i$logStatus(status) {
|
|
347
|
-
console.log(status);
|
|
348
|
-
}
|
|
349
194
|
}
|
|
350
195
|
|
|
351
196
|
// After esbuild minifier
|
|
352
|
-
class
|
|
197
|
+
class A {
|
|
198
|
+
a = "https://api.example.com";
|
|
199
|
+
async get(t) {
|
|
200
|
+
const n = `${this.a}${t}`;
|
|
201
|
+
return await fetch(n);
|
|
202
|
+
}
|
|
203
|
+
b(t) {
|
|
204
|
+
return t;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const s = new A();
|
|
208
|
+
export { s };
|
|
353
209
|
```
|
|
354
|
-
|
|
355
|
-
## License
|
|
356
|
-
|
|
357
|
-
MIT
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -53,7 +53,7 @@ function createTransformerFactory(
|
|
|
53
53
|
|
|
54
54
|
if (ts.isImportSpecifier(node)) {
|
|
55
55
|
const removed = tryRemoveConstEnumImport(node);
|
|
56
|
-
if (removed === undefined) {
|
|
56
|
+
if (removed === undefined || node.isTypeOnly) {
|
|
57
57
|
return undefined;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -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,
|