@eliasku/ts-transformers 0.0.2 → 0.0.3
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 +328 -15
- package/package.json +5 -3
- package/src/const-enum/evaluator.ts +0 -4
- package/src/const-enum/registry.ts +4 -5
- package/src/const-enum/utils.ts +7 -7
- package/src/exports/tracker.ts +6 -7
- package/src/index.ts +20 -18
- package/src/typescript-helpers.ts +2 -2
package/README.md
CHANGED
|
@@ -1,44 +1,357 @@
|
|
|
1
1
|
# @eliasku/ts-transformers
|
|
2
2
|
|
|
3
|
-
TypeScript transformer for code optimization.
|
|
3
|
+
TypeScript transformer for code optimization and preparation for aggressive minification.
|
|
4
|
+
|
|
5
|
+
## ⚠️ Important Requirement
|
|
6
|
+
|
|
7
|
+
**You must compile ALL your code from TypeScript files.**
|
|
8
|
+
|
|
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.
|
|
15
|
+
|
|
16
|
+
## Approach
|
|
17
|
+
|
|
18
|
+
This transformer prepares your code for aggressive minification by analyzing TypeScript types and applying two main optimizations:
|
|
19
|
+
|
|
20
|
+
### 1. Property Renaming with Detectable Prefixes
|
|
21
|
+
|
|
22
|
+
Based on type analysis, properties are categorized into three visibility levels:
|
|
23
|
+
|
|
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`)
|
|
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.
|
|
29
|
+
|
|
30
|
+
**Example:**
|
|
31
|
+
```typescript
|
|
32
|
+
// Before
|
|
33
|
+
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() {} // Internal → renamed to $i$method
|
|
38
|
+
internalHelper() {} // Internal → renamed to $i$internalHelper
|
|
39
|
+
private secret = 1; // Private → renamed to $p$secret
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// After transformer (before minifier)
|
|
43
|
+
class MyClass {
|
|
44
|
+
publicApi() {}
|
|
45
|
+
$i$apiMethod() {}
|
|
46
|
+
$i$internalHelper() {}
|
|
47
|
+
$p$secret = 1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// After esbuild minifier (aggressive property mangling)
|
|
51
|
+
class A{publicApi(){},a(){},b(){},c=1}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Const Enum Inlining
|
|
55
|
+
|
|
56
|
+
Const enums are compile-time constants that should never exist at runtime. This transformer:
|
|
57
|
+
|
|
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
|
+
```typescript
|
|
65
|
+
// Before
|
|
66
|
+
const enum Status {
|
|
67
|
+
Active = 1,
|
|
68
|
+
Inactive = 0
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const status = Status.Active; // Access
|
|
72
|
+
|
|
73
|
+
// After transformer (and minifier)
|
|
74
|
+
const status = 1;
|
|
75
|
+
// Status declaration removed, import removed
|
|
76
|
+
```
|
|
77
|
+
|
|
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
|
|
4
98
|
|
|
5
99
|
## Usage
|
|
6
100
|
|
|
101
|
+
[Example Code](./example/build.ts)
|
|
102
|
+
|
|
7
103
|
```typescript
|
|
8
104
|
import { optimizer } from "@eliasku/ts-transformers";
|
|
105
|
+
import typescript from "@rollup/plugin-typescript";
|
|
106
|
+
import { rollup } from "rollup";
|
|
107
|
+
import { build } from "esbuild";
|
|
9
108
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
109
|
+
// Phase 1: Type-aware optimization with Rollup
|
|
110
|
+
const bundle = await rollup({
|
|
111
|
+
/// ...
|
|
112
|
+
input: "./src/index.ts",
|
|
113
|
+
plugins: [
|
|
114
|
+
typescript({
|
|
115
|
+
transformers: (program) => ({
|
|
116
|
+
before: [
|
|
117
|
+
optimizer(program, {
|
|
118
|
+
entrySourceFiles: ["./src/index.ts"],
|
|
119
|
+
inlineConstEnums: true,
|
|
120
|
+
}),
|
|
121
|
+
],
|
|
122
|
+
}),
|
|
15
123
|
}),
|
|
16
124
|
],
|
|
17
125
|
});
|
|
126
|
+
|
|
127
|
+
await bundle.write({
|
|
128
|
+
/// ...
|
|
129
|
+
file: "./dist/bundle.js",
|
|
130
|
+
format: "es",
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Phase 2: Aggressive minification with esbuild
|
|
134
|
+
await build({
|
|
135
|
+
entryPoints: ["./dist/bundle.js"],
|
|
136
|
+
outfile: "./dist/bundle.min.js",
|
|
137
|
+
minify: true,
|
|
138
|
+
mangleProps: /^\$[ip]\$/, // <- Match your custom prefixes here
|
|
139
|
+
mangleQuoted: false,
|
|
140
|
+
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
|
+
});
|
|
155
|
+
|
|
156
|
+
// Then in esbuild:
|
|
157
|
+
mangleProps: /^(_int_|_priv_)/, // Match custom prefixes
|
|
18
158
|
```
|
|
19
159
|
|
|
20
160
|
## Options
|
|
21
161
|
|
|
22
162
|
### entrySourceFiles (required)
|
|
23
163
|
|
|
24
|
-
An array of entry source files
|
|
164
|
+
An array of entry source files used to detect exported and external fields. This determines your public API surface.
|
|
25
165
|
|
|
26
|
-
|
|
166
|
+
```typescript
|
|
167
|
+
entrySourceFiles: ["./src/index.ts"]
|
|
168
|
+
```
|
|
27
169
|
|
|
28
|
-
|
|
170
|
+
### internalPrefix (optional, default: "$i$")
|
|
29
171
|
|
|
30
|
-
|
|
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
|
+
```
|
|
31
177
|
|
|
32
|
-
|
|
178
|
+
### privatePrefix (optional, default: "$p$")
|
|
33
179
|
|
|
34
|
-
|
|
180
|
+
Prefix for private class members. These will be aggressively mangled by esbuild.
|
|
35
181
|
|
|
36
|
-
|
|
182
|
+
```typescript
|
|
183
|
+
privatePrefix: "$p$" // default: this.private → this.$p$private
|
|
184
|
+
```
|
|
37
185
|
|
|
38
186
|
### publicJSDocTag (optional, default: "public")
|
|
39
187
|
|
|
40
|
-
|
|
188
|
+
JSDoc tag that marks a class/interface/property and all its children as public/external. Set to empty string to disable.
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
publicJSDocTag: "public" // default
|
|
192
|
+
|
|
193
|
+
class MyClass {
|
|
194
|
+
/** @public */
|
|
195
|
+
apiMethod() {} // Treated as external, no prefix applied
|
|
196
|
+
|
|
197
|
+
internalHelper() {} // Treated as internal, gets $i$ prefix
|
|
198
|
+
}
|
|
199
|
+
```
|
|
41
200
|
|
|
42
201
|
### ignoreDecorated (optional, default: false)
|
|
43
202
|
|
|
44
|
-
Whether fields
|
|
203
|
+
Whether decorated fields should be renamed. A field is "decorated" if itself or any parent (on type level) has a decorator.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
ignoreDecorated: true // Don't rename decorated fields
|
|
207
|
+
|
|
208
|
+
@Component({
|
|
209
|
+
selector: "app-root"
|
|
210
|
+
})
|
|
211
|
+
class AppComponent {
|
|
212
|
+
@Input() data: any; // Decorated → not renamed with ignoreDecorated: true
|
|
213
|
+
private internal = 1; // Still renamed to $p$internal
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### inlineConstEnums (optional, default: true)
|
|
218
|
+
|
|
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
|
+
```
|
|
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
|
+
class API {
|
|
314
|
+
private baseUrl = "https://api.example.com";
|
|
315
|
+
/** @public */
|
|
316
|
+
async get(path: string): Promise<Response> {
|
|
317
|
+
const url = `${this.baseUrl}${path}`;
|
|
318
|
+
const response = await fetch(url);
|
|
319
|
+
return this.handleResponse(response);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
private async handleResponse(response: Response): Promise<Response> {
|
|
323
|
+
return response;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
logStatus(status: HttpStatus): void {
|
|
327
|
+
console.log(status);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export const api = new API();
|
|
332
|
+
|
|
333
|
+
// After transformer (before minifier)
|
|
334
|
+
class API {
|
|
335
|
+
$p$baseUrl = "https://api.example.com";
|
|
336
|
+
async get(path) {
|
|
337
|
+
const url = `${this.$p$baseUrl}${path}`;
|
|
338
|
+
const response = await fetch(url);
|
|
339
|
+
return this.$i$handleResponse(response);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
$i$handleResponse(response) {
|
|
343
|
+
return response;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
$i$logStatus(status) {
|
|
347
|
+
console.log(status);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// 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};
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
## License
|
|
356
|
+
|
|
357
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eliasku/ts-transformers",
|
|
3
3
|
"description": "TypeScript transformer for code optimization",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.3",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"test": "bun test",
|
|
9
9
|
"format": "prettier --write **/*.{ts,json,md,yml}",
|
|
10
10
|
"check": "tsc -p .",
|
|
11
|
-
"lint": "eslint ."
|
|
11
|
+
"lint": "eslint .",
|
|
12
|
+
"build:example": "cd example && bun run build.ts"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"typescript": "^5"
|
|
@@ -20,7 +21,8 @@
|
|
|
20
21
|
"@types/bun": "latest",
|
|
21
22
|
"rollup": "latest",
|
|
22
23
|
"@rollup/plugin-typescript": "latest",
|
|
23
|
-
"tslib": "latest"
|
|
24
|
+
"tslib": "latest",
|
|
25
|
+
"esbuild": "latest"
|
|
24
26
|
},
|
|
25
27
|
"module": "./src/index.ts",
|
|
26
28
|
"types": "./src/index.ts",
|
|
@@ -9,13 +9,9 @@ export interface EvaluationContext {
|
|
|
9
9
|
|
|
10
10
|
export class EnumEvaluator {
|
|
11
11
|
private lastImplicitValue = -1;
|
|
12
|
-
private enumType: "numeric" | "string" | "mixed" = "numeric";
|
|
13
|
-
|
|
14
|
-
constructor(private readonly typeChecker: ts.TypeChecker) {}
|
|
15
12
|
|
|
16
13
|
reset(): void {
|
|
17
14
|
this.lastImplicitValue = -1;
|
|
18
|
-
this.enumType = "numeric";
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
evaluate(expr: ts.Expression, context: EvaluationContext): EnumValue {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { EnumValue, EvaluationContext } from "./evaluator";
|
|
3
3
|
import { EnumEvaluator } from "./evaluator";
|
|
4
|
-
import {
|
|
4
|
+
import { isConstEnumSymbol } from "./utils";
|
|
5
|
+
import { hasModifier } from "../typescript-helpers";
|
|
5
6
|
|
|
6
7
|
export interface ConstEnumInfo {
|
|
7
8
|
declaration: ts.EnumDeclaration;
|
|
@@ -19,13 +20,11 @@ export interface ConstEnumMemberInfo {
|
|
|
19
20
|
export class ConstEnumRegistry {
|
|
20
21
|
private readonly program: ts.Program;
|
|
21
22
|
private readonly typeChecker: ts.TypeChecker;
|
|
22
|
-
private readonly entrySourceFiles: readonly string[];
|
|
23
23
|
private readonly enumDeclarations: Map<string, ConstEnumInfo>;
|
|
24
24
|
|
|
25
|
-
constructor(program: ts.Program
|
|
25
|
+
constructor(program: ts.Program) {
|
|
26
26
|
this.program = program;
|
|
27
27
|
this.typeChecker = program.getTypeChecker();
|
|
28
|
-
this.entrySourceFiles = entrySourceFiles || program.getRootFileNames();
|
|
29
28
|
this.enumDeclarations = new Map();
|
|
30
29
|
this.collectConstEnumsFromEntryPoints();
|
|
31
30
|
}
|
|
@@ -110,7 +109,7 @@ export class ConstEnumRegistry {
|
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
private evaluateEnumMembers(enumInfo: ConstEnumInfo): void {
|
|
113
|
-
const evaluator = new EnumEvaluator(
|
|
112
|
+
const evaluator = new EnumEvaluator();
|
|
114
113
|
evaluator.reset();
|
|
115
114
|
const context: EvaluationContext = {
|
|
116
115
|
localMembers: new Map(),
|
package/src/const-enum/utils.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
|
|
3
|
-
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind) =>
|
|
4
|
-
ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((mod: ts.Modifier) => mod.kind === modifier);
|
|
5
|
-
|
|
6
3
|
export const isConstEnumSymbol = (symbol: ts.Symbol): boolean => (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
|
|
7
4
|
|
|
8
5
|
export const isConstEnumType = (type: ts.Type | undefined): boolean => {
|
|
9
|
-
if (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
if (type) {
|
|
7
|
+
const symbol = type.symbol || type.aliasSymbol;
|
|
8
|
+
if (symbol) {
|
|
9
|
+
return (symbol.flags & ts.SymbolFlags.ConstEnum) !== 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
13
|
};
|
package/src/exports/tracker.ts
CHANGED
|
@@ -21,13 +21,12 @@ export class ExportsSymbolTree {
|
|
|
21
21
|
|
|
22
22
|
public isSymbolAccessibleFromExports(symbol: ts.Symbol): boolean {
|
|
23
23
|
symbol = this.getActualSymbol(symbol);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return result;
|
|
24
|
+
for (const [, set] of this.exportsTree) {
|
|
25
|
+
if (set.has(symbol)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
32
|
private computeTreeForExports(entrySourceFiles: readonly string[]): void {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
export type { OptimizerOptions } from "./types";
|
|
2
3
|
|
|
3
4
|
import { ExportsSymbolTree } from "./exports/tracker";
|
|
4
5
|
import {
|
|
@@ -32,8 +33,8 @@ function createTransformerFactory(
|
|
|
32
33
|
const fullOptions: OptimizerOptions = { ...defaultOptions, ...options };
|
|
33
34
|
const typeChecker = program.getTypeChecker();
|
|
34
35
|
const exportsSymbolTree = new ExportsSymbolTree(program, fullOptions.entrySourceFiles);
|
|
35
|
-
const constEnumRegistry = new ConstEnumRegistry(program
|
|
36
|
-
const enumEvaluator = new EnumEvaluator(
|
|
36
|
+
const constEnumRegistry = new ConstEnumRegistry(program);
|
|
37
|
+
const enumEvaluator = new EnumEvaluator();
|
|
37
38
|
|
|
38
39
|
const cache = new Map<ts.Symbol, VisibilityType>();
|
|
39
40
|
|
|
@@ -43,7 +44,7 @@ function createTransformerFactory(
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
return (context: ts.TransformationContext) => {
|
|
46
|
-
function transformNode(node: ts.Node): ts.Node {
|
|
47
|
+
function transformNode(node: ts.Node): ts.Node | undefined {
|
|
47
48
|
if (fullOptions.inlineConstEnums !== false) {
|
|
48
49
|
if (ts.isPropertyAccessExpression(node)) {
|
|
49
50
|
const inlined = tryInlineConstEnum(node);
|
|
@@ -52,12 +53,16 @@ function createTransformerFactory(
|
|
|
52
53
|
|
|
53
54
|
if (ts.isImportSpecifier(node)) {
|
|
54
55
|
const removed = tryRemoveConstEnumImport(node);
|
|
55
|
-
if (removed === undefined)
|
|
56
|
+
if (removed === undefined) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
if (ts.isImportClause(node)) {
|
|
59
62
|
const removed = tryRemoveConstEnumImportClause(node);
|
|
60
|
-
if (removed === undefined)
|
|
63
|
+
if (removed === undefined) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
61
66
|
}
|
|
62
67
|
}
|
|
63
68
|
|
|
@@ -70,9 +75,8 @@ function createTransformerFactory(
|
|
|
70
75
|
if (ts.isBindingElement(node) && node.propertyName === undefined) {
|
|
71
76
|
if (node.parent && ts.isObjectBindingPattern(node.parent)) {
|
|
72
77
|
return handleShorthandObjectBindingElement(node);
|
|
73
|
-
} else {
|
|
74
|
-
console.warn("!!!", node);
|
|
75
78
|
}
|
|
79
|
+
return node;
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
// is not supported:
|
|
@@ -548,7 +552,6 @@ function createTransformerFactory(
|
|
|
548
552
|
}
|
|
549
553
|
|
|
550
554
|
if (nodeSymbol.escapedName === "prototype") {
|
|
551
|
-
// accessing to prototype
|
|
552
555
|
return putToCache(nodeSymbol, VisibilityType.External);
|
|
553
556
|
}
|
|
554
557
|
|
|
@@ -696,22 +699,21 @@ function createTransformerFactory(
|
|
|
696
699
|
return undefined;
|
|
697
700
|
}
|
|
698
701
|
|
|
699
|
-
function wrapTransformNode(node: ts.Node): ts.Node {
|
|
702
|
+
function wrapTransformNode(node: ts.Node): ts.Node | undefined {
|
|
700
703
|
if (ts.isEnumDeclaration(node)) {
|
|
701
704
|
const result = handleEnumDeclaration(node);
|
|
702
|
-
if (result === undefined)
|
|
703
|
-
|
|
705
|
+
if (result === undefined) {
|
|
706
|
+
return undefined;
|
|
707
|
+
}
|
|
708
|
+
if (result !== node) {
|
|
709
|
+
return result;
|
|
710
|
+
}
|
|
704
711
|
}
|
|
705
712
|
return transformNode(node);
|
|
706
713
|
}
|
|
707
714
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
wrapTransformNode(node),
|
|
711
|
-
(childNode: ts.Node) => wrappedTransformNodeAndChildren(childNode),
|
|
712
|
-
context,
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
+
const wrappedTransformNodeAndChildren = (node: ts.Node): ts.Node | undefined =>
|
|
716
|
+
ts.visitEachChild(wrapTransformNode(node), wrappedTransformNodeAndChildren, context);
|
|
715
717
|
|
|
716
718
|
return wrappedTransformNodeAndChildren(sourceFile) as ts.SourceFile;
|
|
717
719
|
};
|
|
@@ -139,8 +139,8 @@ function getModifiers(node: ts.Node): readonly ts.Modifier[] {
|
|
|
139
139
|
return node.modifiers || [];
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind) =>
|
|
143
|
-
getModifiers(node)
|
|
142
|
+
export const hasModifier = (node: ts.Node, modifier: ts.SyntaxKind): boolean =>
|
|
143
|
+
ts.canHaveModifiers(node) && getModifiers(node)?.some((mod) => mod.kind === modifier);
|
|
144
144
|
|
|
145
145
|
function getDecorators(node: ts.Node): readonly unknown[] {
|
|
146
146
|
if (isBreakingTypeScriptApi(ts)) {
|