@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 CHANGED
@@ -1,114 +1,81 @@
1
+ [![NPM Version](https://img.shields.io/npm/v/%40eliasku%2Fts-transformers)](https://www.npmjs.com/package/@eliasku/ts-transformers)
2
+ ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/eliasku/ts-transformers/ci.yml)
3
+
1
4
  # @eliasku/ts-transformers
2
5
 
3
- TypeScript transformer for code optimization and preparation for aggressive minification.
6
+ TypeScript transformer for aggressive code minification through type-aware property renaming and const enum inlining.
4
7
 
5
- ## ⚠️ Important Requirement
8
+ ## Important Requirement
6
9
 
7
10
  **You must compile ALL your code from TypeScript files.**
8
11
 
9
- - No pre-transpiled JavaScript files (`.js`) in your source code
10
- - Transformer requires TypeScript type information to analyze visibility
11
- - Any `.js` files will be included as-is without optimization
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
- ## Approach
16
+ ## Core Concept
17
17
 
18
- This transformer prepares your code for aggressive minification by analyzing TypeScript types and applying two main optimizations:
18
+ Two-phase optimization pipeline:
19
19
 
20
- ### 1. Property Renaming with Detectable Prefixes
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
- Based on type analysis, properties are categorized into three visibility levels:
23
+ ### Visibility Levels
23
24
 
24
- - **External (Public)**: Exported from entry points → **no prefix** (preserved by minifiers)
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
- The special prefixes make these properties easily detectable by downstream minifiers (like **esbuild** or **terser**) for aggressive mangling, while preserving your public API surface.
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 <- annotation in JSDoc to keep symbol name: property, method, field */
35
- publicApi() {} // `publicApi` is not renamed because it's marked by annotation
36
-
37
- public method() {} // Internalrenamed to $i$method
38
- internalHelper() {} // Internalrenamed to $i$internalHelper
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
- $i$apiMethod() {}
46
- $i$internalHelper() {}
47
- $p$secret = 1;
45
+ $_method() {}
46
+ $_secret = 1;
48
47
  }
49
48
 
50
- // After esbuild minifier (aggressive property mangling)
51
- class A{publicApi(){},a(){},b(){},c=1}
49
+ // After esbuild minifier
50
+ class A{publicApi(){},a(){},b=1}
52
51
  ```
53
52
 
54
- ### 2. Const Enum Inlining
53
+ ### Const Enum Inlining
55
54
 
56
- Const enums are compile-time constants that should never exist at runtime. This transformer:
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
- const status = Status.Active; // Access
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 with Rollup
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 with esbuild
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: /^\$[ip]\$/, // <- Match your custom prefixes here
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
- An array of entry source files used to detect exported and external fields. This determines your public API surface.
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
- ### internalPrefix (optional, default: "$i$")
120
+ ### privatePrefix (optional, default: "$\_")
171
121
 
172
- Prefix for internal properties (not exported, but used across your codebase). These will be aggressively mangled by esbuild.
122
+ Prefix for private properties that will be mangled by esbuild.
173
123
 
174
124
  ```typescript
175
- internalPrefix: "$i$" // default: myFunction → $i$myFunction
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 that marks a class/interface/property and all its children as public/external. Set to empty string to disable.
130
+ JSDoc tag marking types/properties as public. Set to empty string to disable.
189
131
 
190
132
  ```typescript
191
- publicJSDocTag: "public" // default
133
+ publicJSDocTag: "public";
192
134
 
193
135
  class MyClass {
194
136
  /** @public */
195
- apiMethod() {} // Treated as external, no prefix applied
137
+ apiMethod() {} // Public, no prefix
196
138
 
197
- internalHelper() {} // Treated as internal, gets $i$ prefix
139
+ internalHelper() {} // Private, gets $_ prefix
198
140
  }
199
141
  ```
200
142
 
201
143
  ### ignoreDecorated (optional, default: false)
202
144
 
203
- Whether decorated fields should be renamed. A field is "decorated" if itself or any parent (on type level) has a decorator.
145
+ Skip renaming decorated fields.
204
146
 
205
147
  ```typescript
206
- ignoreDecorated: true // Don't rename decorated fields
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; // Decorated → not renamed with ignoreDecorated: true
213
- private internal = 1; // Still renamed to $p$internal
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
- Whether to inline const enum values and remove const enum declarations.
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
- ### Example 3: Const Enum Inlining
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 (before minifier)
182
+ // After transformer
334
183
  class API {
335
- $p$baseUrl = "https://api.example.com";
184
+ $_baseUrl = "https://api.example.com";
336
185
  async get(path) {
337
- const url = `${this.$p$baseUrl}${path}`;
186
+ const url = `${this.$_baseUrl}${path}`;
338
187
  const response = await fetch(url);
339
- return this.$i$handleResponse(response);
188
+ return this.$_handleResponse(response);
340
189
  }
341
190
 
342
- $i$handleResponse(response) {
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 t{a="https://api.example.com";async get(t){const n=`${this.a}${t}`;return await fetch(n)}b(t){return t}c(t){console.log(t)}}const s=new t;export{s};
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@eliasku/ts-transformers",
3
3
  "description": "TypeScript transformer for code optimization",
4
- "version": "0.0.3",
4
+ "version": "0.0.5",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "scripts": {
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.Internal, context.factory.createStringLiteral);
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, type);
273
+ const newPropertyName = getNewName(oldPropertyName);
274
274
  return createNode(newPropertyName);
275
275
  }
276
276
 
277
- function getNewName(originalName: string, type: VisibilityType): 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.Internal);
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_' // default
12
- * @example '$p$'
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
- Internal = 0,
46
- Private = 1,
47
- External = 2,
38
+ Private = 0,
39
+ External = 1,
48
40
  }
49
41
 
50
42
  export const defaultOptions: OptimizerOptions = {
51
43
  entrySourceFiles: [],
52
- privatePrefix: "$p$",
53
- internalPrefix: "$i$",
44
+ privatePrefix: "$_",
54
45
  publicJSDocTag: "public",
55
46
  ignoreDecorated: false,
56
47
  inlineConstEnums: true,